@integrity-labs/agt-cli 0.19.23 → 0.19.26

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.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/persistent-session.ts","../src/lib/mcp-sanitize.ts","../src/lib/claude-tools.ts","../src/lib/orphan-channel-mcp-reaper.ts","../src/lib/daily-session.ts"],"sourcesContent":["/**\n * Persistent session manager for Claude Code agents.\n *\n * Hybrid approach:\n * - **tmux** for the interactive session (channels like Slack/Telegram\n * require a real TTY that only tmux provides)\n * - **acpx** for task injection (reliable prompt delivery via --no-wait,\n * avoids the tmux send-keys paste-not-submitting issue)\n *\n * On manager restart, detects existing tmux sessions and reattaches\n * without creating duplicates.\n */\n\nimport { spawn, execSync, execFileSync, type ChildProcess } from 'node:child_process';\nimport { join, dirname } from 'node:path';\nimport { homedir, platform, userInfo } from 'node:os';\nimport { existsSync, readFileSync, readdirSync, writeFileSync, appendFileSync, mkdirSync, chmodSync, copyFileSync, rmSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { sanitizeMcpJson } from './mcp-sanitize.js';\nimport { buildAllowedTools } from './claude-tools.js';\nimport { reapOrphanChannelMcps } from './orphan-channel-mcp-reaper.js';\nimport {\n getOrCreateDailySession,\n rotateDailySession,\n sessionFileExists,\n} from './daily-session.js';\n\n/**\n * When running as root on Linux, the tmux-spawned claude process reads\n * ~/.claude/.credentials.json from /root. But operators log in via `claude\n * /login` as ssm-user or ec2-user, leaving creds under their own home.\n * Copy the first valid creds file into /root/.claude so claude (running as\n * root inside tmux) finds them. Idempotent — safe to call on every spawn.\n *\n * Returns true if a copy was made (or the file is already up to date),\n * false if no creds could be found at all.\n */\nfunction syncClaudeCredsToRoot(): boolean {\n if (platform() !== 'linux') return true;\n if (typeof process.getuid !== 'function' || process.getuid() !== 0) return true;\n\n // Fast path: pair-via-browser writes creds directly to /root/.claude\n // (the throwaway claude session runs as root). If they're already\n // there, no sync needed.\n for (const filename of ['.credentials.json', 'credentials.json']) {\n if (existsSync(join('/root/.claude', filename))) return true;\n }\n\n // Legacy path: an operator ran `claude /login` interactively as\n // ec2-user. Find any /home/*/.claude credentials and copy them up.\n let sourcePath: string | null = null;\n try {\n const entries = readdirSync('/home', { withFileTypes: true });\n outer: for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n // Both filenames Claude Code has historically used — keep in sync\n // with findClaudeCredentialsPaths() in claude-auth-detect.ts.\n for (const filename of ['.credentials.json', 'credentials.json']) {\n const candidate = join('/home', entry.name, '.claude', filename);\n if (existsSync(candidate)) {\n sourcePath = candidate;\n break outer;\n }\n }\n }\n } catch { /* no /home or unreadable — fall through */ }\n\n if (!sourcePath) return false;\n\n const targetDir = '/root/.claude';\n // Preserve source filename so the resulting file matches what claude's\n // reader expects (it accepts either '.credentials.json' or 'credentials.json').\n const sourceFilename = sourcePath.endsWith('credentials.json') && !sourcePath.endsWith('.credentials.json')\n ? 'credentials.json'\n : '.credentials.json';\n const targetPath = join(targetDir, sourceFilename);\n try {\n if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true, mode: 0o700 });\n copyFileSync(sourcePath, targetPath);\n chmodSync(targetPath, 0o600);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve the claude binary to an absolute path. The manager runs under a\n * minimal PATH (cloud-init root env) that doesn't include\n * /home/linuxbrew/.linuxbrew/bin, so a bare `claude` reference in the tmux\n * shell fails immediately — session exits, manager sees it as \"unhealthy\",\n * restarts, loops forever.\n *\n * Cached at first call: claude's location doesn't change between cycles,\n * and `which` spawns aren't free.\n */\nlet cachedClaudePath: string | null = null;\nexport function resolveClaudeBinary(): string {\n if (cachedClaudePath) return cachedClaudePath;\n // Operator override: honour CLAUDE_PATH for non-standard installs.\n const override = process.env.CLAUDE_PATH;\n if (override && existsSync(override)) {\n cachedClaudePath = override;\n return override;\n }\n // Try PATH first — respects an operator's custom install.\n try {\n const out = execSync('which claude 2>/dev/null', { encoding: 'utf-8' }).trim();\n if (out && existsSync(out)) {\n cachedClaudePath = out;\n return out;\n }\n } catch { /* fall through to canonical paths */ }\n const candidates = [\n '/home/linuxbrew/.linuxbrew/bin/claude',\n '/opt/homebrew/bin/claude',\n '/usr/local/bin/claude',\n ];\n for (const p of candidates) {\n if (existsSync(p)) {\n cachedClaudePath = p;\n return p;\n }\n }\n // Last resort — let the shell fail so logs show the missing binary.\n return 'claude';\n}\n\n/**\n * ENG-4717: write the wrapper script that the persistent tmux session\n * exec's instead of putting `KEY=VALUE claude ...` directly on the\n * tmux command line. The wrapper sources `.env.integrations` (mode\n * 0600) inside the spawned shell, so secrets land in the exec'd\n * claude process's env without ever crossing the argv boundary that\n * `ps -eo command` reads from.\n *\n * Returns the wrapper path. Always overwrites — kept idempotent so\n * a manager respawn after a credential rotation picks up the new\n * .env.integrations contents on the next session start.\n *\n * Exported for unit tests; production callers go through startSession.\n */\nexport function writePersistentClaudeWrapper(args: {\n projectDir: string;\n claudeBin: string;\n initPrompt: string;\n claudeArgsJoined: string;\n}): string {\n const { projectDir, claudeBin, initPrompt, claudeArgsJoined } = args;\n const envIntegrationsPath = join(projectDir, '.env.integrations');\n const wrapperPath = join(projectDir, '.claude', 'persistent-claude.sh');\n const wrapperLines = [\n '#!/usr/bin/env bash',\n 'set -e',\n // IS_SANDBOX=1 lets claude run under root/sudo with\n // --dangerously-skip-permissions on dedicated EC2 hosts.\n 'export IS_SANDBOX=1',\n ];\n if (existsSync(envIntegrationsPath)) {\n // `set -a` exports every variable assigned by `source`; `set +a`\n // restores the prior state. Anything in .env.integrations becomes\n // an environment variable for the exec'd claude process.\n wrapperLines.push(\n 'set -a',\n `source ${JSON.stringify(envIntegrationsPath)}`,\n 'set +a',\n );\n }\n wrapperLines.push(\n `exec ${JSON.stringify(claudeBin)} ${JSON.stringify(initPrompt)} ${claudeArgsJoined}`,\n );\n mkdirSync(join(projectDir, '.claude'), { recursive: true });\n // 0700: only the agent process owner can read/execute. The wrapper\n // doesn't contain secrets itself (it sources them from the 0600\n // file) but a hostile reader could still see which env vars get\n // loaded — enough leakage to lock down. The mode option on\n // writeFileSync is only honoured when the file is *created*, so we\n // chmodSync afterwards to enforce 0700 on overwrites too (the\n // common case after the first respawn).\n writeFileSync(wrapperPath, wrapperLines.join('\\n') + '\\n', { mode: 0o700 });\n chmodSync(wrapperPath, 0o700);\n return wrapperPath;\n}\n\n/**\n * Collect MCP server names from the project .mcp.json to build the\n * --allowedTools pattern for tool isolation.\n */\nfunction collectMcpServerNames(mcpConfigPath: string): string[] {\n if (!existsSync(mcpConfigPath)) return [];\n try {\n const data = JSON.parse(readFileSync(mcpConfigPath, 'utf-8'));\n const servers = data.mcpServers as Record<string, unknown> | undefined;\n return servers ? Object.keys(servers) : [];\n } catch {\n return [];\n }\n}\n\n// ---------------------------------------------------------------------------\n// acpx binary resolver (used for task injection only)\n// ---------------------------------------------------------------------------\n\nlet _acpxBin: string | null = null;\nfunction getAcpxBin(): string {\n if (_acpxBin) return _acpxBin;\n\n // Walk up from this file to find node_modules/.bin/acpx.\n // Covers: dev (src/lib → ../../node_modules), built (dist/lib → ../../node_modules),\n // and npm global install (lib/node_modules/@scope/pkg/dist/lib → ../../node_modules).\n const moduleDir = dirname(fileURLToPath(import.meta.url));\n let dir = moduleDir;\n for (let i = 0; i < 6; i++) {\n const candidate = join(dir, 'node_modules', '.bin', 'acpx');\n if (existsSync(candidate)) {\n _acpxBin = candidate;\n return _acpxBin;\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n\n try {\n execSync('which acpx', { stdio: 'ignore' });\n _acpxBin = 'acpx';\n return _acpxBin;\n } catch {\n // acpx not available — injection will fall back to tmux send-keys\n return '';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Types and state\n// ---------------------------------------------------------------------------\n\nexport interface PersistentSessionConfig {\n codeName: string;\n agentId: string;\n projectDir: string;\n mcpConfigPath: string;\n claudeMdPath: string;\n channels: string[];\n devChannels: string[];\n apiHost?: string;\n /**\n * Operator-configured Claude Code auth mode. 'subscription' (default) runs\n * `syncClaudeCredsToRoot()` so claude finds OAuth creds under /root/.claude.\n * 'api_key' puts ANTHROPIC_API_KEY into the spawn env AND deletes any\n * stored OAuth creds so the two auth paths are mutually exclusive.\n */\n claudeAuthMode?: 'subscription' | 'api_key';\n /** Decrypted Anthropic API key. Only used when claudeAuthMode === 'api_key'. */\n anthropicApiKey?: string | null;\n log: (msg: string) => void;\n}\n\nexport interface PersistentSession {\n codeName: string;\n startedAt: number | null;\n restartCount: number;\n status: 'starting' | 'running' | 'stopped' | 'crashed';\n /**\n * ENG-4659: the UUID we passed to claude on the most recent spawn.\n * Set right after `tmux new-session` succeeds. This is the value the\n * recovery hook compares against `lastFailureSessionId` to detect\n * \"same UUID failed twice in a row\" — they used to be the same\n * field, which made the gate compare a value to itself and always\n * trip after the first failure (CodeRabbit catch).\n */\n currentSessionId: string | null;\n /**\n * ENG-4659: tail of the tmux pane the last time the session\n * transitioned to crashed. Captured by readPaneLogTail() when the\n * healthcheck detects no tmux session, so the next \"unhealthy\" log\n * line carries the actual error Claude printed before exiting.\n * Cleared on the next successful spawn.\n */\n lastFailureTail: string | null;\n /**\n * ENG-4659: the session UUID that was in flight when the previous\n * failure was captured. Used to detect \"same UUID failing repeatedly\"\n * — when the just-failed `currentSessionId` matches this, we\n * increment `consecutiveSameUuidFailures`; when they differ (or this\n * is null), we reset to 1.\n */\n lastFailureSessionId: string | null;\n /**\n * Count of consecutive failures with the same `lastFailureSessionId`.\n * Reset on a successful spawn or when the session UUID rotates. The\n * \"Session ID already in use\" rotation gate fires at >= 2 to avoid\n * losing today's history on a single flaky failure.\n */\n consecutiveSameUuidFailures: number;\n}\n\nconst sessions = new Map<string, PersistentSession>();\n\n// ---------------------------------------------------------------------------\n// Pane-log capture (ENG-4659)\n//\n// The tmux child we spawn is detached (`new-session -d`) so its stdio is\n// closed before claude even prints. To capture claude's output for\n// post-mortem we call `tmux pipe-pane -o` immediately after creating the\n// session, redirecting all pane output to a per-agent log file. On\n// unhealthy detection we read the tail of that file and surface it in\n// the log + scan it for known failure signatures.\n// ---------------------------------------------------------------------------\n\nconst PANE_LOG_DIR = join(homedir(), '.augmented');\nconst PANE_TAIL_LINES = 20;\n\nfunction paneLogPath(codeName: string): string {\n return join(PANE_LOG_DIR, codeName, 'pane.log');\n}\n\nfunction setupPaneLog(tmuxSession: string, codeName: string, log: (msg: string) => void): void {\n const logPath = paneLogPath(codeName);\n try {\n mkdirSync(dirname(logPath), { recursive: true });\n // Append a spawn marker rather than truncating — the previous\n // crash's output is exactly what an operator opening the file\n // wants to see, so wiping it on every respawn defeats the\n // post-mortem use case (CodeRabbit catch). The in-memory tail\n // captured at unhealthy-detection time still uses the most recent\n // lines so logs reflect the *current* failure correctly; the\n // on-disk file is the long-form record.\n appendFileSync(\n logPath,\n `\\n--- spawn ${new Date().toISOString()} (session ${tmuxSession}) ---\\n`,\n 'utf-8',\n );\n // Quote the path for the shell-cat invocation tmux runs.\n execSync(\n `tmux pipe-pane -o -t ${tmuxSession} 'cat >> ${logPath.replace(/'/g, `'\\\\''`)}'`,\n { stdio: 'ignore' },\n );\n } catch (err) {\n // Pane logging is diagnostic-only. A failure here just means the\n // next unhealthy log line won't carry the tail — the session still\n // runs. Don't propagate.\n log(`[persistent-session] pipe-pane setup failed for '${codeName}': ${(err as Error).message}`);\n }\n}\n\nfunction readPaneLogTail(codeName: string, lines: number = PANE_TAIL_LINES): string | null {\n const logPath = paneLogPath(codeName);\n if (!existsSync(logPath)) return null;\n try {\n const raw = readFileSync(logPath, 'utf-8');\n if (!raw) return null;\n // Strip ANSI escape sequences so the captured tail is\n // human-readable in operator-facing logs.\n // eslint-disable-next-line no-control-regex\n const stripped = raw.replace(/\\x1b\\[[0-9;?]*[A-Za-z]/g, '');\n const all = stripped.split('\\n').filter((l) => l.length > 0);\n return all.slice(-lines).join('\\n');\n } catch {\n return null;\n }\n}\n\n/**\n * Detect known Claude failure signatures from the captured pane tail.\n * Right now we recognise just one — \"Session ID already in use\" — which\n * was responsible for the multi-hour scout outage that motivated this\n * code (see ENG-4659). Returns 'unknown' when the tail has no signal we\n * can act on.\n */\ntype FailureSignature = 'session_id_in_use' | 'unknown';\n\nfunction detectFailureSignature(tail: string | null): FailureSignature {\n if (!tail) return 'unknown';\n if (/Session ID .* is already in use/i.test(tail)) return 'session_id_in_use';\n return 'unknown';\n}\n\n/**\n * Pre-spawn recovery hook (ENG-4659). Called by the manager between\n * detecting an unhealthy session and respawning. Inspects the captured\n * pane tail and applies one of the known recovery actions:\n *\n * - `session_id_in_use` (>= 2 consecutive failures with the same\n * UUID): rotate the daily session UUID so the next spawn doesn't\n * hit the same Claude rejection.\n *\n * Returns a short human-readable summary of any action taken (or\n * `null` if no action was warranted), suitable for inclusion in the\n * \"Session unhealthy\" log line.\n */\nexport function prepareForRespawn(codeName: string): string | null {\n const session = sessions.get(codeName);\n if (!session) return null;\n const signature = detectFailureSignature(session.lastFailureTail);\n if (\n signature === 'session_id_in_use' &&\n session.consecutiveSameUuidFailures >= 2\n ) {\n // Capture the count BEFORE resetting so the operator-facing log\n // line carries the actual streak length. The original code reset\n // first and then read 0 (CodeRabbit catch).\n const failureCount = session.consecutiveSameUuidFailures;\n const newId = rotateDailySession(codeName);\n // Reset counter — fresh UUID, fresh slate.\n session.consecutiveSameUuidFailures = 0;\n session.lastFailureSessionId = null;\n return `rotated daily-session UUID to ${newId} after ${failureCount}+ \"Session ID already in use\" failures`;\n }\n return null;\n}\n\n/**\n * Read the captured pane tail + restart counter for the manager to\n * include in its unhealthy log. Read-only; doesn't mutate session\n * state.\n */\nexport function getLastFailureContext(codeName: string): {\n tail: string | null;\n signature: FailureSignature;\n consecutiveSameUuid: number;\n restartCount: number;\n} {\n const session = sessions.get(codeName);\n return {\n tail: session?.lastFailureTail ?? null,\n signature: detectFailureSignature(session?.lastFailureTail ?? null),\n consecutiveSameUuid: session?.consecutiveSameUuidFailures ?? 0,\n restartCount: session?.restartCount ?? 0,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Session lifecycle (tmux-based)\n// ---------------------------------------------------------------------------\n\nexport function startPersistentSession(config: PersistentSessionConfig): PersistentSession {\n const existing = sessions.get(config.codeName);\n if (existing && existing.status === 'running') {\n return existing;\n }\n\n // Backoff on repeated crashes\n const restartCount = existing?.restartCount ?? 0;\n if (existing?.status === 'crashed' && existing.startedAt) {\n const backoffMs = Math.min(5000 * Math.pow(2, restartCount), 60_000);\n if (Date.now() - existing.startedAt < backoffMs) {\n return existing;\n }\n }\n\n const session: PersistentSession = {\n codeName: config.codeName,\n startedAt: null,\n restartCount,\n status: 'starting',\n currentSessionId: existing?.currentSessionId ?? null,\n lastFailureTail: existing?.lastFailureTail ?? null,\n lastFailureSessionId: existing?.lastFailureSessionId ?? null,\n consecutiveSameUuidFailures: existing?.consecutiveSameUuidFailures ?? 0,\n };\n sessions.set(config.codeName, session);\n\n spawnSession(config, session);\n return session;\n}\n\nfunction spawnSession(config: PersistentSessionConfig, session: PersistentSession): void {\n const { codeName, projectDir, mcpConfigPath, claudeMdPath, channels, devChannels, apiHost, log } = config;\n const claudeAuthMode = config.claudeAuthMode ?? 'subscription';\n const tmuxSession = `agt-${codeName}`;\n\n log(`[persistent-session] Starting tmux session '${tmuxSession}' for '${codeName}' (auth=${claudeAuthMode})`);\n\n try {\n sanitizeMcpJson(mcpConfigPath, apiHost);\n\n // Also write acpx config for task injection\n writeAcpxConfig(config);\n\n // Kill any existing tmux session (clean slate)\n try {\n execSync(`tmux kill-session -t ${tmuxSession} 2>/dev/null`, { stdio: 'ignore' });\n } catch { /* no existing session */ }\n\n // When running as root, claude looks at $HOME/.claude/.credentials.json\n // Auth mode branch (mutually exclusive — never leave both channels armed):\n //\n // subscription: sync OAuth creds from /home/*/.claude into /root/.claude\n // (idempotent). Do NOT set ANTHROPIC_API_KEY in env.\n // api_key: DELETE any /root/.claude creds so claude can't fall\n // back to a stale OAuth session, then inject\n // ANTHROPIC_API_KEY into the spawn env below.\n //\n // Leaving both present is the \"confused deputy\" path: claude's internal\n // precedence between ANTHROPIC_API_KEY and OAuth has changed between\n // versions and is undocumented. Keep exactly one channel live.\n if (claudeAuthMode === 'subscription') {\n const credsSynced = syncClaudeCredsToRoot();\n if (!credsSynced && platform() === 'linux' && typeof process.getuid === 'function' && process.getuid() === 0) {\n log(`[persistent-session] No Claude Code credentials found under /root/.claude or /home/*. Pair via browser from the host page, or run 'claude /login' on the host.`);\n }\n } else {\n // api_key mode — purge subscription creds under the current user's\n // home. Previously this was hardcoded to /root/.claude, which missed\n // non-root runs and macOS dev setups — letting OAuth creds silently\n // override the api_key in those environments. homedir() is what\n // claude-code itself reads, so that's the directory to clear.\n const claudeDir = join(homedir(), '.claude');\n for (const filename of ['.credentials.json', 'credentials.json']) {\n const p = join(claudeDir, filename);\n if (existsSync(p)) {\n try {\n rmSync(p, { force: true });\n log(`[persistent-session] Removed ${p} (api_key mode active — preventing OAuth fallback)`);\n } catch { /* non-fatal */ }\n }\n }\n if (!config.anthropicApiKey) {\n log(`[persistent-session] api_key mode but no anthropicApiKey passed. Session will fail auth.`);\n }\n }\n\n // Build claude args\n const args: string[] = [];\n\n // ENG-4642: pin a session UUID per agent / per local day. First spawn\n // of the day mints a new UUID and passes it via --session-id so claude\n // creates the conversation under that ID; later spawns the same day\n // (manager restart, host bounce) pass --resume <uuid> to pick up the\n // existing transcript instead of starting a fresh one. Day rolls\n // over by host-local date.\n //\n // Resume safety: if the on-disk JSONL for the pinned UUID is missing\n // (profile wiped, host moved, claude version incompatibility),\n // --resume would fail and dump the agent on the login picker. Detect\n // the missing file and fall back to --session-id with the same UUID\n // — claude materialises the JSONL on the first turn either way.\n const dailySession = getOrCreateDailySession(codeName);\n const claudeWillResume =\n !dailySession.isNew && sessionFileExists(projectDir, dailySession.sessionId);\n if (claudeWillResume) {\n args.push('--resume', dailySession.sessionId);\n log(`[persistent-session] Resuming today's session ${dailySession.sessionId} for '${codeName}'`);\n } else {\n args.push('--session-id', dailySession.sessionId);\n log(\n `[persistent-session] Starting fresh session ${dailySession.sessionId} for '${codeName}' ` +\n `(${dailySession.isNew ? 'new day' : 'no JSONL on disk yet'})`,\n );\n }\n\n if (channels.length > 0) args.push('--channels', ...channels);\n if (devChannels.length > 0) args.push('--dangerously-load-development-channels', ...devChannels);\n args.push('--mcp-config', mcpConfigPath);\n if (existsSync(claudeMdPath)) args.push('--system-prompt-file', claudeMdPath);\n args.push('--allow-dangerously-skip-permissions');\n args.push('--dangerously-skip-permissions');\n args.push('--strict-mcp-config');\n args.push('--name', tmuxSession);\n\n // Restrict tools to only the agent's configured MCP servers + built-in tools.\n // Without this, agents inherit the user's personal MCPs (Gmail, Calendar, etc.)\n const mcpServerNames = collectMcpServerNames(mcpConfigPath);\n args.push('--allowedTools', buildAllowedTools(mcpServerNames));\n\n // NOTE: CLAUDE_CODE_SIMPLE=1 blocks account plugins BUT also breaks\n // channel auth (Slack/Telegram require claude.ai OAuth). Instead, rely on\n // --strict-mcp-config + --allowedTools for tool isolation. Account plugins\n // may appear in the tool list but --allowedTools prevents calling them.\n //\n // IS_SANDBOX=1 bypasses claude's refusal to run under root/sudo with\n // --dangerously-skip-permissions. Dedicated EC2 hosts running only\n // agent workloads are effectively sandboxed (org-scoped VPC, no inbound,\n // no other tenants). Without this, the tmux session exits immediately\n // with \"cannot be used with root/sudo privileges for security reasons\".\n //\n // ENG-4717: previously we read `.env.integrations` and inlined every\n // KEY=VALUE pair onto the bash command string we handed tmux. That\n // string is the long-running shell process's argv — anything in it is\n // visible via `ps -eo command` for the entire session lifetime, which\n // means tokens like XURL_API_KEY and GRANOLA_ACCESS_TOKEN leaked to\n // any user who could ps on the host. We now write a wrapper script\n // (mode 0700) that sources the env file inside the spawned shell and\n // exec's claude — same pattern as the ACP wrapper below. The argv\n // visible to ps is just `bash <wrapper>`; the secrets never cross the\n // command-line boundary.\n const initPrompt = 'You are now online. Say \"Ready.\" and wait for incoming messages. Do not run any tools or load any data until a message arrives.';\n const claudeBin = resolveClaudeBinary();\n const claudeArgsJoined = args\n .map(a => (a.includes(' ') || a.includes('*')) ? JSON.stringify(a) : a)\n .join(' ');\n\n const wrapperPath = writePersistentClaudeWrapper({\n projectDir,\n claudeBin,\n initPrompt,\n claudeArgsJoined,\n });\n\n // ANTHROPIC_API_KEY is passed via `tmux new-session -e` so it lands in\n // the session shell's env without ever appearing in the claude shell's\n // argv — `ps aux` on the long-running `bash -c \"claude ...\"` process\n // would otherwise expose the raw key for the session's lifetime.\n // The `-e` flag's exposure is bounded to the new-session invocation,\n // which exits in well under a second.\n const tmuxSessionEnvArgs: string[] = [];\n if (claudeAuthMode === 'api_key' && config.anthropicApiKey) {\n tmuxSessionEnvArgs.push('-e', `ANTHROPIC_API_KEY=${config.anthropicApiKey}`);\n }\n\n // The command tmux runs is just the wrapper path — no secrets, no\n // long token strings, nothing for ps to expose.\n const claudeCmd = JSON.stringify(wrapperPath);\n\n // ENG-4632: defensively backfill HOME/USER before tmux spawns its\n // shell. When the manager is launched via `aws ssm send-command`\n // (or any non-login init), process.env can lack HOME — tmux\n // inherits that, the agent's claude process can't find\n // ~/.claude/.credentials.json, and falls back to the interactive\n // login picker forever. The managerStartCommand also applies this,\n // but a missing HOME at the persistent-session boundary is a\n // belt-and-braces fail-closed point worth keeping.\n const tmuxEnv: NodeJS.ProcessEnv = {\n ...process.env,\n // Treat empty-string as missing too — `HOME=\"\"` makes ~ resolve\n // to cwd, which is the same broken outcome as no HOME, just\n // better hidden.\n HOME: (process.env.HOME?.trim()) || homedir(),\n USER: (process.env.USER?.trim()) || userInfo().username,\n };\n\n // Start tmux session with claude in it\n const child = spawn('tmux', [\n 'new-session', '-d', '-s', tmuxSession, '-c', projectDir,\n ...tmuxSessionEnvArgs, claudeCmd,\n ], {\n cwd: projectDir,\n stdio: ['ignore', 'pipe', 'pipe'],\n env: tmuxEnv,\n });\n\n child.on('close', (code) => {\n if (code !== 0) {\n log(`[persistent-session] Failed to create tmux session for '${codeName}' (exit ${code})`);\n session.status = 'crashed';\n session.startedAt = Date.now();\n session.restartCount++;\n return;\n }\n log(`[persistent-session] tmux session '${tmuxSession}' created for '${codeName}'`);\n\n // ENG-4659: redirect pane output to a log file so we can recover\n // claude's actual error message after the session dies (claude's\n // stderr is otherwise unreachable since the tmux child is detached\n // before claude even prints).\n setupPaneLog(tmuxSession, codeName, log);\n\n // Track which session UUID we just spawned with so the recovery\n // hook can detect \"same UUID failing repeatedly\" and rotate.\n // Note: currentSessionId (set on spawn) is distinct from\n // lastFailureSessionId (set on the *previous* failure). Comparing\n // them is what makes the rotation gate work.\n session.currentSessionId = dailySession.sessionId;\n\n // Auto-accept startup dialogs\n acceptDialogs(tmuxSession, codeName, log).catch(() => {});\n });\n\n child.on('error', (err) => {\n log(`[persistent-session] Failed to start tmux for '${codeName}': ${err.message}`);\n session.status = 'crashed';\n session.startedAt = Date.now();\n session.restartCount++;\n });\n\n session.startedAt = Date.now();\n session.status = 'running';\n session.restartCount = 0;\n } catch (err) {\n log(`[persistent-session] Failed to start session for '${codeName}': ${(err as Error).message}`);\n session.status = 'crashed';\n session.startedAt = Date.now();\n session.restartCount++;\n }\n}\n\n/**\n * Detect whether Claude Code is showing the **login picker** dialog.\n *\n * ENG-4634: this dialog appears when ~/.claude.json is missing or\n * Claude Code can't validate the saved session. Pressing Enter on\n * the default (1. Claude account with subscription) kicks off a\n * browser-based OAuth flow that an unattended agent can't complete —\n * the helper used to fall through to the generic `❯ no Enter to\n * confirm` exit branch and declare the session \"ready\" while the\n * actual claude REPL was still on the picker. Without explicit\n * detection, every manager respawn would silently flip the agent\n * back to the picker and never recover.\n *\n * Pattern matches the literal option strings claude renders. Both\n * 'Claude account with subscription' and 'Anthropic Console account'\n * are present on the picker (and not in the post-login UI), so the\n * conjunction is unambiguous.\n */\nfunction isLoginPickerVisible(screen: string): boolean {\n return (\n screen.includes('Select login method') ||\n (screen.includes('Claude account with subscription') &&\n screen.includes('Anthropic Console account'))\n );\n}\n\n/**\n * Detect whether the session has actually spawned its MCP server\n * children — the only reliable signal that claude reached the running\n * REPL. tmux pane content alone can't distinguish \"Ready\" prompts\n * from a stuck splash screen, so we shell out to ps and look for\n * children of the claude process.\n *\n * ENG-4634: previously the helper logged 'Session ready' whenever the\n * pane had a `❯` not preceded by 'Enter to confirm' — but the login\n * picker also has a `❯` and would short-circuit out as ready. Verify\n * a real MCP child exists (slack-channel.js / direct-chat-channel.js\n * / etc.) before claiming success.\n */\nfunction hasMcpChildren(tmuxSession: string): boolean {\n try {\n // Find the claude process inside this tmux session by --name flag\n // (set when the manager launches claude — see spawnSession).\n const claudePidOut = execSync(\n `pgrep -f -- \"--name ${tmuxSession}\" 2>/dev/null || true`,\n { encoding: 'utf-8' },\n ).trim();\n if (!claudePidOut) return false;\n // pgrep can match multiple processes (the bash shell wrapping\n // claude, plus claude itself). We want the **claude** process —\n // its children are the MCP servers we're checking for. Process\n // ordering means the wrapper shell is the LOWER PID and claude\n // (forked after the shell parses its args) is HIGHER. Pick the\n // max so `pgrep -P` finds the MCP children, not the bash kids.\n const pids = claudePidOut.split('\\n').map((p) => Number(p)).filter((p) => p > 0);\n if (pids.length === 0) return false;\n const claudePid = Math.max(...pids);\n // List child processes; if any look like an MCP channel server,\n // we're in business.\n const childrenOut = execSync(\n `pgrep -P ${claudePid} 2>/dev/null || true`,\n { encoding: 'utf-8' },\n ).trim();\n if (!childrenOut) return false;\n const childPids = childrenOut.split('\\n').map((p) => p.trim()).filter(Boolean);\n for (const cp of childPids) {\n const cmdline = execSync(\n `cat /proc/${cp}/cmdline 2>/dev/null | tr '\\\\0' ' ' || ps -p ${cp} -o args= 2>/dev/null || true`,\n { encoding: 'utf-8' },\n );\n if (\n /slack-channel\\.js|telegram-channel\\.js|direct-chat-channel\\.js|composio_/i.test(cmdline)\n ) {\n return true;\n }\n }\n return false;\n } catch {\n return false;\n }\n}\n\nasync function acceptDialogs(tmuxSession: string, codeName: string, log: (msg: string) => void): Promise<void> {\n // Track whether we've already surfaced the login-picker warning so\n // operators don't get one log line per polling iteration. The\n // picker won't dismiss itself — once we've reported it, just keep\n // probing for the eventual recovery (e.g. operator completes OAuth\n // out-of-band) without re-spamming the log.\n let loginPickerReported = false;\n\n // Login-picker iterations don't count against the dialog-dismissal\n // budget — the operator can take minutes to complete OAuth via the\n // Hosts page, and we want acceptDialogs to still be running to\n // dismiss the trust + bypass dialogs that follow. Track the two\n // kinds of iterations separately so a slow OAuth doesn't burn the\n // 30s budget meant for the post-pair dialog cascade. Cap login-\n // picker waits at 15 minutes total to avoid leaking a forever-\n // polling helper if the operator walks away.\n let dialogIterations = 0;\n const MAX_DIALOG_ITERATIONS = 15;\n let loginPickerIterations = 0;\n const MAX_LOGIN_PICKER_ITERATIONS = 450; // 450 * 2s = 15 min\n\n while (\n dialogIterations < MAX_DIALOG_ITERATIONS &&\n loginPickerIterations < MAX_LOGIN_PICKER_ITERATIONS\n ) {\n await new Promise((r) => setTimeout(r, 2000));\n try {\n const screen = execSync(`tmux capture-pane -t ${tmuxSession} -p 2>/dev/null`, { encoding: 'utf-8' });\n\n // ENG-4634: handle the login picker BEFORE any other dialog\n // pattern. The picker has a `❯` cursor that the generic exit\n // branch at the bottom of this loop would otherwise read as\n // \"Session ready\". Press no key — sending Enter would trigger\n // an OAuth flow that requires browser interaction the agent\n // can't complete. Surface a clear, parseable log line so\n // operators / monitoring can route the operator to the\n // Hosts page to complete pairing.\n if (isLoginPickerVisible(screen)) {\n if (!loginPickerReported) {\n log(`[persistent-session] CLAUDE LOGIN REQUIRED for '${codeName}' — agent cannot start until ~/.claude.json is provisioned. Pair via the Hosts page or run 'claude /login' on the host.`);\n loginPickerReported = true;\n }\n loginPickerIterations++;\n continue;\n }\n\n // Reached the dialog cascade — count this iteration against the\n // shorter budget.\n dialogIterations++;\n\n // First-run theme picker. Has to be dismissed BEFORE the\n // generic \"❯ no Enter to confirm\" exit branch below, since the\n // picker's selected row also renders with `❯`. Accept the\n // highlighted default (Dark mode); operator can change later\n // via /theme.\n if (\n screen.includes('Choose the text style') ||\n (screen.includes('Dark mode') && screen.includes('Light mode'))\n ) {\n execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: 'ignore' });\n log(`[persistent-session] Auto-accepted theme picker for '${codeName}'`);\n continue;\n }\n if (screen.includes('Yes, I trust this folder')) {\n execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: 'ignore' });\n log(`[persistent-session] Auto-accepted workspace trust for '${codeName}'`);\n continue;\n }\n if (screen.includes('I am using this for local development')) {\n execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: 'ignore' });\n log(`[persistent-session] Auto-accepted dev channels for '${codeName}'`);\n continue;\n }\n if (screen.includes('Enter to confirm') && screen.includes('MCP')) {\n execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: 'ignore' });\n log(`[persistent-session] Auto-accepted MCP servers for '${codeName}'`);\n continue;\n }\n if (screen.includes('Yes, I accept') && screen.includes('Bypass Permissions')) {\n execSync(`tmux send-keys -t ${tmuxSession} 2`, { stdio: 'ignore' });\n await new Promise((r) => setTimeout(r, 300));\n execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: 'ignore' });\n log(`[persistent-session] Auto-accepted bypass permissions for '${codeName}'`);\n continue;\n }\n if (screen.includes('❯') && !screen.includes('Enter to confirm')) {\n // ENG-4634: don't trust the pane alone. Verify at least one\n // MCP server child has actually been spawned by the claude\n // process before declaring the session ready — otherwise a\n // splash-screen-with-cursor false-positive can race the\n // login picker and leave the agent silently broken.\n if (hasMcpChildren(tmuxSession)) {\n log(`[persistent-session] Session ready for '${codeName}' — MCP servers spawned`);\n break;\n }\n // Pane looks idle but no MCP children yet — claude may still\n // be initialising. Keep polling; the loop bound caps total\n // wait at 30s.\n }\n } catch { break; }\n }\n}\n\n// Exported for unit testing — see __tests__/persistent-session-dialogs.test.ts.\nexport const _internals = { isLoginPickerVisible, detectFailureSignature };\n\n// ---------------------------------------------------------------------------\n// Task injection (acpx preferred, tmux send-keys fallback)\n// ---------------------------------------------------------------------------\n\nexport async function injectMessage(\n codeName: string,\n type: 'task' | 'chat' | 'system',\n content: string,\n meta?: Record<string, string>,\n log?: (msg: string) => void,\n): Promise<boolean> {\n const _log = log ?? ((_: string) => {});\n const session = sessions.get(codeName);\n if (!session || session.status !== 'running') {\n _log(`[inject] SKIP '${codeName}' — session ${session ? `status=${session.status}` : 'not found in Map'}`);\n return false;\n }\n\n const prefix = meta?.task_name ? `[Task: ${meta.task_name}] ` : '';\n const text = prefix + content;\n const projectDir = getProjectDir(codeName);\n\n // Preferred: use acpx exec for reliable injection (no paste issues).\n // Fire-and-forget — spawn detached so the manager loop isn't blocked.\n const acpx = getAcpxBin();\n if (acpx) {\n try {\n // Write prompt to temp file to avoid shell escaping issues\n const tmpDir = join(projectDir, '.claude');\n mkdirSync(tmpDir, { recursive: true });\n const tmpFile = join(tmpDir, '.agt-inject-prompt.txt');\n writeFileSync(tmpFile, text);\n\n _log(`[inject] acpx exec (fire-and-forget): cwd=${projectDir}, file=${tmpFile}`);\n const child = spawn(acpx, ['claude', 'exec', '-f', tmpFile], {\n cwd: projectDir,\n stdio: 'ignore',\n detached: true,\n });\n child.on('error', (err) => {\n _log(`[inject] acpx spawn error for '${codeName}': ${err.message}`);\n });\n child.unref();\n return true;\n } catch (err) {\n _log(`[inject] acpx exec failed for '${codeName}': ${(err as Error).message}`);\n // Fall through to tmux\n }\n } else {\n _log(`[inject] acpx binary not found — falling back to tmux send-keys`);\n }\n\n // Fallback: tmux send-keys (may have paste issues with long text)\n // Use execFileSync to avoid shell injection — text passed as literal arg\n try {\n execFileSync('tmux', ['send-keys', '-t', `agt-${codeName}`, text, 'Enter'], { stdio: 'ignore' });\n // tmux send-keys doesn't guarantee submission — return false so caller\n // doesn't advance scheduler state on an unverified keystroke\n _log(`[inject] tmux send-keys sent for '${codeName}' — unverified (returning false)`);\n return false;\n } catch (err) {\n _log(`[inject] tmux send-keys failed for '${codeName}': ${(err as Error).message}`);\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Session management\n// ---------------------------------------------------------------------------\n\nexport function stopPersistentSession(codeName: string, log: (msg: string) => void): void {\n const session = sessions.get(codeName);\n if (!session) return;\n\n log(`[persistent-session] Stopping session for '${codeName}'`);\n session.status = 'stopped';\n\n try {\n execSync(`tmux kill-session -t agt-${codeName} 2>/dev/null`, { stdio: 'ignore' });\n } catch { /* session may already be dead */ }\n\n // Also close any acpx session\n try {\n const acpx = getAcpxBin();\n if (acpx) {\n execFileSync(acpx, ['claude', 'sessions', 'close', `agt-${codeName}`], {\n cwd: getProjectDir(codeName),\n timeout: 5_000,\n stdio: 'ignore',\n });\n }\n } catch { /* non-fatal */ }\n\n sessions.delete(codeName);\n\n // ENG-4808: claude exiting should take its child channel-MCP processes\n // (telegram-channel.js, slack-channel.js, direct-chat-channel.js) with\n // it, but in practice those children survive the parent — node's stdio\n // close-on-parent-exit isn't always honoured, especially when claude is\n // killed via tmux SIGHUP. Without an explicit reap, every restart leaks\n // a tree of long-pollers each holding the agent's bot token (observed\n // 6+ orphans on agt-aws-1 during the Vigil debugging session). Schedule\n // the reap after a short delay so claude has a chance to clean up its\n // own children — anything still alive after that is fair game.\n setTimeout(() => {\n reapOrphanChannelMcps({ log });\n }, 3_000).unref();\n}\n\nexport function getSessionState(codeName: string): PersistentSession | null {\n return sessions.get(codeName) ?? null;\n}\n\n/**\n * Check if a persistent session is healthy.\n * Uses tmux has-session to check if the tmux session exists.\n * Also detects sessions from previous manager runs (not in the Map).\n */\nexport function isSessionHealthy(codeName: string): boolean {\n const tmuxSession = `agt-${codeName}`;\n\n // Check if tmux session exists\n try {\n execSync(`tmux has-session -t ${tmuxSession} 2>/dev/null`, { stdio: 'ignore' });\n } catch {\n // tmux session doesn't exist — mark as crashed but don't increment\n // restartCount here (that happens in spawnSession on actual failure)\n const session = sessions.get(codeName);\n if (session && session.status === 'running') {\n session.status = 'crashed';\n // ENG-4659: capture the pane log tail BEFORE the next spawn\n // overwrites pane.log. Stash it on the session so the next\n // unhealthy log line + the prepareForRespawn recovery hook can\n // both read it. Also track consecutive failures with the same\n // UUID so we only rotate after >= 2 fails (one transient failure\n // doesn't lose today's history).\n session.lastFailureTail = readPaneLogTail(codeName);\n // The UUID just used to spawn (currentSessionId) vs the UUID that\n // last failed (lastFailureSessionId). When they match, we're\n // failing on the same UUID twice in a row — increment the gate.\n // When they differ (fresh spawn after rotation, first-ever\n // failure, etc.) reset to 1. Comparing lastFailureSessionId to\n // itself was the original CodeRabbit-caught bug.\n const failedUuid = session.currentSessionId;\n if (failedUuid && failedUuid === session.lastFailureSessionId) {\n session.consecutiveSameUuidFailures += 1;\n } else {\n session.consecutiveSameUuidFailures = 1;\n }\n session.lastFailureSessionId = failedUuid;\n }\n return false;\n }\n\n // tmux session exists — ensure it's tracked in the Map\n if (!sessions.has(codeName)) {\n sessions.set(codeName, {\n codeName,\n startedAt: Date.now(),\n restartCount: 0,\n status: 'running',\n currentSessionId: null,\n lastFailureTail: null,\n lastFailureSessionId: null,\n consecutiveSameUuidFailures: 0,\n });\n }\n\n const session = sessions.get(codeName)!;\n if (session.status !== 'running') {\n session.status = 'running';\n }\n\n return true;\n}\n\nexport function resetRestartCount(codeName: string): void {\n const session = sessions.get(codeName);\n if (session) session.restartCount = 0;\n}\n\n// ---------------------------------------------------------------------------\n// Diagnostics — collect session health info for remote debugging\n// ---------------------------------------------------------------------------\n\nexport interface SessionDiagnostics {\n codeName: string;\n status: 'running' | 'starting' | 'stopped' | 'crashed' | 'unknown';\n startedAt: string | null;\n restartCount: number;\n tmuxAlive: boolean;\n screenCapture: string | null; // last N lines from tmux pane\n launchArgs: string | null; // process args\n channelStatus: string | null; // extracted from screen capture\n}\n\nexport function collectDiagnostics(codeNames: string[]): SessionDiagnostics[] {\n return codeNames.map((codeName) => {\n const session = sessions.get(codeName);\n const tmuxSession = `agt-${codeName}`;\n let tmuxAlive = false;\n let screenCapture: string | null = null;\n let launchArgs: string | null = null;\n let channelStatus: string | null = null;\n\n // Check tmux session (execFileSync to avoid shell injection)\n try {\n execFileSync('tmux', ['has-session', '-t', tmuxSession], { stdio: 'ignore' });\n tmuxAlive = true;\n } catch { /* session doesn't exist */ }\n\n // Capture last 30 lines from tmux pane\n if (tmuxAlive) {\n try {\n screenCapture = execFileSync('tmux', ['capture-pane', '-t', tmuxSession, '-p', '-S', '-30'], {\n encoding: 'utf-8',\n timeout: 3000,\n }).trim();\n } catch { /* non-fatal */ }\n }\n\n // Get process args via ps (safe — no user input in command)\n try {\n const psOutput = execFileSync('ps', ['aux'], { encoding: 'utf-8', timeout: 3000 });\n const line = psOutput.split('\\n').find((l) => l.includes(`agt-${codeName}`) && !l.includes('grep'));\n if (line) {\n const match = line.match(/claude\\s+.*/);\n launchArgs = match ? match[0].slice(0, 500) : null;\n }\n } catch { /* non-fatal */ }\n\n // Extract channel status from screen capture.\n // Only check the last 5 lines for current state — startup errors\n // may linger in scroll history but the agent could be healthy now.\n if (screenCapture) {\n const recentLines = screenCapture.split('\\n').slice(-5).join('\\n');\n const isIdle = recentLines.includes('❯');\n\n if (isIdle) {\n // Agent is at prompt — channels are likely working\n // Check full capture for persistent errors only\n if (screenCapture.includes('Channels require claude.ai authentication')) {\n channelStatus = 'error: auth required';\n } else {\n channelStatus = 'ok';\n }\n } else if (recentLines.includes('CHANNEL_ERROR') || recentLines.includes('CLOSED')) {\n channelStatus = 'error: disconnected';\n } else if (recentLines.includes('no MCP server configured')) {\n channelStatus = 'error: MCP server not found';\n } else if (recentLines.includes('ignored')) {\n channelStatus = 'error: channels ignored';\n } else {\n channelStatus = 'ok';\n }\n }\n\n return {\n codeName,\n status: tmuxAlive\n ? (session?.status ?? 'running')\n : (session?.status === 'running' ? 'crashed' : session?.status ?? 'unknown'),\n startedAt: session?.startedAt ? new Date(session.startedAt).toISOString() : null,\n restartCount: session?.restartCount ?? 0,\n tmuxAlive,\n screenCapture: screenCapture ? screenCapture.slice(-2000) : null, // limit size\n launchArgs,\n channelStatus,\n };\n });\n}\n\nexport function stopAllSessions(log: (msg: string) => void): void {\n for (const codeName of sessions.keys()) {\n stopPersistentSession(codeName, log);\n }\n}\n\nexport async function stopAllSessionsAndWait(\n log: (msg: string) => void,\n opts: { timeoutMs: number },\n): Promise<void> {\n const codeNames = [...sessions.keys()];\n if (codeNames.length === 0) return;\n\n for (const codeName of codeNames) {\n stopPersistentSession(codeName, log);\n }\n\n await new Promise<void>((resolve) => setTimeout(resolve, Math.min(opts.timeoutMs, 2000)));\n}\n\nexport function getProjectDir(codeName: string): string {\n return join(homedir(), '.augmented', codeName, 'project');\n}\n\n// ---------------------------------------------------------------------------\n// acpx config (needed for prompt-based injection)\n// ---------------------------------------------------------------------------\n\nfunction writeAcpxConfig(config: PersistentSessionConfig): void {\n const {\n projectDir,\n mcpConfigPath,\n claudeMdPath,\n channels,\n devChannels,\n anthropicApiKey,\n } = config;\n const claudeAuthMode = config.claudeAuthMode ?? 'subscription';\n\n const claudeArgs: string[] = [];\n if (channels.length > 0) claudeArgs.push('--channels', ...channels);\n if (devChannels.length > 0) claudeArgs.push('--dangerously-load-development-channels', ...devChannels);\n claudeArgs.push('--mcp-config', mcpConfigPath);\n if (existsSync(claudeMdPath)) claudeArgs.push('--system-prompt-file', claudeMdPath);\n claudeArgs.push('--allow-dangerously-skip-permissions');\n claudeArgs.push('--dangerously-skip-permissions');\n claudeArgs.push('--strict-mcp-config');\n\n // Tool isolation for acpx exec (same as tmux session)\n const mcpServerNames2 = collectMcpServerNames(mcpConfigPath);\n claudeArgs.push('--allowedTools', buildAllowedTools(mcpServerNames2));\n\n // Write a wrapper script that sources .env.integrations then runs the ACP\n // adapter. This avoids ENAMETOOLONG from inlining long tokens (e.g. Xero\n // JWTs) into the command string, and works around acpx not supporting an\n // `env` field on agent configs.\n const acpCmd = `npx -y @agentclientprotocol/claude-agent-acp ${claudeArgs.map(a => (a.includes(' ') || a.includes('*')) ? JSON.stringify(a) : a).join(' ')}`;\n const envIntegrationsPath = join(projectDir, '.env.integrations');\n const wrapperPath = join(projectDir, '.claude', 'acpx-agent.sh');\n const wrapperLines = ['#!/usr/bin/env bash'];\n if (existsSync(envIntegrationsPath)) {\n wrapperLines.push(`set -a`, `source ${JSON.stringify(envIntegrationsPath)}`, `set +a`);\n }\n // Mirror the tmux-session auth branch: when mode=api_key we've purged the\n // OAuth creds under /root/.claude, so ACP task injections (acpx) also need\n // ANTHROPIC_API_KEY or every injected task fails auth. JSON.stringify is\n // shell-safe under bash for sk-ant-* tokens (no $/` chars).\n if (claudeAuthMode === 'api_key' && anthropicApiKey) {\n wrapperLines.push(`export ANTHROPIC_API_KEY=${JSON.stringify(anthropicApiKey)}`);\n }\n wrapperLines.push(`exec ${acpCmd}`);\n mkdirSync(join(projectDir, '.claude'), { recursive: true });\n // ENG-4717: writeFileSync's mode option only applies on file\n // creation; chmodSync afterwards enforces 0755 on overwrites too.\n writeFileSync(wrapperPath, wrapperLines.join('\\n') + '\\n', { mode: 0o755 });\n chmodSync(wrapperPath, 0o755);\n\n const acpxConfig = {\n defaultAgent: 'claude',\n defaultPermissions: 'approve-all',\n agents: {\n claude: {\n command: wrapperPath,\n },\n },\n };\n\n writeFileSync(join(projectDir, '.acpxrc.json'), JSON.stringify(acpxConfig, null, 2));\n}\n","/**\n * Sanitize a Claude Code .mcp.json file for compatibility.\n *\n * Fixes:\n * 1. Relative proxy URLs (e.g., /mcp-proxy/...) — resolved to absolute if\n * apiHost is provided, otherwise removed.\n * 2. URL-based entries (type: \"sse\") — converted to mcp-remote stdio bridge\n * since Claude Code doesn't support SSE MCP servers natively.\n *\n * Returns true if the file was modified.\n */\n\nimport { readFileSync, writeFileSync } from 'node:fs';\n\nexport function sanitizeMcpJson(\n mcpConfigPath: string,\n apiHost?: string,\n): boolean {\n try {\n const mcpRaw = JSON.parse(readFileSync(mcpConfigPath, 'utf-8'));\n const servers = mcpRaw.mcpServers as Record<string, Record<string, unknown>> | undefined;\n if (!servers) return false;\n\n let changed = false;\n for (const [key, val] of Object.entries(servers)) {\n if (typeof val?.url !== 'string') continue;\n\n // Resolve relative URLs\n if (val.url.startsWith('/')) {\n if (apiHost) {\n val.url = `${apiHost}${val.url}`;\n changed = true;\n } else {\n delete servers[key];\n changed = true;\n continue;\n }\n }\n\n // Convert URL-based entries to mcp-remote stdio bridge\n // Claude Code doesn't support type: \"sse\" natively\n const url = val.url as string;\n delete val.url;\n delete val.type;\n val.command = 'npx';\n val.args = ['-y', 'mcp-remote', url, '--allow-http'];\n changed = true;\n }\n\n if (changed) writeFileSync(mcpConfigPath, JSON.stringify(mcpRaw, null, 2));\n return changed;\n } catch {\n return false;\n }\n}\n","// Shared helper for building Claude Code's --allowedTools string (ENG-4487).\n//\n// The manager spawns claude in three modes: persistent tmux session, acpx\n// exec wrapper, and one-shot `claude -p` for scheduled tasks + webapp direct\n// chat. Each site used to hand-roll its own allowedTools list, which drifted:\n// the one-shot paths forgot Skill and Agent, so integration skills under\n// .claude/skills/integration-... were silently invisible during scheduled-task\n// execution. Agents produced apologetic \"no data sources connected\" outputs\n// when the skills were actually on disk and their API keys were in env\n// vars — they just couldn't call the Skill tool.\n//\n// Invariant: every Claude Code invocation the manager spawns must include\n// Skill and Agent. Their absence disables integration-skill activation and\n// subagent dispatch without warning. Keep that list in one place so a new\n// spawn site physically cannot miss them.\n\n// Order is stable for test snapshots.\nconst BASE_TOOLS = ['Bash', 'Read', 'Write', 'Edit', 'Grep', 'Glob', 'Agent', 'Skill'] as const;\n\n// Build the comma-separated allowedTools string for a Claude Code spawn.\n// Each MCP server name becomes a wildcard pattern matching every tool that\n// server exposes; plus the eight base built-ins.\nexport function buildAllowedTools(mcpServerNames: readonly string[]): string {\n // Claude Code's allowedTools patterns use underscore-separated names; MCP\n // server IDs in .mcp.json can use hyphens (e.g. direct-chat), so normalise.\n const mcpPatterns = mcpServerNames.map((name) => `mcp__${name.replace(/-/g, '_')}__*`);\n return [...mcpPatterns, ...BASE_TOOLS].join(',');\n}\n","// ENG-4808: reap channel-MCP processes (telegram-channel.js, slack-channel.js,\n// direct-chat-channel.js) that are still alive but whose parent claude\n// session has exited. Without this, every agent restart leaves a tree of\n// long-polling orphans behind — each holds the agent's bot token, eats\n// memory, and can race the live MCP for inbound updates.\n//\n// The decision is \"PPID-chain to a live claude\" — if a channel-MCP's\n// process ancestry reaches a `claude` process with `--name agt-<codeName>`\n// in argv, it's live. Otherwise it's an orphan and gets reaped.\n//\n// This module exposes:\n// • findOrphanChannelMcps() — pure, takes parsed `ps` rows + returns\n// orphan PIDs. Trivially unit-testable.\n// • reapOrphanChannelMcps() — side-effecting. Walks ps, then SIGTERMs\n// and after a grace window SIGKILLs any orphans.\n//\n// macOS / Linux: both support `ps -eo pid,ppid,args` with the format we\n// parse here. Windows is not a target for the agent host.\n\nimport { execFileSync } from 'node:child_process';\n\nexport interface PsRow {\n pid: number;\n ppid: number;\n args: string;\n}\n\n/** Match the three channel-MCPs the manager spawns into agent sessions. */\nexport const CHANNEL_MCP_PATTERN =\n /\\.augmented\\/_mcp\\/(telegram-channel|slack-channel|direct-chat-channel)\\.js/;\n\n/** Match a claude process bound to an agent session via `--name agt-<x>`. */\nexport const CLAUDE_AGENT_PROCESS_PATTERN = /\\bclaude\\b.*--name\\s+agt-[a-z0-9-]+/;\n\n/**\n * Parse `ps -eo pid,ppid,args` output (header + one row per process).\n * Whitespace-tolerant; the `args` column contains the rest of the line\n * after the second whitespace-delimited token.\n */\nexport function parsePsRows(psOutput: string): PsRow[] {\n const lines = psOutput.split(/\\r?\\n/);\n const rows: PsRow[] = [];\n // Skip header line(s) — anything where pid isn't a positive integer.\n for (const raw of lines) {\n const line = raw.trim();\n if (!line) continue;\n const match = line.match(/^(\\d+)\\s+(\\d+)\\s+(.*)$/);\n if (!match) continue;\n const pid = Number.parseInt(match[1]!, 10);\n const ppid = Number.parseInt(match[2]!, 10);\n if (!Number.isFinite(pid) || !Number.isFinite(ppid)) continue;\n rows.push({ pid, ppid, args: match[3] ?? '' });\n }\n return rows;\n}\n\n/**\n * Pure: given a list of `ps` rows, return the PIDs of channel-MCP\n * processes whose ancestry doesn't reach a live `claude --name agt-*`\n * process. \"Orphan\" = parent claude has exited (so the MCP can no\n * longer deliver `notifications/claude/channel` to anyone).\n *\n * Walks the ppid chain up to `maxDepth` levels (default 8) so an\n * intermediate shell or wrapper doesn't trick us into a false orphan\n * verdict. Stops early on a hit to keep this O(N * maxDepth) at worst.\n */\nexport function findOrphanChannelMcps(rows: PsRow[], opts?: { maxDepth?: number }): number[] {\n const maxDepth = opts?.maxDepth ?? 8;\n const byPid = new Map<number, PsRow>(rows.map((r) => [r.pid, r]));\n\n const orphans: number[] = [];\n for (const row of rows) {\n if (!CHANNEL_MCP_PATTERN.test(row.args)) continue;\n\n // Walk parents looking for a live claude.\n let cur: PsRow | undefined = byPid.get(row.ppid);\n let foundLiveClaude = false;\n for (let depth = 0; depth < maxDepth && cur; depth++) {\n if (CLAUDE_AGENT_PROCESS_PATTERN.test(cur.args)) {\n foundLiveClaude = true;\n break;\n }\n // PID 1 (init) is always reached on Linux for true orphans —\n // stop the walk regardless to bound work.\n if (cur.pid === 1) break;\n cur = byPid.get(cur.ppid);\n }\n\n if (!foundLiveClaude) orphans.push(row.pid);\n }\n\n return orphans;\n}\n\n/**\n * Run `ps` and reap any orphan channel-MCPs. SIGTERM first, then SIGKILL\n * after `graceMs` for any that didn't exit. Logs each reaped pid so\n * operators see what was cleaned up.\n *\n * Idempotent: calling repeatedly is safe (the second call will find\n * nothing).\n *\n * Returns the list of pids that were sent SIGTERM (informational —\n * tests can use this without mocking process.kill).\n */\nexport function reapOrphanChannelMcps(args: {\n log: (msg: string) => void;\n graceMs?: number;\n /** Test seam: substitute the `ps` invocation. */\n runPs?: () => string;\n /** Test seam: substitute the kill function. */\n killProcess?: (pid: number, signal: 'SIGTERM' | 'SIGKILL') => void;\n /** Test seam: substitute the still-alive check. */\n isAlive?: (pid: number) => boolean;\n}): number[] {\n const { log, graceMs = 5_000 } = args;\n const runPs = args.runPs ?? (() => execFileSync('ps', ['-eo', 'pid,ppid,args'], { encoding: 'utf-8', timeout: 5_000 }));\n const killProcess =\n args.killProcess ??\n ((pid: number, signal: 'SIGTERM' | 'SIGKILL') => {\n try {\n process.kill(pid, signal);\n } catch {\n // Process already exited — fine.\n }\n });\n const isAlive =\n args.isAlive ??\n ((pid: number) => {\n try {\n // Signal 0 doesn't kill anything — just probes whether the pid\n // is reachable. Throws if no such process.\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n });\n\n let psOutput: string;\n try {\n psOutput = runPs();\n } catch (err) {\n log(`[orphan-reaper] ps invocation failed: ${(err as Error).message} — skipping reap`);\n return [];\n }\n\n const rows = parsePsRows(psOutput);\n const orphans = findOrphanChannelMcps(rows);\n if (orphans.length === 0) return [];\n\n // CodeRabbit on PR #774: surface the script name in log lines so an\n // operator scanning manager.log can immediately tell which channel-MCP\n // was reaped without cross-referencing PIDs against `ps` output. The\n // AC explicitly asked for `script.js (pid N)` format.\n const byPid = new Map<number, PsRow>(rows.map((r) => [r.pid, r]));\n const describeOrphan = (pid: number): string => {\n const scriptMatch = byPid.get(pid)?.args.match(/([^\\s/]+\\.js)(?:\\s|$)/);\n return scriptMatch ? `${scriptMatch[1]} (pid ${pid})` : `pid ${pid}`;\n };\n\n log(`[orphan-reaper] sending SIGTERM to ${orphans.length} orphan channel-MCP(s): ${orphans.map(describeOrphan).join(', ')}`);\n for (const pid of orphans) {\n killProcess(pid, 'SIGTERM');\n }\n\n // Schedule a SIGKILL pass for any that didn't exit. Don't block the\n // caller — this is purely cleanup hygiene. CodeRabbit on PR #774:\n // wrap the body so a throwing test seam (custom isAlive / killProcess)\n // doesn't escape as an uncaught exception. Production seams already\n // catch internally, so this is purely defence-in-depth for tests.\n setTimeout(() => {\n try {\n const stragglers = orphans.filter(isAlive);\n if (stragglers.length === 0) return;\n log(`[orphan-reaper] ${stragglers.length} orphan(s) survived SIGTERM; sending SIGKILL: ${stragglers.map(describeOrphan).join(', ')}`);\n for (const pid of stragglers) {\n killProcess(pid, 'SIGKILL');\n }\n } catch (err) {\n log(`[orphan-reaper] error in SIGKILL pass: ${(err as Error).message}`);\n }\n }, graceMs).unref();\n\n return orphans;\n}\n","/**\n * ENG-4642: per-agent / per-day Claude session pinning.\n *\n * The persistent-session manager kills the tmux session on every spawn\n * (clean slate) and starts a fresh `claude` invocation. Pre-this-module,\n * that meant a new conversation every restart — operators lost context\n * any time the manager respawned.\n *\n * Goal: each calendar day is a fresh conversation, but every spawn\n * inside that day reuses the same conversation. We achieve this by\n * generating a stable UUID up front (Claude CLI accepts\n * `--session-id <uuid>` for the first spawn, `--resume <uuid>` for\n * subsequent ones) and persisting it to a tiny per-agent JSON file.\n *\n * Storage: `~/.augmented/<codeName>/daily-session.json` — same root the\n * persistent-session manager already owns via getProjectDir(). Schema:\n *\n * { \"date\": \"YYYY-MM-DD\", \"sessionId\": \"<uuid>\", \"history\": [...] }\n *\n * `history` keeps the last few days' entries so an operator can debug\n * which session was bound to which day. We trim to 7 days so the file\n * doesn't grow unbounded.\n *\n * Day boundary: host-local date (server timezone). The agent CHARTER's\n * `limits.timezone` was considered but skipped — multi-host fleets\n * already share one tmux process per agent and we want the rollover to\n * be observable from the host's clock without a config plumb. Easy\n * enough to add later if a single-tenant operator needs it.\n *\n * Failure mode: if the on-disk JSONL Claude writes for the resumed\n * session is missing (host moved, profile wiped, claude version\n * incompatibility), `--resume` would fail and the agent would land on\n * the login picker. Callers verify the JSONL exists via\n * `sessionFileExists()` before choosing `--resume`; if it's gone we\n * fall back to `--session-id` (treat the stored UUID as fresh, claude\n * will materialise the JSONL on first turn).\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nconst HISTORY_DAYS = 7;\n\ninterface DailySessionEntry {\n date: string; // YYYY-MM-DD\n sessionId: string; // UUID v4\n startedAt: string; // ISO 8601\n}\n\ninterface DailySessionFile {\n current: DailySessionEntry | null;\n history: DailySessionEntry[];\n}\n\nexport interface DailySessionResult {\n sessionId: string;\n /** `true` when this call generated a new UUID (first spawn of a new day or first ever). */\n isNew: boolean;\n}\n\nfunction profileDir(codeName: string): string {\n return join(homedir(), '.augmented', codeName);\n}\n\nfunction dailySessionPath(codeName: string): string {\n return join(profileDir(codeName), 'daily-session.json');\n}\n\nfunction todayLocalIso(now: Date = new Date()): string {\n const y = now.getFullYear();\n const m = String(now.getMonth() + 1).padStart(2, '0');\n const d = String(now.getDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\nfunction readFile(codeName: string): DailySessionFile {\n const path = dailySessionPath(codeName);\n if (!existsSync(path)) return { current: null, history: [] };\n try {\n const raw = readFileSync(path, 'utf-8');\n const parsed = JSON.parse(raw) as Partial<DailySessionFile>;\n return {\n current: parsed.current ?? null,\n history: Array.isArray(parsed.history) ? parsed.history : [],\n };\n } catch {\n // Corrupt file — start fresh rather than crashing the manager.\n return { current: null, history: [] };\n }\n}\n\nfunction writeFile(codeName: string, data: DailySessionFile): void {\n const dir = profileDir(codeName);\n mkdirSync(dir, { recursive: true });\n // Atomic write: tmp + rename. A reader catching us mid-writeFileSync\n // would otherwise see truncated JSON and the corrupt-file branch in\n // readFile() would silently treat the agent as fresh state, losing\n // today's UUID and forcing a rollover the operator didn't ask for.\n // PID + randomUUID in the tmp suffix so two managers (or a respawn\n // racing with its predecessor) can't collide on the temp path and\n // have one rename remove the file the other is about to rename.\n // Mirrors the pattern in restart-flags.ts.\n const finalPath = dailySessionPath(codeName);\n const tmpPath = `${finalPath}.${process.pid}.${randomUUID()}.tmp`;\n writeFileSync(tmpPath, JSON.stringify(data, null, 2), 'utf-8');\n renameSync(tmpPath, finalPath);\n}\n\nfunction trimHistory(history: DailySessionEntry[], now: Date): DailySessionEntry[] {\n // Keep newest first, drop entries older than HISTORY_DAYS by date.\n // Take the injected `now` so callers with a frozen clock (tests\n // walking the day forward) don't get inconsistent cutoffs against\n // `new Date()`.\n const cutoff = new Date(now);\n cutoff.setDate(cutoff.getDate() - HISTORY_DAYS);\n const cutoffIso = todayLocalIso(cutoff);\n return history.filter((h) => h.date >= cutoffIso).slice(0, HISTORY_DAYS);\n}\n\n/**\n * Resolve the session UUID this agent should use right now. Generates\n * (and persists) a new UUID on the first call of a new local day, or\n * when the file is missing/corrupt; otherwise returns the day's\n * existing UUID. Idempotent within the same day.\n *\n * Concurrency: the read-then-write here is not under a file lock.\n * In our deployment the manager runs supervised, one process per\n * host (`agt manager start --supervise` / runSupervisorLoop), so\n * concurrent invocation for the same `codeName` is bounded to the\n * sub-second respawn window when the supervisor restarts the\n * worker. The atomic tmp+rename in writeFile() guarantees we never\n * read torn JSON, so the worst-case under a respawn race is two\n * managers minting different UUIDs and one rename winning — both\n * processes converge on the winner's UUID on the next supervisor\n * tick (which re-reads the file). We've taken that trade-off\n * over a proper inter-process lock because a stale lockfile (from\n * a SIGKILL'd manager) would block all subsequent runs and need\n * its own recovery path; the lossy outcome of a UUID race is one\n * tick of conversation churn, not a permanent block.\n */\nexport function getOrCreateDailySession(\n codeName: string,\n now: Date = new Date(),\n): DailySessionResult {\n const today = todayLocalIso(now);\n const file = readFile(codeName);\n\n if (file.current && file.current.date === today) {\n return { sessionId: file.current.sessionId, isNew: false };\n }\n\n // Roll over: yesterday's (or older) entry moves to history, new one\n // takes its place.\n const next: DailySessionEntry = {\n date: today,\n sessionId: randomUUID(),\n startedAt: now.toISOString(),\n };\n const history = trimHistory(\n [...(file.current ? [file.current] : []), ...file.history],\n now,\n );\n writeFile(codeName, { current: next, history });\n return { sessionId: next.sessionId, isNew: true };\n}\n\n/**\n * Reset the day's pin — used as a recovery hatch after `--resume` is\n * rejected by claude (corrupt state, version mismatch). Writes a new\n * UUID for today, demotes the old one to history.\n */\nexport function rotateDailySession(\n codeName: string,\n now: Date = new Date(),\n): string {\n const today = todayLocalIso(now);\n const file = readFile(codeName);\n const next: DailySessionEntry = {\n date: today,\n sessionId: randomUUID(),\n startedAt: now.toISOString(),\n };\n const history = trimHistory(\n [...(file.current ? [file.current] : []), ...file.history],\n now,\n );\n writeFile(codeName, { current: next, history });\n return next.sessionId;\n}\n\n/**\n * Encode an absolute project dir the way Claude Code stores it under\n * ~/.claude/projects/. Claude collapses runs of `/` and `.` into single\n * `-` separators with a leading `-` (no separator at the start). The\n * earlier \"/ only\" encoder produced a stale path for any project dir\n * containing a `.` (e.g. `/root/.augmented/scout/project`), which made\n * sessionFileExists() return false even when the JSONL was on disk.\n *\n * Diagnosed live on prod scout (ENG-4659): the on-disk dir was\n * /root/.claude/projects/-root--augmented-scout-project/\n * but our encoder produced\n * /root/.claude/projects/-root-.augmented-scout-project/\n * — the dot in `.augmented` wasn't translated. Result: every \"is\n * there JSONL on disk\" check returned false, the manager fell back to\n * `--session-id` reuse, and Claude rejected the same UUID with\n * \"Session ID already in use\" forever.\n *\n * Empirical observations from /root/.claude/projects/ on a live host:\n * /usr/bin -> -usr-bin\n * /root/.augmented/scout/project -> -root--augmented-scout-project\n * Behaviour: `[/.]` -> `-`, with consecutive separators preserved\n * (the `/.` between `root/` and `.augmented` becomes `--`).\n */\nfunction encodeProjectPath(projectDir: string): string {\n return '-' + projectDir.replace(/^\\//, '').replace(/[/.]/g, '-');\n}\n\n/**\n * Check whether claude has actually written a session JSONL for this\n * UUID. If the file is missing the `--resume` would fail and put the\n * agent on the login picker; callers should fall back to `--session-id`\n * instead. See encodeProjectPath() for the encoding rules.\n */\nexport function sessionFileExists(\n projectDir: string,\n sessionId: string,\n): boolean {\n const path = join(\n homedir(),\n '.claude',\n 'projects',\n encodeProjectPath(projectDir),\n `${sessionId}.jsonl`,\n );\n return existsSync(path);\n}\n\nfunction sessionFilePath(projectDir: string, sessionId: string): string {\n return join(\n homedir(),\n '.claude',\n 'projects',\n encodeProjectPath(projectDir),\n `${sessionId}.jsonl`,\n );\n}\n\n/**\n * Is the agent's session JSONL idle — i.e. has it not been written for\n * at least `idleSeconds`? Claude appends to the file on every turn\n * (tool calls, assistant messages, user messages) so a stale mtime is\n * a reliable proxy for \"nothing in flight\". Returns true if the file\n * is missing (no in-flight work to interrupt) or if its mtime is\n * older than the threshold.\n *\n * Used by the scheduled-rollover gate so we don't kill a tmux session\n * mid-task at the day boundary — defer the rollover one tick at a\n * time until the agent is between turns.\n */\nexport function isAgentIdle(\n projectDir: string,\n sessionId: string,\n idleSeconds = 60,\n now: Date = new Date(),\n): boolean {\n const path = sessionFilePath(projectDir, sessionId);\n if (!existsSync(path)) return true;\n try {\n const mtimeMs = statSync(path).mtimeMs;\n return now.getTime() - mtimeMs >= idleSeconds * 1000;\n } catch {\n // stat failed (race, permissions). Treat as non-idle to err on the\n // side of NOT interrupting a possibly-running task.\n return false;\n }\n}\n\n/**\n * Cheap \"should we roll over?\" check for the supervisor tick. Reads\n * the persisted current entry and compares its date against today's.\n * Does NOT mint a new UUID — the caller decides what to do with the\n * answer (typically: kill the tmux session iff isAgentIdle is true,\n * letting the next tick respawn fresh via getOrCreateDailySession).\n */\nexport function isStaleForToday(\n codeName: string,\n now: Date = new Date(),\n): boolean {\n const file = readFile(codeName);\n if (!file.current) return false; // never seeded — nothing to roll\n return file.current.date !== todayLocalIso(now);\n}\n\n/**\n * Read-only accessor for the current entry, returns null when the\n * file doesn't exist or has no current entry. Useful to grab the\n * sessionId for the idle check without triggering a roll-over write.\n */\nexport function peekCurrentSession(codeName: string): {\n date: string;\n sessionId: string;\n startedAt: string;\n} | null {\n return readFile(codeName).current;\n}\n\n// Exported for unit tests — keep the surface small.\nexport const _internals = { todayLocalIso, dailySessionPath, profileDir, encodeProjectPath };\n"],"mappings":"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","names":["execFileSync","join","homedir","existsSync","readFileSync","writeFileSync","mkdirSync","readFileSync","writeFileSync","existsSync","join","mkdirSync","writeFileSync","readFileSync","homedir","execFileSync","session"]}