multi-project-gateway 0.5.1 → 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 +23 -0
- package/dist/cli.js +1292 -291
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/persona-presets.ts","../src/logger.ts","../src/config.ts","../src/router.ts","../src/claude-cli.ts","../src/worktree.ts","../src/session-manager.ts","../src/session-store.ts","../src/discord.ts","../src/agent-dispatch.ts","../src/embed-format.ts","../src/role-check.ts","../src/rate-limiter.ts","../src/pulse-events.ts","../src/activity-engine.ts","../src/health-server.ts","../src/turn-counter.ts","../src/init.ts","../src/resolve-home.ts","../src/health.ts","../src/pid.ts","../src/file-logger.ts","../src/daemon.ts","../src/systemd.ts"],"sourcesContent":["import { resolve, dirname } from 'node:path';\nimport { existsSync, readFileSync, copyFileSync, mkdirSync } from 'node:fs';\nimport { config as loadEnv } from 'dotenv';\nimport { loadConfig } from './config.js';\nimport { createRouter } from './router.js';\nimport { createSessionManager } from './session-manager.js';\nimport { createFileSessionStore } from './session-store.js';\nimport { createDiscordBot } from './discord.js';\nimport { createPulseEmitter } from './pulse-events.js';\nimport { createActivityEngine } from './activity-engine.js';\nimport { createHealthServer, type HealthServer } from './health-server.js';\nimport { createTurnCounter } from './turn-counter.js';\nimport { runInit } from './init.js';\nimport { runHealthChecks } from './health.js';\nimport { reconcileWorktrees } from './worktree.js';\nimport { checkPidFile, writePid, removePid } from './pid.js';\nimport { createFileWriter } from './file-logger.js';\nimport { daemonInstall, daemonUninstall, daemonStatus, daemonLogs } from './daemon.js';\nimport {\n resolveEnvPath,\n resolveConfigPath,\n resolveSessionsPath,\n resolveMpgHome,\n resolveProfileDir,\n resolvePidPath,\n resolveLogPath,\n parseFlags,\n} from './resolve-home.js';\nimport { createLogger, parseLogEntry, filterLogEntries, type LogLevel, isValidLogLevel } from './logger.js';\n\nconst args = process.argv.slice(2);\nconst command = args[0] ?? 'start';\nconst flags = parseFlags(args.slice(1));\n\nasync function main() {\n switch (command) {\n case 'start':\n return start();\n case 'init':\n if (flags.migrate) {\n return migrate();\n }\n return runInit(flags.profileFlag);\n case 'status':\n return status();\n case 'stop':\n return stop();\n case 'daemon':\n return daemon();\n case 'logs':\n return logs();\n case 'help':\n case '--help':\n case '-h':\n return help();\n case '--version':\n case '-v':\n return version();\n default:\n console.error(`Unknown command: ${command}`);\n help();\n process.exit(1);\n }\n}\n\nfunction help() {\n console.log(`\nmpg — multi-project gateway for Claude Code\n\nUsage: mpg <command>\n\nCommands:\n start Start the gateway (default)\n stop Stop a running gateway instance\n init Interactive setup wizard\n status Show session status\n logs Filter structured log output (reads stdin)\n daemon install Install systemd user service\n daemon uninstall Remove systemd user service\n daemon status Show systemd service status\n daemon logs Show service logs (journalctl)\n help Show this message\n\nOptions:\n --profile <name> Use a named profile (default: \"default\")\n --config <path> Use a specific config.json path\n --migrate Copy CWD config files into ~/.mpg/profiles/default/\n --project <name> (logs) Filter by project name\n --level <level> (logs) Filter by minimum log level (debug|info|warn|error)\n --follow, -f (daemon logs) Follow log output\n -v, --version Show version\n -h, --help Show this message\n\nEnvironment:\n MPG_HOME Override config home (default: ~/.mpg)\n`.trim());\n}\n\nfunction version() {\n const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));\n console.log(`mpg v${pkg.version}`);\n}\n\nfunction start() {\n // Load .env using resolution order\n const envPath = resolveEnvPath();\n if (envPath) {\n loadEnv({ path: envPath });\n }\n\n const token = process.env.DISCORD_BOT_TOKEN;\n if (!token) {\n console.error('DISCORD_BOT_TOKEN is not set. Run `mpg init` or set it in .env');\n process.exit(1);\n }\n\n // Check for existing instance\n const pidPath = resolvePidPath(flags.profileFlag);\n const pidCheck = checkPidFile(pidPath);\n if (pidCheck.status === 'running') {\n console.error(`MPG is already running (PID ${pidCheck.pid}). Use \\`mpg stop\\` first.`);\n process.exit(1);\n }\n writePid(pidPath);\n\n const configPath = resolveConfigPath({\n configFlag: flags.configFlag,\n profileFlag: flags.profileFlag,\n });\n if (!configPath || !existsSync(configPath)) {\n console.error('config.json not found. Run `mpg init` to create one.');\n process.exit(1);\n }\n\n const rawConfig = JSON.parse(readFileSync(configPath, 'utf-8'));\n const config = loadConfig(rawConfig);\n\n const logPath = resolveLogPath(flags.profileFlag);\n const fileWriter = createFileWriter(logPath);\n const log = createLogger(config.defaults.logLevel, (line: string) => {\n fileWriter(line);\n process.stderr.write(line + '\\n');\n });\n\n const projectCount = Object.keys(config.projects).length;\n if (projectCount === 0) {\n console.error('No projects configured in config.json');\n process.exit(1);\n }\n\n runHealthChecks(config);\n\n log.info(`Loaded ${projectCount} project(s) from ${configPath}`);\n\n const router = createRouter(config);\n const sessionsPath = resolveSessionsPath(configPath);\n const sessionStore = createFileSessionStore(sessionsPath);\n const pulseEmitter = createPulseEmitter();\n const sessionManager = createSessionManager(config.defaults, sessionStore, pulseEmitter);\n\n // Reconcile orphaned worktrees from crashed sessions\n const persistedSessions = sessionStore.load();\n const knownKeysByProject = new Map<string, Set<string>>();\n for (const [key, entry] of persistedSessions) {\n if (entry.projectDir) {\n let keys = knownKeysByProject.get(entry.projectDir);\n if (!keys) {\n keys = new Set();\n knownKeysByProject.set(entry.projectDir, keys);\n }\n keys.add(key);\n }\n }\n for (const [projectDir, keys] of knownKeysByProject) {\n reconcileWorktrees(projectDir, keys);\n }\n\n const turnCounter = createTurnCounter();\n const bot = createDiscordBot(router, sessionManager, config, turnCounter);\n\n let healthServer: HealthServer | undefined;\n\n function shutdown() {\n log.info('Shutting down...');\n removePid(pidPath);\n if (healthServer) {\n healthServer.close().catch(() => {});\n }\n sessionManager.shutdown();\n bot.stop();\n process.exit(0);\n }\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n\n bot.start(token)\n .then(async () => {\n if (config.defaults.httpPort !== false) {\n try {\n const activityEngine = createActivityEngine();\n healthServer = await createHealthServer(config.defaults.httpPort, sessionManager, bot, config, { activityEngine });\n } catch (err) {\n log.warn(`Health server failed to start on port ${config.defaults.httpPort}: ${err}`);\n }\n }\n })\n .catch((err) => {\n log.error(`Failed to start bot: ${err}`);\n process.exit(1);\n });\n}\n\nfunction status() {\n const configPath = resolveConfigPath({\n configFlag: flags.configFlag,\n profileFlag: flags.profileFlag,\n });\n\n const sessionsPath = configPath\n ? resolveSessionsPath(configPath)\n : resolve(process.cwd(), '.sessions.json');\n\n if (!existsSync(sessionsPath)) {\n console.log('No sessions file found. Is the gateway running?');\n return;\n }\n\n let projectNames: Record<string, string> = {};\n if (configPath && existsSync(configPath)) {\n try {\n const raw = JSON.parse(readFileSync(configPath, 'utf-8'));\n const config = loadConfig(raw);\n for (const [channelId, project] of Object.entries(config.projects)) {\n projectNames[channelId] = project.name;\n }\n } catch {\n // ignore config errors for status\n }\n }\n\n const sessions = JSON.parse(readFileSync(sessionsPath, 'utf-8')) as Array<{\n sessionId: string;\n projectKey: string;\n cwd: string;\n lastActivity: number;\n }>;\n\n if (sessions.length === 0) {\n console.log('No active sessions.');\n return;\n }\n\n console.log(`Sessions (${sessions.length}):\\n`);\n for (const s of sessions) {\n const name = projectNames[s.projectKey] ?? s.projectKey;\n const ago = Math.floor((Date.now() - s.lastActivity) / 60000);\n console.log(` ${name}`);\n console.log(` Session: ${s.sessionId}`);\n console.log(` Dir: ${s.cwd}`);\n console.log(` Idle: ${ago}m`);\n console.log();\n }\n}\n\nfunction migrate() {\n const mpgHome = resolveMpgHome();\n const profileDir = resolveProfileDir('default');\n\n const cwdEnv = resolve(process.cwd(), '.env');\n const cwdConfig = resolve(process.cwd(), 'config.json');\n const cwdSessions = resolve(process.cwd(), '.sessions.json');\n\n const copied: string[] = [];\n\n // Create directories\n mkdirSync(profileDir, { recursive: true });\n\n // Copy .env to MPG_HOME root\n if (existsSync(cwdEnv)) {\n const dest = resolve(mpgHome, '.env');\n copyFileSync(cwdEnv, dest);\n copied.push(` ${cwdEnv} → ${dest}`);\n }\n\n // Copy config.json to profile dir\n if (existsSync(cwdConfig)) {\n const dest = resolve(profileDir, 'config.json');\n copyFileSync(cwdConfig, dest);\n copied.push(` ${cwdConfig} → ${dest}`);\n }\n\n // Copy sessions.json to profile dir\n if (existsSync(cwdSessions)) {\n const dest = resolve(profileDir, 'sessions.json');\n copyFileSync(cwdSessions, dest);\n copied.push(` ${cwdSessions} → ${dest}`);\n }\n\n if (copied.length === 0) {\n console.log('No config files found in current directory. Nothing to migrate.');\n return;\n }\n\n console.log(`Migrated files to ${mpgHome}:\\n`);\n for (const line of copied) {\n console.log(line);\n }\n console.log(`\\nProfile directory: ${profileDir}`);\n console.log('You can now run `mpg start` from any directory.');\n}\n\nfunction stop() {\n const pidPath = resolvePidPath(flags.profileFlag);\n const check = checkPidFile(pidPath);\n\n if (check.status === 'none') {\n console.log('No running MPG instance found.');\n return;\n }\n\n if (check.status === 'stale') {\n console.log(`Removed stale PID file (process ${check.pid} was not running).`);\n return;\n }\n\n const { pid } = check;\n console.log(`Sending SIGTERM to MPG (PID ${pid})...`);\n\n try {\n process.kill(pid, 'SIGTERM');\n } catch (err) {\n console.error(`Failed to send signal: ${err}`);\n process.exit(1);\n }\n\n // Wait up to 10 seconds for graceful shutdown\n const deadline = Date.now() + 10_000;\n const poll = setInterval(() => {\n try {\n process.kill(pid, 0);\n // Still running\n if (Date.now() > deadline) {\n clearInterval(poll);\n console.log('Graceful shutdown timed out. Sending SIGKILL...');\n try {\n process.kill(pid, 'SIGKILL');\n } catch { /* ignore */ }\n removePid(pidPath);\n console.log('Killed.');\n process.exit(0);\n }\n } catch {\n // Process is gone\n clearInterval(poll);\n removePid(pidPath);\n console.log('Stopped.');\n process.exit(0);\n }\n }, 200);\n}\n\nfunction daemon() {\n const subcommand = args[1];\n const daemonFlags = parseFlags(args.slice(2));\n const profile = daemonFlags.profileFlag ?? flags.profileFlag;\n\n switch (subcommand) {\n case 'install':\n return daemonInstall(profile);\n case 'uninstall':\n return daemonUninstall(profile);\n case 'status':\n return daemonStatus(profile);\n case 'logs':\n return daemonLogs(profile, daemonFlags.follow);\n default:\n console.error(`Unknown daemon subcommand: ${subcommand}`);\n console.error('Usage: mpg daemon <install|uninstall|status|logs>');\n process.exit(1);\n }\n}\n\nfunction logs() {\n const levelFlag = flags.level;\n const projectFlag = flags.project;\n\n if (levelFlag && !isValidLogLevel(levelFlag)) {\n console.error(`Invalid log level: ${levelFlag}. Must be one of: debug, info, warn, error`);\n process.exit(1);\n }\n\n const minLevel = levelFlag as LogLevel | undefined;\n\n let buffer = '';\n process.stdin.setEncoding('utf-8');\n process.stdin.on('data', (chunk: string) => {\n buffer += chunk;\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (!line.trim()) continue;\n const entry = parseLogEntry(line);\n if (!entry) {\n // Pass through non-JSON lines as-is\n process.stdout.write(line + '\\n');\n continue;\n }\n const [filtered] = filterLogEntries([entry], { project: projectFlag, level: minLevel });\n if (filtered) {\n const ts = entry.timestamp.replace('T', ' ').replace('Z', '');\n const proj = entry.project ? ` [${entry.project}]` : '';\n const sess = entry.session ? ` (${entry.session.slice(0, 8)})` : '';\n process.stdout.write(`${ts} ${entry.level.toUpperCase().padEnd(5)}${proj}${sess} ${entry.message}\\n`);\n }\n }\n });\n\n process.stdin.on('end', () => {\n if (buffer.trim()) {\n const entry = parseLogEntry(buffer);\n if (!entry) {\n process.stdout.write(buffer + '\\n');\n } else {\n const [filtered] = filterLogEntries([entry], { project: projectFlag, level: minLevel });\n if (filtered) {\n const ts = entry.timestamp.replace('T', ' ').replace('Z', '');\n const proj = entry.project ? ` [${entry.project}]` : '';\n const sess = entry.session ? ` (${entry.session.slice(0, 8)})` : '';\n process.stdout.write(`${ts} ${entry.level.toUpperCase().padEnd(5)}${proj}${sess} ${entry.message}\\n`);\n }\n }\n }\n });\n}\n\nmain().catch((err) => {\n console.error(err);\n process.exit(1);\n});\n","import type { AgentConfig } from './config.js';\n\nexport const PERSONA_PRESETS: Record<string, AgentConfig> = {\n pm: {\n role: 'Product Manager',\n prompt: [\n 'You are a Product Manager working in a multi-agent Discord thread.',\n 'Your responsibilities:',\n '- Clarify requirements and acceptance criteria before handing work to engineers.',\n '- Break down features into concrete, actionable tasks.',\n '- Prioritize work based on user impact and feasibility.',\n '- Ask clarifying questions when requirements are ambiguous.',\n '- Summarize decisions and next steps clearly.',\n '',\n 'Communication style: concise, structured, and action-oriented.',\n '',\n 'CRITICAL — Handing off work to other agents:',\n '- To dispatch work, write HANDOFF @engineer: followed by the task description.',\n '- Only use HANDOFF when you are ready to dispatch work NOW, not when describing future plans.',\n '- The gateway routes your HANDOFF to that agent automatically.',\n '- Do NOT use the Agent tool to do engineering work yourself. You are a PM, not an engineer.',\n '- Do NOT implement code, run tests, or create PRs yourself.',\n '- After writing HANDOFF, END your response. The engineer will reply in the same thread.',\n '- Example: \"HANDOFF @engineer: Please implement feature X. Requirements: ...\"',\n '',\n 'IMPORTANT — Referring to other agents without dispatching:',\n '- To reference another agent conversationally, say \"the engineer\" or \"the PM\" — never write @agent outside of a HANDOFF command.',\n '- Writing @engineer without HANDOFF will NOT dispatch work and the engineer will never see it.',\n ].join('\\n'),\n },\n\n engineer: {\n role: 'Software Engineer',\n prompt: [\n 'You are a Software Engineer working in a multi-agent Discord thread.',\n 'Your responsibilities:',\n '- Write clean, well-tested code that meets the requirements.',\n '- Follow existing project conventions and patterns.',\n '- Consider edge cases, error handling, and performance.',\n '- Explain technical trade-offs when relevant.',\n '- Ask for clarification when requirements are unclear rather than guessing.',\n '',\n 'Communication style: precise and technical, but accessible to non-engineers.',\n '',\n 'When you finish your work, report what you did (files changed, tests, PR link if created).',\n 'If you need the PM to review or approve, write HANDOFF @pm: followed by your update.',\n 'Example: \"HANDOFF @pm: Implementation complete. PR #42 is ready for review.\"',\n '',\n 'IMPORTANT — Referring to other agents without dispatching:',\n '- To reference another agent conversationally, say \"the PM\" or \"the designer\" — never write @agent outside of a HANDOFF command.',\n '- Writing @pm without HANDOFF will NOT dispatch and the PM will never see it.',\n ].join('\\n'),\n },\n\n qa: {\n role: 'QA Engineer',\n prompt: [\n 'You are a QA Engineer.',\n 'Your responsibilities:',\n '- Review code and features for correctness, edge cases, and regressions.',\n '- Write and suggest test cases covering happy paths and failure modes.',\n '- Verify that acceptance criteria from the PM are met.',\n '- Report issues clearly with steps to reproduce.',\n '- Think adversarially — try to break things.',\n '',\n 'Communication style: thorough, detail-oriented, and evidence-based.',\n '',\n 'To dispatch work to another agent, write HANDOFF @agent: followed by the task.',\n 'To reference another agent conversationally, say \"the engineer\" or \"the PM\" — never write @agent outside of a HANDOFF command.',\n ].join('\\n'),\n },\n\n designer: {\n role: 'Designer',\n prompt: [\n 'You are a Designer.',\n 'Your responsibilities:',\n '- Propose UI/UX solutions that are intuitive and consistent.',\n '- Consider accessibility, responsiveness, and user flows.',\n '- Provide clear specifications for engineers to implement.',\n '- Challenge assumptions about user needs when appropriate.',\n '',\n 'Communication style: visual-thinking, user-centric, and practical.',\n '',\n 'To dispatch work to another agent, write HANDOFF @agent: followed by the task.',\n 'To reference another agent conversationally, say \"the engineer\" or \"the PM\" — never write @agent outside of a HANDOFF command.',\n ].join('\\n'),\n },\n\n devops: {\n role: 'DevOps Engineer',\n prompt: [\n 'You are a DevOps Engineer.',\n 'Your responsibilities:',\n '- Manage infrastructure, CI/CD pipelines, and deployment processes.',\n '- Ensure reliability, monitoring, and observability.',\n '- Advise on architecture decisions that affect operability.',\n '- Automate repetitive operational tasks.',\n '',\n 'Communication style: systematic, risk-aware, and automation-focused.',\n '',\n 'To dispatch work to another agent, write HANDOFF @agent: followed by the task.',\n 'To reference another agent conversationally, say \"the engineer\" or \"the PM\" — never write @agent outside of a HANDOFF command.',\n ].join('\\n'),\n },\n};\n\nexport function resolvePreset(presetName: string): AgentConfig | undefined {\n return PERSONA_PRESETS[presetName.toLowerCase()];\n}\n","export type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nconst LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\nexport interface LogEntry {\n timestamp: string;\n level: LogLevel;\n message: string;\n project?: string;\n session?: string;\n}\n\nexport interface Logger {\n debug(message: string, ctx?: { project?: string; session?: string }): void;\n info(message: string, ctx?: { project?: string; session?: string }): void;\n warn(message: string, ctx?: { project?: string; session?: string }): void;\n error(message: string, ctx?: { project?: string; session?: string }): void;\n}\n\nexport function shouldLog(entryLevel: LogLevel, minLevel: LogLevel): boolean {\n return LEVEL_ORDER[entryLevel] >= LEVEL_ORDER[minLevel];\n}\n\nexport function formatLogEntry(entry: LogEntry): string {\n return JSON.stringify(entry);\n}\n\nexport function parseLogEntry(line: string): LogEntry | null {\n try {\n const parsed = JSON.parse(line);\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n typeof parsed.timestamp === 'string' &&\n typeof parsed.level === 'string' &&\n typeof parsed.message === 'string' &&\n parsed.level in LEVEL_ORDER\n ) {\n return parsed as LogEntry;\n }\n return null;\n } catch {\n return null;\n }\n}\n\nexport function filterLogEntries(\n entries: LogEntry[],\n opts?: { project?: string; level?: LogLevel },\n): LogEntry[] {\n return entries.filter((entry) => {\n if (opts?.level && !shouldLog(entry.level, opts.level)) {\n return false;\n }\n if (opts?.project && entry.project !== opts.project) {\n return false;\n }\n return true;\n });\n}\n\nexport function createLogger(\n minLevel: LogLevel = 'info',\n writer: (line: string) => void = (line) => process.stderr.write(line + '\\n'),\n): Logger {\n function log(level: LogLevel, message: string, ctx?: { project?: string; session?: string }): void {\n if (!shouldLog(level, minLevel)) return;\n\n const entry: LogEntry = {\n timestamp: new Date().toISOString(),\n level,\n message,\n ...(ctx?.project && { project: ctx.project }),\n ...(ctx?.session && { session: ctx.session }),\n };\n\n writer(formatLogEntry(entry));\n }\n\n return {\n debug: (message, ctx) => log('debug', message, ctx),\n info: (message, ctx) => log('info', message, ctx),\n warn: (message, ctx) => log('warn', message, ctx),\n error: (message, ctx) => log('error', message, ctx),\n };\n}\n\nexport function isValidLogLevel(value: unknown): value is LogLevel {\n return typeof value === 'string' && value in LEVEL_ORDER;\n}\n","import { resolvePreset } from './persona-presets.js';\nimport { isValidLogLevel, type LogLevel } from './logger.js';\n\nexport interface AgentConfig {\n role: string;\n prompt: string;\n}\n\nexport interface AgentInputConfig {\n preset?: string;\n role?: string;\n prompt?: string;\n}\n\nexport interface ProjectConfig {\n name: string;\n directory: string;\n idleTimeoutMs?: number;\n claudeArgs?: string[];\n allowedTools?: string[];\n disallowedTools?: string[];\n agents?: Record<string, AgentConfig>;\n allowedRoles?: string[];\n rateLimitPerUser?: number;\n}\n\nexport const DEFAULT_ALLOWED_TOOLS: string[] = [\n 'Read',\n 'Edit',\n 'Write',\n 'Glob',\n 'Grep',\n 'Bash(git:*)',\n 'TodoWrite',\n];\n\nexport interface GatewayDefaults {\n idleTimeoutMs: number;\n maxConcurrentSessions: number;\n sessionTtlMs: number;\n maxPersistedSessions: number;\n claudeArgs: string[];\n allowedTools: string[];\n disallowedTools: string[];\n maxTurnsPerAgent: number;\n agentTimeoutMs: number;\n httpPort: number | false;\n logLevel: LogLevel;\n}\n\nexport interface GatewayConfig {\n defaults: GatewayDefaults;\n projects: Record<string, ProjectConfig>;\n}\n\nexport function loadConfig(raw: unknown): GatewayConfig {\n if (!raw || typeof raw !== 'object') {\n throw new Error('Config must be an object');\n }\n\n const obj = raw as Record<string, unknown>;\n\n if (!obj.projects || typeof obj.projects !== 'object') {\n throw new Error('Config must have a \"projects\" object');\n }\n\n const projects = obj.projects as Record<string, unknown>;\n const validated: Record<string, ProjectConfig> = {};\n\n for (const [channelId, project] of Object.entries(projects)) {\n if (!project || typeof project !== 'object') {\n throw new Error(`Project for channel ${channelId} must be an object`);\n }\n const p = project as Record<string, unknown>;\n if (typeof p.directory !== 'string' || !p.directory) {\n throw new Error(`Project for channel ${channelId} must have a \"directory\" string`);\n }\n let agents: Record<string, AgentConfig> | undefined;\n if (Array.isArray(p.agents)) {\n // Shorthand: [\"pm\", \"engineer\"] — resolve each as a preset\n agents = {};\n for (const entry of p.agents) {\n if (typeof entry === 'string') {\n const name = entry.toLowerCase();\n const preset = resolvePreset(name);\n if (preset) {\n agents[name] = { ...preset };\n }\n }\n }\n if (Object.keys(agents).length === 0) agents = undefined;\n } else if (p.agents && typeof p.agents === 'object') {\n agents = {};\n for (const [agentName, agentCfg] of Object.entries(p.agents as Record<string, unknown>)) {\n const ac = agentCfg as Record<string, unknown>;\n const name = agentName.toLowerCase();\n\n if (typeof ac.preset === 'string') {\n // Preset-based: resolve preset, then merge overrides\n const preset = resolvePreset(ac.preset);\n if (preset) {\n const role = typeof ac.role === 'string' ? ac.role : preset.role;\n const basePrompt = preset.prompt;\n const extra = typeof ac.prompt === 'string' ? ac.prompt : '';\n const prompt = extra ? `${basePrompt}\\n\\n${extra}` : basePrompt;\n agents[name] = { role, prompt };\n }\n } else if (typeof ac.role === 'string' && typeof ac.prompt === 'string') {\n // Inline: original behavior\n agents[name] = { role: ac.role, prompt: ac.prompt };\n }\n }\n if (Object.keys(agents).length === 0) agents = undefined;\n }\n\n const projectAllowed = Array.isArray(p.allowedTools) ? (p.allowedTools as string[]) : undefined;\n const projectDisallowed = Array.isArray(p.disallowedTools) ? (p.disallowedTools as string[]) : undefined;\n if (projectAllowed && projectDisallowed) {\n console.warn(`Warning: project \"${typeof p.name === 'string' ? p.name : channelId}\" sets both allowedTools and disallowedTools — they conflict. allowedTools takes precedence.`);\n }\n\n const allowedRoles = Array.isArray(p.allowedRoles) ? (p.allowedRoles as string[]).filter(r => typeof r === 'string') : undefined;\n const rateLimitPerUser = typeof p.rateLimitPerUser === 'number' && p.rateLimitPerUser > 0 ? p.rateLimitPerUser : undefined;\n\n validated[channelId] = {\n name: typeof p.name === 'string' ? p.name : channelId,\n directory: p.directory,\n ...(p.idleTimeoutMs !== undefined && { idleTimeoutMs: Number(p.idleTimeoutMs) }),\n ...(Array.isArray(p.claudeArgs) && { claudeArgs: p.claudeArgs as string[] }),\n ...(projectAllowed && { allowedTools: projectAllowed }),\n ...(projectDisallowed && { disallowedTools: projectDisallowed }),\n ...(agents && { agents }),\n ...(allowedRoles && allowedRoles.length > 0 && { allowedRoles }),\n ...(rateLimitPerUser !== undefined && { rateLimitPerUser }),\n };\n }\n\n const defaults = (obj.defaults ?? {}) as Record<string, unknown>;\n\n const defaultAllowed = Array.isArray(defaults.allowedTools) ? (defaults.allowedTools as string[]) : DEFAULT_ALLOWED_TOOLS;\n const defaultDisallowed = Array.isArray(defaults.disallowedTools) ? (defaults.disallowedTools as string[]) : [];\n if (Array.isArray(defaults.allowedTools) && Array.isArray(defaults.disallowedTools)) {\n console.warn('Warning: gateway defaults set both allowedTools and disallowedTools — they conflict. allowedTools takes precedence.');\n }\n\n return {\n defaults: {\n idleTimeoutMs: typeof defaults.idleTimeoutMs === 'number' ? defaults.idleTimeoutMs : 1800000,\n maxConcurrentSessions: typeof defaults.maxConcurrentSessions === 'number' ? defaults.maxConcurrentSessions : 4,\n sessionTtlMs: typeof defaults.sessionTtlMs === 'number' ? defaults.sessionTtlMs : 7 * 24 * 60 * 60 * 1000,\n maxPersistedSessions: typeof defaults.maxPersistedSessions === 'number' ? defaults.maxPersistedSessions : 50,\n claudeArgs: Array.isArray(defaults.claudeArgs) ? (defaults.claudeArgs as string[]) : ['--permission-mode', 'acceptEdits', '--output-format', 'json'],\n allowedTools: defaultAllowed,\n disallowedTools: defaultDisallowed,\n maxTurnsPerAgent: typeof defaults.maxTurnsPerAgent === 'number' ? defaults.maxTurnsPerAgent : 5,\n agentTimeoutMs: typeof defaults.agentTimeoutMs === 'number' ? defaults.agentTimeoutMs : 3 * 60 * 1000,\n httpPort: defaults.httpPort === false ? false : (typeof defaults.httpPort === 'number' ? defaults.httpPort : 3100),\n logLevel: isValidLogLevel(defaults.logLevel) ? defaults.logLevel : 'info',\n },\n projects: validated,\n };\n}\n","import type { GatewayConfig, ProjectConfig } from './config.js';\n\nexport interface ResolvedProject {\n channelId: string;\n name: string;\n directory: string;\n isThread: boolean;\n}\n\nexport interface Router {\n resolve(channelId: string, parentChannelId?: string): ResolvedProject | null;\n}\n\nexport function createRouter(config: GatewayConfig): Router {\n return {\n resolve(channelId: string, parentChannelId?: string): ResolvedProject | null {\n const project = config.projects[channelId];\n if (project) {\n return { channelId, name: project.name, directory: project.directory, isThread: false };\n }\n\n if (parentChannelId) {\n const parentProject = config.projects[parentChannelId];\n if (parentProject) {\n return { channelId, name: parentProject.name, directory: parentProject.directory, isThread: true };\n }\n }\n\n return null;\n },\n };\n}\n","import { spawn } from 'node:child_process';\n\nexport interface ClaudeUsage {\n input_tokens: number;\n output_tokens: number;\n cache_creation_input_tokens: number;\n cache_read_input_tokens: number;\n total_cost_usd: number;\n duration_ms: number;\n duration_api_ms: number;\n num_turns: number;\n model?: string;\n}\n\nexport interface ClaudeResult {\n text: string;\n sessionId: string;\n isError: boolean;\n sessionReset?: boolean;\n sessionChanged?: boolean;\n usage?: ClaudeUsage;\n}\n\nexport function parseClaudeJsonOutput(raw: string): ClaudeResult {\n const data = JSON.parse(raw);\n let usage: ClaudeUsage | undefined;\n if (data.total_cost_usd != null || data.usage) {\n const model = data.model\n ?? (data.modelUsage ? Object.keys(data.modelUsage)[0] : undefined);\n usage = {\n input_tokens: data.usage?.input_tokens ?? 0,\n output_tokens: data.usage?.output_tokens ?? 0,\n cache_creation_input_tokens: data.usage?.cache_creation_input_tokens ?? 0,\n cache_read_input_tokens: data.usage?.cache_read_input_tokens ?? 0,\n total_cost_usd: data.total_cost_usd ?? 0,\n duration_ms: data.duration_ms ?? 0,\n duration_api_ms: data.duration_api_ms ?? 0,\n num_turns: data.num_turns ?? 0,\n model,\n };\n }\n return {\n text: data.result ?? '',\n sessionId: data.session_id ?? '',\n isError: Boolean(data.is_error),\n usage,\n };\n}\n\nexport interface ToolRestrictions {\n allowedTools?: string[];\n disallowedTools?: string[];\n}\n\n/**\n * Build --allowed-tools / --disallowed-tools CLI args from config.\n * Per-project overrides take precedence over gateway defaults.\n * If both allowed and disallowed are set, allowed takes precedence\n * (disallowed is ignored) — config validation already warns about this.\n * Skips tool flags if baseArgs already contain them (manual claudeArgs override).\n */\nexport function buildToolArgs(\n defaults: ToolRestrictions,\n projectOverrides?: ToolRestrictions,\n existingArgs?: string[],\n): string[] {\n // If the user already manually set tool flags in claudeArgs, don't conflict\n if (existingArgs?.includes('--allowed-tools') || existingArgs?.includes('--disallowed-tools')) {\n return [];\n }\n\n // Per-project overrides take precedence over gateway defaults\n const allowed = projectOverrides?.allowedTools ?? defaults.allowedTools;\n const disallowed = projectOverrides?.disallowedTools ?? defaults.disallowedTools;\n\n const args: string[] = [];\n\n if (allowed && allowed.length > 0) {\n args.push('--allowed-tools', ...allowed);\n } else if (disallowed && disallowed.length > 0) {\n args.push('--disallowed-tools', ...disallowed);\n }\n\n return args;\n}\n\nexport function buildClaudeArgs(\n baseArgs: string[],\n prompt: string,\n sessionId: string | undefined,\n systemPrompt?: string,\n): string[] {\n // Prompt MUST come before variadic flags like --allowed-tools which consume\n // all subsequent positional args. Place it right after --print.\n const args = ['--print', prompt, ...baseArgs];\n if (sessionId) {\n args.push('--resume', sessionId);\n }\n if (systemPrompt) {\n args.push('--append-system-prompt', systemPrompt);\n }\n return args;\n}\n\nexport function friendlyError(stderr: string): string {\n const combined = stderr.toLowerCase();\n if (combined.includes('rate limit') || combined.includes('rate_limit_error')) {\n return 'Claude usage limit reached — please wait a few minutes and try again.';\n }\n if (combined.includes('overloaded') || combined.includes('overloaded_error')) {\n return 'Claude API is temporarily overloaded — please try again shortly.';\n }\n if (combined.includes('invalid api key') || combined.includes('authentication_error') || combined.includes('authentication failed')) {\n return 'Claude authentication failed — check your API key or CLI login.';\n }\n if (combined.includes('no messages returned')) {\n return 'Claude returned an empty response — try sending your message again.';\n }\n return `Claude error: ${stderr.slice(0, 500)}`;\n}\n\nconst DEFAULT_TIMEOUT_MS = 20 * 60 * 1000; // 20 minutes\n\nexport function runClaude(\n cwd: string,\n baseArgs: string[],\n prompt: string,\n sessionId: string | undefined,\n systemPrompt?: string,\n timeoutMs: number = DEFAULT_TIMEOUT_MS,\n): Promise<ClaudeResult> {\n return new Promise((resolve, reject) => {\n const args = buildClaudeArgs(baseArgs, prompt, sessionId, systemPrompt);\n const proc = spawn('claude', args, {\n cwd,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n let settled = false;\n\n const timer = setTimeout(() => {\n if (!settled) {\n settled = true;\n proc.kill('SIGTERM');\n reject(new Error(`Claude CLI timed out after ${timeoutMs / 1000}s`));\n }\n }, timeoutMs);\n\n proc.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n\n proc.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n\n proc.on('close', (code) => {\n clearTimeout(timer);\n if (settled) return;\n settled = true;\n if (code !== 0) {\n reject(new Error(friendlyError(stderr)));\n return;\n }\n try {\n const result = parseClaudeJsonOutput(stdout.trim());\n resolve(result);\n } catch (err) {\n reject(new Error(`Failed to parse claude output: ${stdout.slice(0, 200)}`));\n }\n });\n\n proc.on('error', (err) => {\n clearTimeout(timer);\n if (settled) return;\n settled = true;\n reject(new Error(`Failed to spawn claude: ${err.message}`));\n });\n });\n}\n","import { execFileSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\nconst WORKTREE_DIR = '.worktrees';\nconst BRANCH_PREFIX = 'mpg/';\nconst TIMEOUT = 10_000;\n\n/** Sanitize project key for use in filesystem paths and git branch names. */\nfunction sanitizeKey(key: string): string {\n return key.replace(/:/g, '-');\n}\n\nexport function worktreePath(projectDir: string, projectKey: string): string {\n return join(projectDir, WORKTREE_DIR, sanitizeKey(projectKey));\n}\n\nexport function createWorktree(projectDir: string, projectKey: string): string {\n const safeKey = sanitizeKey(projectKey);\n if (!/^[\\w-]+$/.test(safeKey)) {\n throw new Error(`Invalid projectKey for worktree: ${projectKey}`);\n }\n const wtPath = worktreePath(projectDir, projectKey);\n if (existsSync(wtPath)) return wtPath;\n const branch = `${BRANCH_PREFIX}${safeKey}`;\n try {\n execFileSync('git', ['worktree', 'add', '-b', branch, wtPath], {\n cwd: projectDir,\n timeout: TIMEOUT,\n });\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes('already exists')) throw err;\n // Branch already exists (previous session) — attach without creating branch\n execFileSync('git', ['worktree', 'add', wtPath, branch], {\n cwd: projectDir,\n timeout: TIMEOUT,\n });\n }\n return wtPath;\n}\n\nexport function removeWorktree(projectDir: string, projectKey: string): void {\n const wtPath = worktreePath(projectDir, projectKey);\n try {\n execFileSync('git', ['worktree', 'remove', '--force', wtPath], {\n cwd: projectDir,\n timeout: TIMEOUT,\n });\n } catch {\n // Already removed or not a worktree — safe to ignore\n }\n}\n\nexport interface WorktreeInfo {\n path: string;\n branch: string;\n}\n\nexport function listWorktrees(projectDir: string): WorktreeInfo[] {\n try {\n const raw = execFileSync('git', ['worktree', 'list', '--porcelain'], {\n cwd: projectDir,\n timeout: TIMEOUT,\n }).toString();\n\n const entries: WorktreeInfo[] = [];\n let currentPath = '';\n let currentBranch = '';\n\n for (const line of raw.split('\\n')) {\n if (line.startsWith('worktree ')) {\n currentPath = line.slice('worktree '.length);\n } else if (line.startsWith('branch ')) {\n currentBranch = line.slice('branch '.length);\n } else if (line === '') {\n if (currentPath) {\n entries.push({ path: currentPath, branch: currentBranch });\n }\n currentPath = '';\n currentBranch = '';\n }\n }\n return entries;\n } catch {\n return [];\n }\n}\n\nexport function reconcileWorktrees(projectDir: string, knownKeys: Set<string>): void {\n const worktrees = listWorktrees(projectDir);\n const sanitizedKnown = new Set([...knownKeys].map(sanitizeKey));\n let removed = 0;\n for (const wt of worktrees) {\n if (!wt.branch.startsWith(`refs/heads/${BRANCH_PREFIX}`)) continue;\n const key = wt.branch.slice(`refs/heads/${BRANCH_PREFIX}`.length);\n if (!sanitizedKnown.has(key)) {\n removeWorktree(projectDir, key);\n removed++;\n }\n }\n if (removed > 0) {\n console.log(`Reconciled ${removed} orphaned worktree(s) in ${projectDir}`);\n }\n}\n","import { runClaude, type ClaudeResult } from './claude-cli.js';\nimport type { SessionStore, PersistedSession } from './session-store.js';\nimport type { PulseEmitter } from './pulse-events.js';\nimport { createWorktree as gitCreateWorktree, removeWorktree as gitRemoveWorktree } from './worktree.js';\n\nexport interface SessionInfo {\n sessionId: string;\n projectKey: string;\n lastActivity: number;\n queueLength: number;\n}\n\nexport interface SessionManager {\n send(projectKey: string, cwd: string, prompt: string, opts?: { worktree?: boolean; systemPrompt?: string; timeoutMs?: number; extraArgs?: string[] }): Promise<ClaudeResult>;\n getSession(projectKey: string): SessionInfo | undefined;\n listSessions(): SessionInfo[];\n clearSession(projectKey: string): boolean;\n restartSession(projectKey: string): boolean;\n shutdown(): void;\n}\n\ninterface InternalSession {\n sessionId: string | undefined;\n projectKey: string;\n cwd: string;\n projectDir: string | undefined;\n worktreePath: string | undefined;\n lastActivity: number;\n createdAt: number;\n messageCount: number;\n restored: boolean;\n resumeEmitted: boolean;\n processing: boolean;\n queue: Array<{\n prompt: string;\n systemPrompt?: string;\n timeoutMs?: number;\n extraArgs?: string[];\n resolve: (result: ClaudeResult) => void;\n reject: (error: Error) => void;\n }>;\n idleTimer: ReturnType<typeof setTimeout> | null;\n}\n\nexport function createSessionManager(defaults: {\n idleTimeoutMs: number;\n maxConcurrentSessions: number;\n sessionTtlMs?: number;\n maxPersistedSessions?: number;\n claudeArgs: string[];\n}, store?: SessionStore, pulseEmitter?: PulseEmitter): SessionManager {\n const sessions = new Map<string, InternalSession>();\n const sessionTtlMs = defaults.sessionTtlMs ?? 7 * 24 * 60 * 60 * 1000;\n const maxPersistedSessions = defaults.maxPersistedSessions ?? 50;\n\n let activeProcesses = 0;\n const waiters: Array<() => void> = [];\n\n function pruneSessions(persisted: Map<string, PersistedSession>): number {\n const now = Date.now();\n let pruned = 0;\n\n // Remove sessions older than TTL\n for (const [key, entry] of persisted) {\n if (now - entry.lastActivity > sessionTtlMs) {\n persisted.delete(key);\n pruned++;\n }\n }\n\n // Enforce cap: evict oldest if over limit\n if (persisted.size > maxPersistedSessions) {\n const sorted = Array.from(persisted.entries())\n .sort((a, b) => a[1].lastActivity - b[1].lastActivity);\n const toRemove = sorted.slice(0, persisted.size - maxPersistedSessions);\n for (const [key] of toRemove) {\n persisted.delete(key);\n pruned++;\n }\n }\n\n return pruned;\n }\n\n function persistSessions(): void {\n if (!store) return;\n // Merge in-memory sessions with existing persisted data.\n // In-memory sessions take precedence; persisted-only entries are preserved.\n const persisted = store.load();\n for (const [key, s] of sessions) {\n if (s.sessionId) {\n persisted.set(key, {\n sessionId: s.sessionId,\n projectKey: s.projectKey,\n cwd: s.cwd,\n lastActivity: s.lastActivity,\n worktreePath: s.worktreePath,\n projectDir: s.projectDir,\n });\n }\n }\n pruneSessions(persisted);\n store.save(persisted);\n }\n\n async function acquireSlot(): Promise<void> {\n if (activeProcesses < defaults.maxConcurrentSessions) {\n activeProcesses++;\n return;\n }\n return new Promise<void>((resolve) => {\n waiters.push(() => {\n activeProcesses++;\n resolve();\n });\n });\n }\n\n function releaseSlot(): void {\n activeProcesses--;\n const next = waiters.shift();\n if (next) next();\n }\n\n function resetIdleTimer(session: InternalSession) {\n if (session.idleTimer) clearTimeout(session.idleTimer);\n if (session.queue.length > 0) return;\n session.idleTimer = setTimeout(() => {\n if (pulseEmitter && session.sessionId) {\n pulseEmitter.sessionIdle(\n session.sessionId,\n session.projectKey,\n session.cwd,\n Date.now() - session.createdAt,\n session.messageCount,\n );\n }\n sessions.delete(session.projectKey);\n }, defaults.idleTimeoutMs);\n }\n\n async function processQueue(session: InternalSession): Promise<void> {\n if (session.processing || session.queue.length === 0) return;\n session.processing = true;\n\n while (session.queue.length > 0) {\n const item = session.queue.shift()!;\n const effectiveArgs = item.extraArgs ? [...defaults.claudeArgs, ...item.extraArgs] : defaults.claudeArgs;\n await acquireSlot();\n if (pulseEmitter) {\n // Session keys are \"threadId:agentName\" for agent sessions, \"threadId\" for default\n const agentTarget = session.projectKey.includes(':') ? session.projectKey.split(':').pop() : undefined;\n pulseEmitter.messageRouted(\n session.sessionId ?? session.projectKey,\n session.projectKey,\n session.cwd,\n { agentTarget, queueDepth: session.queue.length },\n );\n }\n try {\n const result = await runClaude(\n session.cwd,\n effectiveArgs,\n item.prompt,\n session.sessionId,\n item.systemPrompt,\n item.timeoutMs,\n );\n const sessionChanged = !!(\n session.sessionId &&\n result.sessionId &&\n result.sessionId !== session.sessionId\n );\n session.sessionId = result.sessionId || session.sessionId;\n session.lastActivity = Date.now();\n session.messageCount++;\n if (pulseEmitter && session.sessionId && result.usage) {\n const agentTarget = session.projectKey.includes(':') ? session.projectKey.split(':').pop() : undefined;\n pulseEmitter.messageCompleted(\n session.sessionId,\n session.projectKey,\n session.cwd,\n result.usage,\n { agentTarget },\n );\n }\n resetIdleTimer(session);\n persistSessions();\n if (sessionChanged) {\n item.resolve({ ...result, sessionChanged: true });\n } else {\n item.resolve(result);\n }\n } catch (err) {\n if (session.sessionId) {\n session.sessionId = undefined;\n try {\n const result = await runClaude(session.cwd, effectiveArgs, item.prompt, undefined, item.systemPrompt, item.timeoutMs);\n session.sessionId = result.sessionId || undefined;\n session.lastActivity = Date.now();\n session.messageCount++;\n if (pulseEmitter && session.sessionId && result.usage) {\n const agentTarget = session.projectKey.includes(':') ? session.projectKey.split(':').pop() : undefined;\n pulseEmitter.messageCompleted(\n session.sessionId,\n session.projectKey,\n session.cwd,\n result.usage,\n { agentTarget },\n );\n }\n resetIdleTimer(session);\n persistSessions();\n item.resolve({ ...result, sessionReset: true });\n } catch (retryErr) {\n item.reject(retryErr instanceof Error ? retryErr : new Error(String(retryErr)));\n }\n } else {\n item.reject(err instanceof Error ? err : new Error(String(err)));\n }\n } finally {\n releaseSlot();\n }\n }\n\n session.processing = false;\n }\n\n function getOrCreateSession(projectKey: string, cwd: string, useWorktree?: boolean): InternalSession {\n let session = sessions.get(projectKey);\n if (session && session.restored && !session.resumeEmitted && pulseEmitter && session.sessionId) {\n session.resumeEmitted = true;\n pulseEmitter.sessionResume(\n session.sessionId,\n session.projectKey,\n session.cwd,\n Date.now() - session.lastActivity,\n );\n }\n if (!session) {\n // Check store for a previously persisted session ID\n let restoredSessionId: string | undefined;\n let restoredWorktreePath: string | undefined;\n let restoredLastActivity: number | undefined;\n if (store) {\n const persisted = store.load();\n const entry = persisted.get(projectKey);\n if (entry?.sessionId) {\n restoredSessionId = entry.sessionId;\n restoredWorktreePath = entry.worktreePath;\n restoredLastActivity = entry.lastActivity;\n }\n }\n\n let effectiveCwd = cwd;\n let worktreePath: string | undefined = restoredWorktreePath;\n let projectDir: string | undefined;\n\n if (useWorktree && !worktreePath) {\n worktreePath = gitCreateWorktree(cwd, projectKey);\n }\n if (worktreePath) {\n projectDir = cwd;\n effectiveCwd = worktreePath;\n }\n\n session = {\n sessionId: restoredSessionId,\n projectKey,\n cwd: effectiveCwd,\n projectDir,\n worktreePath,\n lastActivity: Date.now(),\n createdAt: Date.now(),\n messageCount: 0,\n restored: !!restoredSessionId,\n resumeEmitted: false,\n processing: false,\n queue: [],\n idleTimer: null,\n };\n sessions.set(projectKey, session);\n resetIdleTimer(session);\n if (pulseEmitter) {\n if (restoredSessionId) {\n session.resumeEmitted = true;\n pulseEmitter.sessionResume(\n restoredSessionId,\n projectKey,\n effectiveCwd,\n Date.now() - (restoredLastActivity ?? Date.now()),\n );\n } else {\n pulseEmitter.sessionStart(\n session.sessionId ?? projectKey,\n projectKey,\n effectiveCwd,\n { triggerSource: 'discord' },\n );\n }\n }\n }\n return session;\n }\n\n // Restore persisted sessions into memory at startup, pruning stale entries\n if (store) {\n const persisted = store.load();\n const pruned = pruneSessions(persisted);\n if (pruned > 0) {\n console.log(`Pruned ${pruned} expired session(s)`);\n store.save(persisted);\n }\n for (const [key, entry] of persisted) {\n sessions.set(key, {\n sessionId: entry.sessionId,\n projectKey: entry.projectKey,\n cwd: entry.cwd,\n projectDir: entry.projectDir,\n worktreePath: entry.worktreePath,\n lastActivity: entry.lastActivity,\n createdAt: entry.lastActivity,\n messageCount: 0,\n restored: true,\n resumeEmitted: false,\n processing: false,\n queue: [],\n idleTimer: null,\n });\n }\n for (const session of sessions.values()) {\n resetIdleTimer(session);\n }\n if (persisted.size > 0) {\n console.log(`Restored ${persisted.size} session(s) from disk`);\n }\n }\n\n return {\n send(projectKey: string, cwd: string, prompt: string, opts?: { worktree?: boolean; systemPrompt?: string; timeoutMs?: number; extraArgs?: string[] }): Promise<ClaudeResult> {\n const session = getOrCreateSession(projectKey, cwd, opts?.worktree);\n return new Promise<ClaudeResult>((resolve, reject) => {\n session.queue.push({ prompt, systemPrompt: opts?.systemPrompt, timeoutMs: opts?.timeoutMs, extraArgs: opts?.extraArgs, resolve, reject });\n processQueue(session);\n });\n },\n\n getSession(projectKey: string): SessionInfo | undefined {\n const session = sessions.get(projectKey);\n if (!session) return undefined;\n return {\n sessionId: session.sessionId ?? '',\n projectKey: session.projectKey,\n lastActivity: session.lastActivity,\n queueLength: session.queue.length,\n };\n },\n\n listSessions(): SessionInfo[] {\n return Array.from(sessions.values()).map((s) => ({\n sessionId: s.sessionId ?? '',\n projectKey: s.projectKey,\n lastActivity: s.lastActivity,\n queueLength: s.queue.length,\n }));\n },\n\n clearSession(projectKey: string): boolean {\n const session = sessions.get(projectKey);\n if (!session) return false;\n if (session.idleTimer) clearTimeout(session.idleTimer);\n if (pulseEmitter && session.sessionId) {\n pulseEmitter.sessionEnd(\n session.sessionId,\n session.projectKey,\n session.cwd,\n Date.now() - session.createdAt,\n session.messageCount,\n );\n }\n if (session.worktreePath && session.projectDir) {\n gitRemoveWorktree(session.projectDir, session.projectKey);\n }\n sessions.delete(projectKey);\n persistSessions();\n return true;\n },\n\n restartSession(projectKey: string): boolean {\n const session = sessions.get(projectKey);\n if (!session) return false;\n session.sessionId = undefined;\n session.lastActivity = Date.now();\n resetIdleTimer(session);\n persistSessions();\n return true;\n },\n\n shutdown() {\n persistSessions();\n for (const session of sessions.values()) {\n if (session.idleTimer) clearTimeout(session.idleTimer);\n }\n sessions.clear();\n },\n };\n}\n","import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\nexport interface PersistedSession {\n sessionId: string;\n projectKey: string;\n cwd: string;\n lastActivity: number;\n worktreePath?: string;\n projectDir?: string;\n}\n\nexport interface SessionStore {\n load(): Map<string, PersistedSession>;\n save(sessions: Map<string, PersistedSession>): void;\n}\n\nexport function createFileSessionStore(filePath: string): SessionStore {\n return {\n load(): Map<string, PersistedSession> {\n try {\n const raw = readFileSync(filePath, 'utf-8');\n const entries: PersistedSession[] = JSON.parse(raw);\n const map = new Map<string, PersistedSession>();\n for (const entry of entries) {\n if (entry.sessionId && entry.projectKey) {\n map.set(entry.projectKey, entry);\n }\n }\n return map;\n } catch {\n return new Map();\n }\n },\n\n save(sessions: Map<string, PersistedSession>): void {\n const entries = Array.from(sessions.values());\n try {\n mkdirSync(dirname(filePath), { recursive: true });\n writeFileSync(filePath, JSON.stringify(entries, null, 2) + '\\n');\n } catch (err) {\n console.error('Failed to persist sessions:', err);\n }\n },\n };\n}\n","import { readdirSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { Client, GatewayIntentBits, Events, Status, type Message, type TextChannel, type ThreadChannel } from 'discord.js';\nimport type { Router } from './router.js';\nimport type { SessionManager } from './session-manager.js';\nimport type { GatewayConfig } from './config.js';\nimport { buildToolArgs } from './claude-cli.js';\nimport { parseAgentMention, parseAgentCommand, extractAskTarget, parseHandoffCommand } from './agent-dispatch.js';\nimport { sendAgentMessage, buildHandoffEmbed } from './embed-format.js';\nimport type { TurnCounter } from './turn-counter.js';\nimport { hasAllowedRole } from './role-check.js';\nimport { createRateLimiter } from './rate-limiter.js';\n\nexport function chunkMessage(text: string, limit: number): string[] {\n if (text.length <= limit) return [text];\n\n const chunks: string[] = [];\n const lines = text.split('\\n');\n let current = '';\n\n for (const line of lines) {\n if (line.length > limit) {\n if (current) {\n chunks.push(current);\n current = '';\n }\n for (let i = 0; i < line.length; i += limit) {\n chunks.push(line.slice(i, i + limit));\n }\n continue;\n }\n\n const candidate = current ? `${current}\\n${line}` : line;\n if (candidate.length > limit) {\n chunks.push(current);\n current = line;\n } else {\n current = candidate;\n }\n }\n\n if (current || chunks.length === 0) {\n chunks.push(current);\n }\n\n return chunks;\n}\n\nexport interface DiscordBot {\n start(token: string): Promise<void>;\n stop(): void;\n getStatus(): string;\n}\n\nfunction resolveProjectName(config: GatewayConfig, channelId: string): string {\n return config.projects[channelId]?.name ?? channelId;\n}\n\nfunction findProjectByName(config: GatewayConfig, name: string): { channelId: string; name: string } | null {\n const lower = name.toLowerCase();\n for (const [channelId, project] of Object.entries(config.projects)) {\n if (project.name.toLowerCase() === lower) {\n return { channelId, name: project.name };\n }\n }\n return null;\n}\n\nfunction formatTimeSince(timestamp: number): string {\n const seconds = Math.floor((Date.now() - timestamp) / 1000);\n if (seconds < 60) return `${seconds}s ago`;\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m ago`;\n const hours = Math.floor(minutes / 60);\n return `${hours}h ${minutes % 60}m ago`;\n}\n\nexport function handleCommand(\n command: string,\n config: GatewayConfig,\n sessionManager: SessionManager,\n context?: { channelId: string; projectName: string; isThread: boolean },\n): string | null {\n const parts = command.trim().split(/\\s+/);\n const cmd = parts[0]?.toLowerCase();\n\n if (cmd === '!sessions') {\n const allSessions = sessionManager.listSessions();\n if (allSessions.length === 0) {\n return 'No active sessions.';\n }\n const lines = allSessions.map((s) => {\n const name = resolveProjectName(config, s.projectKey);\n const idle = formatTimeSince(s.lastActivity);\n const queue = s.queueLength > 0 ? ` | queue: ${s.queueLength}` : '';\n const sid = s.sessionId ? ` | \\`${s.sessionId.slice(0, 8)}…\\`` : '';\n return `- **${name}** — last active ${idle}${queue}${sid}`;\n });\n return `**Active sessions (${allSessions.length})**\\n${lines.join('\\n')}`;\n }\n\n if (cmd === '!session') {\n const name = parts.slice(1).join(' ');\n\n // No arguments: show session for the current thread (or project channel)\n if (!name && context) {\n const info = sessionManager.getSession(context.channelId);\n if (!info) return `**${context.projectName}** — no active session in this ${context.isThread ? 'thread' : 'channel'}.`;\n const idle = formatTimeSince(info.lastActivity);\n const sid = info.sessionId || 'none';\n return [\n `**${context.projectName}**${context.isThread ? ' (thread)' : ''}`,\n `Session ID: \\`${sid}\\``,\n `Last active: ${idle}`,\n `Queue depth: ${info.queueLength}`,\n ].join('\\n');\n }\n\n if (!name) return 'Usage: `!session <project name>` or run `!session` in a thread';\n const project = findProjectByName(config, name);\n if (!project) return `No project found matching \"${name}\".`;\n const info = sessionManager.getSession(project.channelId);\n if (!info) return `**${project.name}** — no active session.`;\n const idle = formatTimeSince(info.lastActivity);\n const sid = info.sessionId || 'none';\n return [\n `**${project.name}**`,\n `Session ID: \\`${sid}\\``,\n `Last active: ${idle}`,\n `Queue depth: ${info.queueLength}`,\n ].join('\\n');\n }\n\n if (cmd === '!kill') {\n const name = parts.slice(1).join(' ');\n if (!name) return 'Usage: `!kill <project name>`';\n const project = findProjectByName(config, name);\n if (!project) return `No project found matching \"${name}\".`;\n const cleared = sessionManager.clearSession(project.channelId);\n if (cleared) return `Session for **${project.name}** cleared.`;\n return `**${project.name}** — no active session to clear.`;\n }\n\n if (cmd === '!restart') {\n const name = parts.slice(1).join(' ');\n if (!name) return 'Usage: `!restart <project name>`';\n const project = findProjectByName(config, name);\n if (!project) return `No project found matching \"${name}\".`;\n const restarted = sessionManager.restartSession(project.channelId);\n if (restarted) return `Session for **${project.name}** restarted — next message will start fresh context.`;\n return `**${project.name}** — no active session to restart.`;\n }\n\n if (cmd === '!agents') {\n if (!context) return 'Run `!agents` in a project channel or thread.';\n // context.channelId may be a thread ID; look up the project by name\n const match = findProjectByName(config, context.projectName);\n const project = match ? config.projects[match.channelId] : undefined;\n if (!project?.agents || Object.keys(project.agents).length === 0) {\n return `**${context.projectName}** — No agents configured. Messages go to the default session.`;\n }\n const lines = Object.entries(project.agents).map(([name, agent]) =>\n `- \\`${name}\\` — ${agent.role}`\n );\n return `**${context.projectName} agents**\\n${lines.join('\\n')}\\n\\nDispatch: \\`!ask <agent> <message>\\` or shorthand \\`!<agent> <message>\\``;\n }\n\n if (cmd === '!apo') {\n if (!context) return 'Run `!apo` in a project channel or thread.';\n const match = findProjectByName(config, context.projectName);\n const projectDir = match ? config.projects[match.channelId]?.directory : undefined;\n if (!projectDir) return 'Run `!apo` in a project channel or thread.';\n\n const pulseDir = join(projectDir, '.pulse');\n let files: string[];\n try {\n files = readdirSync(pulseDir).filter(f => f.endsWith('.json')).sort();\n } catch {\n return `No pulse reports found for **${context.projectName}**.`;\n }\n if (files.length === 0) return `No pulse reports found for **${context.projectName}**.`;\n\n try {\n const raw = readFileSync(join(pulseDir, files[files.length - 1]), 'utf-8');\n const report = JSON.parse(raw);\n const c = report.convergence ?? {};\n return [\n `Pulse — ${report.project ?? context.projectName}`,\n '═══════════════════════════════',\n `${c.exchanges ?? '?'} exchanges | ${c.outcomes ?? '?'} outcomes | rate ${c.rate ?? '?'}`,\n `Rework: ${c.reworkPercent ?? '?'}%`,\n `Reported: ${report.timestamp ?? 'unknown'}`,\n ].join('\\n');\n } catch {\n return `Failed to read pulse report for **${context.projectName}**.`;\n }\n }\n\n if (cmd === '!help') {\n return [\n '**Gateway commands**',\n '`!ask <agent> <message>` — dispatch a message to a named agent',\n '`!<agent> <message>` — shorthand for `!ask`',\n '`!sessions` — list all active sessions',\n '`!session` — show session for the current thread (or use `!session <name>`)',\n '`!restart <name>` — reset a session (fresh context, keeps worktree)',\n '`!kill <name>` — force-close a project session',\n '`!agents` — list available agents for the current project',\n '`!apo` — show latest pulse interaction report',\n '`!help` — show this message',\n ].join('\\n');\n }\n\n return null;\n}\n\nconst THREAD_HISTORY_LIMIT = 20;\n\n/** Fetch recent thread messages and format as a conversation log. */\nasync function fetchThreadHistory(channel: TextChannel | ThreadChannel, beforeMessageId: string): Promise<string | null> {\n if (!channel.isThread()) return null;\n try {\n const messages = await channel.messages.fetch({ limit: THREAD_HISTORY_LIMIT, before: beforeMessageId });\n if (messages.size === 0) return null;\n const lines = [...messages.values()]\n .reverse()\n .map((m) => `[${m.author.bot ? 'agent' : m.author.username}]: ${m.content}`);\n return `<thread-history>\\n${lines.join('\\n')}\\n</thread-history>\\n\\n`;\n } catch {\n return null;\n }\n}\n\nexport function createDiscordBot(router: Router, sessionManager: SessionManager, config: GatewayConfig, turnCounter?: TurnCounter): DiscordBot {\n const client = new Client({\n intents: [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.MessageContent,\n GatewayIntentBits.GuildMembers,\n ],\n });\n\n const rateLimiter = createRateLimiter();\n\n // Track last active agent per thread for routing plain replies (#48)\n const lastActiveAgent = new Map<string, { agentName: string; agent: import('./config.js').AgentConfig }>();\n\n client.on(Events.MessageCreate, async (message: Message) => {\n if (message.author.bot) return;\n\n if (!('send' in message.channel)) return;\n\n // Handle gateway commands from any mapped channel\n if (message.content.startsWith('!')) {\n const parentId = message.channel.isThread() ? message.channel.parentId ?? undefined : undefined;\n const resolved = router.resolve(message.channelId, parentId);\n if (resolved) {\n const response = handleCommand(message.content, config, sessionManager, {\n channelId: resolved.channelId,\n projectName: resolved.name,\n isThread: resolved.isThread,\n });\n if (response) {\n await message.channel.send(response);\n return;\n }\n\n // Check for !ask <agent> or !<agent> shorthand dispatch\n const projectChannelId = parentId || resolved.channelId;\n const project = config.projects[projectChannelId];\n const agents = project?.agents;\n if (agents) {\n const askMention = parseAgentCommand(message.content, agents);\n if (askMention) {\n // Fall through to normal message handling — inject the parsed mention\n // by rewriting message content to @agent form so the existing path picks it up\n } else {\n // Check if user tried !ask with an unknown agent name\n const target = extractAskTarget(message.content);\n if (target) {\n const agentList = Object.entries(agents)\n .map(([name, a]) => `\\`${name}\\` — ${a.role}`)\n .join('\\n- ');\n await message.channel.send(\n `Unknown agent \\`${target}\\`. Available agents:\\n- ${agentList}\\n\\nUsage: \\`!ask <agent> <message>\\``,\n );\n return;\n }\n }\n }\n }\n }\n\n const parentId = message.channel.isThread() ? message.channel.parentId ?? undefined : undefined;\n const resolved = router.resolve(message.channelId, parentId);\n if (!resolved) return;\n\n // Role-based access control\n const projectChannelIdForAcl = parentId || resolved.channelId;\n const projectForAcl = config.projects[projectChannelIdForAcl];\n if (projectForAcl?.allowedRoles && projectForAcl.allowedRoles.length > 0) {\n if (!hasAllowedRole(message.member, projectForAcl.allowedRoles)) {\n await message.reply(\"You don't have permission to use this bot.\");\n return;\n }\n }\n\n // Per-user rate limiting\n if (projectForAcl?.rateLimitPerUser) {\n const result = rateLimiter.check(`${message.author.id}:${projectChannelIdForAcl}`, projectForAcl.rateLimitPerUser);\n if (!result.allowed) {\n await message.reply(\n `You're sending messages too quickly. Please wait ${result.retryAfterSeconds}s before trying again.`,\n );\n return;\n }\n }\n\n try {\n await message.react('👀');\n } catch {\n // Reaction may fail if permissions are missing — non-critical\n }\n\n // If the message is in a main channel, create a thread for the response.\n // If already in a thread, reply there directly.\n let replyChannel: TextChannel | ThreadChannel;\n if (message.channel.isThread()) {\n replyChannel = message.channel;\n } else {\n try {\n replyChannel = await message.startThread({\n name: message.content.slice(0, 100) || 'Claude response',\n autoArchiveDuration: 1440,\n });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to create thread in ${resolved.name}: ${errMsg}`);\n await message.reply('⚠️ Could not create a thread — check bot permissions (Create Public Threads).');\n return;\n }\n }\n\n // Show typing indicator while Claude is processing\n const typingInterval = setInterval(() => {\n replyChannel.sendTyping().catch(() => {});\n }, 7_000);\n replyChannel.sendTyping().catch(() => {});\n\n // Look up agents for the project (use parent channel ID for threads)\n const projectChannelId = parentId || resolved.channelId;\n const project = config.projects[projectChannelId];\n const agents = project?.agents;\n\n // Build tool restriction args (per-project overrides gateway defaults).\n // Check both gateway-level and per-project claudeArgs for manual --allowed-tools / --disallowed-tools flags.\n const allClaudeArgs = project?.claudeArgs\n ? [...config.defaults.claudeArgs, ...project.claudeArgs]\n : config.defaults.claudeArgs;\n const toolArgs = buildToolArgs(\n config.defaults,\n project ? { allowedTools: project.allowedTools, disallowedTools: project.disallowedTools } : undefined,\n allClaudeArgs,\n );\n\n // Reset turn counter on human messages\n if (turnCounter) turnCounter.reset(replyChannel.id);\n\n // Check for !ask <agent> command, @agent mention, or fall back to last active agent (#48, #60)\n const mention = agents\n ? (parseAgentCommand(message.content, agents) ?? parseAgentMention(message.content, agents))\n : null;\n const activeAgent = mention ?? (message.channel.isThread() ? lastActiveAgent.get(replyChannel.id) ?? null : null);\n\n // Use thread ID for session keys so each thread gets its own agent sessions.\n // For main-channel messages, replyChannel is the newly created thread.\n const threadId = replyChannel.id;\n\n // Build session key and system prompt\n const sessionKey = activeAgent\n ? `${threadId}:${activeAgent.agentName}`\n : threadId;\n const systemPrompt = activeAgent\n ? `Your role: ${activeAgent.agent.role}\\n\\n${activeAgent.agent.prompt}`\n : undefined;\n\n try {\n // Prepend thread history when dispatching to an agent in a thread (#49)\n let userPrompt = mention ? mention.prompt : message.content;\n if (activeAgent && message.channel.isThread()) {\n const history = await fetchThreadHistory(replyChannel, message.id);\n if (history) userPrompt = `${history}${userPrompt}`;\n }\n\n // Guard against empty prompts (e.g. attachment-only messages, bare @agent mentions)\n if (!userPrompt.trim()) {\n await replyChannel.send('Please include a message with your request.');\n return;\n }\n\n const result = await sessionManager.send(\n sessionKey,\n resolved.directory,\n userPrompt,\n {\n worktree: replyChannel.isThread() ? true : undefined,\n systemPrompt,\n extraArgs: toolArgs.length > 0 ? toolArgs : undefined,\n },\n );\n\n if (result.sessionReset) {\n await replyChannel.send('⚠️ Previous session expired — starting fresh.');\n } else if (result.sessionChanged) {\n await replyChannel.send('⚠️ Claude started a new session — previous conversation context may be lost.');\n }\n\n await sendAgentMessage(\n replyChannel,\n result.text,\n activeAgent?.agentName,\n activeAgent?.agent.role,\n );\n\n // Track last active agent for plain reply routing (#48)\n if (activeAgent) {\n lastActiveAgent.set(threadId, { agentName: activeAgent.agentName, agent: activeAgent.agent });\n }\n\n // Auto-handoff loop: check if agent response mentions another agent\n if (agents && turnCounter) {\n let responseText = result.text;\n let currentAgentName = activeAgent?.agentName;\n const maxTurns = config.defaults.maxTurnsPerAgent;\n\n while (true) {\n const handoff = parseHandoffCommand(responseText, agents);\n if (!handoff || handoff.agentName === currentAgentName) break;\n\n turnCounter.increment(replyChannel.id);\n const turn = turnCounter.getTurns(replyChannel.id);\n console.log(`[handoff] thread=${replyChannel.id} turn=${turn}/${maxTurns} ${currentAgentName ?? 'user'} → ${handoff.agentName}`);\n\n if (turnCounter.isOverLimit(replyChannel.id, maxTurns)) {\n console.log(`[handoff] thread=${replyChannel.id} turn limit reached, stopping`);\n await replyChannel.send(\n `⚠️ Agent turn limit reached (${maxTurns}) — send a message to reset.`\n );\n break;\n }\n\n const handoffKey = `${threadId}:${handoff.agentName}`;\n const handoffPrompt = `Your role: ${handoff.agent.role}\\n\\n${handoff.agent.prompt}`;\n\n replyChannel.sendTyping().catch(() => {});\n\n // Post handoff announcement — kept visible so users see progress during long agent runs (#56, #65)\n await replyChannel.send({ embeds: [buildHandoffEmbed(handoff.agentName, handoff.agent.role)] }).catch(() => null);\n\n // Restore typing indicator — posting the announcement clears it (#65)\n replyChannel.sendTyping().catch(() => {});\n\n console.log(`[handoff] thread=${replyChannel.id} sending to ${handoff.agentName} (key=${handoffKey}, prompt length=${responseText.length})`);\n const sendStart = Date.now();\n\n let handoffResult;\n try {\n handoffResult = await sessionManager.send(\n handoffKey,\n resolved.directory,\n responseText,\n { worktree: replyChannel.isThread() ? true : undefined, systemPrompt: handoffPrompt, timeoutMs: config.defaults.agentTimeoutMs, extraArgs: toolArgs.length > 0 ? toolArgs : undefined },\n );\n } catch (handoffErr) {\n const msg = handoffErr instanceof Error ? handoffErr.message : String(handoffErr);\n console.log(`[handoff] thread=${replyChannel.id} ${handoff.agentName} failed: ${msg}`);\n await replyChannel.send(\n `⚠️ Agent \\`@${handoff.agentName}\\` failed: ${msg.slice(0, 1800)}`\n );\n break;\n }\n\n const elapsed = ((Date.now() - sendStart) / 1000).toFixed(1);\n console.log(`[handoff] thread=${replyChannel.id} ${handoff.agentName} responded in ${elapsed}s (${handoffResult.text.length} chars)`);\n\n await sendAgentMessage(\n replyChannel,\n handoffResult.text,\n handoff.agentName,\n handoff.agent.role,\n );\n\n responseText = handoffResult.text;\n currentAgentName = handoff.agentName;\n lastActiveAgent.set(threadId, { agentName: handoff.agentName, agent: handoff.agent });\n }\n }\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n await replyChannel.send(\n `**Error** (${resolved.name}): ${errorMsg.slice(0, 1800)}`,\n );\n } finally {\n clearInterval(typingInterval);\n }\n });\n\n return {\n async start(token: string) {\n await client.login(token);\n console.log(`Gateway connected as ${client.user?.tag}`);\n },\n stop() {\n rateLimiter.dispose();\n client.destroy();\n },\n getStatus(): string {\n const ws = client.ws;\n const statusMap: Record<number, string> = {\n [Status.Ready]: 'connected',\n [Status.Connecting]: 'connecting',\n [Status.Reconnecting]: 'reconnecting',\n [Status.Idle]: 'idle',\n [Status.Nearly]: 'nearly',\n [Status.Disconnected]: 'disconnected',\n [Status.WaitingForGuilds]: 'waiting_for_guilds',\n [Status.Identifying]: 'identifying',\n [Status.Resuming]: 'resuming',\n };\n return statusMap[ws.status] ?? 'unknown';\n },\n };\n}\n","// src/agent-dispatch.ts\nimport type { AgentConfig } from './config.js';\n\nexport type { AgentConfig };\n\nexport interface AgentMention {\n agentName: string;\n agent: AgentConfig;\n prompt: string;\n}\n\n/** Built-in command names that take precedence over agent shorthand (!<agent>). */\nconst BUILT_IN_COMMANDS = new Set([\n 'help', 'sessions', 'session', 'kill', 'restart', 'agents', 'ask',\n]);\n\n/**\n * Parse `!ask <agent> <message>` (canonical) or `!<agent> <message>` (shorthand).\n * Shorthand yields to built-in commands — e.g. `!help` is never treated as an agent dispatch.\n */\nexport function parseAgentCommand(\n text: string,\n agents: Record<string, AgentConfig>,\n): AgentMention | null {\n // Canonical form: !ask <agent> <message>\n const askMatch = text.match(/^!ask\\s+(\\S+)(?:\\s+([\\s\\S]*))?$/i);\n if (askMatch) {\n const name = askMatch[1].toLowerCase();\n const agent = agents[name];\n if (!agent) return null; // unknown agent — caller handles error\n const prompt = (askMatch[2] ?? '').trim();\n return { agentName: name, agent, prompt };\n }\n\n // Shorthand form: !<agent> <message> (only if not a built-in command)\n const shortMatch = text.match(/^!(\\S+)(?:\\s+([\\s\\S]*))?$/i);\n if (shortMatch) {\n const name = shortMatch[1].toLowerCase();\n if (BUILT_IN_COMMANDS.has(name)) return null; // built-in wins\n const agent = agents[name];\n if (!agent) return null;\n const prompt = (shortMatch[2] ?? '').trim();\n return { agentName: name, agent, prompt };\n }\n\n return null;\n}\n\n/**\n * Extract the target agent name from a `!ask` command, even if the agent is unknown.\n * Returns null if the message is not a `!ask` command at all.\n */\nexport function extractAskTarget(text: string): string | null {\n const askMatch = text.match(/^!ask\\s+(\\S+)/i);\n return askMatch ? askMatch[1].toLowerCase() : null;\n}\n\nexport function parseAgentMention(\n text: string,\n agents: Record<string, AgentConfig>,\n): AgentMention | null {\n // Build a pattern that matches @agentName (case-insensitive)\n const agentNames = Object.keys(agents);\n if (agentNames.length === 0) return null;\n\n const escaped = agentNames.map(n => n.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'));\n const pattern = new RegExp(`@(${escaped.join('|')})\\\\b`, 'i');\n const match = text.match(pattern);\n if (!match) return null;\n\n const matchedName = match[1].toLowerCase();\n const agent = agents[matchedName];\n if (!agent) return null;\n\n // If mention is at the start, strip it from the prompt\n let prompt: string;\n if (match.index === 0) {\n prompt = text.slice(match[0].length).trim();\n } else {\n prompt = text;\n }\n\n return { agentName: matchedName, agent, prompt };\n}\n\n/**\n * Parse explicit `HANDOFF @agent: <task>` command in agent responses.\n * Only this syntax triggers auto-handoff — bare @agent mentions are ignored.\n *\n * Note: the `prompt` field captures the rest of the HANDOFF line only.\n * The handoff loop in discord.ts passes the full responseText to the\n * dispatched agent, not this prompt — the dispatched agent sees the\n * complete previous response plus thread context.\n */\nexport function parseHandoffCommand(\n text: string,\n agents: Record<string, AgentConfig>,\n): AgentMention | null {\n const agentNames = Object.keys(agents);\n if (agentNames.length === 0) return null;\n\n const escaped = agentNames.map(n => n.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'));\n const pattern = new RegExp(`^HANDOFF\\\\s+@(${escaped.join('|')})\\\\s*:\\\\s*(.*)$`, 'im');\n const match = text.match(pattern);\n if (!match) return null;\n\n const matchedName = match[1].toLowerCase();\n const agent = agents[matchedName];\n if (!agent) return null;\n\n return { agentName: matchedName, agent, prompt: match[2].trim() };\n}\n","// src/embed-format.ts\nimport { EmbedBuilder, type TextChannel, type ThreadChannel } from 'discord.js';\nimport { chunkMessage } from './discord.js';\n\n/** 10 high-contrast colors for light and dark Discord themes. */\nexport const PALETTE: readonly number[] = [\n 0x3498db, // blue\n 0xe74c3c, // red\n 0x2ecc71, // green\n 0x9b59b6, // purple\n 0xf39c12, // orange\n 0x1abc9c, // teal\n 0xe91e63, // pink\n 0xff9800, // amber\n 0x00bcd4, // cyan\n 0x8bc34a, // lime\n];\n\n/** Deterministic color for an agent key (djb2 hash mod palette length). */\nexport function agentColor(agentKey: string): number {\n let hash = 0;\n for (const ch of agentKey) hash = ((hash << 5) - hash + ch.charCodeAt(0)) | 0;\n return PALETTE[Math.abs(hash) % PALETTE.length];\n}\n\nconst EMBED_DESCRIPTION_LIMIT = 4096;\n\n/** Build Discord embeds for an agent response, chunking at 4096 chars. */\nexport function buildAgentEmbeds(text: string, agentName: string, agentRole: string): EmbedBuilder[] {\n const color = agentColor(agentName);\n const chunks = chunkMessage(text, EMBED_DESCRIPTION_LIMIT);\n\n return chunks.map((chunk, i) => {\n const authorName = i === 0 ? agentRole : `${agentRole} (cont.)`;\n const embed = new EmbedBuilder()\n .setAuthor({ name: authorName })\n .setColor(color);\n if (chunk) {\n embed.setDescription(chunk);\n } else {\n embed.data.description = '';\n }\n return embed;\n });\n}\n\n/** Build a small embed announcing an agent handoff. */\nexport function buildHandoffEmbed(agentName: string, agentRole: string): EmbedBuilder {\n return new EmbedBuilder()\n .setAuthor({ name: agentRole })\n .setDescription(`Handing off to **@${agentName}**...`)\n .setColor(agentColor(agentName));\n}\n\nconst PLAIN_TEXT_LIMIT = 2000;\n\n/** Send a message as embeds (if agent) or plain text (if not). */\nexport async function sendAgentMessage(\n channel: { send(content: unknown): Promise<unknown> },\n text: string,\n agentName?: string,\n agentRole?: string,\n): Promise<void> {\n if (agentName && agentRole) {\n const embeds = buildAgentEmbeds(text, agentName, agentRole);\n for (const embed of embeds) {\n await channel.send({ embeds: [embed] });\n }\n } else {\n const chunks = chunkMessage(text, PLAIN_TEXT_LIMIT);\n for (const chunk of chunks) {\n await channel.send(chunk);\n }\n }\n}\n","/**\n * Discord role-based access control.\n * Checks if a guild member has any of the allowed roles (by name or ID).\n */\n\nimport type { GuildMember } from 'discord.js';\n\n/**\n * Returns true if the member is authorized, i.e. they have at least one of the allowedRoles.\n * If allowedRoles is empty or undefined, everyone is authorized (backward compatible).\n */\nexport function hasAllowedRole(member: GuildMember | null, allowedRoles: string[] | undefined): boolean {\n if (!allowedRoles || allowedRoles.length === 0) return true;\n if (!member) return false;\n\n return member.roles.cache.some(\n (role) => allowedRoles.includes(role.name) || allowedRoles.includes(role.id),\n );\n}\n","/**\n * Simple in-memory per-user rate limiter.\n * Tracks message timestamps per user and enforces a max-messages-per-minute limit.\n */\n\nconst WINDOW_MS = 60_000; // 1 minute window\nconst CLEANUP_INTERVAL_MS = 5 * 60_000; // clean up every 5 minutes\n\nexport interface RateLimitResult {\n allowed: boolean;\n /** Seconds until the user can send another message (only set when blocked). */\n retryAfterSeconds?: number;\n}\n\nexport interface RateLimiter {\n check(userId: string, limit: number): RateLimitResult;\n dispose(): void;\n}\n\nexport function createRateLimiter(): RateLimiter {\n const timestamps = new Map<string, number[]>();\n\n function pruneUser(userId: string, now: number): number[] {\n const ts = timestamps.get(userId);\n if (!ts) return [];\n const valid = ts.filter((t) => now - t < WINDOW_MS);\n if (valid.length === 0) {\n timestamps.delete(userId);\n return [];\n }\n timestamps.set(userId, valid);\n return valid;\n }\n\n const cleanupTimer = setInterval(() => {\n const now = Date.now();\n for (const userId of timestamps.keys()) {\n pruneUser(userId, now);\n }\n }, CLEANUP_INTERVAL_MS);\n\n // Don't keep Node alive just for cleanup\n if (cleanupTimer.unref) cleanupTimer.unref();\n\n return {\n check(userId: string, limit: number): RateLimitResult {\n const now = Date.now();\n const valid = pruneUser(userId, now);\n\n if (valid.length >= limit) {\n // Find the oldest timestamp in the window to calculate retry-after\n const oldest = valid[0];\n const retryAfterMs = WINDOW_MS - (now - oldest);\n return {\n allowed: false,\n retryAfterSeconds: Math.ceil(retryAfterMs / 1000),\n };\n }\n\n // Record this message\n if (!timestamps.has(userId)) {\n timestamps.set(userId, [now]);\n } else {\n timestamps.get(userId)!.push(now);\n }\n\n return { allowed: true };\n },\n\n dispose() {\n clearInterval(cleanupTimer);\n timestamps.clear();\n },\n };\n}\n","import { appendFileSync, mkdirSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { ClaudeUsage } from './claude-cli.js';\n\nexport interface PulseEmitter {\n sessionStart(sessionId: string, projectKey: string, projectDir: string, opts?: { agentName?: string; triggerSource?: string }): void;\n sessionEnd(sessionId: string, projectKey: string, projectDir: string, durationMs: number, messageCount: number): void;\n sessionIdle(sessionId: string, projectKey: string, projectDir: string, durationMs: number, messageCount: number): void;\n sessionResume(sessionId: string, projectKey: string, projectDir: string, idleDurationMs: number): void;\n messageRouted(sessionId: string, projectKey: string, projectDir: string, opts?: { agentTarget?: string; queueDepth?: number }): void;\n messageCompleted(sessionId: string, projectKey: string, projectDir: string, usage: ClaudeUsage, opts?: { agentTarget?: string }): void;\n}\n\nconst DEFAULT_PATH = join(homedir(), '.pulse', 'events', 'mpg-sessions.jsonl');\n\nfunction baseEvent(eventType: string, sessionId: string, projectKey: string, projectDir: string) {\n return {\n schema_version: 1,\n timestamp: new Date().toISOString(),\n event_type: eventType,\n session_id: sessionId,\n project_key: projectKey,\n project_dir: projectDir,\n };\n}\n\nexport function createPulseEmitter(filePath?: string): PulseEmitter {\n const target = filePath ?? DEFAULT_PATH;\n let dirCreated = false;\n\n function emit(event: Record<string, unknown>): void {\n try {\n if (!dirCreated) {\n mkdirSync(dirname(target), { recursive: true });\n dirCreated = true;\n }\n appendFileSync(target, JSON.stringify(event) + '\\n');\n } catch {\n // Fire-and-forget: never crash the gateway for event logging\n }\n }\n\n return {\n sessionStart(sessionId, projectKey, projectDir, opts) {\n emit({\n ...baseEvent('session_start', sessionId, projectKey, projectDir),\n agent_name: opts?.agentName,\n trigger_source: opts?.triggerSource ?? 'unknown',\n });\n },\n\n sessionEnd(sessionId, projectKey, projectDir, durationMs, messageCount) {\n emit({\n ...baseEvent('session_end', sessionId, projectKey, projectDir),\n duration_ms: durationMs,\n message_count: messageCount,\n });\n },\n\n sessionIdle(sessionId, projectKey, projectDir, durationMs, messageCount) {\n emit({\n ...baseEvent('session_idle', sessionId, projectKey, projectDir),\n duration_ms: durationMs,\n message_count: messageCount,\n });\n },\n\n sessionResume(sessionId, projectKey, projectDir, idleDurationMs) {\n emit({\n ...baseEvent('session_resume', sessionId, projectKey, projectDir),\n idle_duration_ms: idleDurationMs,\n });\n },\n\n messageRouted(sessionId, projectKey, projectDir, opts) {\n emit({\n ...baseEvent('message_routed', sessionId, projectKey, projectDir),\n agent_target: opts?.agentTarget,\n queue_depth: opts?.queueDepth ?? 0,\n });\n },\n\n messageCompleted(sessionId, projectKey, projectDir, usage, opts) {\n emit({\n ...baseEvent('message_completed', sessionId, projectKey, projectDir),\n agent_target: opts?.agentTarget,\n input_tokens: usage.input_tokens,\n output_tokens: usage.output_tokens,\n cache_creation_input_tokens: usage.cache_creation_input_tokens,\n cache_read_input_tokens: usage.cache_read_input_tokens,\n total_cost_usd: usage.total_cost_usd,\n duration_ms: usage.duration_ms,\n duration_api_ms: usage.duration_api_ms,\n num_turns: usage.num_turns,\n model: usage.model,\n });\n },\n };\n}\n","import { readFileSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\n\nexport type TimeRange = '24h' | '7d' | '30d';\nexport type Bucket = 'hour' | 'day';\n\nconst DEFAULT_PATH = join(homedir(), '.pulse', 'events', 'mpg-sessions.jsonl');\n\nconst RANGE_MS: Record<TimeRange, number> = {\n '24h': 24 * 60 * 60 * 1000,\n '7d': 7 * 24 * 60 * 60 * 1000,\n '30d': 30 * 24 * 60 * 60 * 1000,\n};\n\ninterface PulseEvent {\n schema_version?: number;\n timestamp: string;\n event_type: string;\n session_id: string;\n project_key: string;\n project_dir: string;\n [key: string]: unknown;\n}\n\nfunction readEvents(filePath: string, range: TimeRange): PulseEvent[] {\n if (!existsSync(filePath)) return [];\n try {\n const content = readFileSync(filePath, 'utf-8').trim();\n if (!content) return [];\n const cutoff = Date.now() - RANGE_MS[range];\n const events: PulseEvent[] = [];\n for (const line of content.split('\\n')) {\n try {\n const e = JSON.parse(line) as PulseEvent;\n if (new Date(e.timestamp).getTime() >= cutoff) {\n events.push(e);\n }\n } catch {\n // Skip malformed lines\n }\n }\n return events;\n } catch {\n return [];\n }\n}\n\nfunction bucketKey(timestamp: string, bucket: Bucket): string {\n const d = new Date(timestamp);\n if (bucket === 'hour') {\n d.setMinutes(0, 0, 0);\n } else {\n d.setHours(0, 0, 0, 0);\n }\n return d.toISOString();\n}\n\nexport interface ActivityEngine {\n computeSummary(range: TimeRange): {\n total_cost_usd: number;\n total_input_tokens: number;\n total_output_tokens: number;\n total_sessions: number;\n total_messages: number;\n avg_session_duration_ms: number;\n };\n tokensByProject(range: TimeRange): Array<{\n project_key: string;\n project_dir: string;\n input_tokens: number;\n output_tokens: number;\n cache_read_input_tokens: number;\n cost_usd: number;\n message_count: number;\n }>;\n tokensBySession(range: TimeRange): Array<{\n session_id: string;\n project_key: string;\n input_tokens: number;\n output_tokens: number;\n cost_usd: number;\n message_count: number;\n duration_ms: number;\n }>;\n bucketed(range: TimeRange, bucket: Bucket, eventType: string, valueField?: string): Array<{ bucket: string; value: number }>;\n sessionDurations(range: TimeRange): Array<{\n session_id: string;\n project_key: string;\n duration_ms: number;\n }>;\n modelBreakdown(range: TimeRange): Array<{\n model: string;\n input_tokens: number;\n output_tokens: number;\n cost_usd: number;\n }>;\n personaBreakdown(range: TimeRange): Array<{\n agent: string;\n count: number;\n }>;\n cacheEfficiency(range: TimeRange): {\n total_input_tokens: number;\n cache_read_tokens: number;\n cache_hit_ratio: number;\n };\n}\n\nexport function createActivityEngine(filePath?: string): ActivityEngine {\n const target = filePath ?? DEFAULT_PATH;\n\n function getEvents(range: TimeRange, eventType?: string): PulseEvent[] {\n const events = readEvents(target, range);\n return eventType ? events.filter(e => e.event_type === eventType) : events;\n }\n\n return {\n computeSummary(range) {\n const events = readEvents(target, range);\n const sessions = events.filter(e => e.event_type === 'session_start');\n const messages = events.filter(e => e.event_type === 'message_completed');\n const endings = events.filter(e => e.event_type === 'session_end' || e.event_type === 'session_idle');\n\n const totalDuration = endings.reduce((s, e) => s + (Number(e.duration_ms) || 0), 0);\n\n return {\n total_cost_usd: messages.reduce((s, e) => s + (Number(e.total_cost_usd) || 0), 0),\n total_input_tokens: messages.reduce((s, e) => s + (Number(e.input_tokens) || 0), 0),\n total_output_tokens: messages.reduce((s, e) => s + (Number(e.output_tokens) || 0), 0),\n total_sessions: sessions.length,\n total_messages: messages.length,\n avg_session_duration_ms: endings.length > 0 ? totalDuration / endings.length : 0,\n };\n },\n\n tokensByProject(range) {\n const messages = getEvents(range, 'message_completed');\n const map = new Map<string, { project_key: string; project_dir: string; input_tokens: number; output_tokens: number; cache_read_input_tokens: number; cost_usd: number; message_count: number }>();\n for (const e of messages) {\n const key = e.project_key;\n const row = map.get(key) ?? { project_key: key, project_dir: e.project_dir, input_tokens: 0, output_tokens: 0, cache_read_input_tokens: 0, cost_usd: 0, message_count: 0 };\n row.input_tokens += Number(e.input_tokens) || 0;\n row.output_tokens += Number(e.output_tokens) || 0;\n row.cache_read_input_tokens += Number(e.cache_read_input_tokens) || 0;\n row.cost_usd += Number(e.total_cost_usd) || 0;\n row.message_count++;\n map.set(key, row);\n }\n return Array.from(map.values());\n },\n\n tokensBySession(range) {\n const messages = getEvents(range, 'message_completed');\n const map = new Map<string, { session_id: string; project_key: string; input_tokens: number; output_tokens: number; cost_usd: number; message_count: number; duration_ms: number }>();\n for (const e of messages) {\n const key = e.session_id;\n const row = map.get(key) ?? { session_id: key, project_key: e.project_key, input_tokens: 0, output_tokens: 0, cost_usd: 0, message_count: 0, duration_ms: 0 };\n row.input_tokens += Number(e.input_tokens) || 0;\n row.output_tokens += Number(e.output_tokens) || 0;\n row.cost_usd += Number(e.total_cost_usd) || 0;\n row.duration_ms += Number(e.duration_ms) || 0;\n row.message_count++;\n map.set(key, row);\n }\n return Array.from(map.values());\n },\n\n bucketed(range, bucket, eventType, valueField) {\n const events = getEvents(range, eventType);\n const map = new Map<string, number>();\n for (const e of events) {\n const key = bucketKey(e.timestamp, bucket);\n const val = valueField ? (Number(e[valueField]) || 0) : 1;\n map.set(key, (map.get(key) ?? 0) + val);\n }\n return Array.from(map.entries())\n .sort((a, b) => a[0].localeCompare(b[0]))\n .map(([b, value]) => ({ bucket: b, value }));\n },\n\n sessionDurations(range) {\n const endings = getEvents(range).filter(e => e.event_type === 'session_end' || e.event_type === 'session_idle');\n return endings.map(e => ({\n session_id: e.session_id,\n project_key: e.project_key,\n duration_ms: Number(e.duration_ms) || 0,\n }));\n },\n\n modelBreakdown(range) {\n const messages = getEvents(range, 'message_completed');\n const map = new Map<string, { model: string; input_tokens: number; output_tokens: number; cost_usd: number }>();\n for (const e of messages) {\n const model = String(e.model ?? 'unknown');\n const row = map.get(model) ?? { model, input_tokens: 0, output_tokens: 0, cost_usd: 0 };\n row.input_tokens += Number(e.input_tokens) || 0;\n row.output_tokens += Number(e.output_tokens) || 0;\n row.cost_usd += Number(e.total_cost_usd) || 0;\n map.set(model, row);\n }\n return Array.from(map.values());\n },\n\n personaBreakdown(range) {\n const routed = getEvents(range, 'message_routed');\n const map = new Map<string, number>();\n for (const e of routed) {\n const agent = String(e.agent_target ?? 'default');\n map.set(agent, (map.get(agent) ?? 0) + 1);\n }\n return Array.from(map.entries()).map(([agent, count]) => ({ agent, count }));\n },\n\n cacheEfficiency(range) {\n const messages = getEvents(range, 'message_completed');\n const totalInput = messages.reduce((s, e) => s + (Number(e.input_tokens) || 0), 0);\n const cacheRead = messages.reduce((s, e) => s + (Number(e.cache_read_input_tokens) || 0), 0);\n return {\n total_input_tokens: totalInput,\n cache_read_tokens: cacheRead,\n cache_hit_ratio: totalInput > 0 ? cacheRead / totalInput : 0,\n };\n },\n };\n}\n","import { createServer, type Server } from 'node:http';\nimport { readFileSync } from 'node:fs';\nimport type { SessionManager } from './session-manager.js';\nimport type { DiscordBot } from './discord.js';\nimport type { GatewayConfig } from './config.js';\nimport type { ActivityEngine, TimeRange, Bucket } from './activity-engine.js';\n\nexport interface HealthServer {\n close(): Promise<void>;\n}\n\nfunction getVersion(): string {\n try {\n const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));\n return pkg.version ?? 'unknown';\n } catch {\n return 'unknown';\n }\n}\n\nexport interface HealthServerOptions {\n activityEngine?: ActivityEngine;\n}\n\nfunction buildDashboardHtml(): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>MPG Dashboard</title>\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js\"></script>\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f1117; color: #e1e4e8; padding: 24px; }\n h1 { font-size: 20px; font-weight: 600; margin-bottom: 4px; }\n .subtitle { color: #8b949e; font-size: 13px; margin-bottom: 8px; }\n .tabs { display: flex; gap: 8px; margin-bottom: 24px; }\n .tab { background: #161b22; border: 1px solid #30363d; border-radius: 6px; color: #8b949e; padding: 8px 16px; cursor: pointer; font-size: 14px; }\n .tab.active { color: #e1e4e8; border-color: #58a6ff; background: #1c2333; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; }\n .card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; }\n .card-label { font-size: 12px; color: #8b949e; text-transform: uppercase; letter-spacing: 0.5px; }\n .card-value { font-size: 28px; font-weight: 700; margin-top: 4px; }\n .status-ok { color: #3fb950; }\n .status-warn { color: #d29922; }\n .status-err { color: #f85149; }\n h2 { font-size: 16px; font-weight: 600; margin-bottom: 12px; }\n h3 { font-size: 14px; font-weight: 600; margin-bottom: 8px; color: #8b949e; }\n table { width: 100%; border-collapse: collapse; background: #161b22; border: 1px solid #30363d; border-radius: 8px; overflow: hidden; margin-bottom: 24px; }\n th { text-align: left; padding: 10px 14px; font-size: 12px; color: #8b949e; text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid #30363d; }\n td { padding: 10px 14px; font-size: 14px; border-bottom: 1px solid #21262d; }\n tr:last-child td { border-bottom: none; }\n .empty { color: #8b949e; font-style: italic; padding: 24px; text-align: center; }\n .refresh-info { color: #484f58; font-size: 12px; text-align: right; }\n .range-selector { display: flex; gap: 8px; margin-bottom: 16px; }\n .range-btn { background: #161b22; border: 1px solid #30363d; border-radius: 4px; color: #8b949e; padding: 6px 12px; cursor: pointer; font-size: 13px; }\n .range-btn.active { color: #e1e4e8; border-color: #58a6ff; }\n .chart-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 16px; margin-bottom: 24px; }\n .chart-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; }\n .chart-card h3 { margin-bottom: 12px; }\n .summary-cards { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; margin-bottom: 24px; }\n .summary-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; text-align: center; }\n .summary-value { font-size: 24px; font-weight: bold; color: #e1e4e8; }\n .summary-label { font-size: 12px; color: #8b949e; margin-top: 4px; }\n</style>\n</head>\n<body>\n<h1>Multi-Project Gateway</h1>\n<p class=\"subtitle\" id=\"version\"></p>\n<div class=\"tabs\">\n <button class=\"tab active\" onclick=\"switchTab('overview')\">Overview</button>\n <button class=\"tab\" onclick=\"switchTab('activity')\">Activity</button>\n</div>\n\n<div id=\"tab-overview\">\n<div class=\"grid\">\n <div class=\"card\">\n <div class=\"card-label\">Status</div>\n <div class=\"card-value\" id=\"status\">—</div>\n </div>\n <div class=\"card\">\n <div class=\"card-label\">Uptime</div>\n <div class=\"card-value\" id=\"uptime\">—</div>\n </div>\n <div class=\"card\">\n <div class=\"card-label\">Active Sessions</div>\n <div class=\"card-value\" id=\"sessions-active\">—</div>\n </div>\n <div class=\"card\">\n <div class=\"card-label\">Queued Messages</div>\n <div class=\"card-value\" id=\"sessions-queued\">—</div>\n </div>\n <div class=\"card\">\n <div class=\"card-label\">Discord</div>\n <div class=\"card-value\" id=\"discord\">—</div>\n </div>\n</div>\n\n<h2>Sessions</h2>\n<div id=\"sessions-table\"></div>\n\n<h2>Projects</h2>\n<div id=\"projects-table\"></div>\n\n<p class=\"refresh-info\">Auto-refreshes every 5s</p>\n</div>\n\n<div id=\"tab-activity\" style=\"display:none\">\n <div class=\"range-selector\">\n <button class=\"range-btn active\" data-range=\"24h\">24h</button>\n <button class=\"range-btn\" data-range=\"7d\">7d</button>\n <button class=\"range-btn\" data-range=\"30d\">30d</button>\n </div>\n <div class=\"summary-cards\">\n <div class=\"summary-card\"><div class=\"summary-value\" id=\"total-cost\">$0.00</div><div class=\"summary-label\">Total Cost</div></div>\n <div class=\"summary-card\"><div class=\"summary-value\" id=\"total-tokens\">0</div><div class=\"summary-label\">Total Tokens</div></div>\n <div class=\"summary-card\"><div class=\"summary-value\" id=\"total-sessions-card\">0</div><div class=\"summary-label\">Sessions</div></div>\n <div class=\"summary-card\"><div class=\"summary-value\" id=\"total-messages\">0</div><div class=\"summary-label\">Messages</div></div>\n <div class=\"summary-card\"><div class=\"summary-value\" id=\"avg-duration\">0m</div><div class=\"summary-label\">Avg Duration</div></div>\n </div>\n <div class=\"chart-grid\">\n <div class=\"chart-card\"><h3>Messages Over Time</h3><canvas id=\"messages-chart\"></canvas></div>\n <div class=\"chart-card\"><h3>Cost Over Time</h3><canvas id=\"cost-chart\"></canvas></div>\n <div class=\"chart-card\"><h3>Sessions Over Time</h3><canvas id=\"sessions-chart\"></canvas></div>\n <div class=\"chart-card\"><h3>Token Usage Over Time</h3><canvas id=\"tokens-chart\"></canvas></div>\n <div class=\"chart-card\"><h3>Persona Breakdown</h3><canvas id=\"persona-chart\"></canvas></div>\n <div class=\"chart-card\"><h3>Model Breakdown</h3><canvas id=\"model-chart\"></canvas></div>\n </div>\n <h3 style=\"margin:16px 0 8px\">Token Usage by Project</h3>\n <div id=\"project-table\"></div>\n <h3 style=\"margin:16px 0 8px\">Token Usage by Session</h3>\n <div id=\"session-table\"></div>\n <h3 style=\"margin:16px 0 8px\">Cache Efficiency</h3>\n <div id=\"cache-table\"></div>\n</div>\n\n<script>\nfunction formatUptime(s) {\n if (s < 60) return s + 's';\n if (s < 3600) return Math.floor(s / 60) + 'm';\n var h = Math.floor(s / 3600);\n var m = Math.floor((s % 3600) / 60);\n return h + 'h ' + m + 'm';\n}\nfunction formatAgo(ts) {\n var diff = Math.floor((Date.now() - ts) / 1000);\n if (diff < 60) return diff + 's ago';\n if (diff < 3600) return Math.floor(diff / 60) + 'm ago';\n return Math.floor(diff / 3600) + 'h ago';\n}\nfunction statusClass(v) {\n if (v === 'ok' || v === 'connected') return 'status-ok';\n if (v === 'reconnecting') return 'status-warn';\n return 'status-err';\n}\nfunction escapeHtml(s) {\n var d = document.createElement('div');\n d.textContent = s;\n return d.innerHTML;\n}\n\nfunction refresh() {\n fetch('/api/status')\n .then(function(r) { return r.json(); })\n .then(function(d) {\n document.getElementById('version').textContent = 'v' + d.version;\n var statusEl = document.getElementById('status');\n statusEl.textContent = d.health.status;\n statusEl.className = 'card-value ' + statusClass(d.health.status);\n document.getElementById('uptime').textContent = formatUptime(d.health.uptime);\n document.getElementById('sessions-active').textContent = d.health.sessions.active;\n document.getElementById('sessions-queued').textContent = d.health.sessions.queued;\n var discordEl = document.getElementById('discord');\n discordEl.textContent = d.health.discord;\n discordEl.className = 'card-value ' + statusClass(d.health.discord);\n\n // Sessions table\n var st = document.getElementById('sessions-table');\n if (d.sessions.length === 0) {\n st.innerHTML = '<div class=\"empty\">No active sessions</div>';\n } else {\n var h = '<table><tr><th>Project</th><th>Session ID</th><th>Last Activity</th><th>Queue</th></tr>';\n for (var i = 0; i < d.sessions.length; i++) {\n var s = d.sessions[i];\n h += '<tr><td>' + escapeHtml(s.projectKey) + '</td><td>' + escapeHtml(s.sessionId ? s.sessionId.slice(0, 12) + '...' : '—') + '</td><td>' + formatAgo(s.lastActivity) + '</td><td>' + s.queueLength + '</td></tr>';\n }\n h += '</table>';\n st.innerHTML = h;\n }\n\n // Projects table\n var pt = document.getElementById('projects-table');\n if (d.projects.length === 0) {\n pt.innerHTML = '<div class=\"empty\">No projects configured</div>';\n } else {\n var h2 = '<table><tr><th>Name</th><th>Directory</th><th>Agents</th></tr>';\n for (var j = 0; j < d.projects.length; j++) {\n var p = d.projects[j];\n h2 += '<tr><td>' + escapeHtml(p.name) + '</td><td>' + escapeHtml(p.directory) + '</td><td>' + escapeHtml(p.agents.join(', ') || '—') + '</td></tr>';\n }\n h2 += '</table>';\n pt.innerHTML = h2;\n }\n })\n .catch(function() {\n document.getElementById('status').textContent = 'error';\n document.getElementById('status').className = 'card-value status-err';\n });\n}\n\nrefresh();\nsetInterval(refresh, 5000);\n\nvar chartInstances = {};\nvar currentRange = '7d';\nvar CHART_COLORS = ['#58a6ff', '#3fb950', '#d29922', '#f85149', '#bc8cff', '#79c0ff'];\n\nfunction switchTab(tab) {\n document.querySelectorAll('.tab').forEach(function(t) { t.classList.remove('active'); });\n document.querySelectorAll('.tab').forEach(function(t) {\n if (t.textContent.toLowerCase() === tab) t.classList.add('active');\n });\n document.getElementById('tab-overview').style.display = tab === 'overview' ? '' : 'none';\n document.getElementById('tab-activity').style.display = tab === 'activity' ? '' : 'none';\n if (tab === 'activity') refreshActivity();\n}\n\ndocument.querySelectorAll('.range-btn').forEach(function(btn) {\n btn.addEventListener('click', function() {\n document.querySelectorAll('.range-btn').forEach(function(b) { b.classList.remove('active'); });\n btn.classList.add('active');\n currentRange = btn.dataset.range;\n refreshActivity();\n });\n});\n\nfunction destroyChart(key) {\n if (chartInstances[key]) { chartInstances[key].destroy(); chartInstances[key] = null; }\n}\n\nfunction refreshActivity() {\n fetch('/api/activity/summary?range=' + currentRange)\n .then(function(r) { return r.json(); })\n .then(function(d) {\n // Summary cards\n var s = d.summary;\n document.getElementById('total-cost').textContent = '$' + s.total_cost_usd.toFixed(2);\n var totalTok = s.total_input_tokens + s.total_output_tokens;\n document.getElementById('total-tokens').textContent = totalTok > 1e6 ? (totalTok / 1e6).toFixed(1) + 'M' : totalTok > 1e3 ? (totalTok / 1e3).toFixed(1) + 'k' : String(totalTok);\n document.getElementById('total-sessions-card').textContent = String(s.total_sessions);\n document.getElementById('total-messages').textContent = String(s.total_messages);\n document.getElementById('avg-duration').textContent = Math.round(s.avg_session_duration_ms / 60000) + 'm';\n\n // Messages Over Time (bar)\n destroyChart('messages');\n chartInstances['messages'] = new Chart(document.getElementById('messages-chart'), {\n type: 'bar',\n data: { labels: d.messages_over_time.map(function(e) { return e.bucket; }), datasets: [{ label: 'Messages', data: d.messages_over_time.map(function(e) { return e.value; }), backgroundColor: '#58a6ff' }] },\n options: { scales: { y: { beginAtZero: true, ticks: { color: '#8b949e' }, grid: { color: '#30363d' } }, x: { ticks: { color: '#8b949e' }, grid: { color: '#30363d' } } }, plugins: { legend: { display: false } } }\n });\n\n // Cost Over Time (line)\n destroyChart('cost');\n chartInstances['cost'] = new Chart(document.getElementById('cost-chart'), {\n type: 'line',\n data: { labels: d.cost_over_time.map(function(e) { return e.bucket; }), datasets: [{ label: 'Cost ($)', data: d.cost_over_time.map(function(e) { return e.value; }), borderColor: '#3fb950', tension: 0.3 }] },\n options: { scales: { y: { beginAtZero: true, ticks: { color: '#8b949e' }, grid: { color: '#30363d' } }, x: { ticks: { color: '#8b949e' }, grid: { color: '#30363d' } } }, plugins: { legend: { display: false } } }\n });\n\n // Sessions Over Time (bar)\n destroyChart('sessions');\n chartInstances['sessions'] = new Chart(document.getElementById('sessions-chart'), {\n type: 'bar',\n data: { labels: d.sessions_over_time.map(function(e) { return e.bucket; }), datasets: [{ label: 'Sessions', data: d.sessions_over_time.map(function(e) { return e.value; }), backgroundColor: '#d29922' }] },\n options: { scales: { y: { beginAtZero: true, ticks: { color: '#8b949e', stepSize: 1 }, grid: { color: '#30363d' } }, x: { ticks: { color: '#8b949e' }, grid: { color: '#30363d' } } }, plugins: { legend: { display: false } } }\n });\n\n // Token Usage Over Time (stacked bar)\n destroyChart('tokens');\n chartInstances['tokens'] = new Chart(document.getElementById('tokens-chart'), {\n type: 'bar',\n data: { labels: d.tokens_over_time.map(function(e) { return e.bucket; }), datasets: [{ label: 'Input Tokens', data: d.tokens_over_time.map(function(e) { return e.value; }), backgroundColor: '#58a6ff' }] },\n options: { scales: { y: { beginAtZero: true, ticks: { color: '#8b949e' }, grid: { color: '#30363d' } }, x: { ticks: { color: '#8b949e' }, grid: { color: '#30363d' } } }, plugins: { legend: { labels: { color: '#8b949e' } } } }\n });\n\n // Persona Breakdown (doughnut)\n destroyChart('persona');\n if (d.persona_breakdown.length > 0) {\n chartInstances['persona'] = new Chart(document.getElementById('persona-chart'), {\n type: 'doughnut',\n data: { labels: d.persona_breakdown.map(function(p) { return p.agent; }), datasets: [{ data: d.persona_breakdown.map(function(p) { return p.count; }), backgroundColor: CHART_COLORS.slice(0, d.persona_breakdown.length) }] },\n options: { plugins: { legend: { labels: { color: '#8b949e' } } } }\n });\n }\n\n // Model Breakdown (doughnut)\n destroyChart('model');\n if (d.model_breakdown.length > 0) {\n chartInstances['model'] = new Chart(document.getElementById('model-chart'), {\n type: 'doughnut',\n data: { labels: d.model_breakdown.map(function(m) { return m.model; }), datasets: [{ data: d.model_breakdown.map(function(m) { return m.cost_usd; }), backgroundColor: CHART_COLORS.slice(0, d.model_breakdown.length) }] },\n options: { plugins: { legend: { labels: { color: '#8b949e' } } } }\n });\n }\n\n // Token Usage by Project table\n var pt = document.getElementById('project-table');\n if (d.tokens_by_project.length === 0) { pt.innerHTML = '<div class=\"empty\">No data</div>'; }\n else {\n var h = '<table><tr><th>Project</th><th>Input</th><th>Output</th><th>Cache Read</th><th>Cost</th><th>Messages</th></tr>';\n d.tokens_by_project.forEach(function(p) { h += '<tr><td>' + escapeHtml(p.project_key) + '</td><td>' + p.input_tokens.toLocaleString() + '</td><td>' + p.output_tokens.toLocaleString() + '</td><td>' + p.cache_read_input_tokens.toLocaleString() + '</td><td>$' + p.cost_usd.toFixed(3) + '</td><td>' + p.message_count + '</td></tr>'; });\n pt.innerHTML = h + '</table>';\n }\n\n // Token Usage by Session table\n var st = document.getElementById('session-table');\n if (d.tokens_by_session.length === 0) { st.innerHTML = '<div class=\"empty\">No data</div>'; }\n else {\n var h2 = '<table><tr><th>Session</th><th>Project</th><th>Input</th><th>Output</th><th>Cost</th><th>Msgs</th><th>Duration</th></tr>';\n d.tokens_by_session.forEach(function(row) { h2 += '<tr><td>' + escapeHtml(row.session_id.substring(0, 8)) + '</td><td>' + escapeHtml(row.project_key) + '</td><td>' + row.input_tokens.toLocaleString() + '</td><td>' + row.output_tokens.toLocaleString() + '</td><td>$' + row.cost_usd.toFixed(3) + '</td><td>' + row.message_count + '</td><td>' + Math.round(row.duration_ms / 60000) + 'm</td></tr>'; });\n st.innerHTML = h2 + '</table>';\n }\n\n // Cache Efficiency table\n var ct = document.getElementById('cache-table');\n var ce = d.cache_efficiency;\n ct.innerHTML = '<table><tr><th>Total Input</th><th>Cache Read</th><th>Hit Ratio</th></tr><tr><td>' + ce.total_input_tokens.toLocaleString() + '</td><td>' + ce.cache_read_tokens.toLocaleString() + '</td><td>' + (ce.cache_hit_ratio * 100).toFixed(1) + '%</td></tr></table>';\n })\n .catch(function(err) { console.error('Activity fetch error:', err); });\n}\n\nsetInterval(function() {\n if (document.getElementById('tab-activity').style.display !== 'none') {\n refreshActivity();\n }\n}, 30000);\n</script>\n</body>\n</html>`;\n}\n\nexport function createHealthServer(\n port: number,\n sessionManager: SessionManager,\n bot: DiscordBot,\n config?: GatewayConfig,\n options?: HealthServerOptions,\n): Promise<HealthServer> {\n const startTime = Date.now();\n const version = getVersion();\n const dashboardHtml = buildDashboardHtml();\n function getHealthData() {\n const sessions = sessionManager.listSessions();\n return {\n status: 'ok',\n uptime: Math.floor((Date.now() - startTime) / 1000),\n sessions: {\n active: sessions.length,\n queued: sessions.reduce((sum, s) => sum + s.queueLength, 0),\n },\n discord: bot.getStatus(),\n };\n }\n\n function getProjectsData() {\n if (!config) return [];\n return Object.entries(config.projects).map(([channelId, project]) => ({\n channelId,\n name: project.name,\n directory: project.directory,\n agents: project.agents ? Object.keys(project.agents) : [],\n }));\n }\n\n const server: Server = createServer((req, res) => {\n const { pathname } = new URL(req.url ?? '/', `http://localhost`);\n\n if (req.method !== 'GET') {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Not Found' }));\n return;\n }\n\n if (pathname === '/health') {\n const body = JSON.stringify(getHealthData());\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(body);\n return;\n }\n\n if (pathname === '/api/sessions') {\n const body = JSON.stringify(sessionManager.listSessions());\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(body);\n return;\n }\n\n if (pathname === '/api/projects') {\n const body = JSON.stringify(getProjectsData());\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(body);\n return;\n }\n\n if (pathname === '/api/status') {\n const body = JSON.stringify({\n version,\n health: getHealthData(),\n sessions: sessionManager.listSessions(),\n projects: getProjectsData(),\n });\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(body);\n return;\n }\n\n if (pathname === '/api/activity/summary') {\n const url = new URL(req.url ?? '/', `http://localhost`);\n const rangeParam = url.searchParams.get('range') || '7d';\n if (rangeParam !== '24h' && rangeParam !== '7d' && rangeParam !== '30d') {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid range. Must be 24h, 7d, or 30d' }));\n return;\n }\n const range: TimeRange = rangeParam;\n const bucket: Bucket = range === '24h' ? 'hour' : 'day';\n const engine = options?.activityEngine;\n\n if (!engine) {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({\n summary: { total_cost_usd: 0, total_input_tokens: 0, total_output_tokens: 0, total_sessions: 0, total_messages: 0, avg_session_duration_ms: 0 },\n tokens_by_project: [], tokens_by_session: [],\n sessions_over_time: [], messages_over_time: [], cost_over_time: [], tokens_over_time: [],\n session_durations: [], model_breakdown: [], persona_breakdown: [],\n cache_efficiency: { total_input_tokens: 0, cache_read_tokens: 0, cache_hit_ratio: 0 },\n }));\n return;\n }\n\n try {\n const data = {\n summary: engine.computeSummary(range),\n tokens_by_project: engine.tokensByProject(range),\n tokens_by_session: engine.tokensBySession(range),\n sessions_over_time: engine.bucketed(range, bucket, 'session_start'),\n messages_over_time: engine.bucketed(range, bucket, 'message_completed'),\n cost_over_time: engine.bucketed(range, bucket, 'message_completed', 'total_cost_usd'),\n tokens_over_time: engine.bucketed(range, bucket, 'message_completed', 'input_tokens'),\n session_durations: engine.sessionDurations(range),\n model_breakdown: engine.modelBreakdown(range),\n persona_breakdown: engine.personaBreakdown(range),\n cache_efficiency: engine.cacheEfficiency(range),\n };\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(data));\n } catch {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Failed to compute activity data' }));\n }\n return;\n }\n\n if (pathname === '/') {\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(dashboardHtml);\n return;\n }\n\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Not Found' }));\n });\n\n return new Promise<HealthServer>((resolve, reject) => {\n server.on('error', reject);\n server.listen(port, () => {\n console.log(`Health endpoint listening on http://localhost:${port}/health`);\n resolve({\n close() {\n return new Promise<void>((res, rej) => {\n server.close((err) => (err ? rej(err) : res()));\n });\n },\n });\n });\n });\n}\n","// src/turn-counter.ts\nexport interface TurnCounter {\n increment(threadId: string): void;\n getTurns(threadId: string): number;\n isOverLimit(threadId: string, limit: number): boolean;\n reset(threadId: string): void;\n}\n\nexport function createTurnCounter(): TurnCounter {\n const turns = new Map<string, number>();\n\n return {\n increment(threadId: string): void {\n turns.set(threadId, (turns.get(threadId) ?? 0) + 1);\n },\n\n getTurns(threadId: string): number {\n return turns.get(threadId) ?? 0;\n },\n\n isOverLimit(threadId: string, limit: number): boolean {\n return (turns.get(threadId) ?? 0) >= limit;\n },\n\n reset(threadId: string): void {\n turns.delete(threadId);\n },\n };\n}\n","import { createInterface } from 'node:readline';\nimport { writeFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { resolveMpgHome, resolveProfileDir } from './resolve-home.js';\n\nfunction createPrompt(): (question: string) => Promise<string> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return (question: string) =>\n new Promise((resolve) => rl.question(question, (answer) => resolve(answer.trim())));\n}\n\nexport async function runInit(profile?: string) {\n const ask = createPrompt();\n\n // Determine output directory: if --profile is given, use MPG_HOME/profiles/<name>;\n // otherwise, use CWD for backward compat (quick single-instance setup).\n let configDir: string;\n let envDir: string;\n if (profile) {\n configDir = resolveProfileDir(profile);\n envDir = resolveMpgHome();\n mkdirSync(configDir, { recursive: true });\n mkdirSync(envDir, { recursive: true });\n console.log(`\\nmpg init — set up profile \"${profile}\"\\n`);\n console.log(`Profile directory: ${configDir}`);\n console.log(`Secrets directory: ${envDir}\\n`);\n } else {\n configDir = process.cwd();\n envDir = process.cwd();\n console.log('\\nmpg init — set up multi-project gateway\\n');\n }\n\n // Check for claude CLI\n try {\n execSync('claude --version', { stdio: 'pipe' });\n console.log('Claude CLI found.');\n } catch {\n console.warn('Warning: `claude` not found on PATH. Make sure it is installed before starting the gateway.');\n }\n\n // Discord bot token\n let token = process.env.DISCORD_BOT_TOKEN ?? '';\n const inputToken = await ask(`Discord bot token${token ? ' (press Enter to keep existing)' : ''}: `);\n if (inputToken) token = inputToken;\n if (!token) {\n console.error('A Discord bot token is required. Create one at https://discord.com/developers/applications');\n process.exit(1);\n }\n\n // Write .env\n const envPath = resolve(envDir, '.env');\n writeFileSync(envPath, `DISCORD_BOT_TOKEN=${token}\\n`);\n console.log(`Wrote ${envPath}`);\n\n // Collect projects\n interface ProjectEntry {\n name: string;\n directory: string;\n channelId: string;\n }\n\n const projects: ProjectEntry[] = [];\n\n // Load existing config if present\n const configPath = resolve(configDir, 'config.json');\n if (existsSync(configPath)) {\n try {\n const existing = JSON.parse(\n (await import('node:fs')).readFileSync(configPath, 'utf-8'),\n );\n if (existing.projects) {\n for (const [channelId, project] of Object.entries(existing.projects)) {\n const p = project as { name?: string; directory: string };\n projects.push({ name: p.name ?? channelId, directory: p.directory, channelId });\n }\n if (projects.length > 0) {\n console.log(`\\nExisting projects (${projects.length}):`);\n for (const p of projects) {\n console.log(` ${p.name} → ${p.directory} (${p.channelId})`);\n }\n }\n }\n } catch {\n // ignore parse errors\n }\n }\n\n console.log('\\nAdd projects (empty name to finish):\\n');\n\n while (true) {\n const name = await ask('Project name: ');\n if (!name) break;\n\n const directory = await ask('Project directory (absolute path): ');\n if (!directory) {\n console.log('Directory is required, skipping.');\n continue;\n }\n if (!existsSync(directory)) {\n console.warn(`Warning: ${directory} does not exist.`);\n }\n\n const channelId = await ask('Discord channel ID: ');\n if (!channelId) {\n console.log('Channel ID is required, skipping.');\n continue;\n }\n\n projects.push({ name, directory, channelId });\n console.log(`Added ${name}\\n`);\n }\n\n if (projects.length === 0) {\n console.log('No projects configured. You can edit config.json later.');\n }\n\n // Build config\n const config = {\n defaults: {\n idleTimeoutMs: 1800000,\n maxConcurrentSessions: 4,\n claudeArgs: ['--permission-mode', 'acceptEdits', '--output-format', 'json'],\n },\n projects: Object.fromEntries(\n projects.map((p) => [p.channelId, { name: p.name, directory: p.directory }]),\n ),\n };\n\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n console.log(`Wrote ${configPath}`);\n\n console.log('\\nSetup complete! Run `mpg start` to launch the gateway.');\n}\n","import { resolve, dirname } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\n\n/**\n * Returns the MPG home directory.\n * Priority: MPG_HOME env var > ~/.mpg\n */\nexport function resolveMpgHome(): string {\n return process.env.MPG_HOME ?? resolve(homedir(), '.mpg');\n}\n\n/**\n * Returns the profile directory for a given profile name.\n */\nexport function resolveProfileDir(profile: string): string {\n return resolve(resolveMpgHome(), 'profiles', profile);\n}\n\nexport interface ResolvedPaths {\n envPath: string | undefined;\n configPath: string;\n sessionsPath: string;\n}\n\n/**\n * Resolve .env path using the resolution order:\n * 1. Environment variables (already set) — highest priority (returns undefined, already loaded)\n * 2. $MPG_HOME/.env\n * 3. $CWD/.env — backward compat\n */\nexport function resolveEnvPath(): string | undefined {\n const mpgHome = resolveMpgHome();\n const mpgEnv = resolve(mpgHome, '.env');\n if (existsSync(mpgEnv)) {\n return mpgEnv;\n }\n\n const cwdEnv = resolve(process.cwd(), '.env');\n if (existsSync(cwdEnv)) {\n return cwdEnv;\n }\n\n return undefined;\n}\n\n/**\n * Resolve config.json path using the resolution order:\n * 1. --config <path> CLI flag (explicit path)\n * 2. --profile <name> → $MPG_HOME/profiles/<name>/config.json\n * 3. $MPG_HOME/profiles/default/config.json\n * 4. $CWD/config.json — backward compat fallback\n *\n * Returns the resolved config path, or undefined if none found.\n */\nexport function resolveConfigPath(options?: {\n configFlag?: string;\n profileFlag?: string;\n}): string | undefined {\n // 1. Explicit --config flag\n if (options?.configFlag) {\n const explicit = resolve(options.configFlag);\n if (existsSync(explicit)) {\n return explicit;\n }\n return explicit; // Return even if missing — let caller handle error\n }\n\n // 2. --profile flag\n if (options?.profileFlag) {\n const profileConfig = resolve(\n resolveProfileDir(options.profileFlag),\n 'config.json',\n );\n if (existsSync(profileConfig)) {\n return profileConfig;\n }\n return profileConfig; // Return even if missing — let caller handle error\n }\n\n // 3. MPG_HOME/profiles/default/config.json\n const mpgHome = resolveMpgHome();\n const defaultConfig = resolve(mpgHome, 'profiles', 'default', 'config.json');\n if (existsSync(defaultConfig)) {\n return defaultConfig;\n }\n\n // 4. CWD/config.json — backward compat\n const cwdConfig = resolve(process.cwd(), 'config.json');\n if (existsSync(cwdConfig)) {\n return cwdConfig;\n }\n\n return undefined;\n}\n\n/**\n * Resolve sessions.json path — always co-located with the resolved config.json.\n */\nexport function resolveSessionsPath(configPath: string): string {\n return resolve(dirname(configPath), 'sessions.json');\n}\n\n/**\n * Resolve PID file path.\n * Default profile or 'default' → mpg.pid\n * Named profile → mpg-<profile>.pid\n */\nexport function resolvePidPath(profile?: string): string {\n const name = !profile || profile === 'default' ? 'mpg' : `mpg-${profile}`;\n return resolve(resolveMpgHome(), `${name}.pid`);\n}\n\n/**\n * Resolve log directory path.\n */\nexport function resolveLogDir(): string {\n return resolve(resolveMpgHome(), 'logs');\n}\n\n/**\n * Resolve log file path.\n * Default profile or 'default' → mpg.log\n * Named profile → mpg-<profile>.log\n */\nexport function resolveLogPath(profile?: string): string {\n const name = !profile || profile === 'default' ? 'mpg' : `mpg-${profile}`;\n return resolve(resolveLogDir(), `${name}.log`);\n}\n\n/**\n * Parse --config, --profile flags from argv.\n */\nexport function parseFlags(argv: string[]): {\n configFlag?: string;\n profileFlag?: string;\n migrate?: boolean;\n project?: string;\n level?: string;\n follow?: boolean;\n} {\n const result: { configFlag?: string; profileFlag?: string; migrate?: boolean; project?: string; level?: string; follow?: boolean } = {};\n\n for (let i = 0; i < argv.length; i++) {\n if (argv[i] === '--config' && i + 1 < argv.length) {\n result.configFlag = argv[i + 1];\n i++;\n } else if (argv[i] === '--profile' && i + 1 < argv.length) {\n result.profileFlag = argv[i + 1];\n i++;\n } else if (argv[i] === '--project' && i + 1 < argv.length) {\n result.project = argv[i + 1];\n i++;\n } else if (argv[i] === '--level' && i + 1 < argv.length) {\n result.level = argv[i + 1];\n i++;\n } else if (argv[i] === '--migrate') {\n result.migrate = true;\n } else if (argv[i] === '--follow' || argv[i] === '-f') {\n result.follow = true;\n }\n }\n\n return result;\n}\n","import { statSync } from 'node:fs';\nimport { execFileSync } from 'node:child_process';\nimport type { GatewayConfig } from './config.js';\n\nexport function runHealthChecks(config: GatewayConfig): void {\n // 1. Check claude CLI is on PATH\n try {\n execFileSync('claude', ['--version'], { timeout: 5000, stdio: 'ignore' });\n } catch {\n console.error(\n 'Health check failed:\\n' +\n ' ✗ \"claude\" CLI not found on PATH. Install: https://docs.anthropic.com/en/docs/claude-code'\n );\n process.exit(1);\n return;\n }\n\n // 2. Check all project directories exist and are directories\n const missing: string[] = [];\n for (const project of Object.values(config.projects)) {\n try {\n if (!statSync(project.directory).isDirectory()) {\n missing.push(` ✗ Project \"${project.name}\" path is not a directory: ${project.directory}`);\n }\n } catch {\n missing.push(` ✗ Project \"${project.name}\" directory not found: ${project.directory}`);\n }\n }\n\n if (missing.length > 0) {\n console.error('Health check failed:\\n' + missing.join('\\n'));\n process.exit(1);\n }\n}\n","import { readFileSync, writeFileSync, unlinkSync, mkdirSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\nexport function writePid(pidPath: string, pid: number = process.pid): void {\n mkdirSync(dirname(pidPath), { recursive: true });\n writeFileSync(pidPath, `${pid}\\n`);\n}\n\nexport function readPid(pidPath: string): number | undefined {\n try {\n const content = readFileSync(pidPath, 'utf-8').trim();\n const pid = Number(content);\n return Number.isFinite(pid) && pid > 0 ? pid : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function removePid(pidPath: string): void {\n try {\n unlinkSync(pidPath);\n } catch {\n // Ignore — file may already be gone\n }\n}\n\nexport function isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nexport type PidStatus =\n | { status: 'none' }\n | { status: 'running'; pid: number }\n | { status: 'stale'; pid: number };\n\nexport function checkPidFile(pidPath: string): PidStatus {\n const pid = readPid(pidPath);\n if (pid === undefined) {\n return { status: 'none' };\n }\n\n if (isProcessRunning(pid)) {\n return { status: 'running', pid };\n }\n\n // Stale PID file — remove it\n removePid(pidPath);\n return { status: 'stale', pid };\n}\n","import { appendFileSync, renameSync, unlinkSync, existsSync, mkdirSync, statSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\nconst DEFAULT_MAX_BYTES = 10 * 1024 * 1024; // 10 MB\nconst DEFAULT_MAX_FILES = 5;\n\n/**\n * Rotate a log file. Shifts .1 → .2 → ... → .maxFiles, then renames current → .1.\n * Files beyond maxFiles are deleted.\n */\nexport function rotateLog(logPath: string, maxFiles: number = DEFAULT_MAX_FILES): void {\n if (!existsSync(logPath)) return;\n\n // Delete the oldest if it would exceed maxFiles\n const oldest = `${logPath}.${maxFiles}`;\n if (existsSync(oldest)) {\n try { unlinkSync(oldest); } catch { /* ignore */ }\n }\n\n // Shift .N-1 → .N, .N-2 → .N-1, ...\n for (let i = maxFiles - 1; i >= 1; i--) {\n const from = `${logPath}.${i}`;\n const to = `${logPath}.${i + 1}`;\n if (existsSync(from)) {\n try { renameSync(from, to); } catch { /* ignore */ }\n }\n }\n\n // Rename current → .1\n try { renameSync(logPath, `${logPath}.1`); } catch { /* ignore */ }\n}\n\n/**\n * Create a writer function that appends lines to a log file.\n * Automatically rotates when the file exceeds maxBytes.\n */\nexport function createFileWriter(\n logPath: string,\n opts?: { maxBytes?: number; maxFiles?: number },\n): (line: string) => void {\n const maxBytes = opts?.maxBytes ?? DEFAULT_MAX_BYTES;\n const maxFiles = opts?.maxFiles ?? DEFAULT_MAX_FILES;\n let currentBytes = 0;\n\n // Ensure log directory exists\n const dir = dirname(logPath);\n mkdirSync(dir, { recursive: true });\n\n // Track current file size\n try {\n currentBytes = statSync(logPath).size;\n } catch {\n currentBytes = 0;\n }\n\n return (line: string) => {\n const data = line + '\\n';\n const byteLength = Buffer.byteLength(data);\n\n if (currentBytes + byteLength > maxBytes && currentBytes > 0) {\n rotateLog(logPath, maxFiles);\n currentBytes = 0;\n }\n\n appendFileSync(logPath, data);\n currentBytes += byteLength;\n };\n}\n","import { resolve } from 'node:path';\nimport { homedir } from 'node:os';\nimport { mkdirSync, writeFileSync, unlinkSync, existsSync } from 'node:fs';\nimport { execFileSync, execSync } from 'node:child_process';\nimport { unitFileName, generateUnitFile } from './systemd.js';\nimport { resolveLogPath } from './resolve-home.js';\n\nexport function resolveServiceDir(): string {\n return resolve(homedir(), '.config', 'systemd', 'user');\n}\n\nexport function resolveServicePath(profile?: string): string {\n return resolve(resolveServiceDir(), unitFileName(profile));\n}\n\nexport function daemonInstall(profile?: string): void {\n const serviceDir = resolveServiceDir();\n mkdirSync(serviceDir, { recursive: true });\n\n const nodePath = process.execPath;\n const mpgPath = resolveOwnBinary();\n\n const unit = generateUnitFile({ nodePath, mpgPath, profile });\n const servicePath = resolveServicePath(profile);\n writeFileSync(servicePath, unit);\n\n const name = unitFileName(profile);\n\n // Enable lingering so service runs without login session\n try {\n execFileSync('loginctl', ['enable-linger'], { stdio: 'ignore' });\n } catch {\n // Non-fatal — may already be enabled or not available\n }\n\n execFileSync('systemctl', ['--user', 'daemon-reload'], { stdio: 'inherit' });\n execFileSync('systemctl', ['--user', 'enable', '--now', name], { stdio: 'inherit' });\n\n console.log(`Installed and started ${name}`);\n console.log(` Unit file: ${servicePath}`);\n console.log(` Status: systemctl --user status ${name}`);\n console.log(` Logs: mpg daemon logs`);\n}\n\nexport function daemonUninstall(profile?: string): void {\n const name = unitFileName(profile);\n const servicePath = resolveServicePath(profile);\n\n try {\n execFileSync('systemctl', ['--user', 'stop', name], { stdio: 'inherit' });\n } catch {\n // May not be running\n }\n try {\n execFileSync('systemctl', ['--user', 'disable', name], { stdio: 'inherit' });\n } catch {\n // May not be enabled\n }\n\n if (existsSync(servicePath)) {\n unlinkSync(servicePath);\n }\n\n try {\n execFileSync('systemctl', ['--user', 'daemon-reload'], { stdio: 'inherit' });\n } catch {\n // Best effort\n }\n\n console.log(`Uninstalled ${name}`);\n}\n\nexport function daemonStatus(profile?: string): void {\n const name = unitFileName(profile);\n try {\n execFileSync('systemctl', ['--user', 'status', name], { stdio: 'inherit' });\n } catch {\n // systemctl status exits non-zero when service is stopped — that's OK\n }\n}\n\nexport function daemonLogs(profile?: string, follow?: boolean): void {\n const name = unitFileName(profile);\n const args = ['--user', '-u', name, '--no-pager'];\n if (follow) args.push('-f');\n try {\n execFileSync('journalctl', args, { stdio: 'inherit' });\n } catch {\n console.error(`journalctl not available. View logs at: ${resolveLogPath(profile)}`);\n }\n}\n\nfunction resolveOwnBinary(): string {\n // Try to find the mpg binary path\n try {\n const which = execSync('which mpg', { encoding: 'utf-8' }).trim();\n if (which) return which;\n } catch {\n // Fall through\n }\n\n // Fallback: use absolute path to current script\n const fallback = resolve(process.argv[1] ?? '.');\n console.warn(`Warning: 'mpg' not found on PATH. Using ${fallback} in service file.`);\n return fallback;\n}\n","import { dirname } from 'node:path';\n\nexport function unitFileName(profile?: string): string {\n if (!profile || profile === 'default') return 'mpg.service';\n return `mpg-${profile}.service`;\n}\n\nexport interface UnitFileOptions {\n nodePath: string;\n mpgPath: string;\n profile?: string;\n}\n\nexport function generateUnitFile(opts: UnitFileOptions): string {\n const profileArg = opts.profile && opts.profile !== 'default'\n ? ` --profile ${opts.profile}`\n : '';\n\n const nodeDir = dirname(opts.nodePath);\n const mpgDir = dirname(opts.mpgPath);\n const pathDirs = new Set([nodeDir, mpgDir, '/usr/local/bin', '/usr/bin', '/bin']);\n const pathValue = [...pathDirs].join(':');\n\n return `[Unit]\nDescription=Multi-Project Gateway for Claude Code\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nExecStart=${opts.mpgPath} start${profileArg}\nRestart=on-failure\nRestartSec=10\nEnvironment=PATH=${pathValue}\nEnvironment=NODE_ENV=production\n\n[Install]\nWantedBy=default.target\n`;\n}\n"],"mappings":"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A;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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","names":["resolve","existsSync","readFileSync","mkdirSync","args","resolve","resolve","worktreePath","readFileSync","join","command","info","idle","sid","join","readFileSync","parentId","resolved","projectChannelId","project","agents","mkdirSync","dirname","join","readFileSync","existsSync","join","homedir","DEFAULT_PATH","readFileSync","version","resolve","writeFileSync","existsSync","mkdirSync","resolve","dirname","existsSync","homedir","resolve","mkdirSync","writeFileSync","existsSync","execFileSync","readFileSync","writeFileSync","mkdirSync","dirname","appendFileSync","unlinkSync","existsSync","mkdirSync","statSync","dirname","resolve","homedir","mkdirSync","writeFileSync","unlinkSync","existsSync","execFileSync","execSync","dirname","resolve","homedir","mkdirSync","writeFileSync","execFileSync","existsSync","unlinkSync","args","execSync","readFileSync","existsSync","resolve","mkdirSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/persona-presets.ts","../src/logger.ts","../src/config.ts","../src/router.ts","../src/session-manager.ts","../src/worktree.ts","../src/attachments.ts","../src/session-store.ts","../src/claude-cli.ts","../src/runtimes/claude-cli-runtime.ts","../src/runtimes/tmux-runtime.ts","../src/tmux.ts","../src/discord.ts","../src/agent-dispatch.ts","../src/embed-format.ts","../src/role-check.ts","../src/rate-limiter.ts","../src/pulse-events.ts","../src/activity-engine.ts","../src/dashboard-server.ts","../src/turn-counter.ts","../src/init.ts","../src/resolve-home.ts","../src/health.ts","../src/pid.ts","../src/file-logger.ts","../src/daemon.ts","../src/systemd.ts"],"sourcesContent":["import { resolve, dirname } from 'node:path';\nimport { existsSync, readFileSync, copyFileSync, mkdirSync } from 'node:fs';\nimport { config as loadEnv } from 'dotenv';\nimport { loadConfig } from './config.js';\nimport { createRouter } from './router.js';\nimport { createSessionManager } from './session-manager.js';\nimport { createFileSessionStore } from './session-store.js';\nimport { ClaudeCliRuntime } from './runtimes/claude-cli-runtime.js';\nimport { TmuxRuntime } from './runtimes/tmux-runtime.js';\nimport type { AgentRuntime } from './agent-runtime.js';\nimport { listSessions, killSession } from './tmux.js';\nimport { createDiscordBot } from './discord.js';\nimport { createPulseEmitter } from './pulse-events.js';\nimport { createActivityEngine } from './activity-engine.js';\nimport { createDashboardServer, type DashboardServer } from './dashboard-server.js';\nimport { createTurnCounter } from './turn-counter.js';\nimport { runInit } from './init.js';\nimport { runHealthChecks } from './health.js';\nimport { reconcileWorktrees } from './worktree.js';\nimport { reconcileAttachments } from './attachments.js';\nimport { checkPidFile, writePid, removePid } from './pid.js';\nimport { createFileWriter } from './file-logger.js';\nimport { daemonInstall, daemonUninstall, daemonStatus, daemonLogs } from './daemon.js';\nimport {\n resolveEnvPath,\n resolveConfigPath,\n resolveSessionsPath,\n resolveMpgHome,\n resolveProfileDir,\n resolvePidPath,\n resolveLogPath,\n parseFlags,\n} from './resolve-home.js';\nimport { createLogger, parseLogEntry, filterLogEntries, type LogLevel, isValidLogLevel } from './logger.js';\n\nconst args = process.argv.slice(2);\nconst command = args[0] ?? 'start';\nconst flags = parseFlags(args.slice(1));\n\nasync function main() {\n switch (command) {\n case 'start':\n return start();\n case 'init':\n if (flags.migrate) {\n return migrate();\n }\n return runInit(flags.profileFlag);\n case 'status':\n return status();\n case 'stop':\n return stop();\n case 'daemon':\n return daemon();\n case 'logs':\n return logs();\n case 'help':\n case '--help':\n case '-h':\n return help();\n case '--version':\n case '-v':\n return version();\n default:\n console.error(`Unknown command: ${command}`);\n help();\n process.exit(1);\n }\n}\n\nfunction help() {\n console.log(`\nmpg — multi-project gateway for Claude Code\n\nUsage: mpg <command>\n\nCommands:\n start Start the gateway (default)\n stop Stop a running gateway instance\n init Interactive setup wizard\n status Show session status\n logs Filter structured log output (reads stdin)\n daemon install Install systemd user service\n daemon uninstall Remove systemd user service\n daemon status Show systemd service status\n daemon logs Show service logs (journalctl)\n help Show this message\n\nOptions:\n --profile <name> Use a named profile (default: \"default\")\n --config <path> Use a specific config.json path\n --migrate Copy CWD config files into ~/.mpg/profiles/default/\n --project <name> (logs) Filter by project name\n --level <level> (logs) Filter by minimum log level (debug|info|warn|error)\n --follow, -f (daemon logs) Follow log output\n -v, --version Show version\n -h, --help Show this message\n\nEnvironment:\n MPG_HOME Override config home (default: ~/.mpg)\n`.trim());\n}\n\nfunction version() {\n const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));\n console.log(`mpg v${pkg.version}`);\n}\n\nfunction start() {\n // Load .env using resolution order\n const envPath = resolveEnvPath();\n if (envPath) {\n loadEnv({ path: envPath });\n }\n\n const token = process.env.DISCORD_BOT_TOKEN;\n if (!token) {\n console.error('DISCORD_BOT_TOKEN is not set. Run `mpg init` or set it in .env');\n process.exit(1);\n }\n\n // Check for existing instance\n const pidPath = resolvePidPath(flags.profileFlag);\n const pidCheck = checkPidFile(pidPath);\n if (pidCheck.status === 'running') {\n console.error(`MPG is already running (PID ${pidCheck.pid}). Use \\`mpg stop\\` first.`);\n process.exit(1);\n }\n writePid(pidPath);\n\n const configPath = resolveConfigPath({\n configFlag: flags.configFlag,\n profileFlag: flags.profileFlag,\n });\n if (!configPath || !existsSync(configPath)) {\n console.error('config.json not found. Run `mpg init` to create one.');\n process.exit(1);\n }\n\n const rawConfig = JSON.parse(readFileSync(configPath, 'utf-8'));\n const config = loadConfig(rawConfig);\n\n const logPath = resolveLogPath(flags.profileFlag);\n const fileWriter = createFileWriter(logPath);\n const log = createLogger(config.defaults.logLevel, (line: string) => {\n fileWriter(line);\n process.stderr.write(line + '\\n');\n });\n\n const projectCount = Object.keys(config.projects).length;\n if (projectCount === 0) {\n console.error('No projects configured in config.json');\n process.exit(1);\n }\n\n runHealthChecks(config);\n\n log.info(`Loaded ${projectCount} project(s) from ${configPath}`);\n\n const router = createRouter(config);\n const sessionsPath = resolveSessionsPath(configPath);\n const sessionStore = createFileSessionStore(sessionsPath);\n const pulseEmitter = createPulseEmitter();\n let runtime: AgentRuntime;\n if (config.defaults.persistence === 'tmux') {\n runtime = new TmuxRuntime();\n log.info('Using tmux-based persistent runtime');\n } else {\n runtime = new ClaudeCliRuntime();\n\n // Sweep stale tmux sessions from a previous tmux-mode run\n try {\n const stale = listSessions('mpg-');\n for (const name of stale) {\n killSession(name);\n }\n if (stale.length > 0) {\n log.info(`Cleaned up ${stale.length} stale tmux session(s)`);\n }\n } catch {\n // tmux not installed — nothing to sweep\n }\n }\n const sessionManager = createSessionManager(config.defaults, runtime, sessionStore, pulseEmitter);\n\n // Reconcile orphaned worktrees from crashed sessions\n const persistedSessions = sessionStore.load();\n const knownKeysByProject = new Map<string, Set<string>>();\n for (const [key, entry] of persistedSessions) {\n if (entry.projectDir) {\n let keys = knownKeysByProject.get(entry.projectDir);\n if (!keys) {\n keys = new Set();\n knownKeysByProject.set(entry.projectDir, keys);\n }\n keys.add(key);\n }\n }\n for (const [projectDir, keys] of knownKeysByProject) {\n reconcileWorktrees(projectDir, keys);\n }\n\n // Remove orphaned attachment directories from all project directories (#117)\n const seenDirs = new Set<string>();\n for (const project of Object.values(config.projects)) {\n if (seenDirs.has(project.directory)) continue;\n seenDirs.add(project.directory);\n reconcileAttachments(project.directory).then((removed) => {\n if (removed) log.info(`Removed orphaned attachments in ${project.directory}`);\n }).catch(() => {});\n }\n\n const turnCounter = createTurnCounter();\n const bot = createDiscordBot(router, sessionManager, config, turnCounter);\n\n let dashboardServer: DashboardServer | undefined;\n\n function shutdown() {\n log.info('Shutting down...');\n removePid(pidPath);\n if (dashboardServer) {\n dashboardServer.close().catch(() => {});\n }\n sessionManager.shutdown();\n bot.stop();\n process.exit(0);\n }\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n\n bot.start(token)\n .then(async () => {\n if (config.defaults.httpPort !== false) {\n try {\n const activityEngine = createActivityEngine();\n dashboardServer = await createDashboardServer(config.defaults.httpPort, sessionManager, bot, config, { activityEngine });\n } catch (err) {\n log.warn(`Dashboard server failed to start on port ${config.defaults.httpPort}: ${err}`);\n }\n }\n\n // Recover orphaned tmux sessions after Discord is connected\n sessionManager.recoverOrphanedSessions((projectKey, result) => {\n bot.deliverOrphanResult(projectKey, result).catch((err) => {\n log.error(`Failed to deliver orphan result for ${projectKey}: ${err}`);\n });\n }).catch((err) => {\n log.error(`Orphan session recovery failed: ${err}`);\n });\n })\n .catch((err) => {\n log.error(`Failed to start bot: ${err}`);\n process.exit(1);\n });\n}\n\nfunction status() {\n const configPath = resolveConfigPath({\n configFlag: flags.configFlag,\n profileFlag: flags.profileFlag,\n });\n\n const sessionsPath = configPath\n ? resolveSessionsPath(configPath)\n : resolve(process.cwd(), '.sessions.json');\n\n if (!existsSync(sessionsPath)) {\n console.log('No sessions file found. Is the gateway running?');\n return;\n }\n\n let projectNames: Record<string, string> = {};\n if (configPath && existsSync(configPath)) {\n try {\n const raw = JSON.parse(readFileSync(configPath, 'utf-8'));\n const config = loadConfig(raw);\n for (const [channelId, project] of Object.entries(config.projects)) {\n projectNames[channelId] = project.name;\n }\n } catch {\n // ignore config errors for status\n }\n }\n\n const sessions = JSON.parse(readFileSync(sessionsPath, 'utf-8')) as Array<{\n sessionId: string;\n projectKey: string;\n cwd: string;\n lastActivity: number;\n }>;\n\n if (sessions.length === 0) {\n console.log('No active sessions.');\n return;\n }\n\n console.log(`Sessions (${sessions.length}):\\n`);\n for (const s of sessions) {\n const name = projectNames[s.projectKey] ?? s.projectKey;\n const ago = Math.floor((Date.now() - s.lastActivity) / 60000);\n console.log(` ${name}`);\n console.log(` Session: ${s.sessionId}`);\n console.log(` Dir: ${s.cwd}`);\n console.log(` Idle: ${ago}m`);\n console.log();\n }\n}\n\nfunction migrate() {\n const mpgHome = resolveMpgHome();\n const profileDir = resolveProfileDir('default');\n\n const cwdEnv = resolve(process.cwd(), '.env');\n const cwdConfig = resolve(process.cwd(), 'config.json');\n const cwdSessions = resolve(process.cwd(), '.sessions.json');\n\n const copied: string[] = [];\n\n // Create directories\n mkdirSync(profileDir, { recursive: true });\n\n // Copy .env to MPG_HOME root\n if (existsSync(cwdEnv)) {\n const dest = resolve(mpgHome, '.env');\n copyFileSync(cwdEnv, dest);\n copied.push(` ${cwdEnv} → ${dest}`);\n }\n\n // Copy config.json to profile dir\n if (existsSync(cwdConfig)) {\n const dest = resolve(profileDir, 'config.json');\n copyFileSync(cwdConfig, dest);\n copied.push(` ${cwdConfig} → ${dest}`);\n }\n\n // Copy sessions.json to profile dir\n if (existsSync(cwdSessions)) {\n const dest = resolve(profileDir, 'sessions.json');\n copyFileSync(cwdSessions, dest);\n copied.push(` ${cwdSessions} → ${dest}`);\n }\n\n if (copied.length === 0) {\n console.log('No config files found in current directory. Nothing to migrate.');\n return;\n }\n\n console.log(`Migrated files to ${mpgHome}:\\n`);\n for (const line of copied) {\n console.log(line);\n }\n console.log(`\\nProfile directory: ${profileDir}`);\n console.log('You can now run `mpg start` from any directory.');\n}\n\nfunction stop() {\n const pidPath = resolvePidPath(flags.profileFlag);\n const check = checkPidFile(pidPath);\n\n if (check.status === 'none') {\n console.log('No running MPG instance found.');\n return;\n }\n\n if (check.status === 'stale') {\n console.log(`Removed stale PID file (process ${check.pid} was not running).`);\n return;\n }\n\n const { pid } = check;\n console.log(`Sending SIGTERM to MPG (PID ${pid})...`);\n\n try {\n process.kill(pid, 'SIGTERM');\n } catch (err) {\n console.error(`Failed to send signal: ${err}`);\n process.exit(1);\n }\n\n // Wait up to 10 seconds for graceful shutdown\n const deadline = Date.now() + 10_000;\n const poll = setInterval(() => {\n try {\n process.kill(pid, 0);\n // Still running\n if (Date.now() > deadline) {\n clearInterval(poll);\n console.log('Graceful shutdown timed out. Sending SIGKILL...');\n try {\n process.kill(pid, 'SIGKILL');\n } catch { /* ignore */ }\n removePid(pidPath);\n console.log('Killed.');\n process.exit(0);\n }\n } catch {\n // Process is gone\n clearInterval(poll);\n removePid(pidPath);\n console.log('Stopped.');\n process.exit(0);\n }\n }, 200);\n}\n\nfunction daemon() {\n const subcommand = args[1];\n const daemonFlags = parseFlags(args.slice(2));\n const profile = daemonFlags.profileFlag ?? flags.profileFlag;\n\n switch (subcommand) {\n case 'install':\n return daemonInstall(profile);\n case 'uninstall':\n return daemonUninstall(profile);\n case 'status':\n return daemonStatus(profile);\n case 'logs':\n return daemonLogs(profile, daemonFlags.follow);\n default:\n console.error(`Unknown daemon subcommand: ${subcommand}`);\n console.error('Usage: mpg daemon <install|uninstall|status|logs>');\n process.exit(1);\n }\n}\n\nfunction logs() {\n const levelFlag = flags.level;\n const projectFlag = flags.project;\n\n if (levelFlag && !isValidLogLevel(levelFlag)) {\n console.error(`Invalid log level: ${levelFlag}. Must be one of: debug, info, warn, error`);\n process.exit(1);\n }\n\n const minLevel = levelFlag as LogLevel | undefined;\n\n let buffer = '';\n process.stdin.setEncoding('utf-8');\n process.stdin.on('data', (chunk: string) => {\n buffer += chunk;\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (!line.trim()) continue;\n const entry = parseLogEntry(line);\n if (!entry) {\n // Pass through non-JSON lines as-is\n process.stdout.write(line + '\\n');\n continue;\n }\n const [filtered] = filterLogEntries([entry], { project: projectFlag, level: minLevel });\n if (filtered) {\n const ts = entry.timestamp.replace('T', ' ').replace('Z', '');\n const proj = entry.project ? ` [${entry.project}]` : '';\n const sess = entry.session ? ` (${entry.session.slice(0, 8)})` : '';\n process.stdout.write(`${ts} ${entry.level.toUpperCase().padEnd(5)}${proj}${sess} ${entry.message}\\n`);\n }\n }\n });\n\n process.stdin.on('end', () => {\n if (buffer.trim()) {\n const entry = parseLogEntry(buffer);\n if (!entry) {\n process.stdout.write(buffer + '\\n');\n } else {\n const [filtered] = filterLogEntries([entry], { project: projectFlag, level: minLevel });\n if (filtered) {\n const ts = entry.timestamp.replace('T', ' ').replace('Z', '');\n const proj = entry.project ? ` [${entry.project}]` : '';\n const sess = entry.session ? ` (${entry.session.slice(0, 8)})` : '';\n process.stdout.write(`${ts} ${entry.level.toUpperCase().padEnd(5)}${proj}${sess} ${entry.message}\\n`);\n }\n }\n }\n });\n}\n\nmain().catch((err) => {\n console.error(err);\n process.exit(1);\n});\n","import type { AgentConfig } from './config.js';\n\nexport const PERSONA_PRESETS: Record<string, AgentConfig> = {\n pm: {\n role: 'Product Manager',\n prompt: [\n 'You are a Product Manager working in a multi-agent Discord thread.',\n 'Your responsibilities:',\n '- Clarify requirements and acceptance criteria before handing work to engineers.',\n '- Break down features into concrete, actionable tasks.',\n '- Prioritize work based on user impact and feasibility.',\n '- Ask clarifying questions when requirements are ambiguous.',\n '- Summarize decisions and next steps clearly.',\n '',\n 'Communication style: concise, structured, and action-oriented.',\n '',\n 'CRITICAL — Handing off work to other agents:',\n '- To dispatch work, write HANDOFF @engineer: followed by the task description.',\n '- Only use HANDOFF when you are ready to dispatch work NOW, not when describing future plans.',\n '- The gateway routes your HANDOFF to that agent automatically.',\n '- Do NOT use the Agent tool to do engineering work yourself. You are a PM, not an engineer.',\n '- Do NOT implement code, run tests, or create PRs yourself.',\n '- After writing HANDOFF, END your response. The engineer will reply in the same thread.',\n '- Example: \"HANDOFF @engineer: Please implement feature X. Requirements: ...\"',\n '',\n 'IMPORTANT — Referring to other agents without dispatching:',\n '- To reference another agent conversationally, say \"the engineer\" or \"the PM\" — never write @agent outside of a HANDOFF command.',\n '- Writing @engineer without HANDOFF will NOT dispatch work and the engineer will never see it.',\n ].join('\\n'),\n },\n\n engineer: {\n role: 'Software Engineer',\n prompt: [\n 'You are a Software Engineer working in a multi-agent Discord thread.',\n 'Your responsibilities:',\n '- Write clean, well-tested code that meets the requirements.',\n '- Follow existing project conventions and patterns.',\n '- Consider edge cases, error handling, and performance.',\n '- Explain technical trade-offs when relevant.',\n '- Ask for clarification when requirements are unclear rather than guessing.',\n '',\n 'Communication style: precise and technical, but accessible to non-engineers.',\n '',\n 'When you finish your work, report what you did (files changed, tests, PR link if created).',\n 'If you need the PM to review or approve, write HANDOFF @pm: followed by your update.',\n 'Example: \"HANDOFF @pm: Implementation complete. PR #42 is ready for review.\"',\n '',\n 'IMPORTANT — Referring to other agents without dispatching:',\n '- To reference another agent conversationally, say \"the PM\" or \"the designer\" — never write @agent outside of a HANDOFF command.',\n '- Writing @pm without HANDOFF will NOT dispatch and the PM will never see it.',\n ].join('\\n'),\n },\n\n qa: {\n role: 'QA Engineer',\n prompt: [\n 'You are a QA Engineer.',\n 'Your responsibilities:',\n '- Review code and features for correctness, edge cases, and regressions.',\n '- Write and suggest test cases covering happy paths and failure modes.',\n '- Verify that acceptance criteria from the PM are met.',\n '- Report issues clearly with steps to reproduce.',\n '- Think adversarially — try to break things.',\n '',\n 'Communication style: thorough, detail-oriented, and evidence-based.',\n '',\n 'To dispatch work to another agent, write HANDOFF @agent: followed by the task.',\n 'To reference another agent conversationally, say \"the engineer\" or \"the PM\" — never write @agent outside of a HANDOFF command.',\n ].join('\\n'),\n },\n\n designer: {\n role: 'Designer',\n prompt: [\n 'You are a Designer.',\n 'Your responsibilities:',\n '- Propose UI/UX solutions that are intuitive and consistent.',\n '- Consider accessibility, responsiveness, and user flows.',\n '- Provide clear specifications for engineers to implement.',\n '- Challenge assumptions about user needs when appropriate.',\n '',\n 'Communication style: visual-thinking, user-centric, and practical.',\n '',\n 'To dispatch work to another agent, write HANDOFF @agent: followed by the task.',\n 'To reference another agent conversationally, say \"the engineer\" or \"the PM\" — never write @agent outside of a HANDOFF command.',\n ].join('\\n'),\n },\n\n devops: {\n role: 'DevOps Engineer',\n prompt: [\n 'You are a DevOps Engineer.',\n 'Your responsibilities:',\n '- Manage infrastructure, CI/CD pipelines, and deployment processes.',\n '- Ensure reliability, monitoring, and observability.',\n '- Advise on architecture decisions that affect operability.',\n '- Automate repetitive operational tasks.',\n '',\n 'Communication style: systematic, risk-aware, and automation-focused.',\n '',\n 'To dispatch work to another agent, write HANDOFF @agent: followed by the task.',\n 'To reference another agent conversationally, say \"the engineer\" or \"the PM\" — never write @agent outside of a HANDOFF command.',\n ].join('\\n'),\n },\n};\n\nexport function resolvePreset(presetName: string): AgentConfig | undefined {\n return PERSONA_PRESETS[presetName.toLowerCase()];\n}\n","export type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nconst LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\nexport interface LogEntry {\n timestamp: string;\n level: LogLevel;\n message: string;\n project?: string;\n session?: string;\n}\n\nexport interface Logger {\n debug(message: string, ctx?: { project?: string; session?: string }): void;\n info(message: string, ctx?: { project?: string; session?: string }): void;\n warn(message: string, ctx?: { project?: string; session?: string }): void;\n error(message: string, ctx?: { project?: string; session?: string }): void;\n}\n\nexport function shouldLog(entryLevel: LogLevel, minLevel: LogLevel): boolean {\n return LEVEL_ORDER[entryLevel] >= LEVEL_ORDER[minLevel];\n}\n\nexport function formatLogEntry(entry: LogEntry): string {\n return JSON.stringify(entry);\n}\n\nexport function parseLogEntry(line: string): LogEntry | null {\n try {\n const parsed = JSON.parse(line);\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n typeof parsed.timestamp === 'string' &&\n typeof parsed.level === 'string' &&\n typeof parsed.message === 'string' &&\n parsed.level in LEVEL_ORDER\n ) {\n return parsed as LogEntry;\n }\n return null;\n } catch {\n return null;\n }\n}\n\nexport function filterLogEntries(\n entries: LogEntry[],\n opts?: { project?: string; level?: LogLevel },\n): LogEntry[] {\n return entries.filter((entry) => {\n if (opts?.level && !shouldLog(entry.level, opts.level)) {\n return false;\n }\n if (opts?.project && entry.project !== opts.project) {\n return false;\n }\n return true;\n });\n}\n\nexport function createLogger(\n minLevel: LogLevel = 'info',\n writer: (line: string) => void = (line) => process.stderr.write(line + '\\n'),\n): Logger {\n function log(level: LogLevel, message: string, ctx?: { project?: string; session?: string }): void {\n if (!shouldLog(level, minLevel)) return;\n\n const entry: LogEntry = {\n timestamp: new Date().toISOString(),\n level,\n message,\n ...(ctx?.project && { project: ctx.project }),\n ...(ctx?.session && { session: ctx.session }),\n };\n\n writer(formatLogEntry(entry));\n }\n\n return {\n debug: (message, ctx) => log('debug', message, ctx),\n info: (message, ctx) => log('info', message, ctx),\n warn: (message, ctx) => log('warn', message, ctx),\n error: (message, ctx) => log('error', message, ctx),\n };\n}\n\nexport function isValidLogLevel(value: unknown): value is LogLevel {\n return typeof value === 'string' && value in LEVEL_ORDER;\n}\n","import { resolvePreset } from './persona-presets.js';\nimport { isValidLogLevel, type LogLevel } from './logger.js';\n\nexport interface AgentConfig {\n role: string;\n prompt: string;\n}\n\nexport interface AgentInputConfig {\n preset?: string;\n role?: string;\n prompt?: string;\n}\n\nexport interface ProjectConfig {\n name: string;\n directory: string;\n idleTimeoutMs?: number;\n claudeArgs?: string[];\n allowedTools?: string[];\n disallowedTools?: string[];\n agents?: Record<string, AgentConfig>;\n allowedRoles?: string[];\n rateLimitPerUser?: number;\n maxAttachmentSizeMb?: number;\n allowedMimeTypes?: string[];\n maxAttachmentsPerMessage?: number;\n}\n\nexport const DEFAULT_ALLOWED_TOOLS: string[] = [\n 'Read',\n 'Edit',\n 'Write',\n 'Glob',\n 'Grep',\n 'Bash(git:*)',\n 'TodoWrite',\n];\n\nexport type RuntimePersistence = 'direct' | 'tmux';\n\nexport interface GatewayDefaults {\n idleTimeoutMs: number;\n maxConcurrentSessions: number;\n sessionTtlMs: number;\n maxPersistedSessions: number;\n claudeArgs: string[];\n allowedTools: string[];\n disallowedTools: string[];\n maxTurnsPerAgent: number;\n agentTimeoutMs: number;\n stuckNotifyMs: number;\n httpPort: number | false;\n logLevel: LogLevel;\n maxAttachmentSizeMb: number;\n allowedMimeTypes: string[];\n maxAttachmentsPerMessage: number;\n persistence: RuntimePersistence;\n}\n\nexport interface GatewayConfig {\n defaults: GatewayDefaults;\n projects: Record<string, ProjectConfig>;\n}\n\nexport function loadConfig(raw: unknown): GatewayConfig {\n if (!raw || typeof raw !== 'object') {\n throw new Error('Config must be an object');\n }\n\n const obj = raw as Record<string, unknown>;\n\n if (!obj.projects || typeof obj.projects !== 'object') {\n throw new Error('Config must have a \"projects\" object');\n }\n\n const projects = obj.projects as Record<string, unknown>;\n const validated: Record<string, ProjectConfig> = {};\n\n for (const [channelId, project] of Object.entries(projects)) {\n if (!project || typeof project !== 'object') {\n throw new Error(`Project for channel ${channelId} must be an object`);\n }\n const p = project as Record<string, unknown>;\n if (typeof p.directory !== 'string' || !p.directory) {\n throw new Error(`Project for channel ${channelId} must have a \"directory\" string`);\n }\n let agents: Record<string, AgentConfig> | undefined;\n if (Array.isArray(p.agents)) {\n // Shorthand: [\"pm\", \"engineer\"] — resolve each as a preset\n agents = {};\n for (const entry of p.agents) {\n if (typeof entry === 'string') {\n const name = entry.toLowerCase();\n const preset = resolvePreset(name);\n if (preset) {\n agents[name] = { ...preset };\n }\n }\n }\n if (Object.keys(agents).length === 0) agents = undefined;\n } else if (p.agents && typeof p.agents === 'object') {\n agents = {};\n for (const [agentName, agentCfg] of Object.entries(p.agents as Record<string, unknown>)) {\n const ac = agentCfg as Record<string, unknown>;\n const name = agentName.toLowerCase();\n\n if (typeof ac.preset === 'string') {\n // Preset-based: resolve preset, then merge overrides\n const preset = resolvePreset(ac.preset);\n if (preset) {\n const role = typeof ac.role === 'string' ? ac.role : preset.role;\n const basePrompt = preset.prompt;\n const extra = typeof ac.prompt === 'string' ? ac.prompt : '';\n const prompt = extra ? `${basePrompt}\\n\\n${extra}` : basePrompt;\n agents[name] = { role, prompt };\n }\n } else if (typeof ac.role === 'string' && typeof ac.prompt === 'string') {\n // Inline: original behavior\n agents[name] = { role: ac.role, prompt: ac.prompt };\n }\n }\n if (Object.keys(agents).length === 0) agents = undefined;\n }\n\n const projectAllowed = Array.isArray(p.allowedTools) ? (p.allowedTools as string[]) : undefined;\n const projectDisallowed = Array.isArray(p.disallowedTools) ? (p.disallowedTools as string[]) : undefined;\n if (projectAllowed && projectDisallowed) {\n console.warn(`Warning: project \"${typeof p.name === 'string' ? p.name : channelId}\" sets both allowedTools and disallowedTools — they conflict. allowedTools takes precedence.`);\n }\n\n const allowedRoles = Array.isArray(p.allowedRoles) ? (p.allowedRoles as string[]).filter(r => typeof r === 'string') : undefined;\n const rateLimitPerUser = typeof p.rateLimitPerUser === 'number' && p.rateLimitPerUser > 0 ? p.rateLimitPerUser : undefined;\n\n validated[channelId] = {\n name: typeof p.name === 'string' ? p.name : channelId,\n directory: p.directory,\n ...(p.idleTimeoutMs !== undefined && { idleTimeoutMs: Number(p.idleTimeoutMs) }),\n ...(Array.isArray(p.claudeArgs) && { claudeArgs: p.claudeArgs as string[] }),\n ...(projectAllowed && { allowedTools: projectAllowed }),\n ...(projectDisallowed && { disallowedTools: projectDisallowed }),\n ...(agents && { agents }),\n ...(allowedRoles && allowedRoles.length > 0 && { allowedRoles }),\n ...(rateLimitPerUser !== undefined && { rateLimitPerUser }),\n ...(typeof p.maxAttachmentSizeMb === 'number' && { maxAttachmentSizeMb: p.maxAttachmentSizeMb }),\n ...(Array.isArray(p.allowedMimeTypes) && { allowedMimeTypes: p.allowedMimeTypes as string[] }),\n ...(typeof p.maxAttachmentsPerMessage === 'number' && { maxAttachmentsPerMessage: p.maxAttachmentsPerMessage }),\n };\n }\n\n const defaults = (obj.defaults ?? {}) as Record<string, unknown>;\n\n const defaultAllowed = Array.isArray(defaults.allowedTools) ? (defaults.allowedTools as string[]) : DEFAULT_ALLOWED_TOOLS;\n const defaultDisallowed = Array.isArray(defaults.disallowedTools) ? (defaults.disallowedTools as string[]) : [];\n if (Array.isArray(defaults.allowedTools) && Array.isArray(defaults.disallowedTools)) {\n console.warn('Warning: gateway defaults set both allowedTools and disallowedTools — they conflict. allowedTools takes precedence.');\n }\n\n return {\n defaults: {\n idleTimeoutMs: typeof defaults.idleTimeoutMs === 'number' ? defaults.idleTimeoutMs : 1800000,\n maxConcurrentSessions: typeof defaults.maxConcurrentSessions === 'number' ? defaults.maxConcurrentSessions : 4,\n sessionTtlMs: typeof defaults.sessionTtlMs === 'number' ? defaults.sessionTtlMs : 7 * 24 * 60 * 60 * 1000,\n maxPersistedSessions: typeof defaults.maxPersistedSessions === 'number' ? defaults.maxPersistedSessions : 50,\n claudeArgs: Array.isArray(defaults.claudeArgs) ? (defaults.claudeArgs as string[]) : ['--permission-mode', 'acceptEdits', '--output-format', 'json'],\n allowedTools: defaultAllowed,\n disallowedTools: defaultDisallowed,\n maxTurnsPerAgent: typeof defaults.maxTurnsPerAgent === 'number' ? defaults.maxTurnsPerAgent : 5,\n agentTimeoutMs: typeof defaults.agentTimeoutMs === 'number' ? defaults.agentTimeoutMs : 3 * 60 * 1000,\n stuckNotifyMs: typeof defaults.stuckNotifyMs === 'number' ? defaults.stuckNotifyMs : 300_000,\n httpPort: defaults.httpPort === false ? false : (typeof defaults.httpPort === 'number' ? defaults.httpPort : 3100),\n logLevel: isValidLogLevel(defaults.logLevel) ? defaults.logLevel : 'info',\n maxAttachmentSizeMb: typeof defaults.maxAttachmentSizeMb === 'number' ? defaults.maxAttachmentSizeMb : 10,\n allowedMimeTypes: Array.isArray(defaults.allowedMimeTypes) ? (defaults.allowedMimeTypes as string[]) : ['image/*', 'text/*', 'application/pdf', 'application/json'],\n maxAttachmentsPerMessage: typeof defaults.maxAttachmentsPerMessage === 'number' ? defaults.maxAttachmentsPerMessage : 5,\n persistence: defaults.persistence === 'tmux' ? 'tmux' : 'direct',\n },\n projects: validated,\n };\n}\n","import type { GatewayConfig, ProjectConfig } from './config.js';\n\nexport interface ResolvedProject {\n channelId: string;\n name: string;\n directory: string;\n isThread: boolean;\n}\n\nexport interface Router {\n resolve(channelId: string, parentChannelId?: string): ResolvedProject | null;\n}\n\nexport function createRouter(config: GatewayConfig): Router {\n return {\n resolve(channelId: string, parentChannelId?: string): ResolvedProject | null {\n const project = config.projects[channelId];\n if (project) {\n return { channelId, name: project.name, directory: project.directory, isThread: false };\n }\n\n if (parentChannelId) {\n const parentProject = config.projects[parentChannelId];\n if (parentProject) {\n return { channelId, name: parentProject.name, directory: parentProject.directory, isThread: true };\n }\n }\n\n return null;\n },\n };\n}\n","import { existsSync } from 'node:fs';\nimport type { ClaudeResult } from './claude-cli.js';\nimport type { AgentRuntime } from './agent-runtime.js';\nimport type { SessionStore, PersistedSession } from './session-store.js';\nimport type { PulseEmitter } from './pulse-events.js';\nimport { createWorktree as gitCreateWorktree, removeWorktree as gitRemoveWorktree } from './worktree.js';\nimport { cleanupAttachments } from './attachments.js';\n\nexport interface SessionInfo {\n sessionId: string;\n projectKey: string;\n cwd: string;\n projectDir?: string;\n lastActivity: number;\n queueLength: number;\n createdAt: number;\n processing: boolean;\n}\n\n/** Callback invoked when an orphaned tmux session produces a result on startup recovery. */\nexport type OrphanResultCallback = (projectKey: string, result: ClaudeResult) => void;\n\nexport interface SessionManager {\n send(projectKey: string, cwd: string, prompt: string, opts?: { worktree?: boolean; systemPrompt?: string; timeoutMs?: number; extraArgs?: string[] }): Promise<ClaudeResult>;\n getSession(projectKey: string): SessionInfo | undefined;\n listSessions(): SessionInfo[];\n clearSession(projectKey: string): boolean;\n restartSession(projectKey: string): boolean;\n shutdown(): void;\n /** Discover orphaned tmux sessions and reattach to them. Call after Discord bot is ready. */\n recoverOrphanedSessions(onResult: OrphanResultCallback): Promise<void>;\n}\n\ninterface InternalSession {\n sessionId: string | undefined;\n projectKey: string;\n cwd: string;\n projectDir: string | undefined;\n worktreePath: string | undefined;\n lastActivity: number;\n createdAt: number;\n messageCount: number;\n restored: boolean;\n resumeEmitted: boolean;\n processing: boolean;\n queue: Array<{\n prompt: string;\n systemPrompt?: string;\n timeoutMs?: number;\n extraArgs?: string[];\n resolve: (result: ClaudeResult) => void;\n reject: (error: Error) => void;\n }>;\n idleTimer: ReturnType<typeof setTimeout> | null;\n}\n\nexport function createSessionManager(defaults: {\n idleTimeoutMs: number;\n maxConcurrentSessions: number;\n sessionTtlMs?: number;\n maxPersistedSessions?: number;\n claudeArgs: string[];\n}, runtime: AgentRuntime, store?: SessionStore, pulseEmitter?: PulseEmitter): SessionManager {\n const sessions = new Map<string, InternalSession>();\n const sessionTtlMs = defaults.sessionTtlMs ?? 7 * 24 * 60 * 60 * 1000;\n const maxPersistedSessions = defaults.maxPersistedSessions ?? 50;\n\n let activeProcesses = 0;\n const waiters: Array<() => void> = [];\n\n function pruneSessions(persisted: Map<string, PersistedSession>): number {\n const now = Date.now();\n let pruned = 0;\n\n // Remove sessions older than TTL\n for (const [key, entry] of persisted) {\n if (now - entry.lastActivity > sessionTtlMs) {\n persisted.delete(key);\n pruned++;\n }\n }\n\n // Enforce cap: evict oldest if over limit\n if (persisted.size > maxPersistedSessions) {\n const sorted = Array.from(persisted.entries())\n .sort((a, b) => a[1].lastActivity - b[1].lastActivity);\n const toRemove = sorted.slice(0, persisted.size - maxPersistedSessions);\n for (const [key] of toRemove) {\n persisted.delete(key);\n pruned++;\n }\n }\n\n return pruned;\n }\n\n function persistSessions(): void {\n if (!store) return;\n // Merge in-memory sessions with existing persisted data.\n // In-memory sessions take precedence; persisted-only entries are preserved.\n const persisted = store.load();\n for (const [key, s] of sessions) {\n if (s.sessionId) {\n persisted.set(key, {\n sessionId: s.sessionId,\n projectKey: s.projectKey,\n cwd: s.cwd,\n lastActivity: s.lastActivity,\n worktreePath: s.worktreePath,\n projectDir: s.projectDir,\n });\n }\n }\n pruneSessions(persisted);\n store.save(persisted);\n }\n\n async function acquireSlot(): Promise<void> {\n if (activeProcesses < defaults.maxConcurrentSessions) {\n activeProcesses++;\n return;\n }\n return new Promise<void>((resolve) => {\n waiters.push(() => {\n activeProcesses++;\n resolve();\n });\n });\n }\n\n function releaseSlot(): void {\n activeProcesses--;\n const next = waiters.shift();\n if (next) next();\n }\n\n function resetIdleTimer(session: InternalSession) {\n if (session.idleTimer) clearTimeout(session.idleTimer);\n if (session.queue.length > 0) return;\n session.idleTimer = setTimeout(() => {\n if (pulseEmitter && session.sessionId) {\n pulseEmitter.sessionIdle(\n session.sessionId,\n session.projectKey,\n session.cwd,\n Date.now() - session.createdAt,\n session.messageCount,\n );\n }\n // Clean up attachment files for the session's working directory (#110)\n cleanupAttachments(session.projectDir ?? session.cwd).catch(() => {});\n if (runtime.cleanup) runtime.cleanup(session.projectKey);\n sessions.delete(session.projectKey);\n }, defaults.idleTimeoutMs);\n }\n\n async function processQueue(session: InternalSession): Promise<void> {\n if (session.processing || session.queue.length === 0) return;\n session.processing = true;\n\n while (session.queue.length > 0) {\n const item = session.queue.shift()!;\n const effectiveArgs = item.extraArgs ? [...defaults.claudeArgs, ...item.extraArgs] : defaults.claudeArgs;\n await acquireSlot();\n if (pulseEmitter) {\n // Session keys are \"threadId:agentName\" for agent sessions, \"threadId\" for default\n const agentTarget = session.projectKey.includes(':') ? session.projectKey.split(':').pop() : undefined;\n pulseEmitter.messageRouted(\n session.sessionId ?? session.projectKey,\n session.projectKey,\n session.cwd,\n { agentTarget, queueDepth: session.queue.length },\n );\n }\n try {\n const result = await runtime.spawn({\n cwd: session.cwd,\n baseArgs: effectiveArgs,\n prompt: item.prompt,\n sessionId: session.sessionId,\n systemPrompt: item.systemPrompt,\n timeoutMs: item.timeoutMs,\n projectKey: session.projectKey,\n });\n const sessionChanged = !!(\n session.sessionId &&\n result.sessionId &&\n result.sessionId !== session.sessionId\n );\n session.sessionId = result.sessionId || session.sessionId;\n session.lastActivity = Date.now();\n session.messageCount++;\n if (pulseEmitter && session.sessionId && result.usage) {\n const agentTarget = session.projectKey.includes(':') ? session.projectKey.split(':').pop() : undefined;\n pulseEmitter.messageCompleted(\n session.sessionId,\n session.projectKey,\n session.cwd,\n result.usage,\n { agentTarget },\n );\n }\n resetIdleTimer(session);\n persistSessions();\n if (sessionChanged) {\n item.resolve({ ...result, sessionChanged: true });\n } else {\n item.resolve(result);\n }\n } catch (err) {\n if (session.sessionId) {\n session.sessionId = undefined;\n try {\n const result = await runtime.spawn({ cwd: session.cwd, baseArgs: effectiveArgs, prompt: item.prompt, sessionId: undefined, systemPrompt: item.systemPrompt, timeoutMs: item.timeoutMs, projectKey: session.projectKey });\n session.sessionId = result.sessionId || undefined;\n session.lastActivity = Date.now();\n session.messageCount++;\n if (pulseEmitter && session.sessionId && result.usage) {\n const agentTarget = session.projectKey.includes(':') ? session.projectKey.split(':').pop() : undefined;\n pulseEmitter.messageCompleted(\n session.sessionId,\n session.projectKey,\n session.cwd,\n result.usage,\n { agentTarget },\n );\n }\n resetIdleTimer(session);\n persistSessions();\n item.resolve({ ...result, sessionReset: true });\n } catch (retryErr) {\n item.reject(retryErr instanceof Error ? retryErr : new Error(String(retryErr)));\n }\n } else {\n item.reject(err instanceof Error ? err : new Error(String(err)));\n }\n } finally {\n releaseSlot();\n }\n }\n\n session.processing = false;\n }\n\n function getOrCreateSession(projectKey: string, cwd: string, useWorktree?: boolean): InternalSession {\n let session = sessions.get(projectKey);\n if (session && session.restored && !session.resumeEmitted && pulseEmitter && session.sessionId) {\n session.resumeEmitted = true;\n pulseEmitter.sessionResume(\n session.sessionId,\n session.projectKey,\n session.cwd,\n Date.now() - session.lastActivity,\n );\n }\n if (!session) {\n // Check store for a previously persisted session ID\n let restoredSessionId: string | undefined;\n let restoredWorktreePath: string | undefined;\n let restoredLastActivity: number | undefined;\n if (store) {\n const persisted = store.load();\n const entry = persisted.get(projectKey);\n if (entry?.sessionId) {\n restoredSessionId = entry.sessionId;\n restoredWorktreePath = entry.worktreePath;\n restoredLastActivity = entry.lastActivity;\n }\n }\n\n let effectiveCwd = cwd;\n let worktreePath: string | undefined =\n restoredWorktreePath && existsSync(restoredWorktreePath)\n ? restoredWorktreePath\n : undefined;\n let projectDir: string | undefined;\n\n if (useWorktree && !worktreePath) {\n worktreePath = gitCreateWorktree(cwd, projectKey);\n }\n if (worktreePath) {\n projectDir = cwd;\n effectiveCwd = worktreePath;\n }\n\n session = {\n sessionId: restoredSessionId,\n projectKey,\n cwd: effectiveCwd,\n projectDir,\n worktreePath,\n lastActivity: Date.now(),\n createdAt: Date.now(),\n messageCount: 0,\n restored: !!restoredSessionId,\n resumeEmitted: false,\n processing: false,\n queue: [],\n idleTimer: null,\n };\n sessions.set(projectKey, session);\n resetIdleTimer(session);\n if (pulseEmitter) {\n if (restoredSessionId) {\n session.resumeEmitted = true;\n pulseEmitter.sessionResume(\n restoredSessionId,\n projectKey,\n effectiveCwd,\n Date.now() - (restoredLastActivity ?? Date.now()),\n );\n } else {\n pulseEmitter.sessionStart(\n session.sessionId ?? projectKey,\n projectKey,\n effectiveCwd,\n { triggerSource: 'discord' },\n );\n }\n }\n }\n return session;\n }\n\n // Restore persisted sessions into memory at startup, pruning stale entries\n if (store) {\n const persisted = store.load();\n const pruned = pruneSessions(persisted);\n if (pruned > 0) {\n console.log(`Pruned ${pruned} expired session(s)`);\n store.save(persisted);\n }\n for (const [key, entry] of persisted) {\n sessions.set(key, {\n sessionId: entry.sessionId,\n projectKey: entry.projectKey,\n cwd: entry.cwd,\n projectDir: entry.projectDir,\n worktreePath: entry.worktreePath,\n lastActivity: entry.lastActivity,\n createdAt: entry.lastActivity,\n messageCount: 0,\n restored: true,\n resumeEmitted: false,\n processing: false,\n queue: [],\n idleTimer: null,\n });\n }\n for (const session of sessions.values()) {\n resetIdleTimer(session);\n }\n if (persisted.size > 0) {\n console.log(`Restored ${persisted.size} session(s) from disk`);\n }\n }\n\n return {\n send(projectKey: string, cwd: string, prompt: string, opts?: { worktree?: boolean; systemPrompt?: string; timeoutMs?: number; extraArgs?: string[] }): Promise<ClaudeResult> {\n const session = getOrCreateSession(projectKey, cwd, opts?.worktree);\n return new Promise<ClaudeResult>((resolve, reject) => {\n session.queue.push({ prompt, systemPrompt: opts?.systemPrompt, timeoutMs: opts?.timeoutMs, extraArgs: opts?.extraArgs, resolve, reject });\n processQueue(session);\n });\n },\n\n getSession(projectKey: string): SessionInfo | undefined {\n const session = sessions.get(projectKey);\n if (!session) return undefined;\n return {\n sessionId: session.sessionId ?? '',\n projectKey: session.projectKey,\n cwd: session.cwd,\n projectDir: session.projectDir,\n lastActivity: session.lastActivity,\n queueLength: session.queue.length,\n createdAt: session.createdAt,\n processing: session.processing,\n };\n },\n\n listSessions(): SessionInfo[] {\n return Array.from(sessions.values()).map((s) => ({\n sessionId: s.sessionId ?? '',\n projectKey: s.projectKey,\n cwd: s.cwd,\n projectDir: s.projectDir,\n lastActivity: s.lastActivity,\n queueLength: s.queue.length,\n createdAt: s.createdAt,\n processing: s.processing,\n }));\n },\n\n clearSession(projectKey: string): boolean {\n const session = sessions.get(projectKey);\n if (!session) return false;\n if (session.idleTimer) clearTimeout(session.idleTimer);\n if (pulseEmitter && session.sessionId) {\n pulseEmitter.sessionEnd(\n session.sessionId,\n session.projectKey,\n session.cwd,\n Date.now() - session.createdAt,\n session.messageCount,\n );\n }\n if (session.worktreePath && session.projectDir) {\n gitRemoveWorktree(session.projectDir, session.projectKey);\n }\n // Clean up attachment files (#110)\n cleanupAttachments(session.projectDir ?? session.cwd).catch(() => {});\n if (runtime.cleanup) runtime.cleanup(projectKey);\n sessions.delete(projectKey);\n persistSessions();\n return true;\n },\n\n restartSession(projectKey: string): boolean {\n const session = sessions.get(projectKey);\n if (!session) return false;\n session.sessionId = undefined;\n session.lastActivity = Date.now();\n resetIdleTimer(session);\n persistSessions();\n return true;\n },\n\n async recoverOrphanedSessions(onResult: OrphanResultCallback): Promise<void> {\n if (!runtime.canResume) {\n console.log('[recovery] Skipping: runtime.canResume is false');\n return;\n }\n\n console.log('[recovery] Checking for orphaned tmux sessions...');\n let orphanedKeys: string[];\n try {\n orphanedKeys = await runtime.listOrphanedSessions();\n } catch (err) {\n console.error('Failed to list orphaned sessions:', err);\n return;\n }\n console.log(`[recovery] Found ${orphanedKeys.length} orphaned tmux session(s): ${JSON.stringify(orphanedKeys)}`);\n if (orphanedKeys.length === 0) return;\n\n console.log(`Discovered ${orphanedKeys.length} orphaned tmux session(s)`);\n\n // Cross-reference with persisted sessions.\n // Tmux session names are sanitized (e.g. \"threadId:agent\" → \"threadId-agent\"),\n // so build a reverse lookup from sanitized key → persisted entry.\n const persisted = store ? store.load() : new Map<string, PersistedSession>();\n console.log(`[recovery] Persisted store has ${persisted.size} entries. Keys: ${JSON.stringify([...persisted.keys()].slice(0, 20))}`);\n const sanitizedLookup = new Map<string, PersistedSession>();\n for (const [key, entry] of persisted) {\n const sanitized = key.replace(/[^a-zA-Z0-9_-]/g, '-');\n sanitizedLookup.set(sanitized, entry);\n }\n\n const reattachPromises: Promise<void>[] = [];\n\n for (const key of orphanedKeys) {\n const entry = sanitizedLookup.get(key);\n console.log(`[recovery] Orphan key \"${key}\" → match: ${entry ? `yes (projectKey=${entry.projectKey})` : 'NO'}`);\n if (!entry) {\n // No persisted record — stale orphan, clean it up\n console.log(`Cleaning up unmatched orphan: ${key}`);\n if (runtime.cleanup) runtime.cleanup(key);\n continue;\n }\n\n // Matched — reattach and deliver result\n reattachPromises.push(\n (async () => {\n try {\n if (pulseEmitter) {\n pulseEmitter.sessionResume(\n entry.sessionId,\n entry.projectKey,\n entry.cwd,\n Date.now() - entry.lastActivity,\n );\n }\n const result = await runtime.reattach(key);\n console.log(`Reattached orphan ${key}: ${result.text.length} chars`);\n onResult(entry.projectKey, result);\n } catch (err) {\n console.error(`Failed to reattach orphan ${key}:`, err);\n } finally {\n if (runtime.cleanup) runtime.cleanup(key);\n }\n })(),\n );\n }\n\n await Promise.all(reattachPromises);\n },\n\n shutdown() {\n persistSessions();\n const activeKeys = [...sessions.values()].map((s) => s.projectKey);\n console.log(`[shutdown] Persisted ${activeKeys.length} session(s). canResume=${runtime.canResume}. Keys: ${JSON.stringify(activeKeys.slice(0, 10))}`);\n for (const session of sessions.values()) {\n if (session.idleTimer) clearTimeout(session.idleTimer);\n // Only clean up tmux sessions if the runtime does NOT support resume.\n // Resumable runtimes leave sessions alive for recovery after restart.\n if (!runtime.canResume && runtime.cleanup) runtime.cleanup(session.projectKey);\n cleanupAttachments(session.projectDir ?? session.cwd).catch(() => {});\n }\n sessions.clear();\n },\n };\n}\n","import { execFileSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\nconst WORKTREE_DIR = '.worktrees';\nconst BRANCH_PREFIX = 'mpg/';\nconst TIMEOUT = 10_000;\n\n/** Sanitize project key for use in filesystem paths and git branch names. */\nfunction sanitizeKey(key: string): string {\n return key.replace(/:/g, '-');\n}\n\nexport function worktreePath(projectDir: string, projectKey: string): string {\n return join(projectDir, WORKTREE_DIR, sanitizeKey(projectKey));\n}\n\nexport function createWorktree(projectDir: string, projectKey: string): string {\n const safeKey = sanitizeKey(projectKey);\n if (!/^[\\w-]+$/.test(safeKey)) {\n throw new Error(`Invalid projectKey for worktree: ${projectKey}`);\n }\n const wtPath = worktreePath(projectDir, projectKey);\n if (existsSync(wtPath)) return wtPath;\n const branch = `${BRANCH_PREFIX}${safeKey}`;\n try {\n execFileSync('git', ['worktree', 'add', '-b', branch, wtPath], {\n cwd: projectDir,\n timeout: TIMEOUT,\n });\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes('already exists')) throw err;\n // Branch already exists (previous session) — attach without creating branch\n execFileSync('git', ['worktree', 'add', wtPath, branch], {\n cwd: projectDir,\n timeout: TIMEOUT,\n });\n }\n return wtPath;\n}\n\nexport function removeWorktree(projectDir: string, projectKey: string): void {\n const wtPath = worktreePath(projectDir, projectKey);\n try {\n execFileSync('git', ['worktree', 'remove', '--force', wtPath], {\n cwd: projectDir,\n timeout: TIMEOUT,\n });\n } catch {\n // Already removed or not a worktree — safe to ignore\n }\n}\n\nexport interface WorktreeInfo {\n path: string;\n branch: string;\n}\n\nexport function listWorktrees(projectDir: string): WorktreeInfo[] {\n try {\n const raw = execFileSync('git', ['worktree', 'list', '--porcelain'], {\n cwd: projectDir,\n timeout: TIMEOUT,\n }).toString();\n\n const entries: WorktreeInfo[] = [];\n let currentPath = '';\n let currentBranch = '';\n\n for (const line of raw.split('\\n')) {\n if (line.startsWith('worktree ')) {\n currentPath = line.slice('worktree '.length);\n } else if (line.startsWith('branch ')) {\n currentBranch = line.slice('branch '.length);\n } else if (line === '') {\n if (currentPath) {\n entries.push({ path: currentPath, branch: currentBranch });\n }\n currentPath = '';\n currentBranch = '';\n }\n }\n return entries;\n } catch {\n return [];\n }\n}\n\nexport function reconcileWorktrees(projectDir: string, knownKeys: Set<string>): void {\n const worktrees = listWorktrees(projectDir);\n const sanitizedKnown = new Set([...knownKeys].map(sanitizeKey));\n let removed = 0;\n for (const wt of worktrees) {\n if (!wt.branch.startsWith(`refs/heads/${BRANCH_PREFIX}`)) continue;\n const key = wt.branch.slice(`refs/heads/${BRANCH_PREFIX}`.length);\n if (!sanitizedKnown.has(key)) {\n removeWorktree(projectDir, key);\n removed++;\n }\n }\n if (removed > 0) {\n console.log(`Reconciled ${removed} orphaned worktree(s) in ${projectDir}`);\n }\n}\n","import { mkdir, writeFile, rm } from 'node:fs/promises';\nimport { basename, join, resolve } from 'node:path';\nimport type { Collection, Attachment } from 'discord.js';\n\nexport interface AttachmentConfig {\n maxAttachmentSizeMb: number;\n allowedMimeTypes: string[];\n maxAttachmentsPerMessage: number;\n}\n\nexport const DEFAULT_ATTACHMENT_CONFIG: AttachmentConfig = {\n maxAttachmentSizeMb: 10,\n allowedMimeTypes: ['image/*', 'text/*', 'application/pdf', 'application/json'],\n maxAttachmentsPerMessage: 5,\n};\n\nexport interface DownloadedAttachment {\n path: string;\n name: string;\n}\n\nexport interface AttachmentResult {\n downloaded: DownloadedAttachment[];\n warnings: string[];\n}\n\nfunction matchesMimeType(contentType: string | null, patterns: string[]): boolean {\n if (!contentType) return false;\n const mime = contentType.split(';')[0].trim().toLowerCase();\n return patterns.some((pattern) => {\n if (pattern.endsWith('/*')) {\n return mime.startsWith(pattern.slice(0, -1));\n }\n return mime === pattern;\n });\n}\n\n/**\n * Download Discord message attachments to a local directory.\n * Returns paths to downloaded files and any warnings for skipped files.\n */\nexport async function downloadAttachments(\n attachments: Collection<string, Attachment>,\n messageId: string,\n baseDir: string,\n config: AttachmentConfig,\n): Promise<AttachmentResult> {\n const warnings: string[] = [];\n const downloaded: DownloadedAttachment[] = [];\n\n const items = [...attachments.values()];\n\n if (items.length > config.maxAttachmentsPerMessage) {\n warnings.push(\n `Only processing first ${config.maxAttachmentsPerMessage} of ${items.length} attachments.`,\n );\n }\n\n const toProcess = items.slice(0, config.maxAttachmentsPerMessage);\n const maxBytes = config.maxAttachmentSizeMb * 1024 * 1024;\n const dir = join(baseDir, '.mpg-attachments', messageId);\n\n let dirCreated = false;\n\n for (const att of toProcess) {\n const rawName = att.name ?? `attachment-${att.id}`;\n // Sanitize filename to prevent path traversal (e.g. \"../../.env\")\n const name = basename(rawName).replace(/^\\.+/, '') || `attachment-${att.id}`;\n\n if (att.size > maxBytes) {\n warnings.push(`Skipped \\`${name}\\` — exceeds ${config.maxAttachmentSizeMb}MB limit.`);\n continue;\n }\n\n if (!matchesMimeType(att.contentType, config.allowedMimeTypes)) {\n warnings.push(`Skipped \\`${name}\\` — type \\`${att.contentType ?? 'unknown'}\\` not allowed.`);\n continue;\n }\n\n try {\n // Validate URL host to prevent SSRF against internal services\n const parsedUrl = new URL(att.url);\n if (!parsedUrl.hostname.endsWith('.discordapp.com') && !parsedUrl.hostname.endsWith('.discord.com')) {\n warnings.push(`Skipped \\`${name}\\` — untrusted URL host.`);\n continue;\n }\n\n const response = await fetch(att.url);\n if (!response.ok) {\n warnings.push(`Failed to download \\`${name}\\` — HTTP ${response.status}.`);\n continue;\n }\n\n if (!dirCreated) {\n await mkdir(dir, { recursive: true });\n dirCreated = true;\n }\n\n const buffer = Buffer.from(await response.arrayBuffer());\n const filePath = join(dir, name);\n // Double-check resolved path stays within the attachment directory\n if (!resolve(filePath).startsWith(resolve(dir) + '/')) {\n warnings.push(`Skipped \\`${rawName}\\` — unsafe filename.`);\n continue;\n }\n await writeFile(filePath, buffer);\n downloaded.push({ path: filePath, name });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n warnings.push(`Failed to download \\`${name}\\` — ${msg}.`);\n }\n }\n\n return { downloaded, warnings };\n}\n\n/**\n * Build a prompt prefix describing attached files.\n */\nexport function buildAttachmentPrompt(attachments: DownloadedAttachment[]): string {\n if (attachments.length === 0) return '';\n const paths = attachments.map((a) => a.path).join('\\n ');\n return `[Attached files — use the Read tool to view these:\\n ${paths}]\\n\\n`;\n}\n\n/**\n * Remove orphaned `.mpg-attachments/` directories at startup.\n * After a restart, any leftover attachment dirs are orphans.\n * Returns true if an orphaned directory was removed.\n */\nexport async function reconcileAttachments(projectDir: string): Promise<boolean> {\n const dir = join(projectDir, '.mpg-attachments');\n try {\n await rm(dir, { recursive: true });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Remove the attachment directory for a given base dir.\n * Called during session cleanup.\n */\nexport async function cleanupAttachments(baseDir: string): Promise<void> {\n const dir = join(baseDir, '.mpg-attachments');\n try {\n await rm(dir, { recursive: true, force: true });\n } catch {\n // Best-effort cleanup — directory may not exist\n }\n}\n","import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\nexport interface PersistedSession {\n sessionId: string;\n projectKey: string;\n cwd: string;\n lastActivity: number;\n worktreePath?: string;\n projectDir?: string;\n}\n\nexport interface SessionStore {\n load(): Map<string, PersistedSession>;\n save(sessions: Map<string, PersistedSession>): void;\n}\n\nexport function createFileSessionStore(filePath: string): SessionStore {\n return {\n load(): Map<string, PersistedSession> {\n try {\n const raw = readFileSync(filePath, 'utf-8');\n const entries: PersistedSession[] = JSON.parse(raw);\n const map = new Map<string, PersistedSession>();\n for (const entry of entries) {\n if (entry.sessionId && entry.projectKey) {\n map.set(entry.projectKey, entry);\n }\n }\n return map;\n } catch {\n return new Map();\n }\n },\n\n save(sessions: Map<string, PersistedSession>): void {\n const entries = Array.from(sessions.values());\n try {\n mkdirSync(dirname(filePath), { recursive: true });\n writeFileSync(filePath, JSON.stringify(entries, null, 2) + '\\n');\n } catch (err) {\n console.error('Failed to persist sessions:', err);\n }\n },\n };\n}\n","import { spawn } from 'node:child_process';\n\nexport interface ClaudeUsage {\n input_tokens: number;\n output_tokens: number;\n cache_creation_input_tokens: number;\n cache_read_input_tokens: number;\n total_cost_usd: number;\n duration_ms: number;\n duration_api_ms: number;\n num_turns: number;\n model?: string;\n}\n\nexport interface ClaudeResult {\n text: string;\n sessionId: string;\n isError: boolean;\n sessionReset?: boolean;\n sessionChanged?: boolean;\n usage?: ClaudeUsage;\n}\n\nexport function parseClaudeJsonOutput(raw: string): ClaudeResult {\n const data = JSON.parse(raw);\n let usage: ClaudeUsage | undefined;\n if (data.total_cost_usd != null || data.usage) {\n const model = data.model\n ?? (data.modelUsage ? Object.keys(data.modelUsage)[0] : undefined);\n usage = {\n input_tokens: data.usage?.input_tokens ?? 0,\n output_tokens: data.usage?.output_tokens ?? 0,\n cache_creation_input_tokens: data.usage?.cache_creation_input_tokens ?? 0,\n cache_read_input_tokens: data.usage?.cache_read_input_tokens ?? 0,\n total_cost_usd: data.total_cost_usd ?? 0,\n duration_ms: data.duration_ms ?? 0,\n duration_api_ms: data.duration_api_ms ?? 0,\n num_turns: data.num_turns ?? 0,\n model,\n };\n }\n return {\n text: data.result ?? '',\n sessionId: data.session_id ?? '',\n isError: Boolean(data.is_error),\n usage,\n };\n}\n\nexport interface ToolRestrictions {\n allowedTools?: string[];\n disallowedTools?: string[];\n}\n\n/**\n * Build --allowed-tools / --disallowed-tools CLI args from config.\n * Per-project overrides take precedence over gateway defaults.\n * If both allowed and disallowed are set, allowed takes precedence\n * (disallowed is ignored) — config validation already warns about this.\n * Skips tool flags if baseArgs already contain them (manual claudeArgs override).\n */\nexport function buildToolArgs(\n defaults: ToolRestrictions,\n projectOverrides?: ToolRestrictions,\n existingArgs?: string[],\n): string[] {\n // If the user already manually set tool flags in claudeArgs, don't conflict\n if (existingArgs?.includes('--allowed-tools') || existingArgs?.includes('--disallowed-tools')) {\n return [];\n }\n\n // Per-project overrides take precedence over gateway defaults\n const allowed = projectOverrides?.allowedTools ?? defaults.allowedTools;\n const disallowed = projectOverrides?.disallowedTools ?? defaults.disallowedTools;\n\n const args: string[] = [];\n\n if (allowed && allowed.length > 0) {\n args.push('--allowed-tools', ...allowed);\n } else if (disallowed && disallowed.length > 0) {\n args.push('--disallowed-tools', ...disallowed);\n }\n\n return args;\n}\n\nexport function buildClaudeArgs(\n baseArgs: string[],\n prompt: string,\n sessionId: string | undefined,\n systemPrompt?: string,\n): string[] {\n // Prompt MUST come before variadic flags like --allowed-tools which consume\n // all subsequent positional args. Place it right after --print.\n const args = ['--print', prompt, ...baseArgs];\n if (sessionId) {\n args.push('--resume', sessionId);\n }\n if (systemPrompt) {\n args.push('--append-system-prompt', systemPrompt);\n }\n return args;\n}\n\nexport function friendlyError(stderr: string): string {\n const combined = stderr.toLowerCase();\n if (combined.includes('rate limit') || combined.includes('rate_limit_error')) {\n return 'Claude usage limit reached — please wait a few minutes and try again.';\n }\n if (combined.includes('overloaded') || combined.includes('overloaded_error')) {\n return 'Claude API is temporarily overloaded — please try again shortly.';\n }\n if (combined.includes('invalid api key') || combined.includes('authentication_error') || combined.includes('authentication failed')) {\n return 'Claude authentication failed — check your API key or CLI login.';\n }\n if (combined.includes('no messages returned')) {\n return 'Claude returned an empty response — try sending your message again.';\n }\n return `Claude error: ${stderr.slice(0, 500)}`;\n}\n\nconst DEFAULT_TIMEOUT_MS = 20 * 60 * 1000; // 20 minutes\n\nexport function runClaude(\n cwd: string,\n baseArgs: string[],\n prompt: string,\n sessionId: string | undefined,\n systemPrompt?: string,\n timeoutMs: number = DEFAULT_TIMEOUT_MS,\n): Promise<ClaudeResult> {\n return new Promise((resolve, reject) => {\n const args = buildClaudeArgs(baseArgs, prompt, sessionId, systemPrompt);\n const proc = spawn('claude', args, {\n cwd,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n let settled = false;\n\n const timer = setTimeout(() => {\n if (!settled) {\n settled = true;\n proc.kill('SIGTERM');\n reject(new Error(`Claude CLI timed out after ${timeoutMs / 1000}s`));\n }\n }, timeoutMs);\n\n proc.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n\n proc.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n\n proc.on('close', (code) => {\n clearTimeout(timer);\n\n if (settled) return;\n settled = true;\n if (code !== 0) {\n reject(new Error(friendlyError(stderr)));\n return;\n }\n try {\n const result = parseClaudeJsonOutput(stdout.trim());\n resolve(result);\n } catch (err) {\n reject(new Error(`Failed to parse claude output: ${stdout.slice(0, 200)}`));\n }\n });\n\n proc.on('error', (err) => {\n clearTimeout(timer);\n\n if (settled) return;\n settled = true;\n reject(new Error(`Failed to spawn claude: ${err.message}`));\n });\n });\n}\n","import { runClaude } from '../claude-cli.js';\nimport type { AgentRuntime, SpawnOpts } from '../agent-runtime.js';\nimport type { ClaudeResult } from '../claude-cli.js';\n\n/**\n * Default AgentRuntime that delegates to the Claude CLI via child_process spawn.\n * This preserves the existing behaviour — a direct, non-persistent subprocess call.\n *\n * Phase 2 will add a `persistent: true` mode backed by tmux (#98).\n */\nexport class ClaudeCliRuntime implements AgentRuntime {\n readonly name = 'claude-cli';\n readonly canResume = false;\n\n spawn(opts: SpawnOpts): Promise<ClaudeResult> {\n return runClaude(\n opts.cwd,\n opts.baseArgs,\n opts.prompt,\n opts.sessionId,\n opts.systemPrompt,\n opts.timeoutMs,\n );\n }\n\n async listOrphanedSessions(): Promise<string[]> {\n return [];\n }\n\n async reattach(_sessionId: string): Promise<ClaudeResult> {\n throw new Error('ClaudeCliRuntime does not support session reattachment');\n }\n}\n","import { mkdirSync, readFileSync, statSync, rmSync, existsSync, watch } from 'node:fs';\nimport { join } from 'node:path';\nimport { buildClaudeArgs, parseClaudeJsonOutput, friendlyError } from '../claude-cli.js';\nimport type { AgentRuntime, SpawnOpts } from '../agent-runtime.js';\nimport type { ClaudeResult } from '../claude-cli.js';\nimport { createSession, sessionExists, listSessions, killSession, ensureTmux } from '../tmux.js';\n\nconst SESSION_PREFIX = 'mpg-';\nconst OUTPUT_BASE_DIR = '/tmp/mpg-sessions';\nconst DEFAULT_TIMEOUT_MS = 20 * 60 * 1000; // 20 minutes\nconst HEALTH_CHECK_DELAY_MS = 2 * 60 * 1000; // 2 minutes\nconst POLL_INTERVAL_MS = 500;\n\n/** Sanitize a session key for use as a tmux session name. */\nfunction sanitizeSessionName(key: string): string {\n return key.replace(/[^a-zA-Z0-9_-]/g, '-');\n}\n\nfunction outputDir(sessionKey: string): string {\n return join(OUTPUT_BASE_DIR, sanitizeSessionName(sessionKey));\n}\n\nfunction outputFile(sessionKey: string): string {\n return join(outputDir(sessionKey), 'output.json');\n}\n\nfunction stderrFile(sessionKey: string): string {\n return join(outputDir(sessionKey), 'stderr.log');\n}\n\n/**\n * tmux-based AgentRuntime that persists Claude sessions across gateway restarts.\n *\n * How it works:\n * 1. spawn() creates a detached tmux session running `claude ... > output.json 2> stderr.log`\n * 2. mpg polls/watches the output file until Claude finishes (file appears and process exits)\n * 3. On gateway restart, listOrphanedSessions() discovers surviving tmux sessions via `tmux ls`\n * 4. reattach() re-reads the output file from a still-running session\n */\nexport class TmuxRuntime implements AgentRuntime {\n readonly name = 'tmux';\n readonly canResume = true;\n\n constructor() {\n ensureTmux();\n }\n\n async spawn(opts: SpawnOpts): Promise<ClaudeResult> {\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const sessionKey = opts.projectKey ?? opts.sessionId ?? `spawn-${Date.now()}`;\n const tmuxName = SESSION_PREFIX + sanitizeSessionName(sessionKey);\n\n // Prepare output directory\n const outDir = outputDir(sessionKey);\n mkdirSync(outDir, { recursive: true });\n const outPath = outputFile(sessionKey);\n const errPath = stderrFile(sessionKey);\n\n // Build the claude command, wrapped with `timeout` so the tmux session\n // self-destructs if mpg never reads the output (prevents zombie sessions).\n const args = buildClaudeArgs(opts.baseArgs, opts.prompt, opts.sessionId, opts.systemPrompt);\n const escapedArgs = args.map((a) => shellEscape(a));\n const bufferMs = 5 * 60 * 1000; // 5 minutes buffer beyond mpg's own timeout\n const timeoutSec = Math.ceil((timeoutMs + bufferMs) / 1000);\n const command = `timeout ${timeoutSec} claude ${escapedArgs.join(' ')} > ${shellEscape(outPath)} 2> ${shellEscape(errPath)}`;\n\n // Kill any stale session with the same name\n if (sessionExists(tmuxName)) {\n killSession(tmuxName);\n }\n\n // Launch in tmux\n createSession(tmuxName, command, { cwd: opts.cwd });\n\n // Wait for Claude to finish by polling for tmux session exit + output file\n return this._waitForResult(tmuxName, sessionKey, outPath, errPath, timeoutMs);\n }\n\n async listOrphanedSessions(): Promise<string[]> {\n const sessions = listSessions(SESSION_PREFIX);\n console.log(`[tmux] listOrphanedSessions: raw tmux sessions with prefix '${SESSION_PREFIX}': ${JSON.stringify(sessions)}`);\n return sessions.map((name) => name.slice(SESSION_PREFIX.length));\n }\n\n async reattach(sessionKey: string): Promise<ClaudeResult> {\n const tmuxName = SESSION_PREFIX + sanitizeSessionName(sessionKey);\n const outPath = outputFile(sessionKey);\n const errPath = stderrFile(sessionKey);\n\n if (!sessionExists(tmuxName)) {\n // Session already finished — try to read its output\n if (existsSync(outPath)) {\n return this._readResult(outPath, errPath);\n }\n throw new Error(`tmux session ${tmuxName} does not exist and no output file found`);\n }\n\n // Session is still running — wait for it\n return this._waitForResult(tmuxName, sessionKey, outPath, errPath, DEFAULT_TIMEOUT_MS);\n }\n\n /** Clean up tmux session and temp files for a given session key. */\n cleanup(sessionKey: string): void {\n const tmuxName = SESSION_PREFIX + sanitizeSessionName(sessionKey);\n killSession(tmuxName);\n const dir = outputDir(sessionKey);\n try {\n rmSync(dir, { recursive: true, force: true });\n } catch {\n // Best effort\n }\n }\n\n private _readResult(outPath: string, errPath: string): ClaudeResult {\n const stdout = existsSync(outPath) ? readFileSync(outPath, 'utf-8').trim() : '';\n const stderr = existsSync(errPath) ? readFileSync(errPath, 'utf-8').trim() : '';\n\n if (!stdout && stderr) {\n throw new Error(friendlyError(stderr));\n }\n if (!stdout) {\n throw new Error('Claude produced no output');\n }\n\n try {\n return parseClaudeJsonOutput(stdout);\n } catch {\n throw new Error(`Failed to parse claude output: ${stdout.slice(0, 200)}`);\n }\n }\n\n private _waitForResult(\n tmuxName: string,\n sessionKey: string,\n outPath: string,\n errPath: string,\n timeoutMs: number,\n ): Promise<ClaudeResult> {\n return new Promise((resolve, reject) => {\n let settled = false;\n let healthCheckDone = false;\n\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n killSession(tmuxName);\n reject(new Error(`Claude CLI timed out after ${timeoutMs / 1000}s`));\n }, timeoutMs);\n\n // Health check: after 2 minutes, verify session is alive and output file exists\n const healthTimer = setTimeout(() => {\n if (settled) return;\n healthCheckDone = true;\n if (!sessionExists(tmuxName)) {\n // Session died early — check if it produced output\n tryResolve();\n }\n }, Math.min(HEALTH_CHECK_DELAY_MS, timeoutMs));\n\n // Watch the output directory for the file to appear/change\n let watcher: ReturnType<typeof watch> | undefined;\n try {\n const dir = outputDir(sessionKey);\n watcher = watch(dir, () => {\n if (!settled) tryResolve();\n });\n } catch {\n // watch may fail — fall through to polling\n }\n\n // Poll as fallback (fs.watch is not always reliable)\n const pollTimer = setInterval(() => {\n if (!settled) tryResolve();\n }, POLL_INTERVAL_MS);\n\n function tryResolve() {\n // tmux session still running → not done yet (unless health check says otherwise)\n if (sessionExists(tmuxName)) return;\n\n // Session has exited — read the result\n if (settled) return;\n settled = true;\n\n clearTimeout(timer);\n clearTimeout(healthTimer);\n clearInterval(pollTimer);\n if (watcher) watcher.close();\n\n try {\n const stdout = existsSync(outPath) ? readFileSync(outPath, 'utf-8').trim() : '';\n const stderr = existsSync(errPath) ? readFileSync(errPath, 'utf-8').trim() : '';\n\n if (!stdout && stderr) {\n reject(new Error(friendlyError(stderr)));\n return;\n }\n if (!stdout) {\n reject(new Error('Claude produced no output'));\n return;\n }\n\n const result = parseClaudeJsonOutput(stdout);\n resolve(result);\n } catch (err) {\n reject(err instanceof Error ? err : new Error(String(err)));\n }\n }\n });\n }\n}\n\n/** Escape a string for safe shell embedding in single quotes. */\nfunction shellEscape(s: string): string {\n return \"'\" + s.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n","import { execFileSync } from 'node:child_process';\n\nconst TIMEOUT = 10_000;\n\n/**\n * Check whether tmux is available on the system.\n * Throws a descriptive error if not installed.\n */\nexport function ensureTmux(): void {\n try {\n execFileSync('tmux', ['-V'], { timeout: TIMEOUT, stdio: 'pipe' });\n } catch {\n throw new Error(\n 'tmux is not installed or not on PATH. Install tmux to use persistent sessions (e.g. `apt install tmux`).',\n );\n }\n}\n\n/**\n * Create a detached tmux session running the given shell command.\n */\nexport function createSession(name: string, command: string, opts?: { cwd?: string }): void {\n ensureTmux();\n execFileSync('tmux', ['new-session', '-d', '-s', name, command], {\n cwd: opts?.cwd,\n timeout: TIMEOUT,\n stdio: 'pipe',\n });\n}\n\n/**\n * Check whether a named tmux session exists.\n */\nexport function sessionExists(name: string): boolean {\n try {\n execFileSync('tmux', ['has-session', '-t', name], {\n timeout: TIMEOUT,\n stdio: 'pipe',\n });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * List tmux sessions whose names start with the given prefix.\n * Returns an array of full session names.\n */\nexport function listSessions(prefix: string): string[] {\n try {\n const raw = execFileSync('tmux', ['ls', '-F', '#{session_name}'], {\n timeout: TIMEOUT,\n stdio: 'pipe',\n }).toString();\n return raw\n .split('\\n')\n .map((l) => l.trim())\n .filter((l) => l.startsWith(prefix));\n } catch {\n // tmux ls fails if no server is running (no sessions) — that's fine\n return [];\n }\n}\n\n/**\n * Kill a named tmux session.\n */\nexport function killSession(name: string): void {\n try {\n execFileSync('tmux', ['kill-session', '-t', name], {\n timeout: TIMEOUT,\n stdio: 'pipe',\n });\n } catch {\n // Session may already be dead — safe to ignore\n }\n}\n","import { Client, GatewayIntentBits, Events, Status, type Message, type TextChannel, type ThreadChannel } from 'discord.js';\nimport type { Router } from './router.js';\nimport type { SessionManager } from './session-manager.js';\nimport type { GatewayConfig } from './config.js';\nimport { buildToolArgs } from './claude-cli.js';\nimport { parseAgentMention, parseAgentCommand, extractAskTarget, parseHandoffCommand } from './agent-dispatch.js';\nimport { sendAgentMessage, buildHandoffEmbed } from './embed-format.js';\nimport type { TurnCounter } from './turn-counter.js';\nimport { hasAllowedRole } from './role-check.js';\nimport { createRateLimiter } from './rate-limiter.js';\nimport { downloadAttachments, buildAttachmentPrompt, type AttachmentConfig, DEFAULT_ATTACHMENT_CONFIG } from './attachments.js';\n\nexport function chunkMessage(text: string, limit: number): string[] {\n if (text.length <= limit) return [text];\n\n const chunks: string[] = [];\n const lines = text.split('\\n');\n let current = '';\n\n for (const line of lines) {\n if (line.length > limit) {\n if (current) {\n chunks.push(current);\n current = '';\n }\n for (let i = 0; i < line.length; i += limit) {\n chunks.push(line.slice(i, i + limit));\n }\n continue;\n }\n\n const candidate = current ? `${current}\\n${line}` : line;\n if (candidate.length > limit) {\n chunks.push(current);\n current = line;\n } else {\n current = candidate;\n }\n }\n\n if (current || chunks.length === 0) {\n chunks.push(current);\n }\n\n return chunks;\n}\n\nexport interface DiscordBot {\n start(token: string): Promise<void>;\n stop(): void;\n getStatus(): string;\n /** Deliver an orphaned session result to the appropriate Discord thread. */\n deliverOrphanResult(projectKey: string, result: import('./claude-cli.js').ClaudeResult): Promise<void>;\n}\n\nfunction resolveProjectName(config: GatewayConfig, channelId: string): string {\n return config.projects[channelId]?.name ?? channelId;\n}\n\nfunction findProjectByName(config: GatewayConfig, name: string): { channelId: string; name: string } | null {\n const lower = name.toLowerCase();\n for (const [channelId, project] of Object.entries(config.projects)) {\n if (project.name.toLowerCase() === lower) {\n return { channelId, name: project.name };\n }\n }\n return null;\n}\n\nfunction formatTimeSince(timestamp: number): string {\n const seconds = Math.floor((Date.now() - timestamp) / 1000);\n if (seconds < 60) return `${seconds}s ago`;\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m ago`;\n const hours = Math.floor(minutes / 60);\n return `${hours}h ${minutes % 60}m ago`;\n}\n\nexport function handleCommand(\n command: string,\n config: GatewayConfig,\n sessionManager: SessionManager,\n context?: { channelId: string; projectName: string; isThread: boolean },\n): string | null {\n const parts = command.trim().split(/\\s+/);\n const cmd = parts[0]?.toLowerCase();\n\n if (cmd === '!sessions') {\n const allSessions = sessionManager.listSessions();\n if (allSessions.length === 0) {\n return 'No active sessions.';\n }\n const lines = allSessions.map((s) => {\n const name = resolveProjectName(config, s.projectKey);\n const idle = formatTimeSince(s.lastActivity);\n const queue = s.queueLength > 0 ? ` | queue: ${s.queueLength}` : '';\n const sid = s.sessionId ? ` | \\`${s.sessionId.slice(0, 8)}…\\`` : '';\n return `- **${name}** — last active ${idle}${queue}${sid}`;\n });\n return `**Active sessions (${allSessions.length})**\\n${lines.join('\\n')}`;\n }\n\n if (cmd === '!session') {\n const name = parts.slice(1).join(' ');\n\n // No arguments: show session for the current thread (or project channel)\n if (!name && context) {\n const info = sessionManager.getSession(context.channelId);\n if (!info) return `**${context.projectName}** — no active session in this ${context.isThread ? 'thread' : 'channel'}.`;\n const idle = formatTimeSince(info.lastActivity);\n const sid = info.sessionId || 'none';\n return [\n `**${context.projectName}**${context.isThread ? ' (thread)' : ''}`,\n `Session ID: \\`${sid}\\``,\n `Last active: ${idle}`,\n `Queue depth: ${info.queueLength}`,\n ].join('\\n');\n }\n\n if (!name) return 'Usage: `!session <project name>` or run `!session` in a thread';\n const project = findProjectByName(config, name);\n if (!project) return `No project found matching \"${name}\".`;\n const info = sessionManager.getSession(project.channelId);\n if (!info) return `**${project.name}** — no active session.`;\n const idle = formatTimeSince(info.lastActivity);\n const sid = info.sessionId || 'none';\n return [\n `**${project.name}**`,\n `Session ID: \\`${sid}\\``,\n `Last active: ${idle}`,\n `Queue depth: ${info.queueLength}`,\n ].join('\\n');\n }\n\n if (cmd === '!kill') {\n const name = parts.slice(1).join(' ');\n if (!name) return 'Usage: `!kill <project name>`';\n const project = findProjectByName(config, name);\n if (!project) return `No project found matching \"${name}\".`;\n const cleared = sessionManager.clearSession(project.channelId);\n if (cleared) return `Session for **${project.name}** cleared.`;\n return `**${project.name}** — no active session to clear.`;\n }\n\n if (cmd === '!restart') {\n const name = parts.slice(1).join(' ');\n if (!name) return 'Usage: `!restart <project name>`';\n const project = findProjectByName(config, name);\n if (!project) return `No project found matching \"${name}\".`;\n const restarted = sessionManager.restartSession(project.channelId);\n if (restarted) return `Session for **${project.name}** restarted — next message will start fresh context.`;\n return `**${project.name}** — no active session to restart.`;\n }\n\n if (cmd === '!agents') {\n if (!context) return 'Run `!agents` in a project channel or thread.';\n // context.channelId may be a thread ID; look up the project by name\n const match = findProjectByName(config, context.projectName);\n const project = match ? config.projects[match.channelId] : undefined;\n if (!project?.agents || Object.keys(project.agents).length === 0) {\n return `**${context.projectName}** — No agents configured. Messages go to the default session.`;\n }\n const lines = Object.entries(project.agents).map(([name, agent]) =>\n `- \\`${name}\\` — ${agent.role}`\n );\n return `**${context.projectName} agents**\\n${lines.join('\\n')}\\n\\nDispatch: \\`!ask <agent> <message>\\` or shorthand \\`!<agent> <message>\\``;\n }\n\n if (cmd === '!help') {\n return [\n '**Gateway commands**',\n '`!ask <agent> <message>` — dispatch a message to a named agent',\n '`!<agent> <message>` — shorthand for `!ask`',\n '`!sessions` — list all active sessions',\n '`!session` — show session for the current thread (or use `!session <name>`)',\n '`!restart <name>` — reset a session (fresh context, keeps worktree)',\n '`!kill <name>` — force-close a project session',\n '`!agents` — list available agents for the current project',\n '`!help` — show this message',\n ].join('\\n');\n }\n\n return null;\n}\n\nconst THREAD_HISTORY_LIMIT = 20;\n\n/** Fetch recent thread messages and format as a conversation log. */\nasync function fetchThreadHistory(channel: TextChannel | ThreadChannel, beforeMessageId: string): Promise<string | null> {\n if (!channel.isThread()) return null;\n try {\n const messages = await channel.messages.fetch({ limit: THREAD_HISTORY_LIMIT, before: beforeMessageId });\n if (messages.size === 0) return null;\n const lines = [...messages.values()]\n .reverse()\n .map((m) => `[${m.author.bot ? 'agent' : m.author.username}]: ${m.content}`);\n return `<thread-history>\\n${lines.join('\\n')}\\n</thread-history>\\n\\n`;\n } catch {\n return null;\n }\n}\n\nexport function createDiscordBot(router: Router, sessionManager: SessionManager, config: GatewayConfig, turnCounter?: TurnCounter): DiscordBot {\n const client = new Client({\n intents: [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.MessageContent,\n GatewayIntentBits.GuildMembers,\n ],\n });\n\n const rateLimiter = createRateLimiter();\n\n // Track last active agent per thread for routing plain replies (#48)\n const lastActiveAgent = new Map<string, { agentName: string; agent: import('./config.js').AgentConfig }>();\n\n client.on(Events.MessageCreate, async (message: Message) => {\n if (message.author.bot) return;\n\n if (!('send' in message.channel)) return;\n\n // Handle gateway commands from any mapped channel\n if (message.content.startsWith('!')) {\n const parentId = message.channel.isThread() ? message.channel.parentId ?? undefined : undefined;\n const resolved = router.resolve(message.channelId, parentId);\n if (resolved) {\n const response = handleCommand(message.content, config, sessionManager, {\n channelId: resolved.channelId,\n projectName: resolved.name,\n isThread: resolved.isThread,\n });\n if (response) {\n await message.channel.send(response);\n return;\n }\n\n // Check for !ask <agent> or !<agent> shorthand dispatch\n const projectChannelId = parentId || resolved.channelId;\n const project = config.projects[projectChannelId];\n const agents = project?.agents;\n if (agents) {\n const askMention = parseAgentCommand(message.content, agents);\n if (askMention) {\n // Fall through to normal message handling — inject the parsed mention\n // by rewriting message content to @agent form so the existing path picks it up\n } else {\n // Check if user tried !ask with an unknown agent name\n const target = extractAskTarget(message.content);\n if (target) {\n const agentList = Object.entries(agents)\n .map(([name, a]) => `\\`${name}\\` — ${a.role}`)\n .join('\\n- ');\n await message.channel.send(\n `Unknown agent \\`${target}\\`. Available agents:\\n- ${agentList}\\n\\nUsage: \\`!ask <agent> <message>\\``,\n );\n return;\n }\n }\n }\n }\n }\n\n const parentId = message.channel.isThread() ? message.channel.parentId ?? undefined : undefined;\n const resolved = router.resolve(message.channelId, parentId);\n if (!resolved) return;\n\n // Role-based access control\n const projectChannelIdForAcl = parentId || resolved.channelId;\n const projectForAcl = config.projects[projectChannelIdForAcl];\n if (projectForAcl?.allowedRoles && projectForAcl.allowedRoles.length > 0) {\n if (!hasAllowedRole(message.member, projectForAcl.allowedRoles)) {\n await message.reply(\"You don't have permission to use this bot.\");\n return;\n }\n }\n\n // Per-user rate limiting\n if (projectForAcl?.rateLimitPerUser) {\n const result = rateLimiter.check(`${message.author.id}:${projectChannelIdForAcl}`, projectForAcl.rateLimitPerUser);\n if (!result.allowed) {\n await message.reply(\n `You're sending messages too quickly. Please wait ${result.retryAfterSeconds}s before trying again.`,\n );\n return;\n }\n }\n\n try {\n await message.react('👀');\n } catch {\n // Reaction may fail if permissions are missing — non-critical\n }\n\n // If the message is in a main channel, create a thread for the response.\n // If already in a thread, reply there directly.\n let replyChannel: TextChannel | ThreadChannel;\n if (message.channel.isThread()) {\n replyChannel = message.channel;\n } else {\n try {\n replyChannel = await message.startThread({\n name: message.content.slice(0, 100) || 'Claude response',\n autoArchiveDuration: 1440,\n });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to create thread in ${resolved.name}: ${errMsg}`);\n await message.reply('⚠️ Could not create a thread — check bot permissions (Create Public Threads).');\n return;\n }\n }\n\n // Show typing indicator while Claude is processing\n const typingInterval = setInterval(() => {\n replyChannel.sendTyping().catch(() => {});\n }, 7_000);\n replyChannel.sendTyping().catch(() => {});\n\n // Periodic status notifications for long-running worktree sessions (#90)\n const stuckNotifyMs = config.defaults.stuckNotifyMs;\n const isWorktreeSession = replyChannel.isThread();\n const sendStartTime = Date.now();\n let stuckNotifyInterval: ReturnType<typeof setInterval> | null = null;\n if (stuckNotifyMs > 0 && isWorktreeSession) {\n stuckNotifyInterval = setInterval(() => {\n const elapsed = Math.floor((Date.now() - sendStartTime) / 60_000);\n replyChannel.send(`⏳ Still working… (${elapsed}m elapsed)`).catch(() => {});\n }, stuckNotifyMs);\n }\n\n // Look up agents for the project (use parent channel ID for threads)\n const projectChannelId = parentId || resolved.channelId;\n const project = config.projects[projectChannelId];\n const agents = project?.agents;\n\n // Build tool restriction args (per-project overrides gateway defaults).\n // Check both gateway-level and per-project claudeArgs for manual --allowed-tools / --disallowed-tools flags.\n const allClaudeArgs = project?.claudeArgs\n ? [...config.defaults.claudeArgs, ...project.claudeArgs]\n : config.defaults.claudeArgs;\n const toolArgs = buildToolArgs(\n config.defaults,\n project ? { allowedTools: project.allowedTools, disallowedTools: project.disallowedTools } : undefined,\n allClaudeArgs,\n );\n\n // Reset turn counter on human messages\n if (turnCounter) turnCounter.reset(replyChannel.id);\n\n // Check for !ask <agent> command, @agent mention, or fall back to last active agent (#48, #60)\n const mention = agents\n ? (parseAgentCommand(message.content, agents) ?? parseAgentMention(message.content, agents))\n : null;\n const activeAgent = mention ?? (message.channel.isThread() ? lastActiveAgent.get(replyChannel.id) ?? null : null);\n\n // Use thread ID for session keys so each thread gets its own agent sessions.\n // For main-channel messages, replyChannel is the newly created thread.\n const threadId = replyChannel.id;\n\n // Build session key and system prompt\n const sessionKey = activeAgent\n ? `${threadId}:${activeAgent.agentName}`\n : threadId;\n const systemPrompt = activeAgent\n ? `Your role: ${activeAgent.agent.role}\\n\\n${activeAgent.agent.prompt}`\n : undefined;\n\n try {\n // Download attachments if present (#110)\n let attachmentPrefix = '';\n if (message.attachments.size > 0) {\n const attachmentConfig: AttachmentConfig = {\n maxAttachmentSizeMb: project?.maxAttachmentSizeMb ?? config.defaults.maxAttachmentSizeMb,\n allowedMimeTypes: project?.allowedMimeTypes ?? config.defaults.allowedMimeTypes,\n maxAttachmentsPerMessage: project?.maxAttachmentsPerMessage ?? config.defaults.maxAttachmentsPerMessage,\n };\n const attachmentResult = await downloadAttachments(\n message.attachments,\n message.id,\n resolved.directory,\n attachmentConfig,\n );\n if (attachmentResult.warnings.length > 0) {\n await replyChannel.send(`⚠️ ${attachmentResult.warnings.join('\\n')}`);\n }\n attachmentPrefix = buildAttachmentPrompt(attachmentResult.downloaded);\n }\n\n // Prepend thread history when dispatching to an agent in a thread (#49)\n let userPrompt = mention ? mention.prompt : message.content;\n if (activeAgent && message.channel.isThread()) {\n const history = await fetchThreadHistory(replyChannel, message.id);\n if (history) userPrompt = `${history}${userPrompt}`;\n }\n\n // For attachment-only messages, use a default prompt (#110)\n if (!userPrompt.trim() && attachmentPrefix) {\n userPrompt = 'Please review the attached files.';\n }\n\n // Prepend attachment file references to the prompt\n if (attachmentPrefix) {\n userPrompt = `${attachmentPrefix}${userPrompt}`;\n }\n\n // Guard against empty prompts (e.g. bare @agent mentions with no attachments)\n if (!userPrompt.trim()) {\n await replyChannel.send('Please include a message with your request.');\n return;\n }\n\n const result = await sessionManager.send(\n sessionKey,\n resolved.directory,\n userPrompt,\n {\n worktree: replyChannel.isThread() ? true : undefined,\n systemPrompt,\n extraArgs: toolArgs.length > 0 ? toolArgs : undefined,\n },\n );\n\n if (result.sessionReset) {\n await replyChannel.send('⚠️ Previous session expired — starting fresh.');\n } else if (result.sessionChanged) {\n await replyChannel.send('⚠️ Claude started a new session — previous conversation context may be lost.');\n }\n\n await sendAgentMessage(\n replyChannel,\n result.text,\n activeAgent?.agentName,\n activeAgent?.agent.role,\n );\n\n // Track last active agent for plain reply routing (#48)\n if (activeAgent) {\n lastActiveAgent.set(threadId, { agentName: activeAgent.agentName, agent: activeAgent.agent });\n }\n\n // Auto-handoff loop: check if agent response mentions another agent\n if (agents && turnCounter) {\n let responseText = result.text;\n let currentAgentName = activeAgent?.agentName;\n const maxTurns = config.defaults.maxTurnsPerAgent;\n\n while (true) {\n const handoff = parseHandoffCommand(responseText, agents);\n if (!handoff || handoff.agentName === currentAgentName) break;\n\n turnCounter.increment(replyChannel.id);\n const turn = turnCounter.getTurns(replyChannel.id);\n console.log(`[handoff] thread=${replyChannel.id} turn=${turn}/${maxTurns} ${currentAgentName ?? 'user'} → ${handoff.agentName}`);\n\n if (turnCounter.isOverLimit(replyChannel.id, maxTurns)) {\n console.log(`[handoff] thread=${replyChannel.id} turn limit reached, stopping`);\n await replyChannel.send(\n `⚠️ Agent turn limit reached (${maxTurns}) — send a message to reset.`\n );\n break;\n }\n\n const handoffKey = `${threadId}:${handoff.agentName}`;\n const handoffPrompt = `Your role: ${handoff.agent.role}\\n\\n${handoff.agent.prompt}`;\n\n replyChannel.sendTyping().catch(() => {});\n\n // Post handoff announcement — kept visible so users see progress during long agent runs (#56, #65)\n await replyChannel.send({ embeds: [buildHandoffEmbed(handoff.agentName, handoff.agent.role)] }).catch(() => null);\n\n // Restore typing indicator — posting the announcement clears it (#65)\n replyChannel.sendTyping().catch(() => {});\n\n console.log(`[handoff] thread=${replyChannel.id} sending to ${handoff.agentName} (key=${handoffKey}, prompt length=${responseText.length})`);\n const sendStart = Date.now();\n\n let handoffResult;\n try {\n handoffResult = await sessionManager.send(\n handoffKey,\n resolved.directory,\n responseText,\n { worktree: replyChannel.isThread() ? true : undefined, systemPrompt: handoffPrompt, timeoutMs: config.defaults.agentTimeoutMs, extraArgs: toolArgs.length > 0 ? toolArgs : undefined },\n );\n } catch (handoffErr) {\n const msg = handoffErr instanceof Error ? handoffErr.message : String(handoffErr);\n console.log(`[handoff] thread=${replyChannel.id} ${handoff.agentName} failed: ${msg}`);\n await replyChannel.send(\n `⚠️ Agent \\`@${handoff.agentName}\\` failed: ${msg.slice(0, 1800)}`\n );\n break;\n }\n\n const elapsed = ((Date.now() - sendStart) / 1000).toFixed(1);\n console.log(`[handoff] thread=${replyChannel.id} ${handoff.agentName} responded in ${elapsed}s (${handoffResult.text.length} chars)`);\n\n await sendAgentMessage(\n replyChannel,\n handoffResult.text,\n handoff.agentName,\n handoff.agent.role,\n );\n\n responseText = handoffResult.text;\n currentAgentName = handoff.agentName;\n lastActiveAgent.set(threadId, { agentName: handoff.agentName, agent: handoff.agent });\n }\n }\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n await replyChannel.send(\n `**Error** (${resolved.name}): ${errorMsg.slice(0, 1800)}`,\n );\n } finally {\n clearInterval(typingInterval);\n if (stuckNotifyInterval) clearInterval(stuckNotifyInterval);\n }\n });\n\n return {\n async start(token: string) {\n await client.login(token);\n console.log(`Gateway connected as ${client.user?.tag}`);\n },\n stop() {\n rateLimiter.dispose();\n client.destroy();\n },\n getStatus(): string {\n const ws = client.ws;\n const statusMap: Record<number, string> = {\n [Status.Ready]: 'connected',\n [Status.Connecting]: 'connecting',\n [Status.Reconnecting]: 'reconnecting',\n [Status.Idle]: 'idle',\n [Status.Nearly]: 'nearly',\n [Status.Disconnected]: 'disconnected',\n [Status.WaitingForGuilds]: 'waiting_for_guilds',\n [Status.Identifying]: 'identifying',\n [Status.Resuming]: 'resuming',\n };\n return statusMap[ws.status] ?? 'unknown';\n },\n async deliverOrphanResult(projectKey: string, result: import('./claude-cli.js').ClaudeResult): Promise<void> {\n // projectKey is \"threadId\" or \"threadId:agentName\"\n const threadId = projectKey.includes(':') ? projectKey.split(':')[0] : projectKey;\n const agentName = projectKey.includes(':') ? projectKey.split(':').pop() : undefined;\n\n try {\n const channel = await client.channels.fetch(threadId);\n if (!channel || !('send' in channel)) {\n console.error(`Cannot deliver orphan result: channel ${threadId} not found or not sendable`);\n return;\n }\n\n // Look up agent role if this was an agent session\n let agentRole: string | undefined;\n if (agentName && channel.isThread() && channel.parentId) {\n const project = config.projects[channel.parentId];\n agentRole = project?.agents?.[agentName]?.role;\n }\n\n await channel.send('🔄 Resumed after gateway restart — here is the pending response:');\n await sendAgentMessage(channel as TextChannel | ThreadChannel, result.text, agentName, agentRole);\n } catch (err) {\n console.error(`Failed to deliver orphan result to ${threadId}:`, err);\n }\n },\n };\n}\n","// src/agent-dispatch.ts\nimport type { AgentConfig } from './config.js';\n\nexport type { AgentConfig };\n\nexport interface AgentMention {\n agentName: string;\n agent: AgentConfig;\n prompt: string;\n}\n\n/** Built-in command names that take precedence over agent shorthand (!<agent>). */\nconst BUILT_IN_COMMANDS = new Set([\n 'help', 'sessions', 'session', 'kill', 'restart', 'agents', 'ask',\n]);\n\n/**\n * Parse `!ask <agent> <message>` (canonical) or `!<agent> <message>` (shorthand).\n * Shorthand yields to built-in commands — e.g. `!help` is never treated as an agent dispatch.\n */\nexport function parseAgentCommand(\n text: string,\n agents: Record<string, AgentConfig>,\n): AgentMention | null {\n // Canonical form: !ask <agent> <message>\n const askMatch = text.match(/^!ask\\s+(\\S+)(?:\\s+([\\s\\S]*))?$/i);\n if (askMatch) {\n const name = askMatch[1].toLowerCase();\n const agent = agents[name];\n if (!agent) return null; // unknown agent — caller handles error\n const prompt = (askMatch[2] ?? '').trim();\n return { agentName: name, agent, prompt };\n }\n\n // Shorthand form: !<agent> <message> (only if not a built-in command)\n const shortMatch = text.match(/^!(\\S+)(?:\\s+([\\s\\S]*))?$/i);\n if (shortMatch) {\n const name = shortMatch[1].toLowerCase();\n if (BUILT_IN_COMMANDS.has(name)) return null; // built-in wins\n const agent = agents[name];\n if (!agent) return null;\n const prompt = (shortMatch[2] ?? '').trim();\n return { agentName: name, agent, prompt };\n }\n\n return null;\n}\n\n/**\n * Extract the target agent name from a `!ask` command, even if the agent is unknown.\n * Returns null if the message is not a `!ask` command at all.\n */\nexport function extractAskTarget(text: string): string | null {\n const askMatch = text.match(/^!ask\\s+(\\S+)/i);\n return askMatch ? askMatch[1].toLowerCase() : null;\n}\n\nexport function parseAgentMention(\n text: string,\n agents: Record<string, AgentConfig>,\n): AgentMention | null {\n // Build a pattern that matches @agentName (case-insensitive)\n const agentNames = Object.keys(agents);\n if (agentNames.length === 0) return null;\n\n const escaped = agentNames.map(n => n.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'));\n const pattern = new RegExp(`@(${escaped.join('|')})\\\\b`, 'i');\n const match = text.match(pattern);\n if (!match) return null;\n\n const matchedName = match[1].toLowerCase();\n const agent = agents[matchedName];\n if (!agent) return null;\n\n // If mention is at the start, strip it from the prompt\n let prompt: string;\n if (match.index === 0) {\n prompt = text.slice(match[0].length).trim();\n } else {\n prompt = text;\n }\n\n return { agentName: matchedName, agent, prompt };\n}\n\n/**\n * Parse explicit `HANDOFF @agent: <task>` command in agent responses.\n * Only this syntax triggers auto-handoff — bare @agent mentions are ignored.\n *\n * Note: the `prompt` field captures the rest of the HANDOFF line only.\n * The handoff loop in discord.ts passes the full responseText to the\n * dispatched agent, not this prompt — the dispatched agent sees the\n * complete previous response plus thread context.\n */\nexport function parseHandoffCommand(\n text: string,\n agents: Record<string, AgentConfig>,\n): AgentMention | null {\n const agentNames = Object.keys(agents);\n if (agentNames.length === 0) return null;\n\n const escaped = agentNames.map(n => n.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'));\n const pattern = new RegExp(`^HANDOFF\\\\s+@(${escaped.join('|')})\\\\s*:\\\\s*(.*)$`, 'im');\n const match = text.match(pattern);\n if (!match) return null;\n\n const matchedName = match[1].toLowerCase();\n const agent = agents[matchedName];\n if (!agent) return null;\n\n return { agentName: matchedName, agent, prompt: match[2].trim() };\n}\n","// src/embed-format.ts\nimport { EmbedBuilder, type TextChannel, type ThreadChannel } from 'discord.js';\nimport { chunkMessage } from './discord.js';\n\n/** 10 high-contrast colors for light and dark Discord themes. */\nexport const PALETTE: readonly number[] = [\n 0x3498db, // blue\n 0xe74c3c, // red\n 0x2ecc71, // green\n 0x9b59b6, // purple\n 0xf39c12, // orange\n 0x1abc9c, // teal\n 0xe91e63, // pink\n 0xff9800, // amber\n 0x00bcd4, // cyan\n 0x8bc34a, // lime\n];\n\n/** Deterministic color for an agent key (djb2 hash mod palette length). */\nexport function agentColor(agentKey: string): number {\n let hash = 0;\n for (const ch of agentKey) hash = ((hash << 5) - hash + ch.charCodeAt(0)) | 0;\n return PALETTE[Math.abs(hash) % PALETTE.length];\n}\n\nconst EMBED_DESCRIPTION_LIMIT = 4096;\n\n/** Build Discord embeds for an agent response, chunking at 4096 chars. */\nexport function buildAgentEmbeds(text: string, agentName: string, agentRole: string): EmbedBuilder[] {\n const color = agentColor(agentName);\n const chunks = chunkMessage(text, EMBED_DESCRIPTION_LIMIT);\n\n return chunks.map((chunk, i) => {\n const authorName = i === 0 ? agentRole : `${agentRole} (cont.)`;\n const embed = new EmbedBuilder()\n .setAuthor({ name: authorName })\n .setColor(color);\n if (chunk) {\n embed.setDescription(chunk);\n } else {\n embed.data.description = '';\n }\n return embed;\n });\n}\n\n/** Build a small embed announcing an agent handoff. */\nexport function buildHandoffEmbed(agentName: string, agentRole: string): EmbedBuilder {\n return new EmbedBuilder()\n .setAuthor({ name: agentRole })\n .setDescription(`Handing off to **@${agentName}**...`)\n .setColor(agentColor(agentName));\n}\n\nconst PLAIN_TEXT_LIMIT = 2000;\n\n/** Send a message as embeds (if agent) or plain text (if not). */\nexport async function sendAgentMessage(\n channel: { send(content: unknown): Promise<unknown> },\n text: string,\n agentName?: string,\n agentRole?: string,\n): Promise<void> {\n if (agentName && agentRole) {\n const embeds = buildAgentEmbeds(text, agentName, agentRole);\n for (const embed of embeds) {\n await channel.send({ embeds: [embed] });\n }\n } else {\n const chunks = chunkMessage(text, PLAIN_TEXT_LIMIT);\n for (const chunk of chunks) {\n await channel.send(chunk);\n }\n }\n}\n","/**\n * Discord role-based access control.\n * Checks if a guild member has any of the allowed roles (by name or ID).\n */\n\nimport type { GuildMember } from 'discord.js';\n\n/**\n * Returns true if the member is authorized, i.e. they have at least one of the allowedRoles.\n * If allowedRoles is empty or undefined, everyone is authorized (backward compatible).\n */\nexport function hasAllowedRole(member: GuildMember | null, allowedRoles: string[] | undefined): boolean {\n if (!allowedRoles || allowedRoles.length === 0) return true;\n if (!member) return false;\n\n return member.roles.cache.some(\n (role) => allowedRoles.includes(role.name) || allowedRoles.includes(role.id),\n );\n}\n","/**\n * Simple in-memory per-user rate limiter.\n * Tracks message timestamps per user and enforces a max-messages-per-minute limit.\n */\n\nconst WINDOW_MS = 60_000; // 1 minute window\nconst CLEANUP_INTERVAL_MS = 5 * 60_000; // clean up every 5 minutes\n\nexport interface RateLimitResult {\n allowed: boolean;\n /** Seconds until the user can send another message (only set when blocked). */\n retryAfterSeconds?: number;\n}\n\nexport interface RateLimiter {\n check(userId: string, limit: number): RateLimitResult;\n dispose(): void;\n}\n\nexport function createRateLimiter(): RateLimiter {\n const timestamps = new Map<string, number[]>();\n\n function pruneUser(userId: string, now: number): number[] {\n const ts = timestamps.get(userId);\n if (!ts) return [];\n const valid = ts.filter((t) => now - t < WINDOW_MS);\n if (valid.length === 0) {\n timestamps.delete(userId);\n return [];\n }\n timestamps.set(userId, valid);\n return valid;\n }\n\n const cleanupTimer = setInterval(() => {\n const now = Date.now();\n for (const userId of timestamps.keys()) {\n pruneUser(userId, now);\n }\n }, CLEANUP_INTERVAL_MS);\n\n // Don't keep Node alive just for cleanup\n if (cleanupTimer.unref) cleanupTimer.unref();\n\n return {\n check(userId: string, limit: number): RateLimitResult {\n const now = Date.now();\n const valid = pruneUser(userId, now);\n\n if (valid.length >= limit) {\n // Find the oldest timestamp in the window to calculate retry-after\n const oldest = valid[0];\n const retryAfterMs = WINDOW_MS - (now - oldest);\n return {\n allowed: false,\n retryAfterSeconds: Math.ceil(retryAfterMs / 1000),\n };\n }\n\n // Record this message\n if (!timestamps.has(userId)) {\n timestamps.set(userId, [now]);\n } else {\n timestamps.get(userId)!.push(now);\n }\n\n return { allowed: true };\n },\n\n dispose() {\n clearInterval(cleanupTimer);\n timestamps.clear();\n },\n };\n}\n","import { appendFileSync, mkdirSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { ClaudeUsage } from './claude-cli.js';\n\nexport interface PulseEmitter {\n sessionStart(sessionId: string, projectKey: string, projectDir: string, opts?: { agentName?: string; triggerSource?: string }): void;\n sessionEnd(sessionId: string, projectKey: string, projectDir: string, durationMs: number, messageCount: number): void;\n sessionIdle(sessionId: string, projectKey: string, projectDir: string, durationMs: number, messageCount: number): void;\n sessionResume(sessionId: string, projectKey: string, projectDir: string, idleDurationMs: number): void;\n messageRouted(sessionId: string, projectKey: string, projectDir: string, opts?: { agentTarget?: string; queueDepth?: number }): void;\n messageCompleted(sessionId: string, projectKey: string, projectDir: string, usage: ClaudeUsage, opts?: { agentTarget?: string }): void;\n}\n\nconst DEFAULT_PATH = join(homedir(), '.pulse', 'events', 'mpg-sessions.jsonl');\n\nfunction baseEvent(eventType: string, sessionId: string, projectKey: string, projectDir: string) {\n return {\n schema_version: 1,\n timestamp: new Date().toISOString(),\n event_type: eventType,\n session_id: sessionId,\n project_key: projectKey,\n project_dir: projectDir,\n };\n}\n\nexport function createPulseEmitter(filePath?: string): PulseEmitter {\n const target = filePath ?? DEFAULT_PATH;\n let dirCreated = false;\n\n function emit(event: Record<string, unknown>): void {\n try {\n if (!dirCreated) {\n mkdirSync(dirname(target), { recursive: true });\n dirCreated = true;\n }\n appendFileSync(target, JSON.stringify(event) + '\\n');\n } catch {\n // Fire-and-forget: never crash the gateway for event logging\n }\n }\n\n return {\n sessionStart(sessionId, projectKey, projectDir, opts) {\n emit({\n ...baseEvent('session_start', sessionId, projectKey, projectDir),\n agent_name: opts?.agentName,\n trigger_source: opts?.triggerSource ?? 'unknown',\n });\n },\n\n sessionEnd(sessionId, projectKey, projectDir, durationMs, messageCount) {\n emit({\n ...baseEvent('session_end', sessionId, projectKey, projectDir),\n duration_ms: durationMs,\n message_count: messageCount,\n });\n },\n\n sessionIdle(sessionId, projectKey, projectDir, durationMs, messageCount) {\n emit({\n ...baseEvent('session_idle', sessionId, projectKey, projectDir),\n duration_ms: durationMs,\n message_count: messageCount,\n });\n },\n\n sessionResume(sessionId, projectKey, projectDir, idleDurationMs) {\n emit({\n ...baseEvent('session_resume', sessionId, projectKey, projectDir),\n idle_duration_ms: idleDurationMs,\n });\n },\n\n messageRouted(sessionId, projectKey, projectDir, opts) {\n emit({\n ...baseEvent('message_routed', sessionId, projectKey, projectDir),\n agent_target: opts?.agentTarget,\n queue_depth: opts?.queueDepth ?? 0,\n });\n },\n\n messageCompleted(sessionId, projectKey, projectDir, usage, opts) {\n emit({\n ...baseEvent('message_completed', sessionId, projectKey, projectDir),\n agent_target: opts?.agentTarget,\n input_tokens: usage.input_tokens,\n output_tokens: usage.output_tokens,\n cache_creation_input_tokens: usage.cache_creation_input_tokens,\n cache_read_input_tokens: usage.cache_read_input_tokens,\n total_cost_usd: usage.total_cost_usd,\n duration_ms: usage.duration_ms,\n duration_api_ms: usage.duration_api_ms,\n num_turns: usage.num_turns,\n model: usage.model,\n });\n },\n };\n}\n","import { readFileSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\n\nexport type TimeRange = '3h' | '12h' | '24h' | '7d' | '30d';\nexport type Bucket = '15min' | 'hour' | 'day';\n\nconst DEFAULT_PATH = join(homedir(), '.pulse', 'events', 'mpg-sessions.jsonl');\n\nconst RANGE_MS: Record<TimeRange, number> = {\n '3h': 3 * 60 * 60 * 1000,\n '12h': 12 * 60 * 60 * 1000,\n '24h': 24 * 60 * 60 * 1000,\n '7d': 7 * 24 * 60 * 60 * 1000,\n '30d': 30 * 24 * 60 * 60 * 1000,\n};\n\ninterface PulseEvent {\n schema_version?: number;\n timestamp: string;\n event_type: string;\n session_id: string;\n project_key: string;\n project_dir: string;\n [key: string]: unknown;\n}\n\nfunction readEvents(filePath: string, range: TimeRange): PulseEvent[] {\n if (!existsSync(filePath)) return [];\n try {\n const content = readFileSync(filePath, 'utf-8').trim();\n if (!content) return [];\n const cutoff = Date.now() - RANGE_MS[range];\n const events: PulseEvent[] = [];\n for (const line of content.split('\\n')) {\n try {\n const e = JSON.parse(line) as PulseEvent;\n if (new Date(e.timestamp).getTime() >= cutoff) {\n events.push(e);\n }\n } catch {\n // Skip malformed lines\n }\n }\n return events;\n } catch {\n return [];\n }\n}\n\nfunction bucketKey(timestamp: string, bucket: Bucket): string {\n const d = new Date(timestamp);\n if (bucket === '15min') {\n d.setMinutes(Math.floor(d.getMinutes() / 15) * 15, 0, 0);\n } else if (bucket === 'hour') {\n d.setMinutes(0, 0, 0);\n } else {\n d.setHours(0, 0, 0, 0);\n }\n return d.toISOString();\n}\n\n/** Resolve a project name from its directory path using a directory→name map.\n * Handles both exact matches and worktree subdirectory matches. */\nfunction resolveNameFromDir(projectDir: string, dirToNameMap?: Record<string, string>): string | undefined {\n if (!dirToNameMap || !projectDir) return undefined;\n if (dirToNameMap[projectDir]) return dirToNameMap[projectDir];\n for (const [dir, name] of Object.entries(dirToNameMap)) {\n if (projectDir.startsWith(dir + '/')) return name;\n }\n return undefined;\n}\n\nexport interface ActivityEngine {\n computeSummary(range: TimeRange): {\n total_cost_usd: number;\n total_input_tokens: number;\n total_output_tokens: number;\n total_sessions: number;\n total_messages: number;\n avg_session_duration_ms: number;\n };\n tokensByProject(range: TimeRange, dirToNameMap?: Record<string, string>): Array<{\n project_name: string;\n input_tokens: number;\n output_tokens: number;\n cache_read_input_tokens: number;\n cost_usd: number;\n message_count: number;\n }>;\n tokensBySession(range: TimeRange): Array<{\n session_id: string;\n project_key: string;\n input_tokens: number;\n output_tokens: number;\n cost_usd: number;\n message_count: number;\n duration_ms: number;\n }>;\n bucketed(range: TimeRange, bucket: Bucket, eventType: string, valueField?: string): Array<{ bucket: string; value: number }>;\n sessionDurations(range: TimeRange): Array<{\n session_id: string;\n project_key: string;\n duration_ms: number;\n }>;\n modelBreakdown(range: TimeRange): Array<{\n model: string;\n input_tokens: number;\n output_tokens: number;\n cost_usd: number;\n }>;\n personaBreakdown(range: TimeRange): Array<{\n agent: string;\n count: number;\n }>;\n cacheEfficiency(range: TimeRange): {\n total_input_tokens: number;\n cache_read_tokens: number;\n cache_hit_ratio: number;\n };\n sessionTimeline(range: TimeRange, projectNameMap?: Record<string, string>, dirToNameMap?: Record<string, string>): Array<{\n session_id: string;\n thread_id: string;\n label: string;\n segments: Array<{\n start: string;\n end: string;\n state: 'processing' | 'idle';\n token_count?: number;\n token_rate?: number;\n }>;\n }>;\n}\n\nexport function createActivityEngine(filePath?: string): ActivityEngine {\n const target = filePath ?? DEFAULT_PATH;\n\n function getEvents(range: TimeRange, eventType?: string): PulseEvent[] {\n const events = readEvents(target, range);\n return eventType ? events.filter(e => e.event_type === eventType) : events;\n }\n\n return {\n computeSummary(range) {\n const events = readEvents(target, range);\n const sessions = events.filter(e => e.event_type === 'session_start');\n const messages = events.filter(e => e.event_type === 'message_completed');\n const endings = events.filter(e => e.event_type === 'session_end' || e.event_type === 'session_idle');\n\n const totalDuration = endings.reduce((s, e) => s + (Number(e.duration_ms) || 0), 0);\n\n return {\n total_cost_usd: messages.reduce((s, e) => s + (Number(e.total_cost_usd) || 0), 0),\n total_input_tokens: messages.reduce((s, e) => s + (Number(e.input_tokens) || 0), 0),\n total_output_tokens: messages.reduce((s, e) => s + (Number(e.output_tokens) || 0), 0),\n total_sessions: sessions.length,\n total_messages: messages.length,\n avg_session_duration_ms: endings.length > 0 ? totalDuration / endings.length : 0,\n };\n },\n\n tokensByProject(range, dirToNameMap) {\n const messages = getEvents(range, 'message_completed');\n const map = new Map<string, { project_name: string; input_tokens: number; output_tokens: number; cache_read_input_tokens: number; cost_usd: number; message_count: number }>();\n for (const e of messages) {\n const name = resolveNameFromDir(e.project_dir, dirToNameMap) ?? e.project_key;\n const row = map.get(name) ?? { project_name: name, input_tokens: 0, output_tokens: 0, cache_read_input_tokens: 0, cost_usd: 0, message_count: 0 };\n row.input_tokens += Number(e.input_tokens) || 0;\n row.output_tokens += Number(e.output_tokens) || 0;\n row.cache_read_input_tokens += Number(e.cache_read_input_tokens) || 0;\n row.cost_usd += Number(e.total_cost_usd) || 0;\n row.message_count++;\n map.set(name, row);\n }\n return Array.from(map.values());\n },\n\n tokensBySession(range) {\n const messages = getEvents(range, 'message_completed');\n const map = new Map<string, { session_id: string; project_key: string; input_tokens: number; output_tokens: number; cost_usd: number; message_count: number; duration_ms: number }>();\n for (const e of messages) {\n const key = e.session_id;\n const row = map.get(key) ?? { session_id: key, project_key: e.project_key, input_tokens: 0, output_tokens: 0, cost_usd: 0, message_count: 0, duration_ms: 0 };\n row.input_tokens += Number(e.input_tokens) || 0;\n row.output_tokens += Number(e.output_tokens) || 0;\n row.cost_usd += Number(e.total_cost_usd) || 0;\n row.duration_ms += Number(e.duration_ms) || 0;\n row.message_count++;\n map.set(key, row);\n }\n return Array.from(map.values());\n },\n\n bucketed(range, bucket, eventType, valueField) {\n const events = getEvents(range, eventType);\n const map = new Map<string, number>();\n for (const e of events) {\n const key = bucketKey(e.timestamp, bucket);\n const val = valueField ? (Number(e[valueField]) || 0) : 1;\n map.set(key, (map.get(key) ?? 0) + val);\n }\n // Fill in all time slots in the range so the x-axis shows absolute time\n const now = new Date();\n const start = new Date(now.getTime() - RANGE_MS[range]);\n const stepMs = bucket === '15min' ? 15 * 60 * 1000\n : bucket === 'hour' ? 60 * 60 * 1000\n : 24 * 60 * 60 * 1000;\n const startKey = bucketKey(start.toISOString(), bucket);\n const cursor = new Date(startKey);\n const endTime = now.getTime();\n while (cursor.getTime() <= endTime) {\n const key = cursor.toISOString();\n if (!map.has(key)) map.set(key, 0);\n cursor.setTime(cursor.getTime() + stepMs);\n }\n return Array.from(map.entries())\n .sort((a, b) => a[0].localeCompare(b[0]))\n .map(([b, value]) => ({ bucket: b, value }));\n },\n\n sessionDurations(range) {\n const endings = getEvents(range).filter(e => e.event_type === 'session_end' || e.event_type === 'session_idle');\n return endings.map(e => ({\n session_id: e.session_id,\n project_key: e.project_key,\n duration_ms: Number(e.duration_ms) || 0,\n }));\n },\n\n modelBreakdown(range) {\n const messages = getEvents(range, 'message_completed');\n const map = new Map<string, { model: string; input_tokens: number; output_tokens: number; cost_usd: number }>();\n for (const e of messages) {\n const model = String(e.model ?? 'unknown');\n const row = map.get(model) ?? { model, input_tokens: 0, output_tokens: 0, cost_usd: 0 };\n row.input_tokens += Number(e.input_tokens) || 0;\n row.output_tokens += Number(e.output_tokens) || 0;\n row.cost_usd += Number(e.total_cost_usd) || 0;\n map.set(model, row);\n }\n return Array.from(map.values());\n },\n\n personaBreakdown(range) {\n const routed = getEvents(range, 'message_routed');\n const map = new Map<string, number>();\n for (const e of routed) {\n const agent = String(e.agent_target ?? 'default');\n map.set(agent, (map.get(agent) ?? 0) + 1);\n }\n return Array.from(map.entries()).map(([agent, count]) => ({ agent, count }));\n },\n\n cacheEfficiency(range) {\n const messages = getEvents(range, 'message_completed');\n const totalInput = messages.reduce((s, e) => s + (Number(e.input_tokens) || 0), 0);\n const cacheRead = messages.reduce((s, e) => s + (Number(e.cache_read_input_tokens) || 0), 0);\n const denominator = cacheRead + totalInput;\n return {\n total_input_tokens: totalInput,\n cache_read_tokens: cacheRead,\n cache_hit_ratio: denominator > 0 ? cacheRead / denominator : 0,\n };\n },\n\n sessionTimeline(range, projectNameMap, dirToNameMap) {\n const events = readEvents(target, range);\n const TIMELINE_TYPES = new Set(['session_start', 'session_resume', 'message_routed', 'message_completed', 'session_end', 'session_idle']);\n const relevant = events.filter(e => TIMELINE_TYPES.has(e.event_type));\n\n // Collect message_completed events indexed by session for token enrichment\n const completedBySession = new Map<string, PulseEvent[]>();\n for (const e of events) {\n if (e.event_type === 'message_completed') {\n const list = completedBySession.get(e.session_id);\n if (list) list.push(e);\n else completedBySession.set(e.session_id, [e]);\n }\n }\n\n // Group by session_id\n const sessionMap = new Map<string, PulseEvent[]>();\n for (const e of relevant) {\n const list = sessionMap.get(e.session_id);\n if (list) list.push(e);\n else sessionMap.set(e.session_id, [e]);\n }\n\n type Segment = { start: string; end: string; state: 'processing' | 'idle'; token_count?: number; token_rate?: number };\n const result: Array<{\n session_id: string;\n thread_id: string;\n label: string;\n segments: Segment[];\n }> = [];\n\n for (const [sessionId, sessionEvents] of sessionMap) {\n // Sort by timestamp\n sessionEvents.sort((a, b) => a.timestamp.localeCompare(b.timestamp));\n\n // Determine persona: prefer agent_name from session_start, then agent_target from message_routed\n let persona = 'default';\n const startEvent = sessionEvents.find(e => e.event_type === 'session_start');\n if (startEvent && startEvent.agent_name) {\n persona = String(startEvent.agent_name);\n } else {\n const routedEvent = sessionEvents.find(e => e.event_type === 'message_routed');\n if (routedEvent && routedEvent.agent_target) {\n persona = String(routedEvent.agent_target);\n }\n }\n\n // Resolve project name: try directory-based lookup first, then channel ID, then fallback\n const projectDir = sessionEvents[0].project_dir;\n const projectKey = sessionEvents[0].project_key;\n const channelId = projectKey?.includes(':') ? projectKey.split(':')[0] : projectKey;\n const projectName = resolveNameFromDir(projectDir, dirToNameMap)\n ?? (projectNameMap && channelId ? projectNameMap[channelId] : undefined)\n ?? 'unknown';\n // Use thread/channel ID as short identifier so sessions from the same thread share it\n const shortId = channelId ? channelId.slice(-8) : sessionId.substring(0, 8);\n const label = `${projectName}/${shortId}/${persona}`;\n\n // Build segments by walking through events\n const segments: Segment[] = [];\n let currentState: 'processing' | 'idle' = 'idle';\n let segmentStart = sessionEvents[0].timestamp;\n\n for (let i = 1; i < sessionEvents.length; i++) {\n const e = sessionEvents[i];\n\n if (e.event_type === 'message_routed' && currentState === 'idle') {\n // End idle segment, start processing\n segments.push({ start: segmentStart, end: e.timestamp, state: 'idle' });\n segmentStart = e.timestamp;\n currentState = 'processing';\n } else if (e.event_type === 'message_completed' && currentState === 'processing') {\n // End processing segment, back to idle\n segments.push({ start: segmentStart, end: e.timestamp, state: 'processing' });\n segmentStart = e.timestamp;\n currentState = 'idle';\n } else if (e.event_type === 'session_resume') {\n // Session was restored after a gap — close any in-progress segment at the\n // previous event's time (not resume time) to avoid spanning the gap, then restart\n if (i > 0) {\n const prevEvent = sessionEvents[i - 1];\n if (segmentStart !== prevEvent.timestamp) {\n segments.push({ start: segmentStart, end: prevEvent.timestamp, state: currentState });\n }\n }\n segmentStart = e.timestamp;\n currentState = 'idle';\n } else if (e.event_type === 'session_end' || e.event_type === 'session_idle') {\n // End whatever current state is\n segments.push({ start: segmentStart, end: e.timestamp, state: currentState });\n segmentStart = e.timestamp;\n }\n }\n\n // If session has no end event but had activity, close the last segment\n const lastEvent = sessionEvents[sessionEvents.length - 1];\n if (lastEvent.event_type !== 'session_end' && lastEvent.event_type !== 'session_idle') {\n if (segmentStart !== lastEvent.timestamp || segments.length === 0) {\n // Only add if there's actually a segment to close\n if (segments.length > 0 || sessionEvents.length > 1) {\n segments.push({ start: segmentStart, end: lastEvent.timestamp, state: currentState });\n }\n }\n }\n\n // Enrich processing segments with token data\n const completed = completedBySession.get(sessionId) || [];\n for (const seg of segments) {\n if (seg.state !== 'processing') continue;\n const segStartMs = new Date(seg.start).getTime();\n const segEndMs = new Date(seg.end).getTime();\n const durationSec = (segEndMs - segStartMs) / 1000;\n\n let tokenCount = 0;\n for (const ev of completed) {\n const evMs = new Date(ev.timestamp).getTime();\n if (evMs >= segStartMs && evMs <= segEndMs) {\n tokenCount += (Number(ev.input_tokens) || 0) + (Number(ev.output_tokens) || 0);\n }\n }\n\n seg.token_count = tokenCount;\n seg.token_rate = durationSec > 0 ? Math.round(tokenCount / durationSec) : 0;\n }\n\n result.push({ session_id: sessionId, thread_id: channelId ?? '', label, segments });\n }\n\n return result;\n },\n };\n}\n","import { createServer, type Server } from 'node:http';\nimport { readFileSync } from 'node:fs';\nimport type { SessionManager } from './session-manager.js';\nimport type { DiscordBot } from './discord.js';\nimport type { GatewayConfig } from './config.js';\nimport type { ActivityEngine, TimeRange, Bucket } from './activity-engine.js';\n\nexport interface DashboardServer {\n close(): Promise<void>;\n}\n\nfunction getVersion(): string {\n try {\n const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));\n return pkg.version ?? 'unknown';\n } catch {\n return 'unknown';\n }\n}\n\nexport interface DashboardServerOptions {\n activityEngine?: ActivityEngine;\n}\n\nfunction buildDashboardHtml(): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>MPG Dashboard</title>\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js\"></script>\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f1117; color: #e1e4e8; padding: 24px; }\n h1 { font-size: 20px; font-weight: 600; margin-bottom: 4px; }\n .subtitle { color: #8b949e; font-size: 13px; margin-bottom: 8px; }\n .tabs { display: flex; gap: 8px; margin-bottom: 24px; }\n .tab { background: #161b22; border: 1px solid #30363d; border-radius: 6px; color: #8b949e; padding: 8px 16px; cursor: pointer; font-size: 14px; }\n .tab.active { color: #e1e4e8; border-color: #58a6ff; background: #1c2333; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; }\n .card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; }\n .card-label { font-size: 12px; color: #8b949e; text-transform: uppercase; letter-spacing: 0.5px; }\n .card-value { font-size: 28px; font-weight: 700; margin-top: 4px; }\n .status-ok { color: #3fb950; }\n .status-warn { color: #d29922; }\n .status-err { color: #f85149; }\n h2 { font-size: 16px; font-weight: 600; margin-bottom: 12px; }\n h3 { font-size: 14px; font-weight: 600; margin-bottom: 8px; color: #8b949e; }\n table { width: 100%; border-collapse: collapse; background: #161b22; border: 1px solid #30363d; border-radius: 8px; overflow: hidden; margin-bottom: 24px; }\n th { text-align: left; padding: 10px 14px; font-size: 12px; color: #8b949e; text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid #30363d; }\n td { padding: 10px 14px; font-size: 14px; border-bottom: 1px solid #21262d; }\n tr:last-child td { border-bottom: none; }\n .empty { color: #8b949e; font-style: italic; padding: 24px; text-align: center; }\n .refresh-info { color: #484f58; font-size: 12px; text-align: right; }\n .range-selector { display: flex; gap: 8px; margin-bottom: 16px; }\n .range-btn { background: #161b22; border: 1px solid #30363d; border-radius: 4px; color: #8b949e; padding: 6px 12px; cursor: pointer; font-size: 13px; }\n .range-btn.active { color: #e1e4e8; border-color: #58a6ff; }\n .chart-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 16px; margin-bottom: 24px; }\n .chart-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; }\n .chart-card h3 { margin-bottom: 12px; }\n .summary-cards { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; margin-bottom: 24px; }\n .summary-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; text-align: center; }\n .summary-value { font-size: 24px; font-weight: bold; color: #e1e4e8; }\n .summary-label { font-size: 12px; color: #8b949e; margin-top: 4px; }\n</style>\n</head>\n<body>\n<h1>Multi-Project Gateway</h1>\n<p class=\"subtitle\" id=\"version\"></p>\n<div class=\"tabs\">\n <button class=\"tab active\" onclick=\"switchTab('overview')\">Overview</button>\n <button class=\"tab\" onclick=\"switchTab('activity')\">Activity</button>\n <button class=\"tab\" onclick=\"switchTab('timeline')\">Timeline</button>\n</div>\n\n<div id=\"tab-overview\">\n<div class=\"grid\">\n <div class=\"card\">\n <div class=\"card-label\">Status</div>\n <div class=\"card-value\" id=\"status\">—</div>\n </div>\n <div class=\"card\">\n <div class=\"card-label\">Uptime</div>\n <div class=\"card-value\" id=\"uptime\">—</div>\n </div>\n <div class=\"card\">\n <div class=\"card-label\">Active Sessions</div>\n <div class=\"card-value\" id=\"sessions-active\">—</div>\n </div>\n <div class=\"card\">\n <div class=\"card-label\">Queued Messages</div>\n <div class=\"card-value\" id=\"sessions-queued\">—</div>\n </div>\n <div class=\"card\">\n <div class=\"card-label\">Discord</div>\n <div class=\"card-value\" id=\"discord\">—</div>\n </div>\n</div>\n\n<h2>Sessions</h2>\n<div id=\"sessions-table\"></div>\n\n<h2>Projects</h2>\n<div id=\"projects-table\"></div>\n\n<p class=\"refresh-info\">Auto-refreshes every 5s</p>\n</div>\n\n<div id=\"tab-activity\" style=\"display:none\">\n <div class=\"range-selector\">\n <button class=\"range-btn active\" data-range=\"24h\">24h</button>\n <button class=\"range-btn\" data-range=\"7d\">7d</button>\n <button class=\"range-btn\" data-range=\"30d\">30d</button>\n </div>\n <div class=\"summary-cards\">\n <div class=\"summary-card\"><div class=\"summary-value\" id=\"total-cost\">$0.00</div><div class=\"summary-label\">Total Cost</div></div>\n <div class=\"summary-card\"><div class=\"summary-value\" id=\"total-tokens\">0</div><div class=\"summary-label\">Total Tokens</div></div>\n <div class=\"summary-card\"><div class=\"summary-value\" id=\"total-sessions-card\">0</div><div class=\"summary-label\">Sessions</div></div>\n <div class=\"summary-card\"><div class=\"summary-value\" id=\"total-messages\">0</div><div class=\"summary-label\">Messages</div></div>\n <div class=\"summary-card\"><div class=\"summary-value\" id=\"avg-duration\">0m</div><div class=\"summary-label\">Avg Duration</div></div>\n </div>\n <div class=\"chart-grid\">\n <div class=\"chart-card\"><h3>Messages Over Time</h3><canvas id=\"messages-chart\"></canvas></div>\n <div class=\"chart-card\"><h3>Cost Over Time</h3><canvas id=\"cost-chart\"></canvas></div>\n <div class=\"chart-card\"><h3>Sessions Over Time</h3><canvas id=\"sessions-chart\"></canvas></div>\n <div class=\"chart-card\"><h3>Token Usage Over Time (Input / Output)</h3><canvas id=\"tokens-chart\"></canvas></div>\n <div class=\"chart-card\"><h3>Cache Read Tokens Over Time</h3><canvas id=\"cache-chart-time\"></canvas></div>\n <div class=\"chart-card\"><h3>Persona Breakdown</h3><canvas id=\"persona-chart\"></canvas></div>\n <div class=\"chart-card\"><h3>Model Breakdown</h3><canvas id=\"model-chart\"></canvas></div>\n </div>\n <h3 style=\"margin:16px 0 8px\">Token Usage by Project</h3>\n <div id=\"project-table\"></div>\n <h3 style=\"margin:16px 0 8px\">Token Usage by Session</h3>\n <div id=\"session-table\"></div>\n <h3 style=\"margin:16px 0 8px\">Cache Efficiency</h3>\n <div id=\"cache-table\"></div>\n</div>\n\n<div id=\"tab-timeline\" style=\"display:none\">\n <div class=\"range-selector timeline-range\">\n <button class=\"tl-range-btn range-btn active\" data-range=\"3h\">3h</button>\n <button class=\"tl-range-btn range-btn\" data-range=\"12h\">12h</button>\n <button class=\"tl-range-btn range-btn\" data-range=\"24h\">24h</button>\n <button class=\"tl-range-btn range-btn\" data-range=\"7d\">7d</button>\n <button class=\"tl-range-btn range-btn\" data-range=\"30d\">30d</button>\n </div>\n <div class=\"chart-card\"><h3>Session Timeline</h3><canvas id=\"timeline-chart\"></canvas><div id=\"timeline-tooltip\" style=\"display:none;position:fixed;background:#161b22;border:1px solid #30363d;border-radius:6px;padding:8px 12px;color:#e1e4e8;font-size:12px;pointer-events:none;z-index:100;white-space:nowrap\"></div></div>\n</div>\n\n<script>\nfunction formatUptime(s) {\n if (s < 60) return s + 's';\n if (s < 3600) return Math.floor(s / 60) + 'm';\n var h = Math.floor(s / 3600);\n var m = Math.floor((s % 3600) / 60);\n return h + 'h ' + m + 'm';\n}\nfunction formatAgo(ts) {\n var diff = Math.floor((Date.now() - ts) / 1000);\n if (diff < 60) return diff + 's ago';\n if (diff < 3600) return Math.floor(diff / 60) + 'm ago';\n return Math.floor(diff / 3600) + 'h ago';\n}\nfunction formatDuration(startTs) {\n var diff = Math.floor((Date.now() - startTs) / 1000);\n if (diff < 60) return diff + 's';\n if (diff < 3600) return Math.floor(diff / 60) + 'm';\n var h = Math.floor(diff / 3600);\n var m = Math.floor((diff % 3600) / 60);\n return h + 'h ' + m + 'm';\n}\nfunction sessionStatus(s) {\n if (s.processing) return '<span style=\"color:#3fb950\">processing</span>';\n if (s.queueLength > 0) return '<span style=\"color:#d29922\">waiting</span>';\n return '<span style=\"color:#8b949e\">idle</span>';\n}\nfunction statusClass(v) {\n if (v === 'ok' || v === 'connected') return 'status-ok';\n if (v === 'reconnecting') return 'status-warn';\n return 'status-err';\n}\nfunction escapeHtml(s) {\n var d = document.createElement('div');\n d.textContent = s;\n return d.innerHTML;\n}\nfunction compactTokens(n) {\n if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M';\n if (n >= 1e3) return (n / 1e3).toFixed(1) + 'K';\n return String(n);\n}\nfunction formatLocalTime(isoStr, isHourBucket) {\n var d = new Date(isoStr);\n if (isHourBucket) return String(d.getHours()).padStart(2, '0') + ':00';\n var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];\n return months[d.getMonth()] + ' ' + d.getDate();\n}\nfunction resolveProjectName(key, nameMap) {\n if (!key) return '—';\n var parts = key.split(':');\n var channelId = parts[0];\n var agent = parts.length > 1 ? parts[1] : null;\n var name = (nameMap && nameMap[channelId]) ? nameMap[channelId] : channelId;\n return agent ? name + ':' + agent : name;\n}\nfunction resolveProjectNameFromDir(dir, dirMap) {\n if (!dir || !dirMap) return null;\n if (dirMap[dir]) return dirMap[dir];\n var keys = Object.keys(dirMap);\n for (var i = 0; i < keys.length; i++) {\n if (dir.indexOf(keys[i] + '/') === 0) return dirMap[keys[i]];\n }\n return null;\n}\nfunction copyToClipboard(text, el) {\n navigator.clipboard.writeText(text).then(function() {\n var orig = el.textContent;\n el.textContent = 'copied!';\n el.style.color = '#3fb950';\n setTimeout(function() { el.textContent = orig; el.style.color = ''; }, 1200);\n });\n}\n\nvar projectNameMap = {};\n\nfunction refresh() {\n fetch('/api/status')\n .then(function(r) { return r.json(); })\n .then(function(d) {\n document.getElementById('version').textContent = 'v' + d.version;\n var statusEl = document.getElementById('status');\n statusEl.textContent = d.health.status;\n statusEl.className = 'card-value ' + statusClass(d.health.status);\n document.getElementById('uptime').textContent = formatUptime(d.health.uptime);\n document.getElementById('sessions-active').textContent = d.health.sessions.active;\n document.getElementById('sessions-queued').textContent = d.health.sessions.queued;\n var discordEl = document.getElementById('discord');\n discordEl.textContent = d.health.discord;\n discordEl.className = 'card-value ' + statusClass(d.health.discord);\n\n // Build name maps from projects list\n var dirToNameMap = {};\n if (d.projects) {\n d.projects.forEach(function(p) {\n if (p.channelId && p.name) projectNameMap[p.channelId] = p.name;\n if (p.directory && p.name) dirToNameMap[p.directory] = p.name;\n });\n }\n\n // Sessions table — sort by most recent last activity first\n d.sessions.sort(function(a, b) {\n return (b.lastActivity || 0) - (a.lastActivity || 0);\n });\n var st = document.getElementById('sessions-table');\n if (d.sessions.length === 0) {\n st.innerHTML = '<div class=\"empty\">No active sessions</div>';\n } else {\n var h = '<table><tr><th>Session</th><th>Status</th><th>Duration</th><th>Last Activity</th></tr>';\n for (var i = 0; i < d.sessions.length; i++) {\n var s = d.sessions[i];\n var sid = s.sessionId || '';\n var shortId = sid ? sid.slice(0, 8) : '';\n var pkParts = (s.projectKey || '').split(':');\n var projName = resolveProjectNameFromDir(s.projectDir || s.cwd, dirToNameMap) || (projectNameMap && projectNameMap[pkParts[0]] ? projectNameMap[pkParts[0]] : null) || pkParts[0];\n var role = pkParts.length > 1 ? pkParts[1] : '';\n var sessionLabel = projName && shortId ? (role ? projName + '/' + shortId + '/' + role : projName + '/' + shortId) : (shortId || '—');\n h += '<tr><td><span class=\"clickable-id\" style=\"cursor:pointer;text-decoration:underline dotted\" title=\"Click to copy: ' + escapeHtml(sid) + '\" onclick=\"copyToClipboard(\\\\'' + escapeHtml(sid) + '\\\\', this)\">' + escapeHtml(sessionLabel) + '</span></td><td>' + sessionStatus(s) + '</td><td>' + formatDuration(s.createdAt) + '</td><td>' + formatAgo(s.lastActivity) + '</td></tr>';\n }\n h += '</table>';\n st.innerHTML = h;\n }\n\n // Projects table\n var pt = document.getElementById('projects-table');\n if (d.projects.length === 0) {\n pt.innerHTML = '<div class=\"empty\">No projects configured</div>';\n } else {\n var h2 = '<table><tr><th>Name</th><th>Directory</th><th>Agents</th></tr>';\n for (var j = 0; j < d.projects.length; j++) {\n var p = d.projects[j];\n h2 += '<tr><td>' + escapeHtml(p.name) + '</td><td>' + escapeHtml(p.directory) + '</td><td>' + escapeHtml(p.agents.join(', ') || '—') + '</td></tr>';\n }\n h2 += '</table>';\n pt.innerHTML = h2;\n }\n })\n .catch(function() {\n document.getElementById('status').textContent = 'error';\n document.getElementById('status').className = 'card-value status-err';\n });\n}\n\nrefresh();\nsetInterval(refresh, 5000);\n\nvar chartInstances = {};\nvar currentRange = '24h';\nvar timelineRange = '3h';\nvar CHART_COLORS = ['#58a6ff', '#3fb950', '#d29922', '#f85149', '#bc8cff', '#79c0ff'];\n\nfunction switchTab(tab) {\n document.querySelectorAll('.tab').forEach(function(t) { t.classList.remove('active'); });\n document.querySelectorAll('.tab').forEach(function(t) {\n if (t.textContent.toLowerCase() === tab) t.classList.add('active');\n });\n document.getElementById('tab-overview').style.display = tab === 'overview' ? '' : 'none';\n document.getElementById('tab-activity').style.display = tab === 'activity' ? '' : 'none';\n document.getElementById('tab-timeline').style.display = tab === 'timeline' ? '' : 'none';\n if (tab === 'activity') refreshActivity();\n if (tab === 'timeline') refreshTimeline();\n}\n\ndocument.querySelectorAll('#tab-activity .range-btn').forEach(function(btn) {\n btn.addEventListener('click', function() {\n document.querySelectorAll('#tab-activity .range-btn').forEach(function(b) { b.classList.remove('active'); });\n btn.classList.add('active');\n currentRange = btn.dataset.range;\n refreshActivity();\n });\n});\n\ndocument.querySelectorAll('.tl-range-btn').forEach(function(btn) {\n btn.addEventListener('click', function() {\n document.querySelectorAll('.tl-range-btn').forEach(function(b) { b.classList.remove('active'); });\n btn.classList.add('active');\n timelineRange = btn.dataset.range;\n refreshTimeline();\n });\n});\n\nfunction destroyChart(key) {\n if (chartInstances[key]) { chartInstances[key].destroy(); chartInstances[key] = null; }\n}\n\nfunction timeAxisOptions(isHourBucket) {\n return {\n ticks: {\n color: '#8b949e',\n callback: function(value, index, ticks) {\n var label = this.getLabelForValue(value);\n return formatLocalTime(label, isHourBucket);\n }\n },\n grid: { color: '#30363d' }\n };\n}\n\nfunction formatSegmentDuration(startIso, endIso) {\n var ms = new Date(endIso).getTime() - new Date(startIso).getTime();\n if (ms < 1000) return ms + 'ms';\n var s = Math.floor(ms / 1000);\n if (s < 60) return s + 's';\n var m = Math.floor(s / 60);\n s = s % 60;\n if (m < 60) return m + 'm ' + s + 's';\n var h = Math.floor(m / 60);\n m = m % 60;\n return h + 'h ' + m + 'm';\n}\n\nvar _tlHitRects = [];\n\nfunction refreshTimeline() {\n var RANGE_MS = { '3h': 10800000, '12h': 43200000, '24h': 86400000, '7d': 604800000, '30d': 2592000000 };\n var now = Date.now();\n var rangeMs = RANGE_MS[timelineRange] || RANGE_MS['3h'];\n var xMin = now - rangeMs;\n var xMax = now;\n\n fetch('/api/activity/timeline?range=' + timelineRange)\n .then(function(r) { return r.json(); })\n .then(function(sessions) {\n var canvas = document.getElementById('timeline-chart');\n var dpr = window.devicePixelRatio || 1;\n var containerW = canvas.parentElement.clientWidth - 32;\n\n // Filter out sessions with only idle segments (no processing)\n var activeSessions = sessions ? sessions.filter(function(s) {\n return s.segments.some(function(seg) { return seg.state === 'processing'; });\n }) : [];\n\n var LABEL_W = 160;\n var ROW_H = 32;\n var HEADER_H = 28;\n var FOOTER_H = 8;\n var chartW = containerW;\n\n if (!activeSessions.length) {\n var h = 60;\n canvas.width = Math.floor(chartW * dpr);\n canvas.height = Math.floor(h * dpr);\n canvas.style.width = chartW + 'px';\n canvas.style.height = h + 'px';\n var c = canvas.getContext('2d');\n c.setTransform(dpr, 0, 0, dpr, 0, 0);\n c.fillStyle = '#0d1117';\n c.fillRect(0, 0, chartW, h);\n c.fillStyle = '#8b949e';\n c.font = '13px -apple-system, sans-serif';\n c.textAlign = 'center';\n c.fillText('No active sessions in range', chartW / 2, h / 2 + 4);\n _tlHitRects = [];\n return;\n }\n\n var LEGEND_H = 28;\n var totalH = HEADER_H + activeSessions.length * ROW_H + FOOTER_H + LEGEND_H;\n canvas.width = Math.floor(chartW * dpr);\n canvas.height = Math.floor(totalH * dpr);\n canvas.style.width = chartW + 'px';\n canvas.style.height = totalH + 'px';\n var ctx = canvas.getContext('2d');\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n\n // Background\n ctx.fillStyle = '#0d1117';\n ctx.fillRect(0, 0, chartW, totalH);\n\n var plotLeft = LABEL_W;\n var plotRight = chartW - 12;\n var plotW = plotRight - plotLeft;\n\n function timeToX(t) {\n return plotLeft + ((t - xMin) / (xMax - xMin)) * plotW;\n }\n\n // X-axis time labels at top\n ctx.fillStyle = '#8b949e';\n ctx.font = '10px -apple-system, sans-serif';\n ctx.textAlign = 'center';\n var tickCount = Math.max(2, Math.min(8, Math.floor(plotW / 90)));\n for (var ti = 0; ti <= tickCount; ti++) {\n var t = xMin + (ti / tickCount) * (xMax - xMin);\n var tx = timeToX(t);\n var d = new Date(t);\n var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];\n var lbl = months[d.getMonth()] + ' ' + d.getDate() + ' ' + String(d.getHours()).padStart(2,'0') + ':' + String(d.getMinutes()).padStart(2,'0');\n ctx.fillText(lbl, tx, 14);\n // Grid line\n ctx.strokeStyle = '#30363d';\n ctx.lineWidth = 0.5;\n ctx.beginPath();\n ctx.moveTo(tx, HEADER_H);\n ctx.lineTo(tx, totalH - FOOTER_H);\n ctx.stroke();\n }\n\n _tlHitRects = [];\n\n // Compute maxTokenRate across all visible processing segments for normalization\n var maxTokenRate = 0;\n for (var mi = 0; mi < activeSessions.length; mi++) {\n for (var mj = 0; mj < activeSessions[mi].segments.length; mj++) {\n var mseg = activeSessions[mi].segments[mj];\n if (mseg.state === 'processing' && mseg.token_rate > maxTokenRate) {\n maxTokenRate = mseg.token_rate;\n }\n }\n }\n\n function tokenRateColor(rate) {\n if (!maxTokenRate || !rate) return 'hsl(220, 40%, 55%)';\n var t = Math.min(rate / maxTokenRate, 1);\n // Interpolate: low rate → cool blue, high rate → warm orange\n var hue = 220 - t * 190; // 220 (blue) → 30 (orange)\n var sat = 40 + t * 40; // 40% → 80%\n var light = 55 - t * 15; // 55% → 40%\n return 'hsl(' + hue + ', ' + sat + '%, ' + light + '%)';\n }\n\n for (var ri = 0; ri < activeSessions.length; ri++) {\n var sess = activeSessions[ri];\n var rowY = HEADER_H + ri * ROW_H;\n var barY = rowY + 6;\n var barH = ROW_H - 12;\n\n // Y-axis label\n ctx.fillStyle = '#8b949e';\n ctx.font = '11px monospace';\n ctx.textAlign = 'right';\n ctx.fillText(sess.label, LABEL_W - 8, rowY + ROW_H / 2 + 4);\n\n // Row separator\n ctx.strokeStyle = '#21262d';\n ctx.lineWidth = 0.5;\n ctx.beginPath();\n ctx.moveTo(plotLeft, rowY + ROW_H);\n ctx.lineTo(plotRight, rowY + ROW_H);\n ctx.stroke();\n\n // Draw segments\n for (var si = 0; si < sess.segments.length; si++) {\n var seg = sess.segments[si];\n var segStart = Math.max(new Date(seg.start).getTime(), xMin);\n var segEnd = Math.min(new Date(seg.end).getTime(), xMax);\n if (segStart >= segEnd) continue;\n\n var x1 = timeToX(segStart);\n var x2 = timeToX(segEnd);\n var w = Math.max(x2 - x1, 1);\n\n ctx.fillStyle = seg.state === 'processing' ? tokenRateColor(seg.token_rate || 0) : '#484f58';\n ctx.fillRect(x1, barY, w, barH);\n\n _tlHitRects.push({ x: x1, y: barY, w: w, h: barH, label: sess.label, state: seg.state, start: seg.start, end: seg.end, token_count: seg.token_count || 0, token_rate: seg.token_rate || 0 });\n }\n }\n\n // Draw intensity legend below the chart\n if (maxTokenRate > 0) {\n var lgX = plotLeft;\n var lgY = totalH - LEGEND_H + 4;\n var lgW = 120;\n var lgH = 10;\n\n ctx.fillStyle = '#8b949e';\n ctx.font = '10px -apple-system, sans-serif';\n ctx.textAlign = 'left';\n ctx.fillText('Token rate:', lgX, lgY + 9);\n\n var gradX = lgX + 68;\n // Draw gradient bar\n for (var gi = 0; gi < lgW; gi++) {\n var gt = gi / lgW;\n var gHue = 220 - gt * 190; // blue → orange\n var gSat = 40 + gt * 40;\n var gLight = 55 - gt * 15;\n ctx.fillStyle = 'hsl(' + gHue + ', ' + gSat + '%, ' + gLight + '%)';\n ctx.fillRect(gradX + gi, lgY + 1, 1, lgH);\n }\n\n ctx.fillStyle = '#8b949e';\n ctx.font = '9px -apple-system, sans-serif';\n ctx.textAlign = 'left';\n ctx.fillText('0', gradX, lgY + 22);\n ctx.textAlign = 'right';\n ctx.fillText(compactTokens(maxTokenRate) + '/s', gradX + lgW, lgY + 22);\n\n // Idle swatch\n var idleX = gradX + lgW + 16;\n ctx.fillStyle = '#484f58';\n ctx.fillRect(idleX, lgY + 1, lgH, lgH);\n ctx.fillStyle = '#8b949e';\n ctx.font = '9px -apple-system, sans-serif';\n ctx.textAlign = 'left';\n ctx.fillText('Idle', idleX + lgH + 4, lgY + 9);\n }\n })\n .catch(function(err) { console.error('Timeline fetch error:', err); });\n}\n\n// Timeline tooltip via mousemove\n(function() {\n var canvas = document.getElementById('timeline-chart');\n var tooltip = document.getElementById('timeline-tooltip');\n canvas.addEventListener('mousemove', function(e) {\n var rect = canvas.getBoundingClientRect();\n var dpr = window.devicePixelRatio || 1;\n var mx = (e.clientX - rect.left);\n var my = (e.clientY - rect.top);\n var hit = null;\n for (var i = _tlHitRects.length - 1; i >= 0; i--) {\n var r = _tlHitRects[i];\n if (mx >= r.x && mx <= r.x + r.w && my >= r.y && my <= r.y + r.h) { hit = r; break; }\n }\n if (hit) {\n var state = hit.state.charAt(0).toUpperCase() + hit.state.slice(1);\n var tokenInfo = '';\n if (hit.state === 'processing' && hit.token_count > 0) {\n tokenInfo = '<br>Tokens: ' + compactTokens(hit.token_count) + ' (' + compactTokens(hit.token_rate) + ' tok/s)';\n }\n tooltip.innerHTML = '<strong>' + hit.label + '</strong><br>' + state + ': ' + formatSegmentDuration(hit.start, hit.end) + tokenInfo;\n tooltip.style.display = 'block';\n tooltip.style.left = (e.clientX + 12) + 'px';\n tooltip.style.top = (e.clientY - 10) + 'px';\n } else {\n tooltip.style.display = 'none';\n }\n });\n canvas.addEventListener('mouseleave', function() {\n tooltip.style.display = 'none';\n });\n})();\n\nfunction refreshActivity() {\n var isHourBucket = currentRange === '24h';\n fetch('/api/activity/summary?range=' + currentRange)\n .then(function(r) { return r.json(); })\n .then(function(d) {\n var nameMap = d.project_name_map || {};\n // Merge into global map\n Object.keys(nameMap).forEach(function(k) { projectNameMap[k] = nameMap[k]; });\n\n // Summary cards\n var s = d.summary;\n document.getElementById('total-cost').textContent = '$' + s.total_cost_usd.toFixed(2);\n var totalTok = s.total_input_tokens + s.total_output_tokens;\n document.getElementById('total-tokens').textContent = compactTokens(totalTok);\n document.getElementById('total-sessions-card').textContent = String(s.total_sessions);\n document.getElementById('total-messages').textContent = String(s.total_messages);\n document.getElementById('avg-duration').textContent = Math.round(s.avg_session_duration_ms / 60000) + 'm';\n\n var xOpts = timeAxisOptions(isHourBucket);\n\n // Messages Over Time (line)\n destroyChart('messages');\n chartInstances['messages'] = new Chart(document.getElementById('messages-chart'), {\n type: 'line',\n data: { labels: d.messages_over_time.map(function(e) { return e.bucket; }), datasets: [{ label: 'Messages', data: d.messages_over_time.map(function(e) { return e.value; }), borderColor: '#58a6ff', tension: 0.3 }] },\n options: { scales: { y: { beginAtZero: true, ticks: { color: '#8b949e' }, grid: { color: '#30363d' } }, x: xOpts }, plugins: { legend: { display: false } } }\n });\n\n // Cost Over Time (line)\n destroyChart('cost');\n chartInstances['cost'] = new Chart(document.getElementById('cost-chart'), {\n type: 'line',\n data: { labels: d.cost_over_time.map(function(e) { return e.bucket; }), datasets: [{ label: 'Cost ($)', data: d.cost_over_time.map(function(e) { return e.value; }), borderColor: '#3fb950', tension: 0.3 }] },\n options: { scales: { y: { beginAtZero: true, ticks: { color: '#8b949e' }, grid: { color: '#30363d' } }, x: xOpts }, plugins: { legend: { display: false } } }\n });\n\n // Sessions Over Time (line)\n destroyChart('sessions');\n chartInstances['sessions'] = new Chart(document.getElementById('sessions-chart'), {\n type: 'line',\n data: { labels: d.sessions_over_time.map(function(e) { return e.bucket; }), datasets: [{ label: 'Sessions', data: d.sessions_over_time.map(function(e) { return e.value; }), borderColor: '#d29922', tension: 0.3 }] },\n options: { scales: { y: { beginAtZero: true, ticks: { color: '#8b949e', stepSize: 1 }, grid: { color: '#30363d' } }, x: xOpts }, plugins: { legend: { display: false } } }\n });\n\n // Token Usage Over Time — Input + Output stacked (NO cache reads)\n var allBuckets = {};\n (d.input_tokens_over_time || []).forEach(function(e) { allBuckets[e.bucket] = true; });\n (d.output_tokens_over_time || []).forEach(function(e) { allBuckets[e.bucket] = true; });\n var bucketKeys = Object.keys(allBuckets).sort();\n var inputMap = {}; (d.input_tokens_over_time || []).forEach(function(e) { inputMap[e.bucket] = e.value; });\n var outputMap = {}; (d.output_tokens_over_time || []).forEach(function(e) { outputMap[e.bucket] = e.value; });\n destroyChart('tokens');\n chartInstances['tokens'] = new Chart(document.getElementById('tokens-chart'), {\n type: 'bar',\n data: {\n labels: bucketKeys,\n datasets: [\n { label: 'Input', data: bucketKeys.map(function(k) { return inputMap[k] || 0; }), backgroundColor: '#58a6ff' },\n { label: 'Output', data: bucketKeys.map(function(k) { return outputMap[k] || 0; }), backgroundColor: '#3fb950' }\n ]\n },\n options: { scales: { y: { beginAtZero: true, stacked: true, ticks: { color: '#8b949e', callback: function(v) { return compactTokens(v); } }, grid: { color: '#30363d' } }, x: Object.assign({}, xOpts, { stacked: true }) }, plugins: { legend: { labels: { color: '#8b949e' } } } }\n });\n\n // Cache Read Tokens Over Time — separate chart\n destroyChart('cache-time');\n chartInstances['cache-time'] = new Chart(document.getElementById('cache-chart-time'), {\n type: 'bar',\n data: { labels: (d.cache_read_over_time || []).map(function(e) { return e.bucket; }), datasets: [{ label: 'Cache Read', data: (d.cache_read_over_time || []).map(function(e) { return e.value; }), backgroundColor: '#bc8cff' }] },\n options: { scales: { y: { beginAtZero: true, ticks: { color: '#8b949e', callback: function(v) { return compactTokens(v); } }, grid: { color: '#30363d' } }, x: xOpts }, plugins: { legend: { display: false } } }\n });\n\n // Persona Breakdown (doughnut)\n destroyChart('persona');\n if (d.persona_breakdown.length > 0) {\n chartInstances['persona'] = new Chart(document.getElementById('persona-chart'), {\n type: 'doughnut',\n data: { labels: d.persona_breakdown.map(function(p) { return p.agent; }), datasets: [{ data: d.persona_breakdown.map(function(p) { return p.count; }), backgroundColor: CHART_COLORS.slice(0, d.persona_breakdown.length) }] },\n options: { plugins: { legend: { labels: { color: '#8b949e' } } } }\n });\n }\n\n // Model Breakdown (doughnut)\n destroyChart('model');\n if (d.model_breakdown.length > 0) {\n chartInstances['model'] = new Chart(document.getElementById('model-chart'), {\n type: 'doughnut',\n data: { labels: d.model_breakdown.map(function(m) { return m.model; }), datasets: [{ data: d.model_breakdown.map(function(m) { return m.cost_usd; }), backgroundColor: CHART_COLORS.slice(0, d.model_breakdown.length) }] },\n options: { plugins: { legend: { labels: { color: '#8b949e' } } } }\n });\n }\n\n // Token Usage by Project table — resolve names, compact token notation\n var pt = document.getElementById('project-table');\n if (d.tokens_by_project.length === 0) { pt.innerHTML = '<div class=\"empty\">No data</div>'; }\n else {\n var h = '<table><tr><th>Project</th><th>Input</th><th>Output</th><th>Cache Read</th><th>Cost</th><th>Messages</th></tr>';\n d.tokens_by_project.forEach(function(p) { h += '<tr><td>' + escapeHtml(p.project_name || 'unknown') + '</td><td>' + compactTokens(p.input_tokens) + '</td><td>' + compactTokens(p.output_tokens) + '</td><td>' + compactTokens(p.cache_read_input_tokens) + '</td><td>$' + p.cost_usd.toFixed(3) + '</td><td>' + p.message_count + '</td></tr>'; });\n pt.innerHTML = h + '</table>';\n }\n\n // Token Usage by Session table — resolve names, copy-to-clipboard session IDs\n var st = document.getElementById('session-table');\n if (d.tokens_by_session.length === 0) { st.innerHTML = '<div class=\"empty\">No data</div>'; }\n else {\n var h2 = '<table><tr><th>Session</th><th>Project</th><th>Input</th><th>Output</th><th>Cost</th><th>Msgs</th><th>Duration</th></tr>';\n d.tokens_by_session.forEach(function(row) {\n var shortId = row.session_id.substring(0, 8) + '...';\n h2 += '<tr><td><span class=\"clickable-id\" style=\"cursor:pointer;text-decoration:underline dotted\" title=\"Click to copy: ' + escapeHtml(row.session_id) + '\" onclick=\"copyToClipboard(\\\\'' + escapeHtml(row.session_id) + '\\\\', this)\">' + escapeHtml(shortId) + '</span></td><td>' + escapeHtml(resolveProjectName(row.project_key, nameMap)) + '</td><td>' + compactTokens(row.input_tokens) + '</td><td>' + compactTokens(row.output_tokens) + '</td><td>$' + row.cost_usd.toFixed(3) + '</td><td>' + row.message_count + '</td><td>' + Math.round(row.duration_ms / 60000) + 'm</td></tr>';\n });\n st.innerHTML = h2 + '</table>';\n }\n\n // Cache Efficiency table\n var ct = document.getElementById('cache-table');\n var ce = d.cache_efficiency;\n ct.innerHTML = '<table><tr><th>Total Input</th><th>Cache Read</th><th>Hit Ratio</th></tr><tr><td>' + compactTokens(ce.total_input_tokens) + '</td><td>' + compactTokens(ce.cache_read_tokens) + '</td><td>' + (ce.cache_hit_ratio * 100).toFixed(1) + '%</td></tr></table>';\n })\n .catch(function(err) { console.error('Activity fetch error:', err); });\n}\n\nsetInterval(function() {\n if (document.getElementById('tab-activity').style.display !== 'none') {\n refreshActivity();\n }\n if (document.getElementById('tab-timeline').style.display !== 'none') {\n refreshTimeline();\n }\n}, 30000);\n</script>\n</body>\n</html>`;\n}\n\nexport function createDashboardServer(\n port: number,\n sessionManager: SessionManager,\n bot: DiscordBot,\n config?: GatewayConfig,\n options?: DashboardServerOptions,\n): Promise<DashboardServer> {\n const startTime = Date.now();\n const version = getVersion();\n const dashboardHtml = buildDashboardHtml();\n function getHealthData() {\n const sessions = sessionManager.listSessions();\n return {\n status: 'ok',\n uptime: Math.floor((Date.now() - startTime) / 1000),\n sessions: {\n active: sessions.length,\n queued: sessions.reduce((sum, s) => sum + s.queueLength, 0),\n },\n discord: bot.getStatus(),\n };\n }\n\n function getProjectsData() {\n if (!config) return [];\n return Object.entries(config.projects).map(([channelId, project]) => ({\n channelId,\n name: project.name,\n directory: project.directory,\n agents: project.agents ? Object.keys(project.agents) : [],\n }));\n }\n\n const server: Server = createServer((req, res) => {\n const { pathname } = new URL(req.url ?? '/', `http://localhost`);\n\n if (req.method !== 'GET') {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Not Found' }));\n return;\n }\n\n if (pathname === '/health') {\n const body = JSON.stringify(getHealthData());\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(body);\n return;\n }\n\n if (pathname === '/api/sessions') {\n const body = JSON.stringify(sessionManager.listSessions());\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(body);\n return;\n }\n\n if (pathname === '/api/projects') {\n const body = JSON.stringify(getProjectsData());\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(body);\n return;\n }\n\n if (pathname === '/api/status') {\n const body = JSON.stringify({\n version,\n health: getHealthData(),\n sessions: sessionManager.listSessions(),\n projects: getProjectsData(),\n });\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(body);\n return;\n }\n\n if (pathname === '/api/activity/timeline') {\n const url = new URL(req.url ?? '/', `http://localhost`);\n const rangeParam = url.searchParams.get('range') || '7d';\n if (rangeParam !== '3h' && rangeParam !== '12h' && rangeParam !== '24h' && rangeParam !== '7d' && rangeParam !== '30d') {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid range. Must be 3h, 12h, 24h, 7d, or 30d' }));\n return;\n }\n const engine = options?.activityEngine;\n if (!engine) {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify([]));\n return;\n }\n try {\n const projectNameMap: Record<string, string> = {};\n const dirToNameMap: Record<string, string> = {};\n if (config) {\n for (const [channelId, project] of Object.entries(config.projects)) {\n projectNameMap[channelId] = project.name;\n dirToNameMap[project.directory] = project.name;\n }\n }\n const data = engine.sessionTimeline(rangeParam as TimeRange, projectNameMap, dirToNameMap);\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(data));\n } catch {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Failed to compute timeline data' }));\n }\n return;\n }\n\n if (pathname === '/api/activity/summary') {\n const url = new URL(req.url ?? '/', `http://localhost`);\n const rangeParam = url.searchParams.get('range') || '7d';\n if (rangeParam !== '24h' && rangeParam !== '7d' && rangeParam !== '30d') {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid range. Must be 24h, 7d, or 30d' }));\n return;\n }\n const range = rangeParam as TimeRange;\n const bucket: Bucket = range === '24h' ? 'hour' : 'day';\n const engine = options?.activityEngine;\n\n if (!engine) {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({\n summary: { total_cost_usd: 0, total_input_tokens: 0, total_output_tokens: 0, total_sessions: 0, total_messages: 0, avg_session_duration_ms: 0 },\n tokens_by_project: [], tokens_by_session: [],\n sessions_over_time: [], messages_over_time: [], cost_over_time: [],\n input_tokens_over_time: [], output_tokens_over_time: [], cache_read_over_time: [],\n session_durations: [], model_breakdown: [], persona_breakdown: [],\n cache_efficiency: { total_input_tokens: 0, cache_read_tokens: 0, cache_hit_ratio: 0 },\n project_name_map: {},\n dir_to_name_map: {},\n }));\n return;\n }\n\n try {\n // Build channel ID → project name map from config\n const projectNameMap: Record<string, string> = {};\n const dirToNameMap: Record<string, string> = {};\n if (config) {\n for (const [channelId, project] of Object.entries(config.projects)) {\n projectNameMap[channelId] = project.name;\n dirToNameMap[project.directory] = project.name;\n }\n }\n\n const data = {\n summary: engine.computeSummary(range),\n tokens_by_project: engine.tokensByProject(range, dirToNameMap),\n tokens_by_session: engine.tokensBySession(range),\n sessions_over_time: engine.bucketed(range, bucket, 'session_start'),\n messages_over_time: engine.bucketed(range, bucket, 'message_completed'),\n cost_over_time: engine.bucketed(range, bucket, 'message_completed', 'total_cost_usd'),\n input_tokens_over_time: engine.bucketed(range, bucket, 'message_completed', 'input_tokens'),\n output_tokens_over_time: engine.bucketed(range, bucket, 'message_completed', 'output_tokens'),\n cache_read_over_time: engine.bucketed(range, bucket, 'message_completed', 'cache_read_input_tokens'),\n session_durations: engine.sessionDurations(range),\n model_breakdown: engine.modelBreakdown(range),\n persona_breakdown: engine.personaBreakdown(range),\n cache_efficiency: engine.cacheEfficiency(range),\n project_name_map: projectNameMap,\n dir_to_name_map: dirToNameMap,\n };\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(data));\n } catch {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Failed to compute activity data' }));\n }\n return;\n }\n\n if (pathname === '/') {\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(dashboardHtml);\n return;\n }\n\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Not Found' }));\n });\n\n return new Promise<DashboardServer>((resolve, reject) => {\n server.on('error', reject);\n server.listen(port, () => {\n console.log(`Dashboard server listening on http://localhost:${port}/`);\n resolve({\n close() {\n return new Promise<void>((res, rej) => {\n server.close((err) => (err ? rej(err) : res()));\n });\n },\n });\n });\n });\n}\n","// src/turn-counter.ts\nexport interface TurnCounter {\n increment(threadId: string): void;\n getTurns(threadId: string): number;\n isOverLimit(threadId: string, limit: number): boolean;\n reset(threadId: string): void;\n}\n\nexport function createTurnCounter(): TurnCounter {\n const turns = new Map<string, number>();\n\n return {\n increment(threadId: string): void {\n turns.set(threadId, (turns.get(threadId) ?? 0) + 1);\n },\n\n getTurns(threadId: string): number {\n return turns.get(threadId) ?? 0;\n },\n\n isOverLimit(threadId: string, limit: number): boolean {\n return (turns.get(threadId) ?? 0) >= limit;\n },\n\n reset(threadId: string): void {\n turns.delete(threadId);\n },\n };\n}\n","import { createInterface } from 'node:readline';\nimport { writeFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { resolveMpgHome, resolveProfileDir } from './resolve-home.js';\n\nfunction createPrompt(): (question: string) => Promise<string> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return (question: string) =>\n new Promise((resolve) => rl.question(question, (answer) => resolve(answer.trim())));\n}\n\nexport async function runInit(profile?: string) {\n const ask = createPrompt();\n\n // Determine output directory: if --profile is given, use MPG_HOME/profiles/<name>;\n // otherwise, use CWD for backward compat (quick single-instance setup).\n let configDir: string;\n let envDir: string;\n if (profile) {\n configDir = resolveProfileDir(profile);\n envDir = resolveMpgHome();\n mkdirSync(configDir, { recursive: true });\n mkdirSync(envDir, { recursive: true });\n console.log(`\\nmpg init — set up profile \"${profile}\"\\n`);\n console.log(`Profile directory: ${configDir}`);\n console.log(`Secrets directory: ${envDir}\\n`);\n } else {\n configDir = process.cwd();\n envDir = process.cwd();\n console.log('\\nmpg init — set up multi-project gateway\\n');\n }\n\n // Check for claude CLI\n try {\n execSync('claude --version', { stdio: 'pipe' });\n console.log('Claude CLI found.');\n } catch {\n console.warn('Warning: `claude` not found on PATH. Make sure it is installed before starting the gateway.');\n }\n\n // Discord bot token\n let token = process.env.DISCORD_BOT_TOKEN ?? '';\n const inputToken = await ask(`Discord bot token${token ? ' (press Enter to keep existing)' : ''}: `);\n if (inputToken) token = inputToken;\n if (!token) {\n console.error('A Discord bot token is required. Create one at https://discord.com/developers/applications');\n process.exit(1);\n }\n\n // Write .env\n const envPath = resolve(envDir, '.env');\n writeFileSync(envPath, `DISCORD_BOT_TOKEN=${token}\\n`);\n console.log(`Wrote ${envPath}`);\n\n // Collect projects\n interface ProjectEntry {\n name: string;\n directory: string;\n channelId: string;\n }\n\n const projects: ProjectEntry[] = [];\n\n // Load existing config if present\n const configPath = resolve(configDir, 'config.json');\n if (existsSync(configPath)) {\n try {\n const existing = JSON.parse(\n (await import('node:fs')).readFileSync(configPath, 'utf-8'),\n );\n if (existing.projects) {\n for (const [channelId, project] of Object.entries(existing.projects)) {\n const p = project as { name?: string; directory: string };\n projects.push({ name: p.name ?? channelId, directory: p.directory, channelId });\n }\n if (projects.length > 0) {\n console.log(`\\nExisting projects (${projects.length}):`);\n for (const p of projects) {\n console.log(` ${p.name} → ${p.directory} (${p.channelId})`);\n }\n }\n }\n } catch {\n // ignore parse errors\n }\n }\n\n console.log('\\nAdd projects (empty name to finish):\\n');\n\n while (true) {\n const name = await ask('Project name: ');\n if (!name) break;\n\n const directory = await ask('Project directory (absolute path): ');\n if (!directory) {\n console.log('Directory is required, skipping.');\n continue;\n }\n if (!existsSync(directory)) {\n console.warn(`Warning: ${directory} does not exist.`);\n }\n\n const channelId = await ask('Discord channel ID: ');\n if (!channelId) {\n console.log('Channel ID is required, skipping.');\n continue;\n }\n\n projects.push({ name, directory, channelId });\n console.log(`Added ${name}\\n`);\n }\n\n if (projects.length === 0) {\n console.log('No projects configured. You can edit config.json later.');\n }\n\n // Build config\n const config = {\n defaults: {\n idleTimeoutMs: 1800000,\n maxConcurrentSessions: 4,\n claudeArgs: ['--permission-mode', 'acceptEdits', '--output-format', 'json'],\n },\n projects: Object.fromEntries(\n projects.map((p) => [p.channelId, { name: p.name, directory: p.directory }]),\n ),\n };\n\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n console.log(`Wrote ${configPath}`);\n\n console.log('\\nSetup complete! Run `mpg start` to launch the gateway.');\n}\n","import { resolve, dirname } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\n\n/**\n * Returns the MPG home directory.\n * Priority: MPG_HOME env var > ~/.mpg\n */\nexport function resolveMpgHome(): string {\n return process.env.MPG_HOME ?? resolve(homedir(), '.mpg');\n}\n\n/**\n * Returns the profile directory for a given profile name.\n */\nexport function resolveProfileDir(profile: string): string {\n return resolve(resolveMpgHome(), 'profiles', profile);\n}\n\nexport interface ResolvedPaths {\n envPath: string | undefined;\n configPath: string;\n sessionsPath: string;\n}\n\n/**\n * Resolve .env path using the resolution order:\n * 1. Environment variables (already set) — highest priority (returns undefined, already loaded)\n * 2. $MPG_HOME/.env\n * 3. $CWD/.env — backward compat\n */\nexport function resolveEnvPath(): string | undefined {\n const mpgHome = resolveMpgHome();\n const mpgEnv = resolve(mpgHome, '.env');\n if (existsSync(mpgEnv)) {\n return mpgEnv;\n }\n\n const cwdEnv = resolve(process.cwd(), '.env');\n if (existsSync(cwdEnv)) {\n return cwdEnv;\n }\n\n return undefined;\n}\n\n/**\n * Resolve config.json path using the resolution order:\n * 1. --config <path> CLI flag (explicit path)\n * 2. --profile <name> → $MPG_HOME/profiles/<name>/config.json\n * 3. $MPG_HOME/profiles/default/config.json\n * 4. $CWD/config.json — backward compat fallback\n *\n * Returns the resolved config path, or undefined if none found.\n */\nexport function resolveConfigPath(options?: {\n configFlag?: string;\n profileFlag?: string;\n}): string | undefined {\n // 1. Explicit --config flag\n if (options?.configFlag) {\n const explicit = resolve(options.configFlag);\n if (existsSync(explicit)) {\n return explicit;\n }\n return explicit; // Return even if missing — let caller handle error\n }\n\n // 2. --profile flag\n if (options?.profileFlag) {\n const profileConfig = resolve(\n resolveProfileDir(options.profileFlag),\n 'config.json',\n );\n if (existsSync(profileConfig)) {\n return profileConfig;\n }\n return profileConfig; // Return even if missing — let caller handle error\n }\n\n // 3. MPG_HOME/profiles/default/config.json\n const mpgHome = resolveMpgHome();\n const defaultConfig = resolve(mpgHome, 'profiles', 'default', 'config.json');\n if (existsSync(defaultConfig)) {\n return defaultConfig;\n }\n\n // 4. CWD/config.json — backward compat\n const cwdConfig = resolve(process.cwd(), 'config.json');\n if (existsSync(cwdConfig)) {\n return cwdConfig;\n }\n\n return undefined;\n}\n\n/**\n * Resolve sessions.json path — always co-located with the resolved config.json.\n */\nexport function resolveSessionsPath(configPath: string): string {\n return resolve(dirname(configPath), 'sessions.json');\n}\n\n/**\n * Resolve PID file path.\n * Default profile or 'default' → mpg.pid\n * Named profile → mpg-<profile>.pid\n */\nexport function resolvePidPath(profile?: string): string {\n const name = !profile || profile === 'default' ? 'mpg' : `mpg-${profile}`;\n return resolve(resolveMpgHome(), `${name}.pid`);\n}\n\n/**\n * Resolve log directory path.\n */\nexport function resolveLogDir(): string {\n return resolve(resolveMpgHome(), 'logs');\n}\n\n/**\n * Resolve log file path.\n * Default profile or 'default' → mpg.log\n * Named profile → mpg-<profile>.log\n */\nexport function resolveLogPath(profile?: string): string {\n const name = !profile || profile === 'default' ? 'mpg' : `mpg-${profile}`;\n return resolve(resolveLogDir(), `${name}.log`);\n}\n\n/**\n * Parse --config, --profile flags from argv.\n */\nexport function parseFlags(argv: string[]): {\n configFlag?: string;\n profileFlag?: string;\n migrate?: boolean;\n project?: string;\n level?: string;\n follow?: boolean;\n} {\n const result: { configFlag?: string; profileFlag?: string; migrate?: boolean; project?: string; level?: string; follow?: boolean } = {};\n\n for (let i = 0; i < argv.length; i++) {\n if (argv[i] === '--config' && i + 1 < argv.length) {\n result.configFlag = argv[i + 1];\n i++;\n } else if (argv[i] === '--profile' && i + 1 < argv.length) {\n result.profileFlag = argv[i + 1];\n i++;\n } else if (argv[i] === '--project' && i + 1 < argv.length) {\n result.project = argv[i + 1];\n i++;\n } else if (argv[i] === '--level' && i + 1 < argv.length) {\n result.level = argv[i + 1];\n i++;\n } else if (argv[i] === '--migrate') {\n result.migrate = true;\n } else if (argv[i] === '--follow' || argv[i] === '-f') {\n result.follow = true;\n }\n }\n\n return result;\n}\n","import { statSync } from 'node:fs';\nimport { execFileSync } from 'node:child_process';\nimport type { GatewayConfig } from './config.js';\n\nexport function runHealthChecks(config: GatewayConfig): void {\n // 1. Check claude CLI is on PATH\n try {\n execFileSync('claude', ['--version'], { timeout: 5000, stdio: 'ignore' });\n } catch {\n console.error(\n 'Health check failed:\\n' +\n ' ✗ \"claude\" CLI not found on PATH. Install: https://docs.anthropic.com/en/docs/claude-code'\n );\n process.exit(1);\n return;\n }\n\n // 2. Check all project directories exist and are directories\n const missing: string[] = [];\n for (const project of Object.values(config.projects)) {\n try {\n if (!statSync(project.directory).isDirectory()) {\n missing.push(` ✗ Project \"${project.name}\" path is not a directory: ${project.directory}`);\n }\n } catch {\n missing.push(` ✗ Project \"${project.name}\" directory not found: ${project.directory}`);\n }\n }\n\n if (missing.length > 0) {\n console.error('Health check failed:\\n' + missing.join('\\n'));\n process.exit(1);\n }\n}\n","import { readFileSync, writeFileSync, unlinkSync, mkdirSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\nexport function writePid(pidPath: string, pid: number = process.pid): void {\n mkdirSync(dirname(pidPath), { recursive: true });\n writeFileSync(pidPath, `${pid}\\n`);\n}\n\nexport function readPid(pidPath: string): number | undefined {\n try {\n const content = readFileSync(pidPath, 'utf-8').trim();\n const pid = Number(content);\n return Number.isFinite(pid) && pid > 0 ? pid : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function removePid(pidPath: string): void {\n try {\n unlinkSync(pidPath);\n } catch {\n // Ignore — file may already be gone\n }\n}\n\nexport function isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nexport type PidStatus =\n | { status: 'none' }\n | { status: 'running'; pid: number }\n | { status: 'stale'; pid: number };\n\nexport function checkPidFile(pidPath: string): PidStatus {\n const pid = readPid(pidPath);\n if (pid === undefined) {\n return { status: 'none' };\n }\n\n if (isProcessRunning(pid)) {\n return { status: 'running', pid };\n }\n\n // Stale PID file — remove it\n removePid(pidPath);\n return { status: 'stale', pid };\n}\n","import { appendFileSync, renameSync, unlinkSync, existsSync, mkdirSync, statSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\nconst DEFAULT_MAX_BYTES = 10 * 1024 * 1024; // 10 MB\nconst DEFAULT_MAX_FILES = 5;\n\n/**\n * Rotate a log file. Shifts .1 → .2 → ... → .maxFiles, then renames current → .1.\n * Files beyond maxFiles are deleted.\n */\nexport function rotateLog(logPath: string, maxFiles: number = DEFAULT_MAX_FILES): void {\n if (!existsSync(logPath)) return;\n\n // Delete the oldest if it would exceed maxFiles\n const oldest = `${logPath}.${maxFiles}`;\n if (existsSync(oldest)) {\n try { unlinkSync(oldest); } catch { /* ignore */ }\n }\n\n // Shift .N-1 → .N, .N-2 → .N-1, ...\n for (let i = maxFiles - 1; i >= 1; i--) {\n const from = `${logPath}.${i}`;\n const to = `${logPath}.${i + 1}`;\n if (existsSync(from)) {\n try { renameSync(from, to); } catch { /* ignore */ }\n }\n }\n\n // Rename current → .1\n try { renameSync(logPath, `${logPath}.1`); } catch { /* ignore */ }\n}\n\n/**\n * Create a writer function that appends lines to a log file.\n * Automatically rotates when the file exceeds maxBytes.\n */\nexport function createFileWriter(\n logPath: string,\n opts?: { maxBytes?: number; maxFiles?: number },\n): (line: string) => void {\n const maxBytes = opts?.maxBytes ?? DEFAULT_MAX_BYTES;\n const maxFiles = opts?.maxFiles ?? DEFAULT_MAX_FILES;\n let currentBytes = 0;\n\n // Ensure log directory exists\n const dir = dirname(logPath);\n mkdirSync(dir, { recursive: true });\n\n // Track current file size\n try {\n currentBytes = statSync(logPath).size;\n } catch {\n currentBytes = 0;\n }\n\n return (line: string) => {\n const data = line + '\\n';\n const byteLength = Buffer.byteLength(data);\n\n if (currentBytes + byteLength > maxBytes && currentBytes > 0) {\n rotateLog(logPath, maxFiles);\n currentBytes = 0;\n }\n\n appendFileSync(logPath, data);\n currentBytes += byteLength;\n };\n}\n","import { resolve } from 'node:path';\nimport { homedir } from 'node:os';\nimport { mkdirSync, writeFileSync, unlinkSync, existsSync } from 'node:fs';\nimport { execFileSync, execSync } from 'node:child_process';\nimport { unitFileName, generateUnitFile } from './systemd.js';\nimport { resolveLogPath } from './resolve-home.js';\n\nexport function resolveServiceDir(): string {\n return resolve(homedir(), '.config', 'systemd', 'user');\n}\n\nexport function resolveServicePath(profile?: string): string {\n return resolve(resolveServiceDir(), unitFileName(profile));\n}\n\nexport function daemonInstall(profile?: string): void {\n const serviceDir = resolveServiceDir();\n mkdirSync(serviceDir, { recursive: true });\n\n const nodePath = process.execPath;\n const mpgPath = resolveOwnBinary();\n\n const unit = generateUnitFile({ nodePath, mpgPath, profile });\n const servicePath = resolveServicePath(profile);\n writeFileSync(servicePath, unit);\n\n const name = unitFileName(profile);\n\n // Enable lingering so service runs without login session\n try {\n execFileSync('loginctl', ['enable-linger'], { stdio: 'ignore' });\n } catch {\n // Non-fatal — may already be enabled or not available\n }\n\n execFileSync('systemctl', ['--user', 'daemon-reload'], { stdio: 'inherit' });\n execFileSync('systemctl', ['--user', 'enable', '--now', name], { stdio: 'inherit' });\n\n console.log(`Installed and started ${name}`);\n console.log(` Unit file: ${servicePath}`);\n console.log(` Status: systemctl --user status ${name}`);\n console.log(` Logs: mpg daemon logs`);\n}\n\nexport function daemonUninstall(profile?: string): void {\n const name = unitFileName(profile);\n const servicePath = resolveServicePath(profile);\n\n try {\n execFileSync('systemctl', ['--user', 'stop', name], { stdio: 'inherit' });\n } catch {\n // May not be running\n }\n try {\n execFileSync('systemctl', ['--user', 'disable', name], { stdio: 'inherit' });\n } catch {\n // May not be enabled\n }\n\n if (existsSync(servicePath)) {\n unlinkSync(servicePath);\n }\n\n try {\n execFileSync('systemctl', ['--user', 'daemon-reload'], { stdio: 'inherit' });\n } catch {\n // Best effort\n }\n\n console.log(`Uninstalled ${name}`);\n}\n\nexport function daemonStatus(profile?: string): void {\n const name = unitFileName(profile);\n try {\n execFileSync('systemctl', ['--user', 'status', name], { stdio: 'inherit' });\n } catch {\n // systemctl status exits non-zero when service is stopped — that's OK\n }\n}\n\nexport function daemonLogs(profile?: string, follow?: boolean): void {\n const name = unitFileName(profile);\n const args = ['--user', '-u', name, '--no-pager'];\n if (follow) args.push('-f');\n try {\n execFileSync('journalctl', args, { stdio: 'inherit' });\n } catch {\n console.error(`journalctl not available. View logs at: ${resolveLogPath(profile)}`);\n }\n}\n\nfunction resolveOwnBinary(): string {\n // Try to find the mpg binary path\n try {\n const which = execSync('which mpg', { encoding: 'utf-8' }).trim();\n if (which) return which;\n } catch {\n // Fall through\n }\n\n // Fallback: use absolute path to current script\n const fallback = resolve(process.argv[1] ?? '.');\n console.warn(`Warning: 'mpg' not found on PATH. Using ${fallback} in service file.`);\n return fallback;\n}\n","import { dirname } from 'node:path';\n\nexport function unitFileName(profile?: string): string {\n if (!profile || profile === 'default') return 'mpg.service';\n return `mpg-${profile}.service`;\n}\n\nexport interface UnitFileOptions {\n nodePath: string;\n mpgPath: string;\n profile?: string;\n}\n\nexport function generateUnitFile(opts: UnitFileOptions): string {\n const profileArg = opts.profile && opts.profile !== 'default'\n ? ` --profile ${opts.profile}`\n : '';\n\n const nodeDir = dirname(opts.nodePath);\n const mpgDir = dirname(opts.mpgPath);\n const pathDirs = new Set([nodeDir, mpgDir, '/usr/local/bin', '/usr/bin', '/bin']);\n const pathValue = [...pathDirs].join(':');\n\n return `[Unit]\nDescription=Multi-Project Gateway for Claude Code\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nExecStart=${opts.mpgPath} start${profileArg}\nRestart=on-failure\nRestartSec=10\nEnvironment=PATH=${pathValue}\nEnvironment=NODE_ENV=production\n\n[Install]\nWantedBy=default.target\n`;\n}\n"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","names":["resolve","existsSync","readFileSync","mkdirSync","existsSync","join","join","resolve","worktreePath","existsSync","args","resolve","mkdirSync","readFileSync","existsSync","join","execFileSync","TIMEOUT","command","DEFAULT_TIMEOUT_MS","join","mkdirSync","args","command","existsSync","readFileSync","resolve","command","info","idle","sid","parentId","resolved","projectChannelId","project","agents","mkdirSync","dirname","join","readFileSync","existsSync","join","homedir","DEFAULT_PATH","start","readFileSync","version","resolve","writeFileSync","existsSync","mkdirSync","resolve","resolve","dirname","existsSync","homedir","resolve","mkdirSync","writeFileSync","existsSync","statSync","execFileSync","readFileSync","writeFileSync","mkdirSync","dirname","appendFileSync","unlinkSync","existsSync","mkdirSync","statSync","dirname","resolve","homedir","mkdirSync","writeFileSync","unlinkSync","existsSync","execFileSync","execSync","dirname","resolve","homedir","mkdirSync","writeFileSync","execFileSync","existsSync","unlinkSync","args","execSync","readFileSync","existsSync","resolve","mkdirSync"]}
|