@integrity-labs/agt-cli 0.19.19 → 0.19.21
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/dist/bin/agt.js +78 -3
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-WQJL6EGC.js → chunk-5WDQ5G5M.js} +367 -16
- package/dist/chunk-5WDQ5G5M.js.map +1 -0
- package/dist/lib/manager-worker.js +98 -17
- package/dist/lib/manager-worker.js.map +1 -1
- package/mcp/slack-channel.js +282 -0
- package/mcp/telegram-channel.js +270 -8
- package/package.json +1 -1
- package/dist/chunk-WQJL6EGC.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/manager-worker.ts","../../src/lib/mcp-config-drift.ts","../../src/lib/integration-hash.ts","../../src/lib/stale-mcp-reaper.ts","../../src/lib/channel-restart-decision.ts","../../src/lib/integration-context-render.ts","../../src/lib/integration-skill-layout.ts","../../src/lib/gateway-client.ts","../../src/lib/claude-auth-detect.ts","../../src/lib/canonical-json.ts","../../src/lib/channel-hash-cache.ts","../../src/lib/channel-sweep.ts","../../src/lib/channel-input-watchdog.ts","../../src/lib/delivery-hint.ts","../../src/lib/delivery-schedule-link.ts","../../src/lib/restart-flags.ts","../../src/lib/restart-handler.ts","../../src/lib/realtime-chat.ts"],"sourcesContent":["/**\n * Manager Worker — forked child process that polls the API for agent config\n * changes and local file drift. Communicates with the watchdog parent via IPC.\n *\n * This file is a standalone entry point built by tsup so `fork()` can target it.\n */\n\nimport { createHash } from 'node:crypto';\nimport { readFileSync, writeFileSync, appendFileSync, mkdirSync, chmodSync, existsSync, rmSync, readdirSync, statSync, unlinkSync, copyFileSync } from 'node:fs';\nimport https from 'node:https';\nimport { execFileSync as syncExecFile } from 'node:child_process';\nimport { join, dirname } from 'node:path';\nimport { homedir } from 'node:os';\nimport { fileURLToPath } from 'node:url';\nimport {\n extractFrontmatter,\n resolveChannels,\n getFramework,\n parseDeliveryTarget,\n isParseError,\n resolveDmTarget,\n isResolveError,\n appendDmFooter,\n wrapScheduledTaskPrompt,\n classifyOutput,\n type PriorRun,\n type FrameworkAdapter,\n type CharterFrontmatter,\n type ToolsFrontmatter,\n type ChannelId,\n type ChannelPolicy,\n type OrgChannelPolicy,\n type DeploymentTarget,\n type ProvisionInput,\n type DeliveryTarget,\n type ResolverAgent,\n type ResolverPerson,\n} from '@augmented/core';\nimport { provision } from '@augmented/core/provisioning/provisioner.js';\nimport { extractCommandNotFound } from '@augmented/core/provisioning/hook-env.js';\nimport { getIntegration } from '@augmented/core/integrations/registry.js';\nimport { reapOrphanChannelMcps } from './orphan-channel-mcp-reaper.js';\nimport { decideMcpDriftAction } from './mcp-config-drift.js';\nimport { computeIntegrationsHash } from './integration-hash.js';\nimport {\n diffEnvIntegrations,\n findMcpServersUsingVars,\n reapStaleMcpChildren,\n} from './stale-mcp-reaper.js';\nimport { decideChannelRestart, launchableChannelIds } from './channel-restart-decision.js';\n\n// Register framework adapters (side-effect imports)\nimport '@augmented/core/provisioning/frameworks/openclaw/index.js';\nimport '@augmented/core/provisioning/frameworks/nemoclaw/index.js';\nimport { provisionStopHook, provisionIsolationHook } from '@augmented/core/provisioning/frameworks/claudecode/index.js';\nimport '@augmented/core/provisioning/frameworks/managed-agents/index.js';\n\nimport { api, getHostId, exchangeApiKey } from './api-client.js';\nimport { getApiKey, requireHost } from './config.js';\nimport { sanitizeMcpJson } from './mcp-sanitize.js';\nimport { renderIntegrationSkillContent } from './integration-context-render.js';\nimport {\n buildIntegrationBundle,\n bundleFingerprint,\n groupSkillsByIntegration,\n type IntegrationSkillInput,\n} from './integration-skill-layout.js';\nimport { GatewayClientPool, type PooledGatewayEvent } from './gateway-client.js';\nimport { detectClaudeAuth } from './claude-auth-detect.js';\nimport { canonicalJson } from './canonical-json.js';\nimport {\n loadChannelHashCache as loadChannelHashCacheFromDisk,\n saveChannelHashCache as saveChannelHashCacheToDisk,\n} from './channel-hash-cache.js';\nimport { buildAllowedTools } from './claude-tools.js';\nimport { SUPERVISOR_RESTART_EXIT_CODE } from '../commands/manager.js';\nimport { sweepChannelProcesses, killAgentChannelProcesses } from './channel-sweep.js';\nimport { checkChannelInputs } from './channel-input-watchdog.js';\nimport { hintProbability, pickHintVariant, shouldIncludeHint } from './delivery-hint.js';\nimport { withScheduleLinkFooter } from './delivery-schedule-link.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface WorkerConfig {\n intervalMs: number;\n configDir: string;\n gatewayPort?: number;\n gatewayToken?: string;\n}\n\ninterface AcpSessionSummary {\n sessionId: string;\n agentCommand: string;\n sessionName?: string;\n queueState: string;\n turnCount: number;\n startedAt: string;\n}\n\ninterface AgentState {\n agentId: string;\n codeName: string;\n status: string;\n charterVersion: string;\n toolsVersion: string;\n secretsHash: string | null;\n lastRefreshAt: string | null;\n lastProvisionAt: string | null;\n lastDriftCheckAt: string | null;\n lastSecretsProvisionAt: string | null;\n gatewayPort: number | null;\n gatewayPid: number | null;\n gatewayRunning: boolean;\n acpSessions: AcpSessionSummary[];\n}\n\ninterface ManagerState {\n pid: number;\n startedAt: string;\n lastPollAt: string | null;\n pollCount: number;\n errorCount: number;\n agents: AgentState[];\n}\n\n// Event types (formerly IPC messages, now just internal events)\ntype ManagerEvent =\n | { type: 'ready' }\n | { type: 'state-update'; state: ManagerState }\n | { type: 'provisioned'; agentId: string; codeName: string }\n | { type: 'drift-detected'; agentId: string; codeName: string; files: string[] }\n | { type: 'gateway-event'; event: string; payload: unknown; agentCodeName?: string }\n | { type: 'error'; message: string }\n | { type: 'shutdown' };\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n// OpenClaw gateway binds the specified port + a few additional ports above it,\n// so we space allocations 10 apart to avoid collisions.\nconst GATEWAY_PORT_BASE = 18800;\nconst GATEWAY_PORT_STEP = 10;\nconst GATEWAY_PORT_MAX = 18899;\nconst AUGMENTED_DIR = join(process.env['HOME'] ?? '/tmp', '.augmented');\nconst GATEWAY_PORTS_FILE = join(AUGMENTED_DIR, 'gateway-ports.json');\n\n// Channel-process zombie sweep (ENG-4453). Throttled to avoid shelling out\n// to `ps eww` every poll. Default 5 min, overridable via env for tuning or\n// stress tests. AGT_CHANNEL_SWEEP_DRY_RUN=1 logs kill targets without sending\n// SIGTERM — operators should flip this on first and verify the targets before\n// letting the sweep act.\nconst CHANNEL_SWEEP_INTERVAL_MS = (() => {\n const raw = parseInt(process.env['AGT_CHANNEL_SWEEP_INTERVAL_MS'] ?? '', 10);\n if (!Number.isFinite(raw)) return 5 * 60 * 1000;\n // Clamp overrides up to the 30s floor rather than falling back to default\n // — an operator who sets =10000 clearly intends a short interval, not the\n // five-minute silent fallback.\n return Math.max(raw, 30_000);\n})();\nconst CHANNEL_SWEEP_DRY_RUN = process.env['AGT_CHANNEL_SWEEP_DRY_RUN'] === '1';\nlet lastChannelSweepAt = 0;\n\n// ---------------------------------------------------------------------------\n// State\n// ---------------------------------------------------------------------------\n\nlet config: WorkerConfig | null = null;\nlet running = false;\nlet pollTimer: ReturnType<typeof setTimeout> | null = null;\n\n// ENG-4659: when persistent-session reports a session unhealthy, the\n// pane log tail is included in the operator-facing log line. Keep the\n// inline preview short — operators reading the log don't need a full\n// 20-line dump on every restart, just enough to identify the failure\n// pattern. The full tail is still on disk at ~/.augmented/<codeName>/pane.log.\nconst PANE_TAIL_PREVIEW_LINES = 5;\n/**\n * ENG-4923: extract `multi_agent.telegram_peers[]` from a CHARTER's raw\n * frontmatter so the manager can pass the resolved roster as the\n * TELEGRAM_PEERS env var on the per-agent MCP child. The shape on\n * disk is `{ code_name, bot_id }`; we add an empty `agent_id` field\n * because the classifier's parsePeersEnv requires it (downstream\n * runtime — ENG-4904 — can resolve agent_id from the API when\n * building the peer preamble).\n *\n * Lenient: any parse / shape error returns []. The CHARTER lint\n * rule from ENG-4901 already validates the shape at write time;\n * a runtime parse failure here just means the classifier sees no\n * declared peers, which falls through to its existing\n * `unknown_peer` drop path. Better than crashing the manager tick.\n */\nfunction extractCharterTelegramPeers(\n rawContent: string,\n): Array<{ code_name: string; bot_id: number; agent_id: string }> {\n if (!rawContent || rawContent.length === 0) return [];\n try {\n const parsed = extractFrontmatter(rawContent);\n const frontmatter = parsed.frontmatter as\n | { multi_agent?: { telegram_peers?: Array<{ code_name?: unknown; bot_id?: unknown }> } }\n | null;\n const peers = frontmatter?.multi_agent?.telegram_peers;\n if (!peers || !Array.isArray(peers)) return [];\n const out: Array<{ code_name: string; bot_id: number; agent_id: string }> = [];\n for (const p of peers) {\n if (\n p &&\n typeof p === 'object' &&\n typeof p.code_name === 'string' &&\n typeof p.bot_id === 'number' &&\n Number.isInteger(p.bot_id) &&\n p.bot_id > 0\n ) {\n out.push({ code_name: p.code_name, bot_id: p.bot_id, agent_id: '' });\n }\n }\n return out;\n } catch {\n return [];\n }\n}\n\nfunction truncateForLog(s: string): string {\n const lines = s.split('\\n').filter((l) => l.length > 0);\n return lines.slice(-PANE_TAIL_PREVIEW_LINES).map((l) => ` | ${l}`).join('\\n');\n}\n// CodeRabbit (PR #618): only embed the raw pane tail in manager.log\n// when the failure signature is one we recognise as a safe-to-quote\n// Claude error string. For anything else (including 'unknown' tails)\n// log only the sha256 prefix and point operators at the on-disk file.\n// Add new signatures here only after auditing what Claude actually\n// prints for that failure mode.\nconst KNOWN_SAFE_TAIL_SIGNATURES = new Set<string>(['session_id_in_use']);\n\n// Track last-known versions + hashes per agent to detect changes\nconst knownVersions = new Map<string, { charterVersion: string; toolsVersion: string }>();\nconst knownStatuses = new Map<string, string>();\nconst knownChannels = new Map<string, Set<string>>();\n\n// ENG-4807 / CodeRabbit on PR #773: coalesce per-agent session-restart\n// timers so two hot-reload triggers in the same tick (e.g. channel set\n// changed AND MCP toolkit changed) don't schedule two stops back-to-back.\n// Without this, the first stop kills the original session and the second\n// stop fires a few seconds later — by which time the persistent-session\n// healthcheck has already respawned, and the second stop kills the\n// freshly-respawned session.\nconst pendingSessionRestarts = new Map<string, ReturnType<typeof setTimeout>>();\n\nfunction scheduleSessionRestart(codeName: string, delayMs: number, reason: string): void {\n const existing = pendingSessionRestarts.get(codeName);\n if (existing) {\n clearTimeout(existing);\n log(`[hot-reload] Coalesced restart for '${codeName}': replacing pending timer with ${reason}`);\n }\n const timer = setTimeout(() => {\n pendingSessionRestarts.delete(codeName);\n stopPersistentSession(codeName, log);\n // ENG-4897: drop the hash baseline when the session actually stops.\n // Without this, the next poll compares the fresh session against the\n // dead one's hash and schedules another spurious restart. Covers the\n // existing hot-reload-restart callers (channel-set change, new MCP\n // servers) that already pre-date the drift check.\n runningMcpHashes.delete(codeName);\n log(`[hot-reload] Session stopped for '${codeName}' — will respawn with ${reason}`);\n }, delayMs);\n // Don't keep the manager alive solely on a pending restart timer.\n timer.unref?.();\n pendingSessionRestarts.set(codeName, timer);\n}\n\n// CodeRabbit on PR #773 (round 2): cancel any pending hot-reload restart\n// timer for an agent so a non-hot-reload teardown path (pause / revoke /\n// unassign / auth-rotation stop / day-rollover / shutdown / restart-flags)\n// doesn't get followed seconds later by a stale timer firing and killing\n// the freshly respawned replacement. Idempotent: safe to call when no\n// timer is pending. Call this BEFORE every direct stopPersistentSession\n// call in this file (the single in-timer call is already self-cleaning\n// via the pendingSessionRestarts.delete above).\nfunction cancelPendingSessionRestart(codeName: string): void {\n const existing = pendingSessionRestarts.get(codeName);\n if (!existing) return;\n clearTimeout(existing);\n pendingSessionRestarts.delete(codeName);\n log(`[hot-reload] Cancelled pending restart timer for '${codeName}' (another teardown path is handling it)`);\n}\nconst writtenHashes = new Map<string, Map<string, string>>();\nconst knownSecretsHashes = new Map<string, string>();\n\n// ENG-4897: per-agent hash of the project `.mcp.json` that the running\n// claude session was launched with. The existing stale-MCP reaper\n// (ENG-4832) only watches env-var-keyed integration changes; structural\n// changes to .mcp.json (new server entries, removed entries, inline env\n// changes for tokens that aren't ${VAR}-substituted) slip past it.\n//\n// Sterling/agt-demo (2026-05-09): operator added Telegram, the manager\n// wrote the new .mcp.json, but the running claude session — launched\n// with --strict-mcp-config — had only the pre-Telegram view. Its\n// `--dangerously-load-development-channels server:telegram` flag then\n// errored \"no MCP server configured with that name\" until manual\n// `tmux kill-session` triggered a respawn.\n//\n// On each poll we hash the project .mcp.json and compare to the value\n// recorded for this agent. If they differ, schedule a session restart\n// (the existing hot-reload coalescer takes care of the rest). On first\n// observation post-manager-start we adopt the current hash as the\n// optimistic baseline — worst case is one missed drift event after a\n// manager restart, the next change is caught.\nconst runningMcpHashes = new Map<string, string>();\n\nfunction projectMcpHash(codeName: string, projectDir: string): string | null {\n try {\n return createHash('sha256').update(readFileSync(join(projectDir, '.mcp.json'))).digest('hex');\n } catch {\n return null;\n }\n}\n\n/**\n * ENG-4897 follow-up (CodeRabbit on PR #829): drop the `runningMcpHashes`\n * entry whenever a session is stopped for ANY reason (auth rotation,\n * day rollover, pause, terminate, hot-reload restart). Without this,\n * the post-`ensurePersistentSession()` drift check on the NEXT poll\n * compares the freshly-respawned session against the dead session's\n * hash, immediately scheduling a spurious second restart.\n *\n * `cancelPendingSessionRestart` is called before every direct\n * `stopPersistentSession` (see its docstring), but the cancel doesn't\n * clear the hash. Call this wrapper instead — it bundles the cancel +\n * the hash drop, so every stop path stays consistent.\n */\nfunction stopPersistentSessionAndForgetMcpBaseline(codeName: string): void {\n cancelPendingSessionRestart(codeName);\n stopPersistentSession(codeName, log);\n runningMcpHashes.delete(codeName);\n}\n\nfunction checkMcpConfigDriftAndScheduleRestart(codeName: string, projectDir: string): void {\n const currentHash = projectMcpHash(codeName, projectDir);\n const action = decideMcpDriftAction(currentHash, runningMcpHashes.get(codeName));\n switch (action.kind) {\n case 'no-config':\n case 'no-drift':\n return;\n case 'baseline':\n runningMcpHashes.set(codeName, action.hash);\n return;\n case 'drift':\n log(\n `[hot-reload] .mcp.json content changed for '${codeName}' ` +\n `(${action.previous.slice(0, 12)} → ${action.current.slice(0, 12)}); scheduling restart (ENG-4897)`,\n );\n scheduleSessionRestart(codeName, 0, '.mcp.json content change (ENG-4897)');\n // Drop the entry — the next poll after respawn captures the new\n // hash as baseline. Keeping the old hash would re-fire every poll\n // until the respawn timer triggers.\n runningMcpHashes.delete(codeName);\n return;\n }\n}\n// Persisted to disk via saveChannelHashCache() so a fresh manager process\n// (post self-update, post launchd restart, etc.) doesn't re-emit\n// `reason=first-write` for credentials that are already on disk and\n// match the desired config (ENG-4712).\nconst knownChannelConfigHashes = new Map<string, string>();\nconst knownModels = new Map<string, string>();\nconst knownTasksHashes = new Map<string, string>();\nconst knownIntegrationHashes = new Map<string, string>();\n// ENG-4481: desired-state hash of managed-toolkit MCP servers (provider/id/url/header-keys).\n// Used to converge `.mcp.json` every poll instead of only when credentials change,\n// so plugin-added toolkits self-heal when the previous write never happened.\nconst knownManagedMcpHashes = new Map<string, string>();\nconst knownSkillHashes = new Map<string, string>();\n// Track last-seen cron run timestamps per job to avoid re-processing\nconst lastCronRunTs = new Map<string, number>();\n// Track last work_trigger_at per agent to avoid duplicate triggers\nconst lastWorkTriggerAt = new Map<string, number>();\n// Track which in_progress items we've already alerted as stale\nconst alertedStaleItems = new Set<string>();\n// Track agents with known API key errors (avoid spamming the API)\nconst apiKeyStatusCache = new Map<string, boolean>();\nconst STALE_TASK_THRESHOLD_MS = 30 * 60 * 1000; // 30 minutes\n\n// Alert webhook URL — cached from first agent's team settings\nlet alertSlackWebhook: string | null = null;\n// Track alerted job IDs to avoid duplicate alerts within a session\nconst alertedJobs = new Set<string>();\n// Map cron job name (aug:template:uuid) → human-readable task info\nconst taskDisplayInfo = new Map<string, { taskName: string; schedule: string; agentDisplayName: string }>();\n// Map agent code_name → display_name for alerts\nconst agentDisplayNames = new Map<string, string>();\n// Map code_name → agent_id for kanban updates\nconst codeNameToAgentId = new Map<string, string>();\n// Map code_name → channel bot tokens (from channel_configs)\nconst agentChannelTokens = new Map<string, { slack?: string; telegram?: string; telegramAllowedChats?: string[] }>();\n\n// Per-cycle channel tracking — populated by processAgent, reconciled after all agents\nconst activeChannels = new Map<string, Set<string>>(); // channelId -> Set<codeName>\n\n// Gateways started this poll cycle — skip health-check restarts for these\nconst gatewaysStartedThisCycle = new Set<string>();\n\n// Cumulative state for the parent\nlet state: ManagerState = {\n pid: process.pid,\n startedAt: new Date().toISOString(),\n lastPollAt: null,\n pollCount: 0,\n errorCount: 0,\n agents: [],\n};\n\n// Cache registered agents per framework per poll cycle\nconst registeredAgentsCache = new Map<string, Set<string>>();\n\n// Per-agent framework ID cache (populated from API response and refresh data)\nconst agentFrameworkCache = new Map<string, string>();\n\n// Track which framework binaries we've already checked/installed this session\nconst frameworkBinaryChecked = new Set<string>();\n\n// Claude Code auth status — set during ensureFrameworkBinary, checked before launching sessions\nlet agentRuntimeAuthenticated = false;\n\n/** Resolve the framework adapter for a given agent, using the cache or defaulting to 'openclaw'. */\nfunction resolveAgentFramework(codeName: string): FrameworkAdapter {\n const frameworkId = agentFrameworkCache.get(codeName) ?? 'openclaw';\n return getFramework(frameworkId);\n}\n\n// Gateway client pool — replaces single GatewayClient\nlet gatewayPool: GatewayClientPool | null = null;\n\n/**\n * Clear all per-agent caches when an agent is unassigned, revoked, or needs\n * a full reprovision. Keeps the cleanup in one place instead of scattering\n * .delete() calls for every new Map we add.\n */\nfunction clearAgentCaches(agentId: string, codeName: string): void {\n // ID-keyed caches\n knownVersions.delete(agentId);\n knownStatuses.delete(agentId);\n knownChannels.delete(agentId);\n writtenHashes.delete(agentId);\n knownSecretsHashes.delete(agentId);\n knownModels.delete(agentId);\n knownTasksHashes.delete(agentId);\n knownIntegrationHashes.delete(agentId);\n knownManagedMcpHashes.delete(agentId);\n\n // codeName-keyed caches\n agentDisplayNames.delete(codeName);\n codeNameToAgentId.delete(codeName);\n agentFrameworkCache.delete(codeName);\n kanbanBoardCache.delete(codeName);\n lastHarvestAt.delete(codeName);\n claudeSchedulerStates.delete(codeName);\n claudeTaskConcurrency.delete(codeName);\n\n // Memory sync caches\n memoryFileHashes.delete(agentId);\n lastDownloadHash.delete(agentId);\n lastLocalFileHash.delete(agentId);\n\n // Compound-keyed caches (agentId:suffix)\n let channelCacheMutated = false;\n for (const key of knownChannelConfigHashes.keys()) {\n if (key.startsWith(`${agentId}:`)) {\n knownChannelConfigHashes.delete(key);\n channelCacheMutated = true;\n }\n }\n if (channelCacheMutated) saveChannelHashCache();\n for (const key of knownSkillHashes.keys()) {\n if (key.startsWith(`${agentId}:`)) knownSkillHashes.delete(key);\n }\n for (const key of taskDisplayInfo.keys()) {\n if (key.startsWith(`${codeName}:`)) taskDisplayInfo.delete(key);\n }\n}\n\n// Cached framework version (detected once at startup, refreshed periodically)\nlet cachedFrameworkVersion: string | null = null;\nlet lastVersionCheckAt = 0;\nconst VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1000; // Re-check every 5 minutes\n\n// agt CLI version, injected by tsup at build time. Reported on heartbeat\n// so the host page can surface \"which agt is actually running here?\".\n// In dev (tsx), the constant isn't defined — fall back to 'dev'.\ndeclare const __CLI_VERSION__: string;\nconst agtCliVersion = typeof __CLI_VERSION__ !== 'undefined' ? __CLI_VERSION__ : 'dev';\n\n// ---------------------------------------------------------------------------\n// Framework binary install / upgrade\n// ---------------------------------------------------------------------------\n\n/**\n * Ensure a Homebrew formula is installed. Used for framework dependencies\n * like tmux that are required but not part of the framework itself.\n * Returns true if the binary is available after the check.\n */\nasync function ensureBrewDependency(binary: string, formula: string): Promise<boolean> {\n if (frameworkBinaryChecked.has(`dep:${binary}`)) return true;\n\n const { execFileSync } = await import('node:child_process');\n\n try {\n execFileSync('which', [binary], { timeout: 5_000 });\n frameworkBinaryChecked.add(`dep:${binary}`);\n return true;\n } catch {\n // Binary not found — try to install\n }\n\n let brewPath: string;\n try {\n brewPath = execFileSync('which', ['brew'], { timeout: 5_000 }).toString().trim();\n } catch {\n log(`${binary} not found and Homebrew not available — install manually: brew install ${formula}`);\n frameworkBinaryChecked.add(`dep:${binary}`);\n return false;\n }\n\n log(`${binary} not found — installing via Homebrew...`);\n try {\n execFileSync(brewPath, ['install', formula], { timeout: 120_000, stdio: 'pipe' });\n } catch (err) {\n log(`Failed to install ${formula}: ${(err as Error).message}`);\n frameworkBinaryChecked.add(`dep:${binary}`);\n return false;\n }\n\n // Prepend brew's bin dir to PATH so the post-install verification (and any\n // downstream subprocesses) can find the binary. Brew installs to a custom\n // prefix that isn't on the manager's PATH when spawned by cloud-init.\n const brewBinDir = dirname(brewPath);\n if (!process.env.PATH?.split(':').includes(brewBinDir)) {\n process.env.PATH = `${brewBinDir}:${process.env.PATH ?? ''}`;\n }\n\n try {\n execFileSync('which', [binary], { timeout: 5_000 });\n log(`${binary} installed successfully`);\n frameworkBinaryChecked.add(`dep:${binary}`);\n return true;\n } catch {\n log(`${formula} install completed but ${binary} not found on PATH`);\n frameworkBinaryChecked.add(`dep:${binary}`);\n return false;\n }\n}\n\n/**\n * Ensure the CLI binary for a framework is available. Currently only handles\n * `claude-code` via Homebrew. Called once per session when we first see an\n * agent using that framework.\n */\n/**\n * Resolve the brew binary. `which brew` fails when PATH doesn't include the\n * Homebrew bin dir (common when the manager is spawned by cloud-init with a\n * minimal env). Fall back to the canonical Linuxbrew install location.\n */\nfunction resolveBrewPath(execFileSync: typeof import('node:child_process').execFileSync): string | null {\n try {\n const out = execFileSync('which', ['brew'], { timeout: 5_000 }).toString().trim();\n if (out) return out;\n } catch {\n // fall through to absolute path check\n }\n // Canonical install locations across the three platforms we run on.\n // Linuxbrew first (the AL2023 hosts the manager runs on most often),\n // then Apple-silicon macOS (`/opt/homebrew`), then Intel macOS\n // (`/usr/local`). Without the macOS entries the launchd-spawned\n // manager bails out of self-update on Macs whose sparse PATH doesn't\n // include the brew bin dir.\n const fallbacks = [\n '/home/linuxbrew/.linuxbrew/bin/brew',\n '/opt/homebrew/bin/brew',\n '/usr/local/bin/brew',\n ];\n for (const path of fallbacks) {\n if (existsSync(path)) return path;\n }\n return null;\n}\n\n/**\n * ENG-4421: resolve a toolkit → install its CLI if missing.\n *\n * Plugins declare `required_toolkits: [...]`. The manager unions these across\n * an agent's installed plugins and calls this per toolkit. Idempotent: a hit\n * on `command -v <binary>` short-circuits; the in-memory guard makes repeat\n * polls cheap.\n *\n * Installer strategies are declared in the catalog (`IntegrationCliTool.installer`):\n * - 'npm' : `npm install -g <package>`\n * - 'brew' : `brew install <package>` (proxied via ec2-user when running as root)\n * - 'script' : runs `cli_tool.script` verbatim. The catalog is the trust boundary —\n * never take the script from plugin-side data.\n * - 'manual' : skip — logged as a hint for the operator.\n *\n * All failures are non-fatal: the manager logs and moves on so a missing CLI\n * doesn't block the rest of the agent's provisioning.\n *\n * Caching semantics:\n * - `toolkitCliEnsured` caches *permanent* outcomes: success, 'manual'\n * installer (operator's responsibility), or catalog authoring errors\n * (missing package/script). Those states won't change poll-to-poll.\n * - `toolkitCliRetryAfter` caches *transient* failures (network, brew\n * refused root, npm 500, post-install verify fail) with a 5-min\n * cooldown. This lets a retry recover automatically instead of\n * requiring a manager restart.\n */\nconst toolkitCliEnsured = new Set<string>();\nconst toolkitCliRetryAfter = new Map<string, number>();\nconst toolkitCliFailureCount = new Map<string, number>();\nconst TOOLKIT_INSTALL_RETRY_MS = 5 * 60_000;\n// After this many consecutive failures, mark the toolkit permanently\n// failed for the lifetime of the manager process. Stops the log spam\n// when an upstream package is broken (e.g. linear-cli's musl fallback\n// doesn't exist) or the host is missing some prereq we can't auto-\n// install. Operator restarts the manager to retry.\nconst TOOLKIT_INSTALL_MAX_FAILURES = 3;\n\nfunction recordToolkitFailure(toolkitSlug: string, reason: string): void {\n const count = (toolkitCliFailureCount.get(toolkitSlug) ?? 0) + 1;\n toolkitCliFailureCount.set(toolkitSlug, count);\n if (count >= TOOLKIT_INSTALL_MAX_FAILURES) {\n log(`[toolkit-install] ${toolkitSlug}: ${reason} (giving up after ${count} attempts — restart the manager to retry)`);\n toolkitCliEnsured.add(toolkitSlug);\n toolkitCliRetryAfter.delete(toolkitSlug);\n } else {\n log(`[toolkit-install] ${toolkitSlug}: ${reason} (attempt ${count}/${TOOLKIT_INSTALL_MAX_FAILURES}, retrying in ${TOOLKIT_INSTALL_RETRY_MS / 60_000}m)`);\n toolkitCliRetryAfter.set(toolkitSlug, Date.now() + TOOLKIT_INSTALL_RETRY_MS);\n }\n}\n\nasync function ensureToolkitCli(toolkitSlug: string): Promise<void> {\n if (toolkitCliEnsured.has(toolkitSlug)) return;\n const retryAfter = toolkitCliRetryAfter.get(toolkitSlug) ?? 0;\n if (retryAfter > Date.now()) return;\n\n const integration = getIntegration(toolkitSlug);\n if (!integration?.cli_tool) {\n // Unknown toolkit or no CLI declared — permanent, won't change between polls.\n toolkitCliEnsured.add(toolkitSlug);\n return;\n }\n\n const { binary, installer, package: pkg, script } = integration.cli_tool;\n const resolvedInstaller = installer ?? 'manual';\n\n const { execFileSync, execSync } = await import('node:child_process');\n\n // Fast path: already on PATH.\n try {\n execFileSync('which', [binary], { timeout: 5_000, stdio: 'pipe' });\n toolkitCliEnsured.add(toolkitSlug);\n toolkitCliRetryAfter.delete(toolkitSlug);\n toolkitCliFailureCount.delete(toolkitSlug);\n return;\n } catch { /* not on PATH — attempt install */ }\n\n if (resolvedInstaller === 'manual') {\n log(`[toolkit-install] ${toolkitSlug}: binary '${binary}' missing, installer=manual — operator must install`);\n // Not going to change without operator action; mark permanent to avoid log spam.\n toolkitCliEnsured.add(toolkitSlug);\n return;\n }\n\n let brewBinDir: string | null = null;\n try {\n if (resolvedInstaller === 'npm') {\n if (!pkg) {\n log(`[toolkit-install] ${toolkitSlug}: installer=npm but no package declared`);\n // Catalog authoring mistake — permanent, retrying won't fix it.\n toolkitCliEnsured.add(toolkitSlug);\n return;\n }\n log(`[toolkit-install] ${toolkitSlug}: installing via npm (${pkg})…`);\n execFileSync('npm', ['install', '-g', pkg], { timeout: 180_000, stdio: 'pipe' });\n } else if (resolvedInstaller === 'brew') {\n if (!pkg) {\n log(`[toolkit-install] ${toolkitSlug}: installer=brew but no package declared`);\n toolkitCliEnsured.add(toolkitSlug);\n return;\n }\n const brewPath = resolveBrewPath(execFileSync);\n if (!brewPath) {\n log(`[toolkit-install] ${toolkitSlug}: installer=brew but Homebrew not available — install manually: brew install ${pkg}`);\n // Env-level problem — unlikely to self-heal; mark permanent.\n toolkitCliEnsured.add(toolkitSlug);\n return;\n }\n brewBinDir = dirname(brewPath);\n const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;\n log(`[toolkit-install] ${toolkitSlug}: installing via brew (${pkg})…`);\n if (isRoot) {\n // cwd: '/tmp' — brew refuses if the cwd isn't readable to\n // ec2-user; the manager's cwd is /root (mode 0700) by default.\n execFileSync('sudo', ['-u', 'ec2-user', '-H', brewPath, 'install', pkg], { timeout: 180_000, stdio: 'pipe', cwd: '/tmp' });\n } else {\n execFileSync(brewPath, ['install', pkg], { timeout: 180_000, stdio: 'pipe' });\n }\n } else if (resolvedInstaller === 'script') {\n if (!script) {\n log(`[toolkit-install] ${toolkitSlug}: installer=script but no script declared`);\n toolkitCliEnsured.add(toolkitSlug);\n return;\n }\n log(`[toolkit-install] ${toolkitSlug}: running declared install script…`);\n // The script comes from the catalog (source-controlled) — never from\n // runtime plugin data. execSync is safe here because the catalog is\n // the trust boundary.\n execSync(script, { timeout: 180_000, stdio: 'pipe' });\n }\n } catch (err) {\n const msg = (err as Error).message.slice(0, 200);\n recordToolkitFailure(toolkitSlug, `installer=${resolvedInstaller} failed — ${msg}`);\n return;\n }\n\n // Prepend brew's bin dir to PATH so the post-install `which` + any downstream\n // spawns can actually find the new binary. Brew installs to a custom prefix\n // (e.g. /home/linuxbrew/.linuxbrew/bin) that isn't on the manager's PATH when\n // spawned by cloud-init with a minimal env.\n if (brewBinDir && !process.env.PATH?.split(':').includes(brewBinDir)) {\n process.env.PATH = `${brewBinDir}:${process.env.PATH ?? ''}`;\n }\n\n // Verify the binary landed on PATH after install.\n try {\n execFileSync('which', [binary], { timeout: 5_000, stdio: 'pipe' });\n log(`[toolkit-install] ${toolkitSlug}: installed — ${binary} now on PATH`);\n toolkitCliEnsured.add(toolkitSlug);\n toolkitCliRetryAfter.delete(toolkitSlug);\n toolkitCliFailureCount.delete(toolkitSlug);\n } catch {\n recordToolkitFailure(toolkitSlug, `installer=${resolvedInstaller} completed but ${binary} still not on PATH`);\n }\n}\n\n/**\n * Run a command and capture stdout, without blocking the Node event loop.\n *\n * `execFileSync` blocks the entire process until the child returns, which on\n * a slow `brew upgrade --cask claude-code` can be 30–120s. Every other\n * manager poll task — HTTP, WS heartbeats, agent processing — stalls for\n * that whole window. Switching to `spawn` + a Promise wrapper keeps the\n * event loop alive while brew runs.\n */\nfunction runAsync(\n cmd: string,\n args: string[],\n opts: { timeout: number; cwd?: string },\n): Promise<{ code: number; stdout: string; stderr: string }> {\n return new Promise((resolve, reject) => {\n import('node:child_process').then(({ spawn }) => {\n const child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'], cwd: opts.cwd });\n let stdout = '';\n let stderr = '';\n // 'close' only fires after stdio streams drain, which can hang\n // indefinitely if the child ignores SIGTERM. Settle the promise the\n // moment the deadline hits so callers don't block past `opts.timeout`,\n // then escalate to SIGKILL as a backstop.\n let settled = false;\n const timer = setTimeout(() => {\n child.kill('SIGTERM');\n if (settled) return;\n settled = true;\n reject(new Error(`${cmd} ${args.join(' ')} timed out after ${opts.timeout}ms`));\n setTimeout(() => { try { child.kill('SIGKILL'); } catch { /* already gone */ } }, 5_000).unref();\n }, opts.timeout);\n\n child.stdout?.on('data', (b) => { stdout += b.toString(); });\n child.stderr?.on('data', (b) => { stderr += b.toString(); });\n child.on('error', (err) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n reject(err);\n });\n child.on('close', (code) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve({ code: code ?? -1, stdout, stderr });\n });\n }).catch(reject);\n });\n}\n\nasync function ensureFrameworkBinary(frameworkId: string): Promise<void> {\n if (frameworkId !== 'claude-code') return;\n if (frameworkBinaryChecked.has(frameworkId)) return;\n frameworkBinaryChecked.add(frameworkId);\n\n const { execFileSync } = await import('node:child_process');\n\n const brewPath = resolveBrewPath(execFileSync);\n if (!brewPath) {\n log('Homebrew not found (no `brew` on PATH, no /home/linuxbrew/.linuxbrew/bin/brew). Cannot auto-install Claude Code. Install manually: https://claude.ai/download');\n return;\n }\n log(`Using brew at ${brewPath}`);\n\n // Brew refuses to run as root. When the manager is spawned by the EC2\n // bootstrap (user-data runs as root), proxy the brew call through ec2-user.\n // Otherwise invoke brew directly.\n //\n // The cwd matters: brew refuses to run if the cwd isn't readable to the\n // user it'll run as. The manager's cwd is typically /root (mode 0700)\n // when launched by cloud-init, which ec2-user can't read — so the\n // sudo'd brew bails out with \"current working directory must be\n // readable to ec2-user\". /tmp is world-readable and has no other\n // semantic meaning to brew.\n const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;\n const runBrew = (args: string[], opts: { timeout: number }) => {\n if (isRoot) {\n return runAsync('sudo', ['-u', 'ec2-user', '-H', brewPath, ...args], { ...opts, cwd: '/tmp' });\n }\n return runAsync(brewPath, args, opts);\n };\n\n // Check if claude binary exists. Check the canonical Linuxbrew path first\n // (since `which claude` can fail when PATH is minimal) then fall back to PATH.\n // `which` is fast (<5ms typical) so a sync call here is acceptable.\n let claudeExists = existsSync('/home/linuxbrew/.linuxbrew/bin/claude');\n if (!claudeExists) {\n try {\n execFileSync('which', ['claude'], { timeout: 5_000 });\n claudeExists = true;\n } catch { /* not installed */ }\n }\n\n if (!claudeExists) {\n log(`Claude Code binary not found — installing via Homebrew${isRoot ? ' (as ec2-user via sudo)' : ''}...`);\n try {\n const r = await runBrew(['install', '--cask', 'claude-code'], { timeout: 120_000 });\n if (r.code !== 0) {\n log(`Claude Code install failed (exit ${r.code}): ${r.stderr.trim() || r.stdout.trim()}`);\n return;\n }\n } catch (err) {\n log(`Claude Code install failed: ${(err as Error).message}`);\n return;\n }\n\n // Prepend brew's bin dir to PATH so subsequent `which claude` /\n // resolveClaudeBinary calls find the new binary on minimal-PATH hosts\n // (e.g. EC2 cloud-init root env). Mirrors ensureBrewDependency().\n const brewBinDir = dirname(brewPath);\n if (!process.env.PATH?.split(':').includes(brewBinDir)) {\n process.env.PATH = `${brewBinDir}:${process.env.PATH ?? ''}`;\n }\n\n // Verify\n if (existsSync('/home/linuxbrew/.linuxbrew/bin/claude')) {\n log('Claude Code installed successfully');\n } else {\n log('Claude Code install completed but binary not found at expected path — check brew logs');\n }\n } else {\n // Upgrade — fire-and-forget. The binary already exists, so agents can run\n // on it immediately; we don't gate the poll loop on a network-bound\n // `brew upgrade`. The new version, if any, is picked up the next time the\n // manager (or a tmux session) starts.\n log(`Checking for Claude Code updates in background${isRoot ? ' (as ec2-user via sudo)' : ''}...`);\n runBrew(['upgrade', '--cask', 'claude-code'], { timeout: 120_000 })\n .then((r) => {\n const combined = `${r.stdout}\\n${r.stderr}`;\n if (r.code === 0) {\n if (combined.includes('already installed') || combined.includes('up-to-date')) {\n log('Claude Code is already up to date');\n } else {\n log('Claude Code upgraded successfully (will apply on next session start)');\n }\n } else if (combined.includes('already installed') || combined.includes('up-to-date') || combined.includes('not upgraded')) {\n // brew upgrade exits non-zero when already up to date on some versions.\n log('Claude Code is already up to date');\n } else {\n log(`Claude Code upgrade failed (exit ${r.code}): ${r.stderr.trim() || r.stdout.trim()}`);\n }\n })\n .catch((err) => log(`Claude Code upgrade failed: ${(err as Error).message}`));\n }\n\n // Check if Claude Code is authenticated. Don't await the upgrade — auth is\n // independent of upgrade state and the binary that's already on disk works.\n agentRuntimeAuthenticated = await checkClaudeAuth();\n}\n\n// ---------------------------------------------------------------------------\n// CLI self-update via Homebrew (throttled to once every 5 minutes)\n// ---------------------------------------------------------------------------\n\nconst UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes\n// ENG-4485: only log \"up to date\" once per manager process so the periodic\n// check doesn't spam the log every 5 minutes. Errors, \"update available\",\n// and \"upgraded to X\" always log.\nlet selfUpdateUpToDateLogged = false;\n// ENG-4488: set after a successful `brew upgrade` so the poll loop exits\n// cleanly and a supervisor can re-spawn onto the new binary. Without this,\n// the running process keeps executing from the old Cellar path whose lazy-\n// imported hashed chunks brew has already removed — the first dynamic\n// import after the swap throws `Cannot find module`.\nlet restartAfterUpgrade = false;\nlet pendingUpgradeVersion: string | null = null;\n// In-flight lock for the self-update path. pollCycle() kicks\n// checkAndUpdateCli() off fire-and-forget, so without this a slow\n// `brew update` or `npm install -g` lets later polls re-enter and\n// launch a second updater concurrently. brew/npm both lock-file\n// internally and would error on the collision; worse, both could\n// flip restartAfterUpgrade and double-schedule a manager respawn.\nlet selfUpdateInFlight = false;\nasync function checkAndUpdateCli(): Promise<void> {\n if (selfUpdateInFlight) return;\n selfUpdateInFlight = true;\n try {\n const cliPath = process.argv[1] ?? '';\n const isDevMode = cliPath.includes('/src/') || cliPath.includes('tsx');\n if (isDevMode) return;\n\n // Distinguish brew formula installs from npm-global installs. A bare\n // `/home/linuxbrew/` or `/opt/homebrew/` prefix isn't enough — Linuxbrew's\n // hosted Node lays npm-global packages under `.../lib/node_modules/...`,\n // so we match on the canonical formula path `/Cellar/<formula>/`.\n let resolvedPath = cliPath;\n try {\n const { realpathSync } = await import('node:fs');\n resolvedPath = realpathSync(cliPath);\n } catch { /* fall back to argv[1] verbatim */ }\n const isBrewFormula = /\\/Cellar\\/[^/]+\\//.test(resolvedPath);\n const isNpmGlobal = !isBrewFormula && resolvedPath.includes('node_modules');\n if (!isBrewFormula && !isNpmGlobal) return; // Unknown install method\n\n // Throttle: check last update time from disk.\n const { readFileSync: readF, writeFileSync: writeF } = await import('node:fs');\n const markerPath = join(homedir(), '.augmented', '.last-update-check');\n try {\n const lastCheck = parseInt(readF(markerPath, 'utf-8').trim(), 10);\n if (Date.now() - lastCheck < UPDATE_CHECK_INTERVAL_MS) return;\n } catch { /* no marker yet — first run */ }\n\n // Stamp the marker BEFORE running the heavy updater so a long\n // brew/npm run doesn't let the same poll cycle re-enter past the\n // throttle as soon as the lock releases. Update again at the end\n // so the next throttle window measures from completion.\n try {\n writeF(markerPath, String(Date.now()));\n } catch { /* non-fatal */ }\n\n if (isBrewFormula) {\n await checkAndUpdateCliViaBrew();\n } else {\n await checkAndUpdateCliViaNpm();\n }\n\n // Update marker regardless of outcome — failures here shouldn't trigger\n // a tight retry loop on every poll cycle.\n try {\n writeF(markerPath, String(Date.now()));\n } catch { /* non-fatal */ }\n } finally {\n selfUpdateInFlight = false;\n }\n}\n\nasync function checkAndUpdateCliViaBrew(): Promise<void> {\n const { execFileSync } = await import('node:child_process');\n // Use the shared resolveBrewPath helper rather than a bare `which brew`.\n // The manager runs with cloud-init's minimal env on EC2 (and a similarly\n // sparse PATH under launchd on macOS), so `which brew` returns nothing\n // even on hosts where the CLI clearly lives in a Cellar — without this,\n // self-update on those hosts silently no-ops.\n const brewPath = resolveBrewPath(execFileSync);\n if (!brewPath) return; // No Homebrew on PATH or canonical install dirs.\n\n // Refresh tap metadata first. `brew outdated` consults the LOCAL formula\n // cache, so without this it will happily report no updates even when the\n // tap has a newer version on GitHub. Hitting this was the reason self-\n // updates silently stopped working for hours after a version bump.\n try {\n execFileSync(brewPath, ['update', '--quiet'], { timeout: 60_000, stdio: 'pipe' });\n } catch (err) {\n log(`[self-update] brew update failed (continuing with stale cache): ${(err as Error).message}`);\n }\n\n try {\n const outdated = execFileSync(brewPath, ['outdated', '--json=v2'], {\n timeout: 30_000,\n encoding: 'utf-8',\n });\n const data = JSON.parse(outdated) as { formulae?: Array<{ name: string; installed_versions: string[]; current_version: string }> };\n const agtOutdated = data.formulae?.find((f) => f.name === 'agt' || f.name === 'integrity-labs/tap/agt');\n\n if (agtOutdated) {\n const installed = agtOutdated.installed_versions?.[0] ?? 'unknown';\n const latest = agtOutdated.current_version ?? 'unknown';\n log(`[self-update] agt CLI update available: ${installed} → ${latest}. Upgrading via brew...`);\n\n try {\n execFileSync(brewPath, ['upgrade', 'integrity-labs/tap/agt'], {\n timeout: 120_000,\n stdio: 'pipe',\n });\n log(`[self-update] agt CLI upgraded to ${latest}. Scheduling manager restart so the new binary takes effect.`);\n restartAfterUpgrade = true;\n pendingUpgradeVersion = latest;\n } catch (err) {\n log(`[self-update] brew upgrade failed: ${(err as Error).message}`);\n }\n } else if (!selfUpdateUpToDateLogged) {\n log(`[self-update] agt CLI is up to date (brew, ${agtCliVersion})`);\n selfUpdateUpToDateLogged = true;\n }\n } catch (err) {\n log(`[self-update] brew outdated failed: ${(err as Error).message}`);\n }\n}\n\n/**\n * npm-global install upgrade path — used on AWS EC2 hosts (the bootstrap\n * runs `npm install -g @integrity-labs/agt-cli`). Queries the npm registry\n * directly (the source of truth that the API also reads from), compares\n * against the build-time `__CLI_VERSION__`, and re-runs `npm install -g`\n * when behind. Requires `npm` on PATH; on AL2023 hosts the manager runs as\n * ec2-user with passwordless sudo so we shell out via `sudo -n` to write\n * into /usr/lib/node_modules (owned by root).\n */\nasync function checkAndUpdateCliViaNpm(): Promise<void> {\n const { execFileSync } = await import('node:child_process');\n\n // Skip the comparison entirely in dev where __CLI_VERSION__ wasn't injected.\n if (agtCliVersion === 'dev') return;\n\n // Query the npm registry's `latest` dist-tag. Direct fetch — same source\n // the /cli/version API endpoint reads from — so the manager doesn't need\n // any extra wiring beyond outbound HTTPS. AbortSignal.timeout keeps a\n // hung registry from blocking the poll loop.\n let latest: string;\n try {\n const res = await fetch(\n 'https://registry.npmjs.org/@integrity-labs/agt-cli/latest',\n {\n signal: AbortSignal.timeout(10_000),\n headers: { Accept: 'application/json' },\n },\n );\n if (!res.ok) {\n log(`[self-update] npm registry returned ${res.status}`);\n return;\n }\n const body = (await res.json()) as { version?: string };\n if (!body.version) {\n log(`[self-update] npm registry response missing version field`);\n return;\n }\n latest = body.version;\n } catch (err) {\n log(`[self-update] npm registry fetch failed: ${(err as Error).message}`);\n return;\n }\n\n if (!isNewerSemver(agtCliVersion, latest)) {\n if (!selfUpdateUpToDateLogged) {\n log(`[self-update] agt CLI is up to date (npm, ${agtCliVersion})`);\n selfUpdateUpToDateLogged = true;\n }\n return;\n }\n\n log(`[self-update] agt CLI update available: ${agtCliVersion} → ${latest}. Upgrading via npm...`);\n\n // /usr/lib/node_modules is root-owned on EC2; the manager runs as\n // ec2-user. `sudo -n` non-interactively elevates — succeeds on hosts\n // where ec2-user has NOPASSWD sudo (the AL2023 default), fails fast\n // otherwise so we don't hang waiting for a TTY password prompt.\n // When already running as root (older bootstraps where the manager\n // never switched to ec2-user) we skip sudo entirely.\n const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;\n const cmd = isRoot ? 'npm' : 'sudo';\n const args = isRoot\n ? [\n 'install',\n '-g',\n `@integrity-labs/agt-cli@${latest}`,\n '--registry=https://registry.npmjs.org',\n ]\n : [\n '-n',\n 'npm',\n 'install',\n '-g',\n `@integrity-labs/agt-cli@${latest}`,\n '--registry=https://registry.npmjs.org',\n ];\n\n try {\n execFileSync(cmd, args, { timeout: 180_000, stdio: 'pipe' });\n log(`[self-update] agt CLI upgraded to ${latest}. Scheduling manager restart so the new binary takes effect.`);\n restartAfterUpgrade = true;\n pendingUpgradeVersion = latest;\n } catch (err) {\n log(`[self-update] npm upgrade failed: ${(err as Error).message}`);\n }\n}\n\n/** Compare two semver strings; returns true when `remote` is strictly newer. */\nfunction isNewerSemver(local: string, remote: string): boolean {\n const parse = (v: string) => v.replace(/^v/, '').split('.').map(Number);\n const l = parse(local);\n const r = parse(remote);\n for (let i = 0; i < 3; i++) {\n const lv = l[i] ?? 0;\n const rv = r[i] ?? 0;\n if (rv !== lv) return rv > lv;\n }\n return false;\n}\n\n/**\n * Check Claude Code auth and return true if logged in.\n *\n * Uses the same file/keychain probe as `detectClaudeAuth` (our heartbeat\n * reporter) instead of shelling out to `claude auth status`. On a root-owned\n * manager that's spawned by cloud-init, `claude` isn't on PATH and even if\n * it were, running it as root would look at `/root/.claude/` — but the\n * operator logs in as `ssm-user` or `ec2-user`, so the creds live under\n * their home. `detectClaudeAuth` probes every user home in `/home` when\n * running as root on Linux, so it finds subscription creds regardless of\n * which user completed `claude /login`.\n */\nasync function checkClaudeAuth(): Promise<boolean> {\n try {\n const report = await detectClaudeAuth();\n if (!report) {\n log('⚠️ Claude Code is not authenticated. Run `claude /login` (or set `ANTHROPIC_API_KEY`) on the host, then the manager will pick it up on the next cycle.');\n return false;\n }\n if (report.status === 'expired') {\n log(`⚠️ Claude Code auth expired (${report.mode}). Re-run \\`claude /login\\` to refresh.`);\n return false;\n }\n if (report.status === 'expiring_soon') {\n log(`[auth] Claude Code token expiring soon (expires_at=${report.expires_at}) — still valid, proceeding`);\n }\n return true;\n } catch (err) {\n log(`⚠️ Claude Code auth probe failed: ${(err as Error).message}`);\n return false;\n }\n}\n\n/**\n * Prepare a child-process env for non-tmux one-shot Claude spawns\n * (claude-scheduler + direct-chat). Mirrors what spawnSession() in\n * persistent-session does for the tmux path.\n *\n * - Force-refreshes /host/exchange so mode flips and key rotations are\n * picked up immediately instead of lagging the ~50m JWT cache.\n * - When mode=api_key, injects the decrypted key AND purges any OAuth\n * credential files under homedir()/.claude so Claude can't silently\n * fall back to a stale subscription session.\n * - When mode=subscription, strips ANTHROPIC_API_KEY from the env\n * (a key inherited from the manager's shell would override OAuth).\n * - Fails closed: throws if mode=api_key but no decrypted key is in\n * the exchange response, or if the exchange itself errors. Callers\n * must catch and skip the spawn — never spawn Claude with ambiguous\n * auth under the wrong mode.\n */\nasync function applyClaudeAuthToEnv(\n childEnv: Record<string, string | undefined>,\n label: string,\n): Promise<void> {\n const apiKey = getApiKey();\n if (!apiKey) {\n throw new Error('AGT_API_KEY is not set');\n }\n const exchange = await exchangeApiKey(apiKey, false, { forceRefresh: true });\n\n if (exchange.claudeAuthMode === 'api_key') {\n if (!exchange.anthropicApiKey) {\n throw new Error('claude_auth_mode=api_key but /host/exchange returned no decrypted key');\n }\n childEnv.ANTHROPIC_API_KEY = exchange.anthropicApiKey;\n // Purge OAuth creds for the active user so Claude doesn't silently\n // prefer a stale subscription session. Matches the persistent-session\n // purge (ENG-4417); use homedir() not a hardcoded /root path so this\n // works for non-root and macOS dev setups.\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(`[${label}] Removed ${p} (api_key mode — preventing OAuth fallback)`);\n } catch { /* non-fatal */ }\n }\n }\n } else {\n // subscription mode — strip any inherited key so Claude uses its OAuth login\n delete childEnv.ANTHROPIC_API_KEY;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Port allocation\n// ---------------------------------------------------------------------------\n\nfunction loadGatewayPorts(): Record<string, number> {\n try {\n return JSON.parse(readFileSync(GATEWAY_PORTS_FILE, 'utf-8'));\n } catch {\n return {};\n }\n}\n\nfunction saveGatewayPorts(ports: Record<string, number>): void {\n mkdirSync(AUGMENTED_DIR, { recursive: true });\n writeFileSync(GATEWAY_PORTS_FILE, JSON.stringify(ports, null, 2));\n}\n\nfunction allocatePort(codeName: string): number {\n const ports = loadGatewayPorts();\n\n // Already allocated\n if (ports[codeName]) return ports[codeName];\n\n // Find next free port (spaced by GATEWAY_PORT_STEP to avoid OpenClaw's extra port binds)\n const usedPorts = new Set(Object.values(ports));\n for (let port = GATEWAY_PORT_BASE; port <= GATEWAY_PORT_MAX; port += GATEWAY_PORT_STEP) {\n if (!usedPorts.has(port)) {\n ports[codeName] = port;\n saveGatewayPorts(ports);\n return port;\n }\n }\n\n throw new Error(`No free gateway ports in range ${GATEWAY_PORT_BASE}-${GATEWAY_PORT_MAX}`);\n}\n\nfunction freePort(codeName: string): void {\n const ports = loadGatewayPorts();\n if (ports[codeName]) {\n delete ports[codeName];\n saveGatewayPorts(ports);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n// State file lives under the manager's configDir so a non-default dir\n// doesn't split manager artifacts across locations.\nfunction getStateFile(): string {\n return join(config?.configDir ?? join(process.env['HOME'] ?? '/tmp', '.augmented'), 'manager-state.json');\n}\n\n// ENG-4712: see channel-hash-cache.ts for the rationale. We keep the\n// load/save call sites inline here so every cache mutation pairs with a\n// persist; the module holds the file format + IO and stays trivially\n// testable.\nfunction channelHashCacheDir(): string {\n return config?.configDir ?? join(process.env['HOME'] ?? '/tmp', '.augmented');\n}\n\nfunction loadChannelHashCache(): void {\n loadChannelHashCacheFromDisk(knownChannelConfigHashes, channelHashCacheDir());\n}\n\nfunction saveChannelHashCache(): void {\n saveChannelHashCacheToDisk(knownChannelConfigHashes, channelHashCacheDir());\n}\n\nfunction send(msg: ManagerEvent): void {\n // Write state updates to file for `agt manager status` to read\n if (msg.type === 'state-update') {\n try {\n writeFileSync(getStateFile(), JSON.stringify(msg.state, null, 2));\n } catch { /* non-fatal */ }\n }\n // Log notable events\n if (msg.type === 'provisioned') {\n log(`Provisioned ${msg.codeName}`);\n } else if (msg.type === 'drift-detected') {\n log(`Drift detected: ${msg.codeName} (${msg.files?.join(', ')})`);\n } else if (msg.type === 'error') {\n log(`Error: ${msg.message}`);\n }\n}\n\n// Path initialized lazily on first log() call so we don't need a second\n// explicit init step. The manager always has access to ~/.augmented.\nlet managerLogPath: string | null = null;\n// Whether log() should mirror to manager.log. If the first appendFileSync\n// throws, flip to false and stay stderr-only for the rest of this worker\n// process — no per-poll append-failure spam, no retry. The next worker\n// (after a respawn) starts fresh with managerLogWritable=true and gets\n// its own one-shot opportunity. The first failure also writes a single\n// reason line to stderr so operators see what's wrong.\nlet managerLogWritable = true;\n\n// Redact secret-shaped tokens from log messages before they hit any durable\n// sink. Applied to both stderr and the on-disk mirror — stderr gets captured\n// by journald / cloud-init / supervisor logs in production, so \"raw on\n// stderr\" is not actually safe. No raw-secret escape hatch: if you need to\n// inspect a live token, read it from its source of truth (env, secrets\n// manager) rather than scraping it from a log stream.\n//\n// Patterns:\n// - host API keys tlk_…\n// - Slack tokens xox[baprs]-…\n// - Anthropic keys sk-ant-…\n// - Telegram bot tokens <digits>:<base64url>\n// - Bearer JWTs Bearer …\n// - env-var assignments *_TOKEN=, *_SECRET=, *_API_KEY=, *_PASSWORD=\n// (broad shape match so future secrets we forget to enumerate are\n// still redacted).\nfunction redactForDiskLog(value: string): string {\n try {\n return value\n .replace(/\\b(Bearer\\s+)[A-Za-z0-9._-]+\\b/gi, '$1[REDACTED]')\n .replace(/\\bxox[baprs]-[A-Za-z0-9-]+\\b/g, '[REDACTED-SLACK]')\n .replace(/\\btlk_[A-Za-z0-9._-]+\\b/g, '[REDACTED-HOST]')\n .replace(/\\bsk-ant-[A-Za-z0-9_-]+\\b/g, '[REDACTED-ANTHROPIC]')\n .replace(/\\b\\d{8,12}:[A-Za-z0-9_-]{30,}\\b/g, '[REDACTED-TELEGRAM]')\n .replace(\n /\\b([A-Z0-9_]*(?:TOKEN|SECRET|API[_-]?KEY|PASSWORD)[A-Z0-9_]*)=(?:\"[^\"\\r\\n]*\"|'[^'\\r\\n]*'|[^\\s\\r\\n]+)/gi,\n '$1=[REDACTED]',\n );\n } catch {\n return '[REDACTED]';\n }\n}\n\nfunction log(msg: string): void {\n const ts = new Date().toISOString();\n const safeMsg = redactForDiskLog(msg);\n const line = `[manager-worker ${ts}] ${safeMsg}\\n`;\n\n // ENG-4658: Write directly to ~/.augmented/manager.log via O_APPEND\n // rather than relying on the supervisor having inherited a redirected\n // stderr fd. The previous behaviour assumed `agt manager start\n // --supervise` was launched with `nohup ... >> manager.log 2>&1` (or\n // a launchd/systemd unit doing the equivalent), and the spawned\n // worker would inherit those file descriptors. When systemd respawns\n // the supervisor without that shell redirection — or any time\n // logrotate moves the file out from under an inherited fd — the\n // worker's stderr silently goes to the journal / /dev/null and\n // operators see manager.log freeze.\n //\n // Direct append makes the worker self-sufficient: it always writes to\n // the configured path with O_APPEND, regardless of how stdio was set\n // up by whichever process spawned us. We still echo to stderr so\n // foreground runs (interactive `agt manager start` without\n // `--supervise`) and any supervisor that DOES capture stdio get the\n // same lines for free.\n if (!managerLogPath) {\n try {\n managerLogPath = join(homedir(), '.augmented', 'manager.log');\n mkdirSync(dirname(managerLogPath), { recursive: true });\n if (existsSync(managerLogPath)) {\n chmodSync(managerLogPath, 0o600);\n }\n } catch { /* non-fatal — first-touch perm hardening is best-effort */ }\n }\n let appendedToFile = false;\n if (managerLogPath && managerLogWritable) {\n try {\n // O_APPEND on every call: cheap (no persistent fd to drift), and\n // each append is atomic for writes <= PIPE_BUF on Linux which\n // these single-line entries always are.\n appendFileSync(managerLogPath, line, { encoding: 'utf-8', mode: 0o600 });\n appendedToFile = true;\n } catch (err) {\n // First failure flips the gate so we don't spam appendFileSync\n // attempts every poll. Print the reason to stderr once so an\n // operator knows the on-disk log is gone.\n managerLogWritable = false;\n process.stderr.write(\n `[manager-worker ${ts}] [log] manager.log append failed; falling back to stderr-only: ${(err as Error).message}\\n`,\n );\n }\n }\n // Echo to stderr only when:\n // - we couldn't write to the file (fallback diagnostic), OR\n // - stderr is a real TTY (foreground operator running interactively)\n // Under supervision, launchd / systemd / nohup all redirect stderr at\n // manager.log already (apps/cli/src/__tests__/manager-supervisor.test.ts\n // confirms launchd routes both StandardOutPath and StandardErrorPath\n // there). With the direct append above, an unconditional stderr write\n // would land each line in the file twice — once via our append, once\n // via the supervisor's redirect. CodeRabbit catch on PR #619.\n if (!appendedToFile || process.stderr.isTTY === true) {\n process.stderr.write(line);\n }\n}\n\nfunction sha256(content: string): string {\n return createHash('sha256').update(content, 'utf8').digest('hex');\n}\n\nfunction hashFile(filePath: string): string | null {\n try {\n const content = readFileSync(filePath, 'utf-8');\n return sha256(content);\n } catch {\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Skills index — refresh the \"## Available Skills\" section in CLAUDE.md by\n// scanning the agent's .claude/skills/ directory and parsing frontmatter.\n// ---------------------------------------------------------------------------\n\nconst SKILLS_INDEX_START = '<!-- AGT:SKILLS_INDEX_START -->';\nconst SKILLS_INDEX_END = '<!-- AGT:SKILLS_INDEX_END -->';\n\nfunction sanitizeSkillsIndexText(value: string): string {\n return value\n .replaceAll(SKILLS_INDEX_START, '')\n .replaceAll(SKILLS_INDEX_END, '')\n .replace(/<!--[\\s\\S]*?-->/g, '')\n .replace(/\\r?\\n+/g, ' ')\n .trim();\n}\n\nfunction parseSkillFrontmatter(content: string): { name?: string; description?: string } {\n if (!content.startsWith('---')) return {};\n const end = content.indexOf('\\n---', 3);\n if (end === -1) return {};\n const block = content.slice(3, end);\n const out: { name?: string; description?: string } = {};\n for (const line of block.split('\\n')) {\n const m = line.match(/^(name|description)\\s*:\\s*(.*)$/);\n if (m && m[1] && m[2] !== undefined) out[m[1] as 'name' | 'description'] = m[2].trim().replace(/^[\"']|[\"']$/g, '');\n }\n return out;\n}\n\nasync function refreshSkillsIndexInClaudeMd(configDir: string, codeName: string, log: (msg: string) => void): Promise<void> {\n const { readdirSync, readFileSync: rfs, existsSync: ex, writeFileSync } = await import('node:fs');\n const skillsDir = join(configDir, codeName, 'project', '.claude', 'skills');\n const claudeMdPath = join(configDir, codeName, 'project', 'CLAUDE.md');\n if (!ex(skillsDir) || !ex(claudeMdPath)) return;\n\n const entries: { id: string; name: string; description: string }[] = [];\n for (const dir of readdirSync(skillsDir).sort()) {\n const skillFile = join(skillsDir, dir, 'SKILL.md');\n if (!ex(skillFile)) continue;\n try {\n const { name, description } = parseSkillFrontmatter(rfs(skillFile, 'utf-8'));\n entries.push({\n id: dir,\n name: sanitizeSkillsIndexText(name ?? dir),\n description: sanitizeSkillsIndexText(description ?? '(no description)'),\n });\n } catch { /* skip unreadable */ }\n }\n\n const body = entries.length\n ? entries.map((e) => `- **${e.name}** — ${e.description}`).join('\\n')\n : '_(no skills installed)_';\n const section = `${SKILLS_INDEX_START}\n## Available Skills\n\nThe following skills are installed in \\`.claude/skills/\\`. Claude Code auto-activates them when relevant — you don't need to read them manually, but you should know they exist.\n\n${body}\n\n## Updating Integrations (ENG-4341)\n\nIntegration skills under \\`.claude/skills/integration-*/SKILL.md\\` are **read-only** and managed by the platform. They are derived from the integration's database row plus any per-agent context overrides, and are re-rendered every time the manager polls or a context change is broadcast over Supabase Realtime.\n\n**Never edit \\`.claude/skills/integration-*/SKILL.md\\` files directly.** If you do, your edit will be silently overwritten on the next manager refresh, AND it won't propagate to other agents using the same integration.\n\nTo change an integration's behavior (add a rule, update a default, tell the integration not to do something), call the **\\`plugin.improve\\`** MCP tool with the user's request. The tool calls the platform API, which uses an LLM to translate the request into a structured update of the integration's typed context fields and/or freeform overrides text. You'll get a diff back to show the user; on confirmation, call the tool again with \\`auto_apply: true\\` to apply.\n\nExamples of when to use \\`plugin.improve\\`:\n- *\"Update the Coding integration so we always use trunk instead of main\"*\n- *\"Add a rule to the Coding integration: never merge directly to main, always open a PR\"*\n- *\"Tell the Knowledge Base integration not to return results below 70% relevance\"*\n- *\"The Slack integration should post incidents to #oncall, not #general\"*\n${SKILLS_INDEX_END}`;\n\n const current = rfs(claudeMdPath, 'utf-8');\n let next: string;\n if (current.includes(SKILLS_INDEX_START) && current.includes(SKILLS_INDEX_END)) {\n next = current.replace(new RegExp(`${SKILLS_INDEX_START}[\\\\s\\\\S]*?${SKILLS_INDEX_END}`), section);\n } else {\n next = current.trimEnd() + '\\n\\n' + section + '\\n';\n }\n\n if (next !== current) {\n writeFileSync(claudeMdPath, next, 'utf-8');\n log(`Refreshed skills index in CLAUDE.md for '${codeName}' (${entries.length} skills)`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Migration: shared config → per-agent profiles\n// ---------------------------------------------------------------------------\n\nasync function migrateToProfiles(): Promise<void> {\n const homeDir = process.env['HOME'] ?? '/tmp';\n const sharedConfigPath = join(homeDir, '.openclaw', 'openclaw.json');\n\n // Check if shared config exists\n let sharedConfig: Record<string, unknown>;\n try {\n sharedConfig = JSON.parse(readFileSync(sharedConfigPath, 'utf-8'));\n } catch {\n return; // No shared config — nothing to migrate\n }\n\n // Check if there are multiple agents registered in shared config\n const agents = sharedConfig['agents'] as Record<string, unknown> | undefined;\n const agentList = (agents?.['list'] as Array<Record<string, unknown>>) ?? [];\n if (agentList.length === 0) return;\n\n const adapter = getFramework('openclaw');\n let migrated = 0;\n\n for (const agentEntry of agentList) {\n const codeName = agentEntry['id'] as string;\n if (!codeName) continue;\n\n // Skip 'main' — that's the interactive CLI agent, not managed by Augmented\n if (codeName === 'main') continue;\n\n const profileDir = join(homeDir, `.openclaw-${codeName}`);\n\n // Skip agents that already have profile dirs\n if (existsSync(join(profileDir, 'openclaw.json'))) continue;\n\n log(`Migrating agent '${codeName}' to per-agent profile`);\n\n // Seed profile config from shared config\n if (adapter.seedProfileConfig) {\n adapter.seedProfileConfig(codeName);\n }\n\n // Copy auth profiles from shared to profile dir\n const sharedAuthDir = join(homeDir, '.openclaw', 'agents', codeName, 'agent');\n const profileAuthDir = join(profileDir, 'agents', codeName, 'agent');\n const authFile = join(sharedAuthDir, 'auth-profiles.json');\n if (existsSync(authFile)) {\n mkdirSync(profileAuthDir, { recursive: true });\n const authContent = readFileSync(authFile, 'utf-8');\n writeFileSync(join(profileAuthDir, 'auth-profiles.json'), authContent);\n }\n\n // Allocate a gateway port\n allocatePort(codeName);\n\n migrated++;\n }\n\n if (migrated > 0) {\n log(`Migration complete: ${migrated} agent(s) migrated to per-agent profiles`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Gateway lifecycle\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve model tiers through the inheritance chain:\n * Agent override → Org default → Platform default\n */\nfunction resolveModelChain(refreshData: Record<string, unknown>): { primary?: string; secondary?: string; tertiary?: string } {\n const agent = refreshData.agent as Record<string, unknown> | undefined;\n const modelDefaults = refreshData.model_defaults as { platform?: Record<string, unknown> | null; org?: Record<string, unknown> | null } | undefined;\n const platform = modelDefaults?.platform ?? {};\n const org = modelDefaults?.org ?? {};\n\n function resolve(tier: 'primary' | 'secondary' | 'tertiary'): string | undefined {\n const agentField = `${tier}_model`;\n const platformField = `default_${tier}_model`;\n\n // 1. Agent override\n const agentVal = agent?.[agentField] as string | undefined;\n if (agentVal) return agentVal;\n\n // 2. Org default\n const orgVal = org?.[platformField] as string | undefined;\n if (orgVal) return orgVal;\n\n // 3. Platform default\n const platformVal = platform?.[platformField] as string | undefined;\n if (platformVal) return platformVal;\n\n return undefined;\n }\n\n return {\n primary: resolve('primary'),\n secondary: resolve('secondary'),\n tertiary: resolve('tertiary'),\n };\n}\n\nfunction readGatewayToken(codeName: string): string | undefined {\n const adapter = resolveAgentFramework(codeName);\n if (adapter.readGatewayToken) {\n return adapter.readGatewayToken(codeName);\n }\n // Fallback for adapters without readGatewayToken\n const homeDir = process.env['HOME'] ?? '/tmp';\n try {\n const cfg = JSON.parse(readFileSync(join(homeDir, `.openclaw-${codeName}`, 'openclaw.json'), 'utf-8'));\n return cfg?.gateway?.auth?.token as string | undefined;\n } catch {\n return undefined;\n }\n}\n\nconst GATEWAY_HUNG_TIMEOUT_MS = 5 * 60_000; // 5 minutes\n\n/**\n * Detect if a gateway is hung by checking for cron jobs stuck in \"running\" state.\n * Reads the cron jobs.json directly (no gateway communication needed since the\n * gateway itself may be unresponsive).\n */\nfunction isGatewayHung(codeName: string): boolean {\n const homeDir = process.env['HOME'] ?? '/tmp';\n const jobsPath = join(homeDir, `.openclaw-${codeName}`, 'cron', 'jobs.json');\n if (!existsSync(jobsPath)) return false;\n\n try {\n const data = JSON.parse(readFileSync(jobsPath, 'utf-8')) as Record<string, unknown>;\n const jobs = (data.jobs ?? data) as Array<Record<string, unknown>>;\n if (!Array.isArray(jobs)) return false;\n\n const now = Date.now();\n for (const job of jobs) {\n const state = job.state as Record<string, unknown> | undefined;\n if (!state) continue;\n const runStartedAt = state.runStartedAtMs as number | undefined;\n const isRunning = state.status === 'running' || state.running === true;\n if (isRunning && runStartedAt && (now - runStartedAt) > GATEWAY_HUNG_TIMEOUT_MS) {\n return true;\n }\n }\n } catch { /* non-fatal */ }\n\n return false;\n}\n\nasync function ensureGatewayRunning(codeName: string, adapter: FrameworkAdapter): Promise<{ pid: number | null; port: number | null; running: boolean }> {\n if (!adapter.isGatewayRunning || !adapter.startGateway) {\n return { pid: null, port: null, running: false };\n }\n\n const status = await adapter.isGatewayRunning(codeName);\n if (status.running) {\n // Check if gateway is hung — if a cron has been \"running\" for too long,\n // the gateway is likely stuck and needs a restart\n if (await isGatewayHung(codeName)) {\n log(`Gateway for '${codeName}' appears hung (cron stuck >5min) — restarting`);\n if (adapter.stopGateway) {\n try { await adapter.stopGateway(codeName); } catch { /* force kill below */ }\n }\n // Give it a moment to die\n await new Promise((r) => setTimeout(r, 2000));\n // Clear stale cron state so it doesn't immediately re-hang\n const homeDir = process.env['HOME'] ?? '/tmp';\n const cronJobsPath = join(homeDir, `.openclaw-${codeName}`, 'cron', 'jobs.json');\n clearStaleCronRunState(cronJobsPath);\n // Fall through to start a new gateway below\n } else {\n // Update config port in case it changed (e.g. after manual restart)\n if (status.port) {\n try {\n const homeDir = process.env['HOME'] ?? '/tmp';\n const configPath = join(homeDir, `.openclaw-${codeName}`, 'openclaw.json');\n if (existsSync(configPath)) {\n const cfg = JSON.parse(readFileSync(configPath, 'utf-8'));\n if (cfg.gateway?.port !== status.port) {\n if (!cfg.gateway) cfg.gateway = {};\n cfg.gateway.port = status.port;\n writeFileSync(configPath, JSON.stringify(cfg, null, 2));\n }\n }\n } catch { /* Non-fatal */ }\n }\n // Ensure pool has a connection to this gateway\n if (gatewayPool && status.port && !gatewayPool.hasAgent(codeName)) {\n const token = readGatewayToken(codeName);\n gatewayPool.addAgent(codeName, status.port, token);\n }\n return { pid: status.pid ?? null, port: status.port ?? null, running: true };\n }\n }\n\n // Allocate port and start\n const port = allocatePort(codeName);\n try {\n const result = await adapter.startGateway(codeName, port);\n log(`Gateway started for '${codeName}' on port ${port} (PID ${result.pid})`);\n gatewaysStartedThisCycle.add(codeName);\n\n // Write port to profile config so `openclaw cron run` can find the gateway\n try {\n const homeDir = process.env['HOME'] ?? '/tmp';\n const configPath = join(homeDir, `.openclaw-${codeName}`, 'openclaw.json');\n if (existsSync(configPath)) {\n const cfg = JSON.parse(readFileSync(configPath, 'utf-8'));\n if (!cfg.gateway) cfg.gateway = {};\n cfg.gateway.port = port;\n writeFileSync(configPath, JSON.stringify(cfg, null, 2));\n }\n } catch { /* Non-fatal */ }\n\n // Connect gateway client pool — the client silently handles ECONNREFUSED\n // on first attempt and retries with backoff while the gateway boots.\n if (gatewayPool) {\n const token = readGatewayToken(codeName);\n gatewayPool.addAgent(codeName, port, token);\n }\n\n return { pid: result.pid, port, running: true };\n } catch (err) {\n log(`Failed to start gateway for '${codeName}': ${(err as Error).message}`);\n return { pid: null, port, running: false };\n }\n}\n\nasync function stopGatewayIfRunning(codeName: string, adapter: FrameworkAdapter): Promise<void> {\n if (!adapter.stopGateway) return;\n\n try {\n const stopped = await adapter.stopGateway(codeName);\n if (stopped) {\n log(`Gateway stopped for '${codeName}'`);\n }\n } catch (err) {\n log(`Failed to stop gateway for '${codeName}': ${(err as Error).message}`);\n }\n\n // Disconnect from pool\n if (gatewayPool) {\n gatewayPool.removeAgent(codeName);\n }\n}\n\nasync function stopAllGateways(): Promise<void> {\n const ports = loadGatewayPorts();\n\n for (const codeName of Object.keys(ports)) {\n const adapter = resolveAgentFramework(codeName);\n await stopGatewayIfRunning(codeName, adapter);\n }\n\n if (gatewayPool) {\n gatewayPool.disconnectAll();\n }\n}\n\nasync function healthCheckGateways(agentStates: AgentState[]): Promise<void> {\n for (const agentState of agentStates) {\n if (agentState.status !== 'active' || !agentState.gatewayPort) continue;\n\n const adapter = resolveAgentFramework(agentState.codeName);\n if (!adapter.isGatewayRunning || !adapter.startGateway) continue;\n\n // Skip gateways that were just started this cycle — they may still be booting\n if (gatewaysStartedThisCycle.has(agentState.codeName)) continue;\n\n const status = await adapter.isGatewayRunning(agentState.codeName);\n if (!status.running && agentState.gatewayRunning) {\n // Gateway crashed — alert and restart\n const displayName = agentDisplayNames.get(agentState.codeName) ?? agentState.codeName;\n log(`Gateway for '${agentState.codeName}' crashed, restarting...`);\n sendSlackWebhookMessage(\n `:red_circle: *Host Down* — *${displayName}* (\\`${agentState.codeName}\\`)\\nOpenClaw gateway crashed. Attempting automatic restart...`,\n ).catch(() => {});\n\n try {\n const result = await adapter.startGateway(agentState.codeName, agentState.gatewayPort);\n agentState.gatewayPid = result.pid;\n agentState.gatewayRunning = true;\n log(`Gateway restarted for '${agentState.codeName}' (PID ${result.pid})`);\n\n // Give the gateway process time to bind the port before connecting WebSocket\n await new Promise((resolve) => setTimeout(resolve, 2000));\n\n // Reconnect pool client\n if (gatewayPool) {\n const token = readGatewayToken(agentState.codeName);\n gatewayPool.addAgent(agentState.codeName, agentState.gatewayPort, token);\n }\n\n sendSlackWebhookMessage(\n `:large_green_circle: *Host Recovered* — *${displayName}* (\\`${agentState.codeName}\\`)\\nGateway restarted successfully (PID ${result.pid}).`,\n ).catch(() => {});\n } catch (err) {\n agentState.gatewayRunning = false;\n log(`Failed to restart gateway for '${agentState.codeName}': ${(err as Error).message}`);\n sendSlackWebhookMessage(\n `:x: *Host Restart Failed* — *${displayName}* (\\`${agentState.codeName}\\`)\\nAutomatic restart failed: ${(err as Error).message}`,\n ).catch(() => {});\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Poll cycle\n// ---------------------------------------------------------------------------\n\nasync function pollCycle(): Promise<void> {\n if (!config) return;\n\n // ENG-4488: short-circuit every entrypoint into pollCycle — not just the\n // normal scheduleNext timer. triggerEarlyPoll() (fired by Realtime\n // events, webapp-direct-chat, gateway pool signals, etc.) queues\n // pollCycle() with zero delay, which could otherwise sneak one more\n // cycle in on the old Cellar path after restartAfterUpgrade flipped.\n if (restartAfterUpgrade) return;\n\n // ENG-4568: process /restart flag files written by channel MCPs. Run\n // before the rest of the cycle so a kill takes effect immediately and\n // the ensure-session pass below respawns the killed agent in the same\n // poll. Errors are logged inside the handler — never let a flag take\n // down the whole cycle.\n try {\n await processRestartFlags({\n log,\n resolveFramework: (codeName) => agentFrameworkCache.get(codeName) ?? null,\n stopSession: (codeName) => {\n // CodeRabbit on PR #773 (round 2): cancel any pending hot-reload\n // restart timer so it can't fire after this teardown and kill\n // the replacement session.\n stopPersistentSessionAndForgetMcpBaseline(codeName);\n persistentSessionAgents.delete(codeName);\n claudeAuthTupleBySession.delete(codeName);\n },\n getTelegramTokens: (codeName) => {\n const t = agentChannelTokens.get(codeName);\n if (!t?.telegram) return null;\n return { token: t.telegram, allowedChats: t.telegramAllowedChats };\n },\n sendTelegram: (botToken, method, body) => telegramApiCall(botToken, method, body),\n getSlackToken: (codeName) => agentChannelTokens.get(codeName)?.slack ?? null,\n sendSlack: async (botToken, body) => {\n // Match the timeout pattern other chat.postMessage call sites in\n // this file already use (lines ~5543, ~5762) — without this, a\n // hung Slack API would block pollCycle and stall every agent's\n // ensure-session pass behind the restart ack (CodeRabbit feedback).\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 5_000);\n try {\n const res = await fetch('https://slack.com/api/chat.postMessage', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json; charset=utf-8',\n Authorization: `Bearer ${botToken}`,\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n const data = (await res.json()) as { ok: boolean; error?: string };\n return data;\n } catch (err) {\n const isAbort = (err as Error).name === 'AbortError';\n return { ok: false, error: isAbort ? 'timeout' : (err as Error).message };\n } finally {\n clearTimeout(timer);\n }\n },\n });\n } catch (err) {\n log(`[restart-handler] processRestartFlags threw: ${(err as Error).message}`);\n }\n\n // ENG-4485: Kick off the CLI self-update check on every poll. The function\n // throttles the actual `brew outdated` call via an on-disk marker\n // (`.last-update-check`) + `UPDATE_CHECK_INTERVAL_MS`, so calling it each\n // poll is cheap — most invocations return immediately without shelling out.\n // Fire-and-forget: we don't want poll latency coupled to brew.\n checkAndUpdateCli().catch((err) => log(`[self-update] Check failed: ${(err as Error).message}`));\n\n try {\n // Clear per-cycle caches\n registeredAgentsCache.clear();\n gatewaysStartedThisCycle.clear();\n\n // 1. Discover assigned agents\n const hostId = await getHostId();\n if (!hostId) {\n send({ type: 'error', message: 'Could not resolve host ID from API key' });\n return;\n }\n\n // 1b. Detect framework version (cached, refreshed every 5 min)\n const now = Date.now();\n if (now - lastVersionCheckAt > VERSION_CHECK_INTERVAL_MS) {\n try {\n // Use the first known agent's framework, or default to openclaw\n const firstAgent = state.agents[0];\n const versionAdapter = firstAgent ? resolveAgentFramework(firstAgent.codeName) : getFramework('openclaw');\n if (versionAdapter.getVersion) {\n cachedFrameworkVersion = await versionAdapter.getVersion();\n }\n } catch {\n // Non-fatal — version detection is supplementary\n }\n lastVersionCheckAt = now;\n }\n\n // 1c. Heartbeat — update last_seen_at, framework_version, host security, diagnostics, and hostname\n try {\n const { detectHostSecurity } = await import('./host-security.js');\n const { collectDiagnostics } = await import('./persistent-session.js');\n\n // Collect diagnostics for all known persistent session agents\n const diagCodeNames = [...persistentSessionAgents];\n const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : undefined;\n\n // Detect Tailscale hostname for remote SSH attach\n let tailscaleHostname: string | undefined;\n try {\n const { execSync: es } = await import('node:child_process');\n const tsJson = es('tailscale status --self --json 2>/dev/null', {\n encoding: 'utf-8', timeout: 3000,\n }).trim();\n const ts = JSON.parse(tsJson);\n tailscaleHostname = ts.Self?.DNSName?.replace(/\\.$/, '') || undefined;\n } catch {\n // Tailscale not installed or not connected — fall back to hostname\n try {\n const { execSync: es } = await import('node:child_process');\n tailscaleHostname = es('hostname', { encoding: 'utf-8', timeout: 1000 }).trim();\n } catch { /* non-fatal */ }\n }\n\n // Get OS username for SSH command\n let osUsername: string | undefined;\n try {\n const { userInfo } = await import('node:os');\n osUsername = userInfo().username;\n } catch { /* non-fatal */ }\n\n // Claude Code auth freshness — console uses this to show a re-pair prompt\n // before the OAuth token expires silently. Returns null if no auth is\n // detectable (e.g. a fresh host with no Claude Code login yet).\n let claudeAuth: Awaited<ReturnType<typeof detectClaudeAuth>> = null;\n try {\n claudeAuth = await detectClaudeAuth();\n } catch (err) {\n // Per coding guidelines: default to hash-only/redacted logging in prod.\n // Auth detection errors can surface credential file paths — hash\n // instead of logging raw.\n const errText = err instanceof Error ? err.message : String(err);\n const errId = createHash('sha256').update(errText).digest('hex').slice(0, 12);\n log(`Claude auth detection failed (error_id=${errId})`);\n }\n\n await api.post('/host/heartbeat', {\n host_id: hostId,\n framework_version: cachedFrameworkVersion ?? undefined,\n agt_version: agtCliVersion,\n host_security: detectHostSecurity() ?? undefined,\n agent_runtime_authenticated: agentRuntimeAuthenticated,\n agent_diagnostics: agentDiagnostics,\n hostname: tailscaleHostname,\n os_username: osUsername,\n claude_auth: claudeAuth ?? undefined,\n });\n } catch (err) {\n log(`Heartbeat failed: ${(err as Error).message}`);\n }\n\n const data = await api.post<{\n agents: Array<{\n agent_id: string;\n code_name: string;\n display_name: string;\n status: string;\n environment: string;\n framework?: string;\n }>;\n }>('/host/agents', { host_id: hostId });\n\n const agents = data.agents ?? [];\n\n // Ensure framework binaries are installed/upgraded for any new frameworks\n const frameworksThisCycle = new Set(agents.map((a) => a.framework).filter(Boolean));\n for (const fw of frameworksThisCycle) {\n await ensureFrameworkBinary(fw!);\n }\n\n // Track which channels have at least one active agent per profile\n activeChannels.clear();\n\n // Update state agents list\n const agentStates: AgentState[] = [];\n\n for (const agent of agents) {\n try {\n await processAgent(agent, agentStates);\n } catch (err) {\n log(`Error processing agent '${agent.code_name}': ${(err as Error).message}`);\n // Preserve existing state for this agent on error\n const existing = state.agents.find((a) => a.agentId === agent.agent_id);\n if (existing) {\n agentStates.push(existing);\n } else {\n agentStates.push({\n agentId: agent.agent_id,\n codeName: agent.code_name,\n status: agent.status,\n charterVersion: '',\n toolsVersion: '',\n secretsHash: null,\n lastRefreshAt: null,\n lastProvisionAt: null,\n lastDriftCheckAt: null,\n lastSecretsProvisionAt: null,\n gatewayPort: null,\n gatewayPid: null,\n gatewayRunning: false,\n acpSessions: [],\n });\n }\n }\n }\n\n // Reconcile channel enabled state per profile.\n // Each agent has its own profile config, so enable/disable within that profile.\n try {\n for (const [channelId, codeNames] of activeChannels) {\n for (const codeName of codeNames) {\n const adapter = resolveAgentFramework(codeName);\n if (adapter.setChannelEnabled) {\n adapter.setChannelEnabled(channelId, true, codeName);\n }\n }\n }\n } catch { /* non-fatal */ }\n\n // Detect unassigned/deleted agents (were in state but not in current list)\n const currentIds = new Set(agents.map((a) => a.agent_id));\n for (const prev of state.agents) {\n if (!currentIds.has(prev.agentId)) {\n log(`Agent '${prev.codeName}' removed from host (deleted or unassigned)`);\n const adapter = resolveAgentFramework(prev.codeName);\n await stopGatewayIfRunning(prev.codeName, adapter);\n // Stop the persistent tmux session first — that takes the Claude Code\n // process and most of its MCP children with it. The explicit kill-session\n // mirrors the revoked branch and covers sessions started by a previous\n // manager run that this manager's `sessions` Map doesn't track.\n stopPersistentSessionAndForgetMcpBaseline(prev.codeName);\n try {\n const { execSync: es } = await import('node:child_process');\n es(`tmux kill-session -t agt-${prev.codeName} 2>/dev/null`, { stdio: 'ignore' });\n } catch { /* no session to kill */ }\n // ENG: tear down slack/telegram/direct-chat MCPs explicitly. Channel\n // children sometimes reparent to init when their Claude Code parent\n // exits, and the periodic channel-sweep won't touch them after this\n // point because their codeName is no longer in the manager's active\n // set. Without this, a Telegram poller for the de-provisioned agent\n // can keep running and conflict with whichever new host now owns it.\n killAgentChannelProcesses(prev.codeName, { log });\n freePort(prev.codeName);\n const agentDir = join(adapter.getAgentDir(prev.codeName), 'provision');\n await cleanupAgentFiles(prev.codeName, agentDir);\n clearAgentCaches(prev.agentId, prev.codeName);\n }\n }\n\n // ENG-4581: poll for pending Claude Code OAuth pairing sessions across\n // all agents on this host and dispatch each one. Best-effort —\n // failures are logged and the next refresh will retry.\n try {\n await processClaudePairSessions(agents.map((a) => ({\n agentId: a.agent_id,\n codeName: a.code_name,\n })));\n } catch (err) {\n log(`[claude-pair] poll failed: ${(err as Error).message}`);\n }\n\n // Health check: restart crashed gateways\n await healthCheckGateways(agentStates);\n\n // Channel MCP zombie sweep (ENG-4453) — every CHANNEL_SWEEP_INTERVAL_MS,\n // kill surplus per-agent channel MCP processes (orphans reparented to\n // init after a parent Claude Code died). ENG-4434 added signal handlers\n // that *should* prevent this, but on hosts we've still seen channel\n // zombies accumulate; this is the belt-and-braces net.\n if (Date.now() - lastChannelSweepAt >= CHANNEL_SWEEP_INTERVAL_MS) {\n lastChannelSweepAt = Date.now();\n const agentCodeNames = new Set(agentStates.map((a) => a.codeName));\n sweepChannelProcesses({\n agentCodeNames,\n dryRun: CHANNEL_SWEEP_DRY_RUN,\n log,\n }).catch((err) => {\n log(`[channel-sweep] sweep error: ${(err as Error).message}`);\n });\n }\n\n // ENG-4705: channel input watchdog. Detect channel-server-injected\n // messages that landed in the Claude Code TUI input buffer but never\n // got their auto-submit Enter (the bug: text sits as `❯ <message>` and\n // never becomes a user turn). Fire Enter for any input that's been\n // unchanged for >5s on a session with no human attached. Cheap — one\n // tmux capture-pane per claude-code agent per poll.\n {\n const claudeCodeAgents = agentStates\n .filter((a) => a.status === 'active')\n .filter((a) => (agentFrameworkCache.get(a.codeName) ?? 'openclaw') === 'claude-code')\n .map((a) => a.codeName);\n // Always call checkChannelInputs — when the list is empty it still\n // performs the per-cycle state cleanup pass for agents that have\n // been paused/revoked since last check.\n checkChannelInputs(claudeCodeAgents, {\n capturePane: (codeName) => {\n try {\n return syncExecFile('tmux', ['capture-pane', '-t', `agt-${codeName}`, '-p'], {\n stdio: ['ignore', 'pipe', 'ignore'],\n timeout: 2_000,\n }).toString();\n } catch {\n return null;\n }\n },\n isClientAttached: (codeName) => {\n try {\n const out = syncExecFile('tmux', ['list-clients', '-t', `agt-${codeName}`], {\n stdio: ['ignore', 'pipe', 'ignore'],\n timeout: 2_000,\n }).toString();\n return out.trim().length > 0;\n } catch {\n // Fail closed: if we can't tell whether a human is attached,\n // assume they are and skip the watchdog action. Better to\n // miss a stuck buffer than to fire Enter while someone is\n // typing.\n return true;\n }\n },\n sendEnter: (codeName) => {\n try {\n syncExecFile('tmux', ['send-keys', '-t', `agt-${codeName}`, 'Enter'], {\n stdio: 'ignore',\n timeout: 2_000,\n });\n } catch {\n // Best-effort watchdog action — never fail the poll cycle on\n // a tmux hiccup. Next cycle will retry.\n }\n },\n log,\n now: () => Date.now(),\n });\n }\n\n // Monitor cron health — detect late/failed jobs and alert (throttled)\n const lastHealthCheck = lastHarvestAt.get('__cron_health__') ?? 0;\n if (Date.now() - lastHealthCheck >= HARVEST_INTERVAL_MS) {\n lastHarvestAt.set('__cron_health__', Date.now());\n monitorCronHealth(agentStates).catch((err) => {\n log(`Cron health monitor error: ${(err as Error).message}`);\n });\n }\n\n // Direct chat: use Realtime if connected, fall back to polling\n if (!isRealtimeConnected()) {\n pollDirectChatMessages(agentStates).catch((err) => {\n log(`Direct chat poll error: ${(err as Error).message}`);\n });\n }\n\n // Start Realtime subscription if not yet started and we have Supabase config\n ensureRealtimeStarted(agentStates);\n ensureRealtimeDriftStarted(agentStates);\n ensureRealtimeAssignStarted(agentStates);\n ensureRealtimeConfigStarted(agentStates);\n ensureRealtimeKanbanStarted(agentStates);\n ensureRealtimeIntegrationContextStarted(agentStates);\n\n // Spawn due recurring kanban templates\n try {\n const spawnData = await api.post<{ spawned: number }>('/host/kanban/recurring/spawn');\n if (spawnData.spawned > 0) {\n log(`Spawned ${spawnData.spawned} recurring kanban item(s)`);\n }\n } catch { /* non-fatal */ }\n\n state = {\n ...state,\n lastPollAt: new Date().toISOString(),\n pollCount: state.pollCount + 1,\n agents: agentStates,\n };\n\n send({ type: 'state-update', state });\n } catch (err) {\n state.errorCount++;\n const message = (err as Error).message;\n log(`Poll error: ${message}`);\n send({ type: 'error', message });\n\n // Fatal auth error — exit so watchdog can restart with backoff\n if (message.includes('exchange failed') || message.includes('401')) {\n log('Fatal auth error, exiting for watchdog restart');\n send({ type: 'shutdown' });\n process.exit(1);\n }\n }\n}\n\nasync function getOrCacheRegisteredAgents(adapter: FrameworkAdapter, profile?: string): Promise<Set<string>> {\n const cacheKey = profile ? `${adapter.id}:${profile}` : adapter.id;\n let cached = registeredAgentsCache.get(cacheKey);\n if (!cached) {\n cached = await adapter.getRegisteredAgents(profile);\n registeredAgentsCache.set(cacheKey, cached);\n }\n return cached;\n}\n\nasync function processAgent(\n agent: { agent_id: string; code_name: string; display_name: string; status: string; environment: string; framework?: string },\n agentStates: AgentState[],\n): Promise<void> {\n if (!config) return;\n\n log(`==================== ${agent.display_name} (${agent.code_name}) ====================`);\n agentDisplayNames.set(agent.code_name, agent.display_name);\n codeNameToAgentId.set(agent.code_name, agent.agent_id);\n\n // Cache the framework from the agent list response (refreshAgentConfig may override with more specific data)\n if (agent.framework) {\n agentFrameworkCache.set(agent.code_name, agent.framework);\n }\n\n const now = new Date().toISOString();\n const adapter = resolveAgentFramework(agent.code_name);\n // ENG-4418: agentDir resolves through the adapter so manager + framework\n // share a single provision tree. Initial value uses the cached framework\n // from /host/agents; it is RE-resolved once /host/refresh returns the\n // authoritative framework below (see `agentDir = join(frameworkAdapter...)`\n // after agentFrameworkCache.set). Without the re-resolution, a stale\n // cache would trigger claudecodeAdapter.getAgentDir's migration on the\n // wrong framework's tree.\n let agentDir = join(adapter.getAgentDir(agent.code_name), 'provision');\n\n // 1a. Status-aware branching\n if (agent.status === 'draft' || agent.status === 'paused') {\n log(`Agent '${agent.code_name}' is ${agent.status}, skipping provisioning`);\n await stopGatewayIfRunning(agent.code_name, adapter);\n // Kill persistent tmux session if it exists (prevents paused agents responding on channels)\n // Must use tmux kill-session directly — the sessions Map doesn't track sessions from previous manager runs\n stopPersistentSessionAndForgetMcpBaseline(agent.code_name);\n try {\n const { execSync: es } = await import('node:child_process');\n es(`tmux kill-session -t agt-${agent.code_name} 2>/dev/null`, { stdio: 'ignore' });\n log(`Killed tmux session for paused agent '${agent.code_name}'`);\n } catch { /* no session to kill */ }\n agentStates.push({\n agentId: agent.agent_id,\n codeName: agent.code_name,\n status: agent.status,\n charterVersion: '',\n toolsVersion: '',\n secretsHash: null,\n lastRefreshAt: now,\n lastProvisionAt: null,\n lastDriftCheckAt: null,\n lastSecretsProvisionAt: null,\n gatewayPort: null,\n gatewayPid: null,\n gatewayRunning: false,\n acpSessions: [],\n });\n return;\n }\n\n if (agent.status === 'revoked') {\n log(`Agent '${agent.code_name}' is revoked, cleaning up`);\n await stopGatewayIfRunning(agent.code_name, adapter);\n stopPersistentSessionAndForgetMcpBaseline(agent.code_name);\n try { const { execSync: es } = await import('node:child_process'); es(`tmux kill-session -t agt-${agent.code_name} 2>/dev/null`, { stdio: 'ignore' }); } catch { /* no session */ }\n killAgentChannelProcesses(agent.code_name, { log });\n freePort(agent.code_name);\n await cleanupAgentFiles(agent.code_name, agentDir);\n clearAgentCaches(agent.agent_id, agent.code_name);\n knownStatuses.set(agent.agent_id, agent.status);\n agentStates.push({\n agentId: agent.agent_id,\n codeName: agent.code_name,\n status: agent.status,\n charterVersion: '',\n toolsVersion: '',\n secretsHash: null,\n lastRefreshAt: now,\n lastProvisionAt: null,\n lastDriftCheckAt: null,\n lastSecretsProvisionAt: null,\n gatewayPort: null,\n gatewayPid: null,\n gatewayRunning: false,\n acpSessions: [],\n });\n return;\n }\n\n // 1b. Detect status change → force reprovision and channel re-evaluation\n const previousStatus = knownStatuses.get(agent.agent_id);\n if (previousStatus && previousStatus !== agent.status) {\n log(`Agent '${agent.code_name}' status changed: ${previousStatus} → ${agent.status}`);\n knownVersions.delete(agent.agent_id); // Force reprovision\n // Invalidate channel config hashes so credentials/bindings/enabled state get rewritten\n let channelCacheMutated = false;\n for (const key of knownChannelConfigHashes.keys()) {\n if (key.startsWith(`${agent.agent_id}:`)) {\n knownChannelConfigHashes.delete(key);\n channelCacheMutated = true;\n }\n }\n if (channelCacheMutated) saveChannelHashCache();\n }\n knownStatuses.set(agent.agent_id, agent.status);\n\n // 2. Refresh — get latest charter/tools from API\n let refreshData: {\n agent: Record<string, unknown>;\n charter: { raw_content: string; version: string } | null;\n tools: { raw_content: string; version: string } | null;\n channel_configs: Record<string, { config: unknown; status: string }> | null;\n team_channel_policy: {\n team_id: string;\n allowed_channels: string[];\n denied_channels: string[];\n require_elevated_for_pii: boolean;\n } | null;\n team: { name: string; description: string | null; settings?: Record<string, unknown> } | null;\n scheduled_tasks: Array<{\n id: string;\n template_id: string;\n name: string;\n schedule_kind: string;\n schedule_expr: string | null;\n schedule_every: string | null;\n schedule_at: string | null;\n timezone: string;\n prompt: string;\n session_target: string;\n delivery_mode: string;\n delivery_channel: string;\n delivery_to: string | null;\n enabled: boolean;\n }> | null;\n plugin_skills?: Array<{\n plugin_slug: string;\n skill_id: string;\n skill_name: string;\n content: string;\n }>;\n plugin_install_hooks?: Array<{\n plugin_slug: string;\n script: string;\n }>;\n /** ENG-4421: toolkits required by installed plugins — manager resolves CLI installs from these. */\n plugin_toolkits?: Array<{\n plugin_slug: string;\n toolkits: string[];\n }>;\n /** ENG-4341: pre-resolved plugin contexts (defaults applied, agent-scope row merged in). */\n plugin_contexts?: Array<{\n plugin_id: string;\n plugin_slug: string;\n values: Record<string, unknown>;\n overrides: string;\n }>;\n /** Org + team knowledge items for skill generation. */\n knowledge?: Array<{ title: string; slug: string; content: string | null; scope: 'org' | 'team' }>;\n /** Team members for CLAUDE.md directory. */\n team_members?: Array<{ display_name: string; email?: string; role: string; title?: string; contact_channel?: string }>;\n /** People/contacts for CLAUDE.md directory. */\n people?: Array<{ display_name: string; email?: string; title?: string; department?: string; relationship?: string; contact_channel?: string }>;\n };\n\n try {\n refreshData = await api.post<typeof refreshData>('/host/refresh', {\n agent_id: agent.agent_id,\n });\n } catch (err) {\n log(`Refresh failed for '${agent.code_name}': ${(err as Error).message}`);\n const existing = state.agents.find((a) => a.agentId === agent.agent_id);\n agentStates.push(existing ?? {\n agentId: agent.agent_id,\n codeName: agent.code_name,\n status: agent.status,\n charterVersion: '',\n toolsVersion: '',\n secretsHash: null,\n lastRefreshAt: null,\n lastProvisionAt: null,\n lastDriftCheckAt: null,\n lastSecretsProvisionAt: null,\n gatewayPort: null,\n gatewayPid: null,\n gatewayRunning: false,\n acpSessions: [],\n });\n return;\n }\n\n // Cache alert webhook from team settings (first agent wins)\n if (!alertSlackWebhook && refreshData.team?.settings) {\n const webhook = refreshData.team.settings['alert_slack_webhook'];\n if (typeof webhook === 'string' && webhook.startsWith('https://')) {\n alertSlackWebhook = webhook;\n }\n }\n\n if (!refreshData.charter || !refreshData.tools) {\n log(`No charter/tools for '${agent.code_name}', skipping`);\n agentStates.push({\n agentId: agent.agent_id,\n codeName: agent.code_name,\n status: agent.status,\n charterVersion: '',\n toolsVersion: '',\n secretsHash: null,\n lastRefreshAt: now,\n lastProvisionAt: null,\n lastDriftCheckAt: null,\n lastSecretsProvisionAt: null,\n gatewayPort: null,\n gatewayPid: null,\n gatewayRunning: false,\n acpSessions: [],\n });\n return;\n }\n\n // Resolve framework adapter from agent data and update cache\n const frameworkId = (refreshData.agent.framework as string) ?? 'openclaw';\n agentFrameworkCache.set(agent.code_name, frameworkId);\n const frameworkAdapter = getFramework(frameworkId);\n // ENG-4418: re-resolve agentDir now that /host/refresh has given us the\n // authoritative framework. If the cache was stale (agent switched\n // framework since last poll), the initial agentDir would have pointed at\n // the wrong tree — recalculate before any writes or adapter calls use it.\n agentDir = join(frameworkAdapter.getAgentDir(agent.code_name), 'provision');\n\n // ENG-4422 §5: cache the agent delivery metadata so the DM resolver has\n // what it needs without an extra API call at fire time.\n // The host-runtime payload carries reports_to_* + owner_team_name (see §8).\n cacheAgentDeliveryMetadata(agent.code_name, refreshData);\n\n // Ensure per-agent profile config exists\n if (frameworkAdapter.seedProfileConfig) {\n frameworkAdapter.seedProfileConfig(agent.code_name);\n }\n\n const charterVersion = refreshData.charter.version;\n const toolsVersion = refreshData.tools.version;\n const known = knownVersions.get(agent.agent_id);\n\n let lastProvisionAt = state.agents.find((a) => a.agentId === agent.agent_id)?.lastProvisionAt ?? null;\n\n // CodeRabbit on PR #773 (round 4): diff against the *launchable* channel\n // set, not raw channel_configs membership. The Claude launch flags\n // (`--dangerously-load-development-channels`, `--allowedTools`) only\n // include channels whose entry has `status` in {active, pending} AND a\n // non-null config — the same gate writeChannelCredentials runs at line\n // 2571. A status-only flip like `slack: active → revoked` leaves the\n // channel id in `Object.keys(channel_configs)` and would have been\n // missed by a raw-key diff, leaving the running session with stale\n // launch flags. Inversely, a row that never becomes launchable would\n // have triggered an unnecessary restart. Compute the same launchable\n // filter here so add/remove + restart decisions agree with what the\n // session actually loads.\n //\n // Detect channel changes (launchable filter via the shared helper so\n // unit tests cover the same gate this code uses at runtime).\n const currentChannelIds = launchableChannelIds(refreshData.channel_configs);\n const previousChannelIds = knownChannels.get(agent.agent_id);\n const channelsChanged = !previousChannelIds ||\n currentChannelIds.size !== previousChannelIds.size ||\n [...currentChannelIds].some((ch) => !previousChannelIds.has(ch)) ||\n [...previousChannelIds].some((ch) => !currentChannelIds.has(ch));\n\n // CodeRabbit on PR #773 (round 2): track whether the on-disk channel\n // credential state actually converges to `currentChannelIds` before we\n // (a) advance the diff cache (`knownChannels.set`) and (b) trigger an\n // ENG-4807 hot-reload restart. If a remove or write fails, leave the\n // diff live so the next refresh tick sees the change again and retries\n // the credential sync — and don't restart the session on broken\n // credential state.\n let channelConfigConverged = true;\n\n // Remove credentials for channels that were unbound\n if (previousChannelIds && channelsChanged && frameworkAdapter.removeChannelCredentials) {\n for (const ch of previousChannelIds) {\n if (!currentChannelIds.has(ch)) {\n try {\n frameworkAdapter.removeChannelCredentials(agent.code_name, ch);\n log(`Removed ${ch} credentials for '${agent.code_name}'`);\n } catch (err) {\n channelConfigConverged = false;\n log(`Failed to remove ${ch} credentials for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n }\n }\n\n // 3. Content-based re-provisioning: always generate artifacts, compare against\n // what's on disk, and only write files that actually changed. This catches\n // template code changes, DB data changes (role, description), and version bumps.\n try {\n const artifacts = generateArtifacts(agent, refreshData, frameworkAdapter);\n // Some artifacts (`.mcp.json`) need their on-disk merge computed *before*\n // the write loop, so track an optional override content alongside the\n // artifact. When set, it replaces artifact.content at write time.\n const changedFiles: { relativePath: string; content: string }[] = [];\n\n mkdirSync(agentDir, { recursive: true });\n for (const artifact of artifacts) {\n const filePath = join(agentDir, artifact.relativePath);\n\n // For CLAUDE.md: strip the skills index section before comparing,\n // since it's managed separately by refreshSkillsIndexInClaudeMd.\n // Without this, the provisioner re-writes CLAUDE.md every cycle\n // (it generates without the index, but on-disk has it).\n //\n // Both sides must be trimmed the same way — the stripped project copy\n // loses its trailing newline via trimEnd(), and the generated artifact\n // content preserves its trailing newline. Without trimming both,\n // hashes never match and the provisioner churns every poll.\n let existingHash: string | null;\n let newHash: string;\n let writeContent = artifact.content;\n if (artifact.relativePath === 'CLAUDE.md') {\n // Strip dynamically-patched sections from BOTH sides before hashing.\n // Two sections get added to project/CLAUDE.md by side-effect code paths\n // that the generator doesn't know about:\n // 1. `## Available Skills` + `## Updating Plugins` (index) — added by\n // refreshSkillsIndexInClaudeMd, bracketed by AGT:SKILLS_INDEX markers.\n // 2. `## Integrations` — added by frameworkAdapter.writeIntegrations,\n // inserted immediately before `## Rules`.\n // Without stripping both, the hashes never match and every poll churns.\n // Long-term fix (ENG-4418 follow-up): wire integrations through\n // refreshData so generateArtifacts produces the same content.\n const stripDynamicSections = (s: string): string => {\n let out = s.replace(new RegExp(`${SKILLS_INDEX_START}[\\\\s\\\\S]*?${SKILLS_INDEX_END}`), '');\n out = out.replace(/## Integrations[\\s\\S]*?(?=## Rules)/, '');\n return out.trimEnd();\n };\n newHash = sha256(stripDynamicSections(artifact.content));\n try {\n const projectClaudeMd = join(config.configDir, agent.code_name, 'project', 'CLAUDE.md');\n const existing = readFileSync(projectClaudeMd, 'utf-8');\n existingHash = sha256(stripDynamicSections(existing));\n } catch {\n existingHash = null;\n }\n } else if (artifact.relativePath === '.mcp.json') {\n // The `.mcp.json` generator only owns a subset of mcpServers (augmented,\n // qmd, ...); channel servers (slack, direct-chat, …) and plugin-added\n // servers are merged in by other code paths. A naive overwrite would\n // wipe them every poll, which then makes step 5b re-add direct-chat,\n // step 5 re-add slack, etc. — the loop that produces:\n // Updating 'agent': .mcp.json\n // Channel credentials written for 'agent/direct-chat'\n // every cycle. Compare and write only the generator-owned keys,\n // preserving everything else.\n const parseMcp = (raw: string): Record<string, unknown> => {\n try {\n const parsed = JSON.parse(raw) as { mcpServers?: Record<string, unknown> };\n return parsed.mcpServers ?? {};\n } catch {\n return {};\n }\n };\n const generatorServers = parseMcp(artifact.content);\n const generatorKeys = Object.keys(generatorServers);\n let existingRaw = '';\n try { existingRaw = readFileSync(filePath, 'utf-8'); } catch { /* new file */ }\n const existingServers = parseMcp(existingRaw);\n const merged = { mcpServers: { ...existingServers, ...generatorServers } };\n writeContent = JSON.stringify(merged, null, 2);\n // Hash only the generator-owned slice on both sides. If the generator\n // produces the same shape as what's already there, skip the write.\n const sliceGeneratorKeys = (src: Record<string, unknown>): Record<string, unknown> => {\n const out: Record<string, unknown> = {};\n for (const k of generatorKeys) if (k in src) out[k] = src[k];\n return out;\n };\n newHash = sha256(JSON.stringify(generatorServers));\n existingHash = existingRaw\n ? sha256(JSON.stringify(sliceGeneratorKeys(existingServers)))\n : null;\n } else {\n newHash = sha256(artifact.content);\n existingHash = hashFile(filePath);\n }\n\n if (newHash !== existingHash) {\n changedFiles.push({ relativePath: artifact.relativePath, content: writeContent });\n }\n }\n\n if (changedFiles.length > 0) {\n const isFirst = !existsSync(join(agentDir, 'CHARTER.md'));\n const verb = isFirst ? 'Provisioning' : 'Updating';\n const fileNames = changedFiles.map((f) => f.relativePath).join(', ');\n log(`${verb} '${agent.code_name}': ${fileNames}`);\n\n for (const file of changedFiles) {\n const filePath = join(agentDir, file.relativePath);\n mkdirSync(dirname(filePath), { recursive: true });\n writeFileSync(filePath, file.content);\n }\n\n // Prune stale legacy knowledge-* skill dirs from provision dir\n // (replaced by single core-knowledge skill)\n try {\n const provSkillsDir = join(agentDir, '.claude', 'skills');\n if (existsSync(provSkillsDir)) {\n for (const folder of readdirSync(provSkillsDir)) {\n if (folder.startsWith('knowledge-')) {\n try { rmSync(join(provSkillsDir, folder), { recursive: true }); } catch { /* ignore */ }\n }\n }\n }\n } catch { /* non-fatal */ }\n\n lastProvisionAt = new Date().toISOString();\n\n knownVersions.set(agent.agent_id, { charterVersion, toolsVersion });\n\n // Update written hashes for drift detection\n const trackedFiles = frameworkAdapter.driftTrackedFiles();\n const hashes = new Map<string, string>();\n for (const file of trackedFiles) {\n const h = hashFile(join(agentDir, file));\n if (h) hashes.set(file, h);\n }\n writtenHashes.set(agent.agent_id, hashes);\n\n // Register in framework runtime if not already present\n const resolvedModelsForRegistration = resolveModelChain(refreshData);\n const primaryModel = resolvedModelsForRegistration.primary ?? (refreshData.agent.primary_model as string | null | undefined);\n const registeredAgents = await getOrCacheRegisteredAgents(frameworkAdapter, agent.code_name);\n if (!registeredAgents.has(agent.code_name)) {\n const registered = await frameworkAdapter.registerAgent(agent.code_name, agentDir, primaryModel);\n if (registered) {\n registeredAgents.add(agent.code_name);\n log(`Registered '${agent.code_name}' in ${frameworkAdapter.label}`);\n }\n }\n\n send({ type: 'provisioned', agentId: agent.agent_id, codeName: agent.code_name });\n }\n\n // Always deploy artifacts to project dir — the provision dir is the source\n // of truth, and channel MCP servers are merged from a separate path.\n // This must run outside changedFiles so the merge always produces a\n // complete .mcp.json (base servers + channel servers).\n if (frameworkAdapter.deployArtifactsToProject) {\n frameworkAdapter.deployArtifactsToProject(agent.code_name, agentDir);\n }\n } catch (err) {\n log(`Provision failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n\n // 3b. Ensure model is set correctly in per-agent profile (on first run and on change)\n const resolvedForModel = resolveModelChain(refreshData);\n const primaryModel = resolvedForModel.primary ?? (refreshData.agent.primary_model as string | null | undefined);\n if (primaryModel && frameworkAdapter.updateAgentModel) {\n const previousModel = knownModels.get(agent.agent_id);\n if (previousModel !== primaryModel) {\n try {\n const updated = await frameworkAdapter.updateAgentModel(agent.code_name, primaryModel);\n if (updated) {\n if (previousModel) {\n log(`Model updated for '${agent.code_name}': ${previousModel} → ${primaryModel}`);\n } else {\n log(`Model set for '${agent.code_name}': ${primaryModel}`);\n }\n }\n } catch (err) {\n log(`Failed to update model for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n knownModels.set(agent.agent_id, primaryModel);\n }\n\n // 4. Drift check — hash local files and compare\n let lastDriftCheckAt = now;\n const written = writtenHashes.get(agent.agent_id);\n\n if (written && existsSync(agentDir)) {\n const driftedFiles: string[] = [];\n\n for (const [file, expectedHash] of written) {\n const localHash = hashFile(join(agentDir, file));\n if (localHash && localHash !== expectedHash) {\n driftedFiles.push(file);\n }\n }\n\n if (driftedFiles.length > 0) {\n log(`Drift detected for '${agent.code_name}': ${driftedFiles.join(', ')}`);\n send({ type: 'drift-detected', agentId: agent.agent_id, codeName: agent.code_name, files: driftedFiles });\n\n // Report drift to API\n try {\n const localHashes: Record<string, string | null> = {};\n for (const file of driftedFiles) {\n localHashes[file] = hashFile(join(agentDir, file));\n }\n await api.post('/host/drift', {\n agent_id: agent.agent_id,\n drifted_files: driftedFiles,\n local_hashes: localHashes,\n });\n } catch (err) {\n log(`Failed to report drift for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n }\n\n // Cache bot tokens from channel_configs for notification delivery\n if (refreshData.channel_configs) {\n const tokens: { slack?: string; telegram?: string; telegramAllowedChats?: string[] } = {};\n const slackCfg = refreshData.channel_configs['slack'];\n if (slackCfg?.config) {\n const bt = (slackCfg.config as Record<string, unknown>).bot_token;\n if (typeof bt === 'string' && bt) tokens.slack = bt;\n }\n const tgCfg = refreshData.channel_configs['telegram'];\n if (tgCfg?.config) {\n const tgConfig = tgCfg.config as Record<string, unknown>;\n const bt = tgConfig.bot_token;\n if (typeof bt === 'string' && bt) tokens.telegram = bt;\n const allowedChats = tgConfig.allowed_chat_ids;\n if (Array.isArray(allowedChats)) tokens.telegramAllowedChats = allowedChats as string[];\n }\n if (tokens.slack || tokens.telegram) {\n agentChannelTokens.set(agent.code_name, tokens);\n } else {\n agentChannelTokens.delete(agent.code_name);\n }\n }\n\n let needsGatewayRestart = false;\n\n // 5. Write channel credentials to per-agent profile config (only if changed)\n const hasChannelConfigs = refreshData.channel_configs && Object.keys(refreshData.channel_configs).length > 0;\n\n if (refreshData.channel_configs && frameworkAdapter.writeChannelCredentials) {\n if (agent.status === 'active') {\n for (const [channelId, entry] of Object.entries(refreshData.channel_configs)) {\n if ((entry.status === 'active' || entry.status === 'pending') && entry.config) {\n // Track that this channel is active for this agent's profile\n if (!activeChannels.has(channelId)) {\n activeChannels.set(channelId, new Set());\n }\n activeChannels.get(channelId)!.add(agent.code_name);\n\n // Hash the config to detect changes — skip credential write if\n // identical to last poll. ENG-4439: also verify the on-disk state\n // actually holds the entry before skipping. The cache is purely\n // \"what I intended to write\"; if an external process modified the\n // agent's runtime config between polls (manual edit, migration,\n // another tool), the cache would otherwise refuse to re-write\n // forever and the channel stays broken until the manager restarts.\n // Adapters without `hasChannelCredentials` are treated as \"trust\n // the cache\" so older framework adapters keep their existing\n // behavior.\n //\n // ENG-4468: hash the CANONICAL (recursively key-sorted) JSON,\n // not plain `JSON.stringify`. Postgres JSONB doesn't preserve\n // key order, so the same semantic config can serialise to\n // different strings across polls — producing different hashes\n // and forcing constant rewrites. The churn cascaded into\n // .mcp.json drift and MCP subprocess instability on augmenteds-mbp\n // (Phil / Pepper). Canonicalising fixes it regardless of which\n // field moved.\n // ENG-4912: fold the team-scoped Telegram kill switch into the\n // hash input so a team-admin flip invalidates the cache and\n // forces a credential rewrite (which propagates the\n // TELEGRAM_PEER_DISABLED env to the spawned MCP child). Without\n // this the per-channel hash on `entry.config` alone would skip\n // the rewrite — entry.config has nothing to do with team\n // settings — and the kill switch wouldn't take effect until\n // the channel's own config drifted.\n const teamSettingsForHash =\n channelId === 'telegram'\n ? {\n telegram_peer_disabled: Boolean(\n (refreshData.team as { settings?: Record<string, unknown> } | undefined)\n ?.settings?.['telegram_peer_disabled'] ?? false,\n ),\n }\n : null;\n // ENG-4923: also fold the resolved peer roster into the hash\n // input. entry.config carries peer_agent_mode + peer_group_ids\n // natively (TelegramChannelConfig fields), but the CHARTER-\n // derived peer list does not — so a CHARTER edit that changes\n // declared peers would otherwise leave the hash unchanged and\n // never propagate the new TELEGRAM_PEERS env to the child.\n const peersForHash =\n channelId === 'telegram'\n ? extractCharterTelegramPeers(refreshData.charter?.raw_content ?? '')\n : null;\n const configHash = createHash('sha256')\n .update(canonicalJson({ config: entry.config, team: teamSettingsForHash, peers: peersForHash }))\n .digest('hex');\n const cacheKey = `${agent.agent_id}:${channelId}`;\n let onDiskPresent = true;\n try {\n onDiskPresent =\n frameworkAdapter.hasChannelCredentials?.(agent.code_name, channelId) ?? true;\n } catch (err) {\n // A faulty adapter hook must not abort the provisioning loop.\n // Treat the failure as \"on-disk entry missing\" so we force a\n // rewrite rather than silently trusting a stale cache.\n log(`hasChannelCredentials failed for '${agent.code_name}/${channelId}': ${(err as Error).message} — forcing credential rewrite`);\n onDiskPresent = false;\n }\n if (knownChannelConfigHashes.get(cacheKey) === configHash && onDiskPresent) {\n continue;\n }\n // ENG-4468: capture prevHash BEFORE the on-disk-missing branch\n // can delete it, so the `reason` label below is accurate. Reading\n // after the delete would mislabel on-disk-missing as first-write.\n const prevHash = knownChannelConfigHashes.get(cacheKey);\n const reason = !prevHash\n ? 'first-write'\n : !onDiskPresent\n ? 'on-disk-missing'\n : 'content-changed';\n if (!onDiskPresent && prevHash) {\n log(`Cached hash for '${agent.code_name}/${channelId}' but on-disk entry missing — re-writing credentials`);\n knownChannelConfigHashes.delete(cacheKey);\n }\n try {\n const sessionMode = (refreshData.agent as Record<string, unknown>).session_mode as string | undefined;\n // ENG-4573: pass agent_id through so the slack-channel MCP can\n // wire AGT_AGENT_ID for slack.ask_user (block_kit_ask_user_enabled).\n // ENG-4912: thread the team-scoped Telegram kill switch through\n // so the adapter sets TELEGRAM_PEER_DISABLED on the MCP child env\n // when on-call has flipped it via the webapp. Read explicitly\n // (Boolean(...)) so an absent settings key reads as `false`.\n const telegramPeerDisabled =\n channelId === 'telegram'\n ? Boolean(\n (refreshData.team as { settings?: Record<string, unknown> } | undefined)\n ?.settings?.['telegram_peer_disabled'] ?? false,\n )\n : undefined;\n // ENG-4923: assemble the per-agent peer roster from CHARTER\n // multi_agent.telegram_peers. Each entry is the CHARTER's\n // declared { code_name, bot_id }; agent_id is left empty\n // here because the manager doesn't yet have a cheap way to\n // resolve it from the team roster (would need either an\n // extension to /host/refresh or a separate /host/team-agents\n // call). The classifier doesn't gate on agent_id — only on\n // bot_id — so leaving it blank is safe today; ENG-4904's\n // runtime can fetch the agent_id when it needs to surface\n // the peer's identity in the prompt preamble.\n const telegramPeers =\n channelId === 'telegram'\n ? extractCharterTelegramPeers(refreshData.charter?.raw_content ?? '')\n : undefined;\n frameworkAdapter.writeChannelCredentials(\n agent.code_name,\n channelId,\n entry.config as Record<string, unknown>,\n { sessionMode, agentId: agent.agent_id, telegramPeerDisabled, telegramPeers },\n );\n knownChannelConfigHashes.set(cacheKey, configHash);\n saveChannelHashCache();\n log(`Channel credentials written for '${agent.code_name}/${channelId}' (reason=${reason}, hash=${configHash.slice(0, 8)}${prevHash ? `, prev=${prevHash.slice(0, 8)}` : ''})`);\n } catch (err) {\n channelConfigConverged = false;\n log(`Failed to write channel credentials for '${agent.code_name}/${channelId}': ${(err as Error).message}`);\n }\n }\n }\n\n } else if (agent.status === 'paused') {\n // Paused agents: disable channels in their profile\n if (frameworkAdapter.setChannelEnabled) {\n for (const channelId of Object.keys(refreshData.channel_configs)) {\n frameworkAdapter.setChannelEnabled(channelId, false, agent.code_name);\n }\n }\n }\n }\n\n // CodeRabbit on PR #773 (round 2): only advance the per-agent channel\n // diff cache when the on-disk credential state actually converged. If\n // a remove/write failed, leave `previousChannelIds` in place so the\n // next refresh tick re-sees the diff and retries the credential sync.\n // Otherwise we'd swallow the change forever the moment one transient\n // I/O failure occurred — and worse, fire a hot-reload restart on top\n // of broken credential state.\n if (channelConfigConverged) {\n knownChannels.set(agent.agent_id, currentChannelIds);\n } else {\n log(`[channels] Credential sync did not converge for '${agent.code_name}' — leaving diff live for next tick retry`);\n }\n\n // ENG-4807: when the channel set changes for a live persistent claude-code\n // session, restart the session so its launch flags reflect the new\n // channels. The flags baked at spawn time\n // (`--dangerously-load-development-channels` and `--allowedTools`) don't\n // pick up new channels without a respawn — the MCP loads but its\n // `notifications/claude/channel` get dropped, and tool calls to the new\n // channel's MCP (e.g. `telegram.reply`) get auto-denied. Pre-fix this\n // required an operator to manually run `tmux kill-session`. See ENG-4807\n // for the full diagnosis from Vigil's Telegram setup. The MCP-toolkit\n // hot-reload at line ~2926 already follows the same shape; this mirrors\n // it for channels.\n //\n // The decision logic lives in a pure helper so it's unit-testable\n // without standing up the rest of processAgent.\n //\n // CodeRabbit on PR #773 (round 2): suppress restart when credential\n // sync didn't converge — restarting on broken state would just reload\n // the session with stale flags that match the pre-failure state, then\n // the next tick's successful retry sees no diff (because previousChannelIds\n // is still the pre-change set) and never restarts again. Wait for\n // sync to land first.\n const restartDecision = channelConfigConverged\n ? decideChannelRestart({\n previousChannelIds,\n currentChannelIds,\n sessionMode: (refreshData.agent as Record<string, unknown>).session_mode as string | undefined,\n framework: agentFrameworkCache.get(agent.code_name) ?? 'openclaw',\n sessionHealthy: isSessionHealthy(agent.code_name),\n })\n : { restart: false, added: [], removed: [] };\n if (restartDecision.restart) {\n const reasonParts: string[] = [];\n if (restartDecision.added.length > 0) reasonParts.push(`added=${restartDecision.added.join(',')}`);\n if (restartDecision.removed.length > 0) reasonParts.push(`removed=${restartDecision.removed.join(',')}`);\n const reason = reasonParts.join(' ');\n log(`[hot-reload] Channel set changed for '${agent.code_name}' (${reason}) — restarting session`);\n\n const restartNotice = restartDecision.added.length > 0\n ? `New channels have been wired up (${restartDecision.added.join(', ')}). Note: channels require a session restart to attach their MCP servers as channel listeners. Your manager will restart your session shortly.`\n : `Channels were removed (${restartDecision.removed.join(', ')}). Your manager will restart your session shortly so the launch flags drop those channels.`;\n const delivered = await injectMessage(agent.code_name, 'system', restartNotice, { task_name: 'channel-update' }, log).catch(() => false);\n const delay = delivered ? 8_000 : 3_000;\n if (!delivered) {\n log(`[hot-reload] Inject notification unconfirmed for '${agent.code_name}' — proceeding with shorter delay`);\n }\n scheduleSessionRestart(agent.code_name, delay, 'new channel set');\n }\n\n // 5b. Always provision direct-chat channel for persistent sessions.\n // This runs as a standalone MCP channel server and pushes webapp direct\n // chat messages into the live session.\n //\n // Must live in project-scope .mcp.json (not a separate file). Claude Code's\n // channel name matcher (yG5 in 2.1.114) only resolves server:<name> against\n // MCP servers in enterprise/user/project/local scopes — --mcp-config loads\n // as session scope and is invisible to the lookup, which is why the separate\n // .mcp-channels.json approach always failed with \"no MCP server configured\".\n //\n // Write to the provision-dir .mcp.json (source of truth) then copy to the\n // project dir, so subsequent artifact syncs don't wipe the entry.\n const agentSessionMode = (refreshData.agent as Record<string, unknown>).session_mode as string | undefined;\n if (agentSessionMode === 'persistent' && (agentFrameworkCache.get(agent.code_name) ?? 'openclaw') === 'claude-code') {\n try {\n // ENG-4418: provision dir now lives at ~/.augmented/<codeName>/provision/\n // (formerly split between <codeName>/provision/ and <codeName>/claudecode/provision/).\n // Reuse the refresh-resolved agentDir so this always matches the\n // authoritative framework (not the cached one from /host/agents).\n const agentProvisionDir = agentDir;\n const projectDir = join(homedir(), '.augmented', agent.code_name, 'project');\n mkdirSync(agentProvisionDir, { recursive: true });\n mkdirSync(projectDir, { recursive: true });\n const provisionMcpPath = join(agentProvisionDir, '.mcp.json');\n const projectMcpPath = join(projectDir, '.mcp.json');\n\n let mcpConfig: { mcpServers: Record<string, unknown> } = { mcpServers: {} };\n try {\n mcpConfig = JSON.parse(readFileSync(provisionMcpPath, 'utf-8'));\n if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};\n } catch { /* new file */ }\n\n const localDirectChatChannel = join(homedir(), '.augmented', '_mcp', 'direct-chat-channel.js');\n if (existsSync(localDirectChatChannel) && !mcpConfig.mcpServers['direct-chat']) {\n mcpConfig.mcpServers['direct-chat'] = {\n command: 'node',\n args: [localDirectChatChannel],\n env: {\n AGT_HOST: requireHost(),\n AGT_API_KEY: getApiKey() ?? '',\n AGT_AGENT_ID: agent.agent_id,\n },\n };\n const serialized = JSON.stringify(mcpConfig, null, 2);\n writeFileSync(provisionMcpPath, serialized);\n writeFileSync(projectMcpPath, serialized);\n log(`Channel credentials written for '${agent.code_name}/direct-chat'`);\n }\n\n // Remove stale .mcp-channels.json from the pre-fix layout.\n const staleChannelsPath = join(projectDir, '.mcp-channels.json');\n if (existsSync(staleChannelsPath)) {\n try { rmSync(staleChannelsPath, { force: true }); } catch { /* non-fatal */ }\n }\n } catch (err) {\n log(`Failed to provision direct-chat channel for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n\n // 6. Fetch and write auth profiles (secrets)\n let lastSecretsProvisionAt = state.agents.find((a) => a.agentId === agent.agent_id)?.lastSecretsProvisionAt ?? null;\n let secretsHash = knownSecretsHashes.get(agent.agent_id) ?? null;\n\n try {\n const secretsData = await api.post<{\n profiles: Array<{\n provider: string;\n profile_name: string;\n auth_type: string;\n api_key?: string;\n metadata: Record<string, unknown>;\n }>;\n secrets_hash: string | null;\n }>('/host/secrets', { agent_id: agent.agent_id });\n\n const newHash = secretsData.secrets_hash;\n const hashChanged = newHash !== secretsHash;\n\n if (hashChanged && secretsData.profiles.length > 0) {\n frameworkAdapter.writeAuthProfiles(agent.code_name, secretsData.profiles);\n lastSecretsProvisionAt = new Date().toISOString();\n log(`Secrets updated for '${agent.code_name}'`);\n }\n\n if (newHash) {\n knownSecretsHashes.set(agent.agent_id, newHash);\n secretsHash = newHash;\n } else {\n knownSecretsHashes.delete(agent.agent_id);\n secretsHash = null;\n }\n } catch (err) {\n // Non-fatal — secrets are supplementary to provisioning\n log(`Secrets fetch failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n\n // 6b. Integration provisioning — fetch integrations, resolve satisfied capabilities,\n // install skills + CLI tools\n try {\n const integrationsData = await api.post<{\n integrations: Array<{\n id: string;\n definition_id: string;\n credentials: Record<string, unknown>;\n config: Record<string, unknown>;\n capabilities: Array<{ id: string; name: string; description: string; access: string }>;\n auth_type: string;\n display_name: string;\n scope: string;\n }>;\n }>('/host/agent-integrations', { agent_id: agent.agent_id });\n\n const integrations = integrationsData.integrations ?? [];\n\n // 6b-i. Refresh OAuth tokens nearing expiry (< 10 min remaining)\n for (const integration of integrations) {\n if (integration.auth_type !== 'oauth2') continue;\n const expiresAt = integration.credentials?.token_expires_at as string | undefined;\n const refreshToken = integration.credentials?.refresh_token as string | undefined;\n if (!expiresAt || !refreshToken) continue;\n\n const msRemaining = new Date(expiresAt).getTime() - Date.now();\n if (msRemaining > 10 * 60 * 1000) continue; // Still valid for > 10 min\n\n try {\n const integrationId = integration.id;\n if (!integrationId) continue;\n const refreshResult = await api.post<{ ok: boolean; expires_at?: string; access_token?: string }>(\n `/integrations/oauth/${integrationId}/refresh`,\n {},\n );\n if (refreshResult.ok) {\n // Update in-memory credentials so writeIntegrations() uses the new token\n integration.credentials.token_expires_at = refreshResult.expires_at;\n if (refreshResult.access_token) {\n integration.credentials.access_token = refreshResult.access_token;\n }\n log(`OAuth token refreshed for '${agent.code_name}/${integration.definition_id}'`);\n\n // Write live token file as fallback for agents not yet using the\n // token_refresh MCP tool. Agents with the plugin can call token.refresh\n // on demand instead of relying on this file-based handoff.\n if (frameworkAdapter.writeTokenFile) {\n frameworkAdapter.writeTokenFile(agent.code_name, integrations as Parameters<typeof frameworkAdapter.writeTokenFile>[1]);\n }\n }\n } catch (err) {\n log(`OAuth token refresh failed for '${agent.code_name}/${integration.definition_id}': ${(err as Error).message}`);\n }\n }\n\n // 6b-ii. Integration credentials — env vars + CLAUDE.md snippets. Gated on\n // an integration hash that covers `definition_id + credentials + config`,\n // so config-only DB changes (e.g. switching Xero's `xero_tenant_id`\n // without rotating the access token) propagate to .env.integrations and\n // trigger the env-var-aware MCP child reap below. ENG-4911.\n if (integrations.length > 0) {\n const intHash = computeIntegrationsHash(integrations);\n const prevIntHash = knownIntegrationHashes.get(agent.agent_id);\n\n if (intHash !== prevIntHash) {\n // ENG-4832 (CodeRabbit on PR #797 round 3): capture the\n // pre-write .env.integrations BEFORE writeIntegrations runs,\n // so we can diff against the post-write content and pass the\n // PRECISELY-rotated subset to findMcpServersUsingVars.\n // Without the diff, changedVars would be \"every var currently\n // present in the file\" and we'd over-reap MCP children whose\n // env didn't actually change. read returns undefined when the\n // file doesn't exist yet (first-ever integrations write for\n // this agent) — diffEnvIntegrations treats that as \"every\n // post-write var is new\", which is the correct first-write\n // behaviour: there's no prior state to preserve, so any new\n // vars warrant a reap.\n const projectDir = join(homedir(), '.augmented', agent.code_name, 'project');\n const envIntPath = join(projectDir, '.env.integrations');\n let preWriteEnv: string | undefined;\n try {\n preWriteEnv = readFileSync(envIntPath, 'utf-8');\n } catch {\n // No prior file — treat as empty so the diff shows all\n // post-write vars as added. fine.\n preWriteEnv = undefined;\n }\n\n if (frameworkAdapter.writeIntegrations) {\n // ENG-4920: thread agent.agent_id through so the claudecode\n // adapter can wire AGT_AGENT_ID + AGT_INTEGRATION_ID into the\n // Xero MCP server's spawn env. Older adapters (openclaw,\n // nemoclaw) ignore the third argument.\n frameworkAdapter.writeIntegrations(agent.code_name, integrations as Parameters<typeof frameworkAdapter.writeIntegrations>[1], agent.agent_id);\n }\n log(`Integrations provisioned for '${agent.code_name}' (${integrations.length} integration(s))`);\n\n const fw = agentFrameworkCache.get(agent.code_name) ?? 'openclaw';\n\n // Rotated env vars only reach MCP children that get RE-spawned.\n // The pre-existing children hold their spawn-time env, so the\n // access_token they're calling Xero/Gmail with is whatever was\n // in the env block when claude first launched them. Once that\n // token's 30-min TTL ticks past, every upstream call 401s and\n // the agent reports \"refresh failed\" (Stirling, 3-day blackout).\n // Identify which MCP server keys reference any of the\n // just-rotated env vars and SIGTERM their children so Claude\n // Code's MCP transport detects the dead child and respawns it\n // on next request — fresh env, fresh token, conversation\n // context preserved.\n //\n // CodeRabbit on PR #797 round 4: defer the\n // knownIntegrationHashes.set() update until the reap pipeline\n // completes. If the read/parse/reap path throws, we leave the\n // hash unchanged so the NEXT manager tick retries the rotation\n // end-to-end. Without this defer, a single transient failure\n // (file briefly missing, ps temporarily unavailable, JSON\n // parse error mid-write) would mark the rotation handled and\n // leave the stale MCP child to keep returning 401s\n // indefinitely — exactly the silent failure mode this whole\n // PR exists to eliminate.\n let rotationHandled = true;\n\n if (fw === 'claude-code' && isSessionHealthy(agent.code_name)) {\n try {\n const projectMcpPath = join(projectDir, '.mcp.json');\n const postWriteEnv = readFileSync(envIntPath, 'utf-8');\n const mcpContent = readFileSync(projectMcpPath, 'utf-8');\n // Diff pre-write vs post-write to get ONLY the rotated\n // var names. Without this we'd reap every MCP whose env\n // touches any var in the file — interrupting unrelated\n // tool traffic on every refresh tick.\n const changedVars = diffEnvIntegrations(preWriteEnv, postWriteEnv);\n const mcpJsonForReap = JSON.parse(mcpContent) as Parameters<typeof findMcpServersUsingVars>[0];\n const affectedServerKeys = findMcpServersUsingVars(mcpJsonForReap, changedVars);\n\n if (affectedServerKeys.length > 0) {\n reapStaleMcpChildren({\n log,\n codeName: agent.code_name,\n serverKeys: affectedServerKeys,\n mcpJson: mcpJsonForReap,\n });\n }\n\n const names = integrations.map((i) => i.display_name || i.definition_id).join(', ');\n // Accurate now that the reaper above forces the affected\n // MCP children to respawn with fresh env. There's a brief\n // window where the next call to one of those tools lands\n // during the respawn; the MCP client's retry policy\n // handles that.\n const reapNote = affectedServerKeys.length > 0\n ? ` The MCP servers that depend on rotating credentials (${affectedServerKeys.join(', ')}) have been signalled to reconnect.`\n : '';\n injectMessage(agent.code_name, 'system', `Your integrations have been refreshed. You have access to: ${names}.${reapNote}`, {\n task_name: 'integration-update',\n }, log).catch(() => {});\n log(`[hot-reload] Notified '${agent.code_name}' about integration update: ${names} (reaped ${affectedServerKeys.length} stale MCP server(s))`);\n } catch (err) {\n // Don't mark handled — the next tick will reattempt the\n // full read/parse/reap pipeline. Idempotent\n // writeIntegrations means the second call is a no-op\n // write; the only practical cost is one extra\n // \"Integrations provisioned\" log line per retry, which is\n // the right operator signal that something's wrong.\n rotationHandled = false;\n log(`[hot-reload] Failed to compute / reap affected MCP servers for '${agent.code_name}': ${(err as Error).message} — will retry next tick`);\n }\n }\n\n if (rotationHandled) {\n knownIntegrationHashes.set(agent.agent_id, intHash);\n }\n\n // Env vars changed — gateway needs a restart for openclaw hosts.\n needsGatewayRestart = true;\n }\n }\n\n // 6b-iii. Managed-toolkit MCP server convergence. Runs every poll (not\n // gated on integration credential changes) so the `.mcp.json` desired\n // state self-heals when an earlier write was skipped or lost — plugins\n // added to an agent now always propagate, not only on the exact poll\n // where credentials happened to change. ENG-4481.\n //\n // Idempotency: we hash the desired-state set (server_id → url + header\n // keys) and only re-write .mcp.json (and trigger the restart-session\n // notification) when that hash changes. `writeMcpServer` itself is\n // idempotent via canonical-json compare, so a redundant call with the\n // same config is a cheap no-op.\n const fwForMcp = agentFrameworkCache.get(agent.code_name) ?? 'openclaw';\n if (frameworkAdapter.writeMcpServer) {\n try {\n const toolkitData = await api.post<{\n toolkits: Array<{ agent_id: string; toolkit_id: string; toolkit_name: string; proxy_url: string }>;\n }>('/host/managed-toolkits', { agent_ids: [agent.agent_id] });\n\n const agentToolkits = (toolkitData.toolkits ?? []).filter((tk) => tk.agent_id === agent.agent_id);\n const expectedServerIds = new Set<string>();\n const desiredEntries: Array<{ serverId: string; url: string; headers?: Record<string, string>; name: string }> = [];\n for (const tk of agentToolkits) {\n const serverId = tk.toolkit_id.replace(/[^a-z0-9]/gi, '_').toLowerCase();\n expectedServerIds.add(serverId);\n const mcpUrl = (tk as Record<string, unknown>).mcp_url as string | undefined;\n const mcpHeaders = (tk as Record<string, unknown>).mcp_headers as Record<string, string> | undefined;\n const proxyUrl = tk.proxy_url.startsWith('/') ? `${requireHost()}${tk.proxy_url}` : tk.proxy_url;\n const url = mcpUrl ?? proxyUrl;\n desiredEntries.push({ serverId, url, headers: mcpHeaders, name: tk.toolkit_name });\n }\n\n // Desired-state hash — stable ordering (sort by serverId), url + full\n // headers (keys AND values). Header values must participate so that a\n // provider rotating a credential-bearing header triggers a rewrite;\n // hashing only keys would silently leave `.mcp.json` on stale auth\n // until an unrelated toolkit change bumped the hash.\n const hashBasis = desiredEntries\n .slice()\n .sort((a, b) => a.serverId.localeCompare(b.serverId))\n .map((e) => {\n const headersHash = createHash('sha256')\n .update(canonicalJson(e.headers ?? {}))\n .digest('hex')\n .slice(0, 16);\n return `${e.serverId}|${e.url}|${headersHash}`;\n })\n .join('\\n');\n const mcpHash = createHash('sha256').update(hashBasis).digest('hex').slice(0, 16);\n const prevMcpHash = knownManagedMcpHashes.get(agent.agent_id);\n\n if (mcpHash !== prevMcpHash) {\n for (const e of desiredEntries) {\n frameworkAdapter.writeMcpServer(agent.code_name, e.serverId, { url: e.url, headers: e.headers });\n // Hash the URL — `e.url` can be a provider-issued direct MCP URL\n // that carries signed query tokens. manager.log is persisted to\n // disk, so don't let raw creds land there.\n const urlHash = createHash('sha256').update(e.url).digest('hex').slice(0, 12);\n log(`[managed-toolkit] ${agent.code_name}: wrote '${e.name}' (serverId=${e.serverId}, url_hash=${urlHash})`);\n }\n\n // Prune stale managed MCP servers — skip entirely for adapters that\n // don't expose a local MCP file (e.g. cloud-hosted frameworks). The\n // framework-specific path comes from `getMcpPath`; reconstructing\n // `join(getAgentDir, 'provision', '.mcp.json')` here would hardcode\n // the claude-code layout.\n if (frameworkAdapter.removeMcpServer && frameworkAdapter.getMcpPath) {\n const mcpPath = frameworkAdapter.getMcpPath(agent.code_name);\n if (mcpPath) {\n try {\n const { readFileSync } = await import('node:fs');\n const mcpConfig = JSON.parse(readFileSync(mcpPath, 'utf-8')) as { mcpServers?: Record<string, unknown> };\n if (mcpConfig.mcpServers) {\n const managedPrefixes = ['composio_', 'one_', 'pipedream_', 'nango_', 'paragon_',\n 'composio-', 'one-', 'pipedream-', 'nango-', 'paragon-'];\n for (const key of Object.keys(mcpConfig.mcpServers)) {\n if (managedPrefixes.some((p) => key.startsWith(p)) && !expectedServerIds.has(key)) {\n frameworkAdapter.removeMcpServer(agent.code_name, key);\n log(`[managed-toolkit] Removed stale MCP server '${key}' for '${agent.code_name}'`);\n }\n }\n }\n } catch {\n // MCP file doesn't exist yet — nothing to clean up\n }\n }\n }\n\n knownManagedMcpHashes.set(agent.agent_id, mcpHash);\n\n // Only notify about MCP server changes after the first poll — on\n // initial convergence the session is either not yet started or will\n // be started with the fresh .mcp.json, so a restart notification\n // would be a noisy false alarm. Full-removal case is still a valid\n // change (we need to restart so the session drops the tools it had\n // loaded), so don't gate on `agentToolkits.length > 0`.\n if (prevMcpHash !== undefined && fwForMcp === 'claude-code' && isSessionHealthy(agent.code_name)) {\n const mcpNames = agentToolkits.map((tk) => tk.toolkit_name).join(', ') || 'none (all removed)';\n log(`[hot-reload] MCP servers changed for '${agent.code_name}': ${mcpNames} — restarting session`);\n\n const restartNotice = agentToolkits.length > 0\n ? `New MCP tool servers have been configured: ${mcpNames}. Note: MCP servers require a session restart to connect. Your manager will restart your session shortly.`\n : 'Managed MCP tool servers were removed. Your manager will restart your session shortly so the session drops those tools.';\n const delivered = await injectMessage(agent.code_name, 'system',\n restartNotice,\n { task_name: 'mcp-update' }, log).catch(() => false);\n\n const delay = delivered ? 8_000 : 3_000;\n if (!delivered) {\n log(`[hot-reload] Inject notification unconfirmed for '${agent.code_name}' — proceeding with shorter delay`);\n }\n // ENG-4807 / CodeRabbit on PR #773: route through the\n // coalescing scheduler so a same-tick channel-set change\n // doesn't double-restart the agent.\n scheduleSessionRestart(agent.code_name, delay, 'new MCP servers');\n }\n }\n } catch (err) {\n log(`[managed-toolkit] Failed to provision for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n\n // ENG-4337: QMD CLI install + collection setup are now handled by the QMD\n // plugin (slug 'qmd') via its on_install hook. See refreshData.plugin_install_hooks\n // below — the manager runs the hook script `qmd collection add` after deploying\n // plugin skills. The QMD plugin is auto-installed for any agent that has the\n // QMD integration active (see migration 20260408000002_qmd_native_plugin.sql).\n } catch (err) {\n // Non-fatal — integration provisioning is supplementary\n log(`Integration provisioning failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n\n // 7. Gateway lifecycle — ensure running for active agents with channels\n let gatewayPort: number | null = null;\n let gatewayPid: number | null = null;\n let gatewayRunning = false;\n\n if (agent.status === 'active' && hasChannelConfigs) {\n const gwStatus = await ensureGatewayRunning(agent.code_name, frameworkAdapter);\n gatewayPort = gwStatus.port;\n gatewayPid = gwStatus.pid;\n gatewayRunning = gwStatus.running;\n } else if (agent.status === 'paused') {\n // Stop gateway for paused agents\n await stopGatewayIfRunning(agent.code_name, frameworkAdapter);\n }\n\n // Note: gateway restart after integration changes removed — was killing gateways.\n // New env vars take effect on next gateway restart (manual or via manager cycle).\n\n // 7b. Auto-provision org/team default schedules\n let tasks = refreshData.scheduled_tasks ?? [];\n const existingTemplateIds = new Set(tasks.map((t) => t.template_id));\n\n try {\n const defaultsData = await api.post<{\n schedules: Array<{\n template_id: string;\n name: string;\n schedule_expr: string;\n prompt: string;\n session_target?: string;\n enabled?: boolean;\n }>;\n team_id: string;\n timezone: string;\n }>('/host/resolve-default-schedules', { agent_id: agent.agent_id });\n\n const missing = (defaultsData.schedules ?? []).filter(\n (s) => !existingTemplateIds.has(s.template_id),\n );\n\n // Always call ensure-default-schedules to insert missing tasks and\n // fix delivery_mode on existing tasks that don't match the template default.\n const allSchedules = defaultsData.schedules ?? [];\n if (allSchedules.length > 0) {\n await api.post('/host/ensure-default-schedules', {\n agent_id: agent.agent_id,\n team_id: defaultsData.team_id,\n timezone: defaultsData.timezone,\n schedules: allSchedules,\n });\n if (missing.length > 0) {\n log(`Auto-provisioned ${missing.length} default schedule(s) for '${agent.code_name}': ${missing.map((s) => s.template_id).join(', ')}`);\n }\n\n // Re-fetch tasks since schedules may have changed\n const freshData = await api.post<{ scheduled_tasks?: typeof tasks }>('/host/refresh', { agent_id: agent.agent_id });\n tasks = freshData.scheduled_tasks ?? tasks;\n }\n } catch (err) {\n log(`Default schedule provisioning failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n\n // 7c. Auto-provision Augmented plugin and kanban skill for all active agents\n if (agent.status === 'active') {\n // Augmented OpenClaw plugin — provides kanban_list, kanban_add, kanban_move,\n // kanban_update, kanban_done, status_standup, status_update tools natively\n if (frameworkAdapter.installPlugin) {\n try {\n const pluginPath = join(process.cwd(), 'packages', 'openclaw-plugin-augmented', 'src', 'index.ts');\n if (existsSync(pluginPath)) {\n frameworkAdapter.installPlugin(agent.code_name, 'augmented', pluginPath, {\n agtHost: requireHost(),\n agtApiKey: getApiKey() ?? undefined,\n agentId: agent.agent_id,\n });\n }\n } catch (err) {\n log(`Augmented plugin install failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n\n // Kanban skill files\n if (frameworkAdapter.installSkillFiles) {\n try {\n const skillContent = getBuiltInSkillContent('kanban');\n if (skillContent) {\n frameworkAdapter.installSkillFiles(agent.code_name, 'kanban', skillContent);\n }\n } catch (err) {\n log(`Kanban skill install failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n\n // Integration skills — deploy scope-filtered skills and clean up orphans\n if (frameworkAdapter.installSkillFiles) {\n const currentIntegrationSkillIds = new Set<string>();\n const installedIntegrationSkills: string[] = [];\n const { createHash } = await import('node:crypto');\n\n // ENG-4341: Index resolved integration contexts by slug for fast\n // lookup when rendering each skill. This map is small (one entry\n // per installed integration) and built once per refresh.\n // ENG-4629: prefer the new `integration_*` wire keys; fall back to\n // the legacy `plugin_*` shape so the CLI keeps working against\n // older API deploys mid-rollout. Each row also carries both\n // `integration_slug` and `plugin_slug` aliases.\n const refreshAny = refreshData as unknown as {\n integration_contexts?: Array<{ integration_slug?: string; plugin_slug?: string; values?: Record<string, unknown>; overrides?: string }>;\n plugin_contexts?: Array<{ plugin_slug?: string; integration_slug?: string; values?: Record<string, unknown>; overrides?: string }>;\n integration_skills?: unknown[];\n plugin_skills?: unknown[];\n integration_install_hooks?: Array<{ integration_slug?: string; plugin_slug?: string; script: string }>;\n plugin_install_hooks?: Array<{ plugin_slug?: string; integration_slug?: string; script: string }>;\n integration_toolkits?: Array<{ integration_slug?: string; plugin_slug?: string; toolkits: string[] }>;\n plugin_toolkits?: Array<{ plugin_slug?: string; integration_slug?: string; toolkits: string[] }>;\n };\n const contexts = refreshAny.integration_contexts ?? refreshAny.plugin_contexts ?? [];\n const contextBySlug = new Map<string, { values: Record<string, unknown>; overrides: string }>();\n for (const ctx of contexts) {\n const slug = ctx.integration_slug ?? ctx.plugin_slug;\n if (!slug) continue;\n contextBySlug.set(slug, { values: ctx.values ?? {}, overrides: (ctx.overrides ?? '').trim() });\n }\n\n // ENG-4502: group scopes by plugin and write a single plugin folder per\n // plugin. Claude Code's skill discovery does not recurse, so each\n // plugin's scopes go side-by-side under `plugin-<slug>/scopes/*.md`\n // while `plugin-<slug>/SKILL.md` is the umbrella index Claude actually\n // sees. Previous per-scope top-level folders (`plugin-<slug>-<id>/`)\n // are garbage-collected below alongside the orphan sweep.\n // ENG-4629: prefer integration_skills, fall back to plugin_skills.\n const integrationGroups = groupSkillsByIntegration(\n (refreshAny.integration_skills ?? refreshAny.plugin_skills ?? []) as IntegrationSkillInput[],\n );\n for (const [integrationSlug, scopes] of integrationGroups) {\n try {\n // ENG-4554: write under `integration-<slug>/` to align with the\n // post-cutover vocabulary. The orphan-sweep below recognises both\n // prefixes for one release so existing `plugin-<slug>/` folders\n // get cleaned up the first time the new manager runs.\n const integrationSkillId = `integration-${integrationSlug}`.replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');\n currentIntegrationSkillIds.add(integrationSkillId);\n\n // ENG-4341: substitute {{context.<field>}} placeholders and\n // append the freeform overrides as \"## Team Overrides\".\n const ctx = contextBySlug.get(integrationSlug);\n const renderedScopes: IntegrationSkillInput[] = scopes.map((s) => ({\n plugin_slug: s.plugin_slug,\n skill_id: s.skill_id,\n skill_name: s.skill_name,\n content: renderIntegrationSkillContent(\n s.content,\n ctx?.values ?? {},\n ctx?.overrides ?? '',\n (warning) => log(`[plugin-context] ${s.plugin_slug}/${s.skill_id}: ${warning}`),\n ),\n }));\n\n const bundle = buildIntegrationBundle(renderedScopes);\n\n // Hash the whole bundle so any scope addition / removal / content\n // tweak triggers a re-install. Unchanged bundles skip disk I/O.\n const contentHash = createHash('sha256').update(bundleFingerprint(bundle.files)).digest('hex').slice(0, 12);\n const hashKey = `plugin-skill:${agent.agent_id}:${integrationSkillId}`;\n if (knownSkillHashes.get(hashKey) === contentHash) continue;\n\n frameworkAdapter.installSkillFiles(agent.code_name, integrationSkillId, bundle.files);\n knownSkillHashes.set(hashKey, contentHash);\n for (const s of scopes) installedIntegrationSkills.push(s.skill_name);\n log(`Installed integration skill bundle '${integrationSkillId}' for '${agent.code_name}' (${scopes.length} scope(s))`);\n } catch (err) {\n log(`Integration skill install failed for '${agent.code_name}' / '${integrationSlug}': ${(err as Error).message}`);\n }\n }\n\n // Clean up orphaned plugin skills (removed plugins) and sweep the\n // pre-ENG-4502 flat layout (`plugin-<slug>-<skill_id>/`). After the\n // switch to one folder per plugin, those per-scope folders become\n // stale duplicates — leaving them confuses Claude's skill list.\n try {\n const { readdirSync, rmSync } = await import('node:fs');\n const { homedir } = await import('node:os');\n\n // All on-disk directories that installSkillFiles() may have\n // written into across the two frameworks. installSkillFiles for\n // Claude Code writes to the agent dir's `skills/` AND the project\n // dir's `.claude/skills/`; OpenClaw writes to `~/.openclaw-<cn>/\n // skills/`. The provision/.claude/skills path isn't an install\n // target but is swept defensively in case older manager versions\n // stashed copies there.\n const frameworkId = frameworkAdapter.id;\n const candidateSkillDirs: string[] = [\n // Claude Code — framework runtime tree\n join(homedir(), '.augmented', agent.code_name, 'skills'),\n // Claude Code — project tree\n join(homedir(), '.augmented', agent.code_name, 'project', '.claude', 'skills'),\n // OpenClaw — framework runtime tree\n join(homedir(), `.openclaw-${agent.code_name}`, 'skills'),\n // Defensive: legacy provision-side path, not currently an\n // install target but cheap to sweep.\n join(agentDir, '.claude', 'skills'),\n ];\n\n const existingDirs = candidateSkillDirs.filter((d) => existsSync(d));\n\n // Union of platform-managed entries across every existing skill\n // dir — gives a complete view of what might need cleaning even if\n // the agent has since switched framework or only some paths got\n // written during the transition.\n //\n // ENG-4554: accept BOTH the legacy `plugin-` and the current\n // `integration-` prefix. Old `plugin-<slug>/` folders aren't in\n // currentIntegrationSkillIds (which now holds `integration-<slug>`),\n // so they fall through to the orphan branch below and get removed\n // — that's the migration. The two-prefix logic stays for one\n // release; once everyone's on a manager that writes\n // `integration-`, the `plugin-` half can drop.\n const discoveredEntries = new Set<string>();\n for (const dir of existingDirs) {\n try {\n for (const entry of readdirSync(dir)) {\n if (entry.startsWith('plugin-') || entry.startsWith('integration-')) {\n discoveredEntries.add(entry);\n }\n }\n } catch { /* non-fatal per dir */ }\n }\n\n const removeSkillFolder = (entry: string, reason: string): void => {\n for (const dir of existingDirs) {\n const p = join(dir, entry);\n if (existsSync(p)) {\n rmSync(p, { recursive: true, force: true });\n }\n }\n log(`Removed ${reason} '${entry}' for '${agent.code_name}' (framework=${frameworkId})`);\n };\n\n for (const entry of discoveredEntries) {\n // An entry that isn't in currentIntegrationSkillIds is one of:\n // - a removed integration (uninstalled)\n // - a stale pre-ENG-4502 per-scope folder\n // (`plugin-<slug>-<skill_id>`)\n // - an ENG-4554 legacy `plugin-<slug>/` superseded by the new\n // `integration-<slug>/` written this poll\n // All three are right to delete.\n if (!currentIntegrationSkillIds.has(entry)) {\n removeSkillFolder(entry, 'orphaned skill folder');\n }\n }\n } catch (err) {\n log(`Integration skill cleanup failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n\n // Refresh the \"Available Skills\" index in CLAUDE.md by scanning installed skills\n try {\n const agentFwForIndex = agentFrameworkCache.get(agent.code_name) ?? 'openclaw';\n if (agentFwForIndex === 'claude-code') {\n await refreshSkillsIndexInClaudeMd(config.configDir, agent.code_name, log);\n }\n } catch (err) {\n log(`Skills index refresh failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n\n // Run integration on_install hooks (idempotent — runs every refresh).\n // Hooks are deduplicated by content hash so we only re-run on script changes.\n // ENG-4629: prefer integration_install_hooks; fall back to plugin_install_hooks.\n const installHooks = refreshAny.integration_install_hooks ?? refreshAny.plugin_install_hooks ?? [];\n if (frameworkAdapter.executePluginHook && installHooks.length) {\n for (const hook of installHooks) {\n const slug = hook.integration_slug ?? hook.plugin_slug;\n if (!slug) continue;\n try {\n const scriptHash = createHash('sha256').update(hook.script).digest('hex').slice(0, 12);\n const hookKey = `${agent.agent_id}:${frameworkAdapter.id}:plugin-hook:${slug}:on_install`;\n if (knownSkillHashes.get(hookKey) === scriptHash) continue;\n\n const result = await frameworkAdapter.executePluginHook({\n codeName: agent.code_name,\n pluginSlug: slug,\n hookName: 'on_install',\n script: hook.script,\n });\n\n if (result.exitCode === 0) {\n knownSkillHashes.set(hookKey, scriptHash);\n log(`Integration hook on_install '${slug}' succeeded for '${agent.code_name}' (${result.durationMs}ms)`);\n } else if (result.timedOut) {\n log(`Integration hook on_install '${slug}' TIMED OUT for '${agent.code_name}' after ${result.durationMs}ms`);\n } else {\n // Don't log raw stderr — hooks inherit manager env which may include secrets.\n // Log a fingerprint instead so failures are identifiable without leaking content.\n const stderrHash = createHash('sha256').update(result.stderr).digest('hex').slice(0, 12);\n // ENG-4510: on exit 127, bash prints `bash: <cmd>: command not found`\n // before any user code runs. extractCommandNotFound parses + validates\n // that line, but we still log only a hash — the project guideline is\n // hash-only logging by default, and operators can correlate the hash\n // with `pnpm install <pkg>` failures or recompute it locally with a\n // suspected binary name when triaging.\n const missingCmd = result.exitCode === 127 ? extractCommandNotFound(result.stderr) : null;\n const missingCmdHash = missingCmd ? createHash('sha256').update(missingCmd).digest('hex').slice(0, 8) : null;\n log(\n `Integration hook on_install '${slug}' exited ${result.exitCode} for '${agent.code_name}' ` +\n (missingCmdHash ? `[missing_command_hash=${missingCmdHash}] ` : '') +\n `[stderr_hash=${stderrHash} stderr_len=${result.stderr.length}]`,\n );\n }\n } catch (err) {\n log(`Integration hook on_install failed for '${agent.code_name}' / '${slug}': ${(err as Error).message}`);\n }\n }\n }\n\n // ENG-4421: install CLIs for toolkits declared by the agent's plugins.\n // Union across plugins, dedup per process via toolkitCliEnsured. Runs\n // only for claude-code hosts — openclaw toolkit install lives in its\n // own framework path and isn't wired yet.\n const agentFwForToolkits = agentFrameworkCache.get(agent.code_name) ?? 'openclaw';\n // ENG-4629: prefer integration_toolkits; fall back to plugin_toolkits.\n const toolkitsList = refreshAny.integration_toolkits ?? refreshAny.plugin_toolkits ?? [];\n if (agentFwForToolkits === 'claude-code' && toolkitsList.length) {\n const toolkitUnion = new Set<string>();\n for (const { toolkits } of toolkitsList) {\n for (const t of toolkits) toolkitUnion.add(t);\n }\n for (const toolkitSlug of toolkitUnion) {\n await ensureToolkitCli(toolkitSlug);\n }\n }\n\n // Hot-reload: notify running Claude Code session about new integration skills\n const agentFw = agentFrameworkCache.get(agent.code_name) ?? 'openclaw';\n if (agentFw === 'claude-code' && installedIntegrationSkills.length > 0 && isSessionHealthy(agent.code_name)) {\n const names = installedIntegrationSkills.join(', ');\n injectMessage(agent.code_name, 'system',\n `New integration skills installed: ${names}. These are available immediately — Claude Code loads skills on demand from .claude/skills/.`,\n { task_name: 'plugin-skill-update' }, log).catch(() => {});\n log(`[hot-reload] Notified '${agent.code_name}' about new integration skills: ${names}`);\n }\n }\n }\n\n // 7d. Fetch kanban board for prompt injection into board-aware templates\n let boardItems: BoardItem[] = [];\n const hasBoardTemplates = tasks.some((t) => BOARD_INJECT_TEMPLATES.has(t.template_id));\n if (hasBoardTemplates) {\n try {\n const boardData = await api.post<{ items?: BoardItem[] }>('/host/my-kanban', { agent_id: agent.agent_id });\n boardItems = (boardData.items ?? []).map(sanitizeBoardItem);\n kanbanBoardCache.set(agent.code_name, boardItems);\n } catch {\n // Non-fatal — board may not exist yet\n boardItems = kanbanBoardCache.get(agent.code_name) ?? [];\n }\n }\n\n // Resolve framework once for sections 8-11\n const agentFw = agentFrameworkCache.get(agent.code_name) ?? 'openclaw';\n\n // 8. Sync scheduled tasks\n const sessionMode = (refreshData.agent as Record<string, unknown>).session_mode as string ?? 'oneshot';\n\n if (agentFw === 'claude-code' && sessionMode === 'persistent') {\n // Claude Code persistent mode: manage long-running session with channels\n await ensurePersistentSession(agent, tasks, boardItems, refreshData);\n // ENG-4897: detect structural .mcp.json drift since the session was\n // launched and schedule a respawn. Runs only on persistent claude-code\n // agents — the only path that uses --strict-mcp-config and therefore\n // pins the MCP server set at startup. Other framework/mode combos\n // either re-read .mcp.json on every spawn or don't use it.\n try {\n checkMcpConfigDriftAndScheduleRestart(agent.code_name, psGetProjectDir(agent.code_name));\n } catch (err) {\n log(`[hot-reload] .mcp.json drift check failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n } else if (agentFw === 'claude-code' && tasks.length > 0) {\n // Claude Code oneshot mode: fire-and-forget via claude -p\n await syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData);\n } else if (frameworkAdapter.syncScheduledTasks && gatewayRunning && gatewayPort) {\n // Hash ONLY stable task fields (without board-injected prompts) to decide\n // whether a re-sync is needed. Board context changes every poll cycle as\n // kanban items move around — hashing it caused syncScheduledTasks to fire\n // on every cycle, spawning duplicate openclaw-cron processes.\n const stableTasksHash = createHash('sha256')\n .update(JSON.stringify(tasks))\n .digest('hex')\n .slice(0, 16);\n\n // Board context hash — triggers a re-sync when the board meaningfully changes\n // (items added/removed, moved between columns, reprioritised, or renamed)\n const boardHash = boardItems.length > 0\n ? createHash('sha256')\n .update(JSON.stringify(boardItems.map((b) => ({ id: b.id, title: b.title, status: b.status, priority: b.priority, deliverable: b.deliverable }))))\n .digest('hex')\n .slice(0, 16)\n : 'empty';\n\n // Include resolved models in the hash so default changes trigger resync\n const resolvedModels = resolveModelChain(refreshData);\n const modelsHash = createHash('sha256')\n .update(JSON.stringify(resolvedModels))\n .digest('hex')\n .slice(0, 16);\n\n const combinedHash = `${stableTasksHash}:${boardHash}:${modelsHash}`;\n const prevTasksHash = knownTasksHashes.get(agent.agent_id);\n\n if (combinedHash !== prevTasksHash) {\n // Inject board context into prompts for board-aware templates\n const enrichedTasks = tasks.map((t) => {\n if (BOARD_INJECT_TEMPLATES.has(t.template_id) && boardItems.length > 0) {\n const template = PLAN_TEMPLATES.has(t.template_id) ? 'morning-plan' : 'follow-up';\n const boardPrefix = formatBoardForPrompt(boardItems, template);\n return { ...t, prompt: boardPrefix + t.prompt };\n }\n return t;\n });\n\n try {\n const token = readGatewayToken(agent.code_name);\n await frameworkAdapter.syncScheduledTasks(\n agent.code_name,\n enrichedTasks.map((t) => ({\n id: t.id,\n template_id: t.template_id,\n name: t.name,\n schedule_kind: t.schedule_kind as 'cron' | 'every' | 'at',\n schedule_expr: t.schedule_expr,\n schedule_every: t.schedule_every,\n schedule_at: t.schedule_at,\n timezone: t.timezone,\n prompt: t.prompt,\n session_target: t.session_target as 'main' | 'isolated',\n delivery_mode: t.delivery_mode as 'announce' | 'none',\n delivery_channel: t.delivery_channel,\n delivery_to: t.delivery_to,\n enabled: t.enabled,\n model_tier: ((t as Record<string, unknown>).model_tier as 'primary' | 'secondary' | 'tertiary' | undefined) ?? 'primary',\n })),\n gatewayPort,\n token,\n {\n models: resolvedModels,\n },\n );\n knownTasksHashes.set(agent.agent_id, combinedHash);\n log(`Scheduled tasks synced for '${agent.code_name}' (${enrichedTasks.length} task(s))`);\n } catch (err) {\n log(`Failed to sync scheduled tasks for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n }\n\n // Update task display info map for cron health alerts\n for (const t of tasks) {\n const jobName = `aug:${t.template_id}:${t.id ?? t.name.toLowerCase().replace(/\\s+/g, '-')}`;\n taskDisplayInfo.set(`${agent.code_name}:${jobName}`, {\n taskName: t.name,\n schedule: t.schedule_expr ?? t.schedule_every ?? '',\n agentDisplayName: agent.display_name,\n });\n }\n\n // 9. Harvest cron run results for standup/task updates (OpenClaw only — throttled to reduce process spawning)\n if (agentFw === 'openclaw' && gatewayRunning && gatewayPort && tasks.length > 0) {\n const lastHarvest = lastHarvestAt.get(agent.code_name) ?? 0;\n if (Date.now() - lastHarvest >= HARVEST_INTERVAL_MS) {\n lastHarvestAt.set(agent.code_name, Date.now());\n harvestCronResults(agent.code_name, tasks, gatewayPort).catch((err) => {\n log(`Cron result harvest failed for '${agent.code_name}': ${(err as Error).message}`);\n });\n }\n }\n\n // 10. Immediate work trigger — if webapp signalled a new task, fire kanban-work now\n {\n const triggerAt = (refreshData.agent as Record<string, unknown>).work_trigger_at as string | null;\n if (triggerAt) {\n const triggerTs = new Date(triggerAt).getTime();\n const lastTrigger = lastWorkTriggerAt.get(agent.code_name) ?? 0;\n if (triggerTs > lastTrigger) {\n lastWorkTriggerAt.set(agent.code_name, triggerTs);\n\n if (agentFw === 'openclaw' && gatewayRunning && gatewayPort) {\n // OpenClaw: find kanban-work job ID from cron jobs file\n const homeDir = process.env['HOME'] ?? '/tmp';\n const jobsPath = join(homeDir, `.openclaw-${agent.code_name}`, 'cron', 'jobs.json');\n if (existsSync(jobsPath)) {\n try {\n const jobsData = JSON.parse(readFileSync(jobsPath, 'utf-8'));\n const kanbanJob = (jobsData.jobs ?? []).find((j: Record<string, unknown>) =>\n typeof j.name === 'string' && j.name.includes('kanban-work'),\n );\n if (kanbanJob?.id) {\n const cliBin = resolveAgentFramework(agent.code_name).cliBinary ?? 'openclaw';\n log(`Work trigger: firing kanban-work for '${agent.code_name}'`);\n execFilePromise(cliBin, ['--profile', agent.code_name, 'cron', 'run', kanbanJob.id])\n .then(() => log(`Work trigger succeeded for '${agent.code_name}'`))\n .catch((err) => log(`Work trigger failed for '${agent.code_name}': ${(err as Error).message}`));\n }\n } catch { /* jobs.json read failed — non-fatal */ }\n }\n } else if (agentFw === 'claude-code') {\n // Claude Code: for persistent sessions, inject via tmux; for oneshot, fire claude -p\n if (sessionMode === 'persistent') {\n // Persistent mode: inject into tmux session if healthy, skip if still booting\n if (isSessionHealthy(agent.code_name)) {\n const todayItem = boardItems.find((b) => b.status === 'todo');\n const taskHint = todayItem ? ` Top item: \"${todayItem.title}\" (priority ${todayItem.priority}).` : '';\n injectMessage(agent.code_name, 'task', `New work triggered. Check your kanban board with kanban_list and pick up the next item.${taskHint}`, {\n task_name: 'kanban-work-trigger',\n }, log);\n log(`[persistent-session] Work trigger injected for '${agent.code_name}'`);\n } else {\n log(`[persistent-session] Work trigger skipped for '${agent.code_name}' — session not yet healthy`);\n }\n } else {\n fireClaudeWorkTrigger(agent.code_name, agent.agent_id, boardItems);\n }\n }\n }\n }\n }\n\n // 11. Clean up stale cron session files to prevent context overflow (OpenClaw only)\n if (agentFw === 'openclaw') {\n cleanupStaleSessions(agent.code_name);\n }\n\n // 12. Detect newly-done kanban items and send notifications (runs every cycle)\n {\n const agentId = codeNameToAgentId.get(agent.code_name);\n if (agentId) {\n try {\n const boardData = await api.post<{ items?: BoardItem[] }>('/host/my-kanban', { agent_id: agentId });\n const freshBoard = (boardData.items ?? []).map(sanitizeBoardItem);\n const freshDoneIds = new Set(freshBoard.filter((b) => b.status === 'done' || b.status === 'failed').map((b) => b.id));\n\n const previousDoneIds = notifyBoardCache.get(agent.code_name);\n notifyBoardCache.set(agent.code_name, freshDoneIds);\n\n // Skip first cycle — no previous state to compare against\n if (!previousDoneIds) {\n log(`[${agent.code_name}] Board diff: initial cache (${freshDoneIds.size} done items)`);\n } else {\n const newlyDone = freshBoard.filter(\n (b) => (b.status === 'done' || b.status === 'failed') && !previousDoneIds.has(b.id),\n );\n\n log(`[${agent.code_name}] Board diff: ${previousDoneIds.size} prev done → ${freshDoneIds.size} now, ${newlyDone.length} newly done`);\n\n if (newlyDone.length > 0) {\n const displayName = agentDisplayNames.get(agent.code_name) ?? agent.code_name;\n for (const item of newlyDone) {\n log(`Newly done: '${item.title}' notify_channel=${item.notify_channel ?? 'none'} notify_to=${item.notify_to ?? 'none'}`);\n if (item.notify_channel && item.notify_to) {\n const isFailed = item.status === 'failed';\n const resultLine = item.result ? `\\n${isFailed ? 'Reason' : 'Result'}: ${item.result}` : '';\n const emoji = isFailed ? '❌' : '✅';\n const message = `${emoji} ${isFailed ? 'Task Failed' : 'Task Complete'} — ${displayName}\\n${item.title}${resultLine}`;\n sendTaskNotification(agent.code_name, item.notify_channel, item.notify_to, message).catch((err) => {\n log(`sendTaskNotification failed for '${agent.code_name}': ${(err as Error).message}`);\n });\n }\n }\n }\n }\n // Check for stale in_progress items\n const staleItems = freshBoard.filter((b) => {\n if (b.status !== 'in_progress' || !b.updated_at) return false;\n const age = Date.now() - new Date(b.updated_at).getTime();\n return age > STALE_TASK_THRESHOLD_MS && !alertedStaleItems.has(`${agent.code_name}:${b.id}`);\n });\n\n if (staleItems.length > 0) {\n const displayName = agentDisplayNames.get(agent.code_name) ?? agent.code_name;\n for (const item of staleItems) {\n const age = Math.round((Date.now() - new Date(item.updated_at!).getTime()) / 60_000);\n log(`Stale task: '${item.title}' (id=${item.id}) in_progress for ${age}m — auto-failing for '${agent.code_name}'`);\n alertedStaleItems.add(`${agent.code_name}:${item.id}`);\n\n // Auto-fail the task via API (try ID first, fallback to title)\n try {\n const failResult = await api.post<{ ok?: boolean; updated?: number; error?: string }>('/host/kanban', {\n agent_id: agentId,\n update: [{ id: item.id, title: item.title, status: 'failed', result: `Timed out — in progress for ${age} minutes with no update` }],\n });\n log(`Auto-fail result for '${item.title}': updated=${failResult.updated}`);\n // Mark as done only when mutation actually succeeded,\n // so we don't suppress legitimate later notifications.\n if ((failResult.updated ?? 0) > 0 || failResult.ok === true) {\n freshDoneIds.add(item.id);\n }\n } catch (err) {\n log(`Auto-fail API error for '${item.title}': ${(err as Error).message}`);\n }\n\n // Notify\n const message = `⏳ Task Timed Out — ${displayName}\\n${item.title}\\nIn progress for ${age} minutes with no update — auto-failed`;\n if (item.notify_channel && item.notify_to) {\n sendTaskNotification(agent.code_name, item.notify_channel, item.notify_to, message).catch(() => {});\n }\n sendSlackWebhookMessage(`⏳ *Task Timed Out* — *${displayName}*\\n*${item.title}*\\nIn progress for ${age} minutes — auto-failed`).catch(() => {});\n }\n }\n\n // Clear stale alerts for items no longer in_progress (scoped to this agent)\n const prefix = `${agent.code_name}:`;\n for (const key of alertedStaleItems) {\n if (key.startsWith(prefix)) {\n const itemId = key.slice(prefix.length);\n if (!freshBoard.some((b) => b.id === itemId && b.status === 'in_progress')) {\n alertedStaleItems.delete(key);\n }\n }\n }\n } catch (err) {\n log(`Board diff failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n }\n\n // 13. Memory sync — upload local memory files to DB, download DB memories to local\n if (frameworkId === 'claude-code' && agent.status === 'active') {\n try {\n await syncMemories(agent, config.configDir, log);\n } catch (err) {\n log(`Memory sync failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n\n // ENG-4473: Re-capture writtenHashes AFTER all tracked-file writers have\n // finished this poll — channel credentials (step 5), the direct-chat\n // provisioner (step 5b), managed-provider proxies (step 7), etc. The\n // provisioner write loop at step 3 records `writtenHashes` before those\n // later writers run, so the drift check at the TOP of the next poll\n // sees the final file state vs the step-3 hash and mis-reports every\n // poll as drifted (even when the state is perfectly stable). Re-hashing\n // here converges writtenHashes to what's actually on disk after this\n // agent's full processing, so:\n // - genuine external edits between polls are still detected (drift\n // check runs BEFORE our writers in the next poll)\n // - our own multi-writer sequence stops reading as drift\n const trackedFiles = frameworkAdapter.driftTrackedFiles();\n if (trackedFiles.length > 0 && existsSync(agentDir)) {\n // Rebuild from scratch so files no longer tracked (or missing on disk)\n // don't leave stale hashes that would mis-report drift next poll.\n const hashes = new Map<string, string>();\n for (const file of trackedFiles) {\n const h = hashFile(join(agentDir, file));\n if (h) hashes.set(file, h);\n }\n writtenHashes.set(agent.agent_id, hashes);\n } else {\n writtenHashes.delete(agent.agent_id);\n }\n\n agentStates.push({\n agentId: agent.agent_id,\n codeName: agent.code_name,\n status: agent.status,\n charterVersion,\n toolsVersion,\n secretsHash,\n lastRefreshAt: now,\n lastProvisionAt,\n lastDriftCheckAt,\n lastSecretsProvisionAt,\n gatewayPort,\n gatewayPid,\n gatewayRunning,\n acpSessions: [],\n });\n}\n\n// ---------------------------------------------------------------------------\n// Session cleanup — prevent context overflow from accumulated cron sessions\n// ---------------------------------------------------------------------------\n\nconst CRON_SESSION_KEEP_COUNT = 10;\nconst CRON_RUN_RETENTION_DAYS = 7;\nconst lastCleanupAt = new Map<string, number>();\nconst CLEANUP_INTERVAL_MS = 60_000; // Run cleanup every 1 minute per agent\n\nfunction cleanupStaleSessions(codeName: string): void {\n // Run immediately on first poll (lastRun defaults to 0), then throttle\n const lastRun = lastCleanupAt.get(codeName) ?? 0;\n if (lastRun > 0 && Date.now() - lastRun < CLEANUP_INTERVAL_MS) return;\n lastCleanupAt.set(codeName, Date.now());\n\n const homeDir = process.env['HOME'] ?? '/tmp';\n\n // 1. Clean CRON session files only (preserve chat/Slack/Telegram sessions)\n for (const agentDir of ['main', codeName]) {\n const sessionsDir = join(homeDir, `.openclaw-${codeName}`, 'agents', agentDir, 'sessions');\n cleanupCronSessions(sessionsDir, CRON_SESSION_KEEP_COUNT);\n }\n\n // 2. Clean cron run logs older than 7 days\n const cronRunsDir = join(homeDir, `.openclaw-${codeName}`, 'cron', 'runs');\n cleanupOldFiles(cronRunsDir, CRON_RUN_RETENTION_DAYS, '.jsonl');\n\n // 3. Clear stale \"running\" state from cron jobs.json.\n // If a cron run was in progress when the gateway crashed, the state persists\n // as \"running\" and blocks all future runs with \"already-running\". Reset any\n // run that's been \"running\" for more than 5 minutes.\n const cronJobsPath = join(homeDir, `.openclaw-${codeName}`, 'cron', 'jobs.json');\n clearStaleCronRunState(cronJobsPath);\n}\n\n/**\n * Clean up cron session entries from sessions.json and their corresponding\n * .jsonl files. Preserves chat, Slack, Telegram, and other non-cron sessions.\n *\n * Cron sessions have keys matching `agent:*:cron:*:run:*` in sessions.json.\n */\nfunction cleanupCronSessions(sessionsDir: string, keepCount: number): void {\n const indexPath = join(sessionsDir, 'sessions.json');\n if (!existsSync(indexPath)) return;\n\n try {\n const raw = readFileSync(indexPath, 'utf-8');\n const index = JSON.parse(raw) as Record<string, { sessionId?: string; updatedAt?: number }>;\n\n // Find cron run entries (pattern: agent:*:cron:*:run:*)\n const cronRunKeys = Object.keys(index)\n .filter((k) => k.includes(':cron:') && k.includes(':run:'))\n .map((k) => ({\n key: k,\n sessionId: index[k]?.sessionId,\n updatedAt: index[k]?.updatedAt ?? 0,\n }))\n .sort((a, b) => b.updatedAt - a.updatedAt); // newest first\n\n if (cronRunKeys.length <= keepCount) return;\n\n // Keep the most recent N, delete the rest\n const toDelete = cronRunKeys.slice(keepCount);\n let deletedFiles = 0;\n\n for (const entry of toDelete) {\n // Remove from index\n delete index[entry.key];\n\n // Delete the session .jsonl file\n if (entry.sessionId) {\n const sessionFile = join(sessionsDir, `${entry.sessionId}.jsonl`);\n try {\n if (existsSync(sessionFile)) {\n unlinkSync(sessionFile);\n deletedFiles++;\n }\n } catch { /* ignore */ }\n }\n }\n\n // Also clean up parent cron session keys that have no remaining runs\n const cronParentKeys = Object.keys(index).filter(\n (k) => k.includes(':cron:') && !k.includes(':run:') && k !== 'agent:main:main',\n );\n for (const parentKey of cronParentKeys) {\n const hasRuns = Object.keys(index).some(\n (k) => k.startsWith(parentKey + ':run:'),\n );\n if (!hasRuns) {\n const parentSessionId = index[parentKey]?.sessionId;\n delete index[parentKey];\n if (parentSessionId) {\n try {\n const f = join(sessionsDir, `${parentSessionId}.jsonl`);\n if (existsSync(f)) { unlinkSync(f); deletedFiles++; }\n } catch { /* ignore */ }\n }\n }\n }\n\n // Write updated index\n writeFileSync(indexPath, JSON.stringify(index));\n\n if (toDelete.length > 0) {\n log(`Cleaned ${toDelete.length} cron session(s) and ${deletedFiles} file(s) from ${sessionsDir}`);\n }\n } catch { /* non-fatal */ }\n}\n\nconst STALE_RUN_TIMEOUT_MS = 5 * 60_000; // 5 minutes\n\nfunction clearStaleCronRunState(jobsPath: string): void {\n if (!existsSync(jobsPath)) return;\n\n try {\n const raw = readFileSync(jobsPath, 'utf-8');\n const data = JSON.parse(raw) as Record<string, unknown>;\n const jobs = (data.jobs ?? data) as Array<Record<string, unknown>>;\n if (!Array.isArray(jobs)) return;\n\n let changed = false;\n const now = Date.now();\n\n for (const job of jobs) {\n const state = job.state as Record<string, unknown> | undefined;\n if (!state) continue;\n\n // Check if a run is stuck in \"running\" state\n // OpenClaw uses `running: true` + `runningAtMs` (not `runStartedAtMs`)\n const runStartedAt = (state.runningAtMs ?? state.runStartedAtMs) as number | undefined;\n const isRunning = state.running === true || state.status === 'running';\n\n if (isRunning && runStartedAt && (now - runStartedAt) > STALE_RUN_TIMEOUT_MS) {\n state.running = false;\n delete state.status;\n delete state.runStartedAtMs;\n delete state.currentRunId;\n changed = true;\n log(`Cleared stale running state for cron job '${job.name}' (stuck for ${Math.round((now - runStartedAt) / 60_000)}min)`);\n } else if (isRunning && !runStartedAt) {\n // running=true but no timestamp — clear it\n state.running = false;\n changed = true;\n log(`Cleared stale running state for cron job '${job.name}' (no start timestamp)`);\n }\n }\n\n if (changed) {\n writeFileSync(jobsPath, JSON.stringify(data, null, 2));\n }\n } catch { /* non-fatal */ }\n}\n\nfunction cleanupOldFiles(dir: string, maxAgeDays: number, ext: string): void {\n if (!existsSync(dir)) return;\n\n const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;\n let removed = 0;\n\n try {\n for (const f of readdirSync(dir)) {\n if (!f.endsWith(ext)) continue;\n const fullPath = join(dir, f);\n try {\n const st = statSync(fullPath);\n if (st.mtimeMs < cutoff) {\n unlinkSync(fullPath);\n removed++;\n }\n } catch { /* ignore */ }\n }\n\n if (removed > 0) {\n log(`Cleaned ${removed} old cron run log(s) from ${dir}`);\n }\n } catch { /* non-fatal */ }\n}\n\n// ---------------------------------------------------------------------------\n// Claude Code in-process scheduler\n// ---------------------------------------------------------------------------\n\nimport {\n syncTasksToScheduler,\n getReadyTasks,\n markTaskFired,\n loadSchedulerState,\n findTaskByTemplate,\n getProjectDir as ccGetProjectDir,\n type SchedulerTaskInput,\n type SchedulerTaskState,\n} from './claude-scheduler.js';\n\n// Track in-flight Claude scheduled tasks and concurrency per agent\nconst inFlightClaudeTasks = new Set<string>();\nconst claudeTaskConcurrency = new Map<string, number>();\nconst MAX_CLAUDE_CONCURRENCY = 2;\n\n// In-memory scheduler states (loaded from disk on first access)\nconst claudeSchedulerStates = new Map<string, ReturnType<typeof loadSchedulerState>>();\n\nasync function syncAndCheckClaudeScheduler(\n agent: { agent_id: string; code_name: string; display_name: string },\n tasks: Array<Record<string, unknown>>,\n boardItems: Array<{ id: string; title: string; status: string; priority: number; estimated_minutes?: number; deliverable?: string; result?: string; notify_channel?: string; notify_to?: string }>,\n refreshData: Record<string, unknown>,\n): Promise<void> {\n const codeName = agent.code_name;\n\n // Hash-based change detection (same as OpenClaw path)\n const stableTasksHash = createHash('sha256')\n .update(JSON.stringify(tasks))\n .digest('hex')\n .slice(0, 16);\n\n const boardHash = boardItems.length > 0\n ? createHash('sha256')\n .update(JSON.stringify(boardItems.map((b) => ({ id: b.id, title: b.title, status: b.status, priority: b.priority, deliverable: b.deliverable }))))\n .digest('hex')\n .slice(0, 16)\n : 'empty';\n\n const resolvedModels = resolveModelChain(refreshData);\n const modelsHash = createHash('sha256')\n .update(JSON.stringify(resolvedModels))\n .digest('hex')\n .slice(0, 16);\n\n const combinedHash = `${stableTasksHash}:${boardHash}:${modelsHash}`;\n const prevHash = knownTasksHashes.get(agent.agent_id);\n\n if (combinedHash !== prevHash) {\n // Sync tasks to scheduler state\n const taskInputs: SchedulerTaskInput[] = tasks.map((t) => ({\n id: t.id as string,\n template_id: t.template_id as string,\n name: t.name as string,\n schedule_kind: t.schedule_kind as 'cron' | 'every' | 'at',\n schedule_expr: (t.schedule_expr as string) ?? null,\n schedule_every: (t.schedule_every as string) ?? null,\n schedule_at: (t.schedule_at as string) ?? null,\n timezone: (t.timezone as string) ?? 'UTC',\n prompt: (t.prompt as string) ?? '',\n session_target: (t.session_target as string) ?? 'isolated',\n delivery_mode: (t.delivery_mode as string) ?? 'none',\n delivery_channel: (t.delivery_channel as string) ?? null,\n delivery_to: (t.delivery_to as string) ?? null,\n enabled: (t.enabled as boolean) ?? true,\n triggered_at: (t.triggered_at as string) ?? null,\n }));\n\n const state = syncTasksToScheduler(codeName, agent.agent_id, taskInputs);\n claudeSchedulerStates.set(codeName, state);\n knownTasksHashes.set(agent.agent_id, combinedHash);\n log(`[claude-scheduler] Tasks synced for '${codeName}' (${taskInputs.length} task(s))`);\n }\n\n // Load state if not cached\n if (!claudeSchedulerStates.has(codeName)) {\n claudeSchedulerStates.set(codeName, loadSchedulerState(codeName));\n }\n\n const state = claudeSchedulerStates.get(codeName)!;\n const ready = getReadyTasks(state, inFlightClaudeTasks);\n if (ready.length === 0) return;\n\n for (const task of ready) {\n if ((claudeTaskConcurrency.get(codeName) ?? 0) >= MAX_CLAUDE_CONCURRENCY) break;\n\n // ENG-4410: Skip kanban-work when board has no actionable items.\n // Saves an LLM call every 5 minutes when the agent has nothing to do.\n if (KANBAN_WORK_TEMPLATES.has(task.templateId) && !hasActionableItems(boardItems)) {\n log(`[claude-scheduler] Skipping '${task.name}' for '${codeName}' — board is empty`);\n const updated = markTaskFired(codeName, task.taskId, 'ok');\n claudeSchedulerStates.set(codeName, updated);\n continue;\n }\n\n // Enrich prompt with board context\n let prompt = task.prompt;\n if (BOARD_INJECT_TEMPLATES.has(task.templateId) && boardItems.length > 0) {\n const template = PLAN_TEMPLATES.has(task.templateId) ? 'morning-plan' : 'follow-up';\n const boardPrefix = formatBoardForPrompt(boardItems, template);\n prompt = boardPrefix + prompt;\n }\n\n // For kanban-work: move the top \"todo\" item to in_progress before starting\n if (KANBAN_WORK_TEMPLATES.has(task.templateId)) {\n const todayItem = boardItems.find((b) => b.status === 'todo');\n if (todayItem) {\n try {\n await api.post('/host/kanban', {\n agent_id: agent.agent_id,\n update: [{ id: todayItem.id, title: todayItem.title, status: 'in_progress' }],\n });\n log(`[claude-scheduler] Moved '${todayItem.title}' to in_progress for '${codeName}'`);\n } catch (err) {\n log(`[claude-scheduler] Failed to move item to in_progress: ${(err as Error).message}`);\n }\n }\n }\n\n inFlightClaudeTasks.add(task.taskId);\n claudeTaskConcurrency.set(codeName, (claudeTaskConcurrency.get(codeName) ?? 0) + 1);\n\n log(`[claude-scheduler] Firing '${task.name}' for '${codeName}'`);\n\n executeAndProcessClaudeTask(codeName, agent.agent_id, task, prompt)\n .finally(() => {\n inFlightClaudeTasks.delete(task.taskId);\n claudeTaskConcurrency.set(codeName, Math.max(0, (claudeTaskConcurrency.get(codeName) ?? 1) - 1));\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Runs telemetry helpers (ENG-4561)\n// ---------------------------------------------------------------------------\n//\n// Wraps /host/runs/start and /host/runs/finish so each Claude Code spawn site\n// records a runs row. Best-effort — neither call ever throws to the caller;\n// telemetry failures log and return null so the task itself still fires. The\n// Claude Code spawn passes AGT_RUN_ID into the child env so MCP tools can\n// stamp the run_id onto rows they insert (wiring on the API side lands in a\n// follow-up; the env var is harmless until then).\n\ninterface StartRunOptions {\n agent_id: string;\n source_type: 'kanban' | 'scheduled_task' | 'channel' | 'manual' | 'system';\n source_ref?: string;\n channel?: string;\n trace_id?: string;\n metadata?: Record<string, unknown>;\n // ENG-4540 Phase 2: when set + source_type='scheduled_task', the API also\n // creates an in_progress kanban card tied to this run. The card id comes\n // back so finishRun can close it.\n materialize_kanban?: { title: string; description?: string; priority?: number };\n}\n\ninterface StartRunResult {\n run_id: string | null;\n kanban_item_id: string | null;\n}\n\nasync function startRun(opts: StartRunOptions): Promise<StartRunResult> {\n try {\n const res = await api.post<{ run_id?: string; kanban_item_id?: string | null }>(\n '/host/runs/start',\n opts,\n );\n return { run_id: res.run_id ?? null, kanban_item_id: res.kanban_item_id ?? null };\n } catch (err) {\n // Hash the raw error text so durable manager logs don't carry\n // potentially-sensitive payloads (per CLAUDE.md log policy). The\n // 12-char prefix is enough to correlate with server-side logs.\n const errText = err instanceof Error ? err.message : String(err);\n const errId = createHash('sha256').update(errText).digest('hex').slice(0, 12);\n log(`[runs] start failed for agent_id=${opts.agent_id} source_type=${opts.source_type} error_id=${errId}`);\n return { run_id: null, kanban_item_id: null };\n }\n}\n\ninterface FinishRunOptions {\n metadata?: Record<string, unknown>;\n outcomeMessage?: string;\n // ENG-4540 Phase 2: when startRun materialised a kanban card, pass the id\n // back here so the API closes the card to done/failed in lockstep.\n completeKanbanItemId?: string | null;\n result?: string;\n}\n\nasync function finishRun(\n runId: string,\n outcome: 'completed' | 'failed' | 'cancelled' | 'timeout',\n options: FinishRunOptions = {},\n): Promise<void> {\n try {\n await api.post('/host/runs/finish', {\n run_id: runId,\n outcome,\n outcome_message: options.outcomeMessage,\n metadata: options.metadata,\n complete_kanban_item_id: options.completeKanbanItemId ?? undefined,\n result: options.result,\n });\n } catch (err) {\n const errText = err instanceof Error ? err.message : String(err);\n const errId = createHash('sha256').update(errText).digest('hex').slice(0, 12);\n log(`[runs] finish failed for run_id=${runId} outcome=${outcome} error_id=${errId}`);\n }\n}\n\n// Fetch the recent output text of prior ticks of the same scheduled task so\n// the wrapper can inject \"here's what you already reported today\" context.\n// Best-effort — a lookup failure must not block the firing; the agent just\n// runs without prior context (same as the pre-injection behaviour).\nconst MAX_PRIOR_RUNS = 5;\nasync function fetchPriorScheduledRuns(\n agentId: string,\n taskId: string,\n): Promise<PriorRun[]> {\n try {\n const data = await api.post<{\n runs?: Array<{ started_at: string; output_text: string | null }>;\n }>('/host/scheduled-tasks/recent-outputs', {\n agent_id: agentId,\n task_id: taskId,\n since_hours: 24,\n limit: MAX_PRIOR_RUNS,\n });\n // Defensive client-side clamp: even though the request asks for\n // `limit: MAX_PRIOR_RUNS`, an upstream regression that ever over-returns\n // would balloon prompt size and break scheduled-task executions. Slice\n // here so the wrapper can never see more than the contracted maximum.\n const rows = Array.isArray(data?.runs) ? data.runs.slice(0, MAX_PRIOR_RUNS) : [];\n return rows\n .filter((r): r is { started_at: string; output_text: string } => typeof r.output_text === 'string' && r.output_text.length > 0)\n .map((r) => ({ startedAt: r.started_at, output: r.output_text }));\n } catch (err) {\n const errText = err instanceof Error ? err.message : String(err);\n // Log a short hash instead of the raw error text — upstream error\n // messages can include row data, JWT fragments, or other sensitive\n // context. Matches the redaction pattern already used elsewhere in\n // this file (see the `errId` log around line 1691).\n const errId = createHash('sha256').update(errText).digest('hex').slice(0, 12);\n log(`[runs] prior-runs lookup failed for task_id=${taskId} error_id=${errId}`);\n return [];\n }\n}\n\nasync function executeAndProcessClaudeTask(\n codeName: string,\n agentId: string,\n task: SchedulerTaskState,\n prompt: string,\n): Promise<void> {\n const projectDir = ccGetProjectDir(codeName);\n const mcpConfigPath = join(projectDir, '.mcp.json');\n\n // ENG-4561: track the run across the try/catch boundary so the failure\n // path can close it.\n // ENG-4540 Phase 2: also track the materialised kanban card id so the\n // success/failure paths can close it in lockstep with the run. taskResult\n // captures the truncated stdout — populated in both the success path and\n // the ChildProcessError branch so failed runs still surface partial agent\n // output on the closed card.\n let runId: string | null = null;\n let kanbanItemId: string | null = null;\n let taskResult: string | undefined;\n\n sanitizeMcpJson(mcpConfigPath, requireHost());\n\n // Wrap in the scheduled-task execution preamble so the agent knows this is a\n // non-interactive run (no human to answer clarifications) and that only its\n // final text response reaches the recipient. Raw prompts stored in\n // scheduler-state.json are never pre-wrapped; the claude-code framework\n // adapter's mapScheduledTasks wraps for the native /schedule path but this\n // isolated-session path was missing the wrapper. Idempotent — safe to call\n // on already-wrapped prompts.\n //\n // Also fetch the prior outputs of this scheduled task within the last 24h so\n // the wrapper can inject them as context — the agent then surfaces only what\n // is new or changed instead of re-reporting the same items each tick.\n const priorRuns = await fetchPriorScheduledRuns(agentId, task.taskId);\n prompt = wrapScheduledTaskPrompt(prompt, { priorRuns });\n\n try {\n const claudeMdPath = join(projectDir, 'CLAUDE.md');\n // Build --allowedTools from the agent's configured MCP servers\n const serverNames: string[] = [];\n if (existsSync(mcpConfigPath)) {\n try {\n const d = JSON.parse(readFileSync(mcpConfigPath, 'utf-8'));\n if (d.mcpServers) serverNames.push(...Object.keys(d.mcpServers));\n } catch { /* non-fatal */ }\n }\n // ENG-4487: includes Skill + Agent via shared helper so scheduled tasks\n // can actually invoke plugin skills in `.claude/skills/`. Previously this\n // list omitted them and agents silently ran without their plugins.\n //\n // ENG-4671: kept here for compatibility, but in auto mode (below) the\n // classifier — not the allowlist — is the primary gate. buildAllowedTools\n // also has a known mismatch (e.g. kanban tools live under the\n // `augmented` MCP server, not `kanban`), so the allowlist alone would\n // deny calls Claude needs to make. Auto mode side-steps that.\n const allowedTools = buildAllowedTools(serverNames);\n\n // ENG-4671: Claude CLI refuses --dangerously-skip-permissions (and its\n // alias --permission-mode bypassPermissions) under root, which broke every\n // scheduled task on AWS hosts where the manager supervises as root.\n // --permission-mode auto routes each tool call through a server-side\n // classifier that approves routine work and blocks escalations, with no\n // permission prompts in headless -p — the failure mode is \"task fails one\n // cycle, retries next\" rather than the silent root-error hang. Verified\n // live on the Scout AWS host before merging.\n const claudeArgs = [\n '-p', prompt,\n '--output-format', 'text',\n '--mcp-config', mcpConfigPath,\n '--strict-mcp-config',\n '--permission-mode', 'auto',\n '--allowedTools', allowedTools,\n ];\n // Inject agent identity as system prompt so personality is always present\n if (existsSync(claudeMdPath)) {\n claudeArgs.push('--system-prompt-file', claudeMdPath);\n }\n // Source .env.integrations for integration credentials (Xero, etc.)\n const childEnv = { ...process.env };\n const envIntPath = join(projectDir, '.env.integrations');\n if (existsSync(envIntPath)) {\n try {\n for (const line of readFileSync(envIntPath, 'utf-8').split('\\n')) {\n if (!line || line.startsWith('#') || !line.includes('=')) continue;\n const eqIdx = line.indexOf('=');\n childEnv[line.slice(0, eqIdx)] = line.slice(eqIdx + 1);\n }\n } catch { /* non-fatal */ }\n }\n // Honor the host's configured auth mode (ENG-4417). Force-refresh +\n // fail-closed: if we can't confirm the auth config, don't spawn.\n try {\n await applyClaudeAuthToEnv(childEnv, 'claude-scheduler');\n } catch (err) {\n log(`[claude-scheduler] Skipping task '${task.name}' for '${codeName}' — auth resolve failed: ${(err as Error).message}`);\n return;\n }\n\n // ENG-4561: open a runs telemetry record around the spawn. AGT_RUN_ID\n // is exposed in childEnv so MCP tools can stamp it onto inserted rows.\n //\n // ENG-4540 Phase 2: ask the API to materialise an in_progress kanban\n // card tied to this run. The task firing then shows up live on the\n // user's Todo column, and finishRun closes it to done/failed. If the\n // materialisation fails (kanban_item_id stays null) the task still\n // fires — we just lose the visible record of the firing.\n const startResult = await startRun({\n agent_id: agentId,\n source_type: 'scheduled_task',\n source_ref: task.taskId,\n metadata: { template_id: task.templateId, name: task.name },\n materialize_kanban: { title: task.name, priority: 2 },\n });\n runId = startResult.run_id;\n kanbanItemId = startResult.kanban_item_id;\n if (runId) childEnv['AGT_RUN_ID'] = runId;\n\n const { stdout, stderr } = await execFilePromiseLong(resolveClaudeBinary(), claudeArgs, {\n cwd: projectDir, timeout: 300_000, stdin: 'ignore', env: childEnv,\n });\n\n if (stderr) {\n log(`[claude-scheduler] Task '${task.name}' stderr for '${codeName}': ${stderr.slice(0, 500)}`);\n }\n\n const output = stdout.trim();\n taskResult = output.slice(0, 4000) || undefined;\n log(`[claude-scheduler] Task '${task.name}' completed for '${codeName}' (${output.length} chars): ${output.slice(0, 300)}`);\n\n // Route result based on template + deliver via configured delivery target\n await processClaudeTaskResult(codeName, agentId, task.templateId, output, {\n mode: task.deliveryMode,\n channel: task.deliveryChannel,\n to: task.deliveryTo,\n taskId: task.taskId,\n });\n\n // Close the runs telemetry record (success). When a kanban card was\n // materialised at start, /host/runs/finish moves it to 'done' and\n // stamps the agent's output as the result so the user can see what\n // the firing produced without leaving the board.\n if (runId) {\n await finishRun(runId, 'completed', {\n metadata: { output_length: output.length },\n completeKanbanItemId: kanbanItemId,\n result: taskResult,\n });\n }\n\n // Update scheduler state\n const updated = markTaskFired(codeName, task.taskId, 'ok');\n claudeSchedulerStates.set(codeName, updated);\n\n // Disable one-off 'at' tasks in the DB so they don't re-fire on manager restart\n if (task.scheduleKind === 'at') {\n api.post('/host/schedules/disable', { agent_id: agentId, task_id: task.taskId }).catch((err) =>\n log(`[claude-scheduler] Failed to disable one-off task '${task.name}': ${(err as Error).message}`),\n );\n log(`[claude-scheduler] One-off task '${task.name}' fired and disabled for '${codeName}'`);\n }\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log(`[claude-scheduler] Task '${task.name}' failed for '${codeName}': ${errMsg}`);\n // If the error is a ChildProcessError, dump both streams so we can\n // debug cases where Claude CLI writes errors to stdout and leaves\n // stderr empty (common for startup/MCP failures).\n if (err instanceof ChildProcessError) {\n // Surface partial stdout on the failed card so the user can still see\n // what the agent emitted before the failure (e.g. an MCP startup\n // error printed to stdout). Falls back to whatever was captured in\n // the success path if the failure happened post-spawn.\n const errStdout = err.stdout.trim();\n if (errStdout) {\n taskResult = errStdout.slice(0, 4000) || taskResult;\n log(`[claude-scheduler] Task '${task.name}' stdout for '${codeName}': ${errStdout.slice(0, 1000)}`);\n }\n if (err.stderr.trim()) {\n log(`[claude-scheduler] Task '${task.name}' stderr for '${codeName}': ${err.stderr.trim().slice(0, 1000)}`);\n }\n }\n // Close the runs telemetry record (failure). Best-effort — never let\n // a telemetry write block the task error path. The materialised\n // kanban card (if any) flips to 'failed' in the same call and carries\n // partial agent stdout in `result` so the user has something to read.\n if (runId) {\n try {\n await finishRun(runId, 'failed', {\n outcomeMessage: errMsg,\n completeKanbanItemId: kanbanItemId,\n result: taskResult,\n });\n } catch { /* swallow */ }\n }\n const updated = markTaskFired(codeName, task.taskId, 'error');\n claudeSchedulerStates.set(codeName, updated);\n }\n}\n\nasync function processClaudeTaskResult(\n codeName: string,\n agentId: string,\n templateId: string,\n rawOutput: string,\n delivery?: { mode: string; channel: string | null; to: unknown | null; taskId?: string },\n): Promise<void> {\n try {\n // ENG-4463 / ENG-4480: classify the output before ANYTHING else —\n // suppress / strip / deliver. Conditional tasks (\"DO NOT notify me\n // unless X\") return `<no-delivery/>` alone to signal \"nothing worth\n // delivering this run\". Some agents mix the sentinel into otherwise\n // real content (observed: \"Nothing urgent ...\\n\\n<no-delivery/>\\n\\n—\n // scheduled by ...\"); rather than ship the token to the recipient,\n // we strip every sentinel occurrence and deliver the cleaned text.\n // This must short-circuit template handlers too — otherwise a standup\n // scheduled task could still write \"current_tasks: <no-delivery/>\"\n // into /host/agent-status before we decide to skip the outbound.\n const classification = classifyOutput(rawOutput);\n if (classification.action === 'suppress') {\n // Hash + length only — suppressed output can contain recipient text\n // or PII the agent pulled from Linear/Gmail/etc. and `manager.log`\n // is persisted to disk. redactForDiskLog catches token-shaped\n // secrets but not message bodies, so we never log them raw here.\n const trimmed = (rawOutput ?? '').trim();\n const outputHash = trimmed.length === 0\n ? 'empty'\n : createHash('sha256').update(trimmed).digest('hex').slice(0, 12);\n log(`[claude-scheduler] Suppressing delivery for '${codeName}' (template=${templateId}, task=${delivery?.taskId ?? 'n/a'}) — output_len=${trimmed.length} output_hash=${outputHash}`);\n if (classification.suppressedNotes) {\n const notesHash = createHash('sha256').update(classification.suppressedNotes).digest('hex').slice(0, 12);\n log(`[claude-scheduler] Suppressed notes for '${codeName}' (task=${delivery?.taskId ?? 'n/a'}) — notes_len=${classification.suppressedNotes.length} notes_hash=${notesHash}`);\n }\n if (delivery?.mode === 'announce' && delivery.to) {\n await reportDeliveryStatus(agentId, delivery.taskId, {\n status: 'skipped',\n error_code: 'NO_CONTENT',\n });\n }\n return;\n }\n\n // Either 'deliver' (no sentinel present) or 'strip' (sentinel removed,\n // clean deliverable remains). Template handlers and the outbound both\n // use the classified deliverable, never the raw output.\n const output = classification.deliverable;\n if (classification.action === 'strip') {\n log(`[claude-scheduler] Stripped '<no-delivery/>' sentinel from '${codeName}' output (template=${templateId}, task=${delivery?.taskId ?? 'n/a'}) — agent mixed it with real content; delivering the rest.`);\n }\n\n if (STANDUP_TEMPLATES.has(templateId)) {\n const standup = parseStandupSummary(output);\n await api.post('/host/agent-status', {\n agent_code_name: codeName,\n standup,\n current_status: 'idle',\n });\n log(`[claude-scheduler] Standup posted for '${codeName}'`);\n } else if (TASK_UPDATE_TEMPLATES.has(templateId)) {\n await api.post('/host/agent-status', {\n agent_code_name: codeName,\n current_tasks: output.slice(0, 2000),\n });\n log(`[claude-scheduler] Task update posted for '${codeName}'`);\n } else if (PLAN_TEMPLATES.has(templateId)) {\n const planItems = parsePlanItems(output);\n if (planItems.length > 0) {\n await api.post('/host/kanban', {\n agent_id: agentId,\n add: planItems,\n });\n log(`[claude-scheduler] Plan items posted for '${codeName}' (${planItems.length} items)`);\n }\n } else if (KANBAN_WORK_TEMPLATES.has(templateId)) {\n const kanbanUpdates = parseKanbanUpdates(output);\n if (kanbanUpdates.length > 0) {\n await api.post('/host/kanban', {\n agent_id: agentId,\n update: kanbanUpdates,\n });\n log(`[claude-scheduler] Kanban updates posted for '${codeName}' (${kanbanUpdates.length} updates)`);\n }\n }\n\n // ENG-4422 §5–§6: deliver output via the configured target.\n // Target is either a legacy-shape `channel:<id>` / `chat:<id>` string\n // (OpenClaw agents still produce this form) OR a JSONB DeliveryTarget\n // object (Claude Code agents post-migration). Suppressed output was\n // already short-circuited above, so if we reach here the output is\n // deliverable.\n if (delivery?.mode === 'announce' && delivery.to) {\n await deliverScheduledTaskOutput(\n codeName,\n agentId,\n delivery.to,\n output.slice(0, 4000),\n delivery.taskId,\n );\n }\n } catch (err) {\n log(`[claude-scheduler] Failed to post result for '${codeName}': ${(err as Error).message}`);\n }\n}\n\n\nfunction fireClaudeWorkTrigger(\n codeName: string,\n agentId: string,\n boardItems: Array<{ id: string; title: string; status: string; priority: number; estimated_minutes?: number; deliverable?: string; result?: string }>,\n): void {\n const state = claudeSchedulerStates.get(codeName) ?? loadSchedulerState(codeName);\n const kanbanTask = findTaskByTemplate(state, 'kanban-work');\n if (!kanbanTask) {\n log(`[claude-scheduler] Work trigger: no kanban-work task found for '${codeName}'`);\n return;\n }\n if (inFlightClaudeTasks.has(kanbanTask.taskId)) {\n log(`[claude-scheduler] Work trigger: kanban-work already in-flight for '${codeName}'`);\n return;\n }\n\n let prompt = kanbanTask.prompt;\n if (boardItems.length > 0) {\n const boardPrefix = formatBoardForPrompt(boardItems, 'follow-up');\n prompt = boardPrefix + prompt;\n }\n\n inFlightClaudeTasks.add(kanbanTask.taskId);\n claudeTaskConcurrency.set(codeName, (claudeTaskConcurrency.get(codeName) ?? 0) + 1);\n log(`[claude-scheduler] Work trigger: firing kanban-work for '${codeName}'`);\n\n executeAndProcessClaudeTask(codeName, agentId, kanbanTask, prompt)\n .finally(() => {\n inFlightClaudeTasks.delete(kanbanTask.taskId);\n claudeTaskConcurrency.set(codeName, Math.max(0, (claudeTaskConcurrency.get(codeName) ?? 1) - 1));\n });\n}\n\n// ---------------------------------------------------------------------------\n// Persistent session management (session_mode = 'persistent')\n// ---------------------------------------------------------------------------\n\nimport {\n startPersistentSession,\n stopPersistentSession,\n injectMessage,\n isSessionHealthy,\n getSessionState,\n resetRestartCount,\n stopAllSessions,\n stopAllSessionsAndWait,\n getProjectDir as psGetProjectDir,\n resolveClaudeBinary,\n getLastFailureContext,\n prepareForRespawn,\n} from './persistent-session.js';\nimport { isAgentIdle, isStaleForToday, peekCurrentSession } from './daily-session.js';\nimport { processRestartFlags } from './restart-handler.js';\n\n// Track which agents have persistent sessions started\nconst persistentSessionAgents = new Set<string>();\n\n/**\n * Per-agent fingerprint of the Claude Code auth config used to spawn the\n * current session: `\"<mode>:<fingerprint ?? 'none'>\"`. When this changes\n * between polls (operator toggled subscription→api_key, or rotated the key),\n * the session is stopped so the next cycle respawns it with fresh auth.\n */\nconst claudeAuthTupleBySession = new Map<string, string>();\n\nasync function ensurePersistentSession(\n agent: { agent_id: string; code_name: string; display_name: string },\n tasks: Array<Record<string, unknown>>,\n boardItems: Array<{ id: string; title: string; status: string; priority: number; estimated_minutes?: number; deliverable?: string; result?: string }>,\n refreshData: Record<string, unknown>,\n): Promise<void> {\n const codeName = agent.code_name;\n const projectDir = psGetProjectDir(codeName);\n const mcpConfigPath = join(projectDir, '.mcp.json');\n const claudeMdPath = join(projectDir, 'CLAUDE.md');\n\n // Resolve channel plugins from agent's channel configs\n const channelConfigs = refreshData.channel_configs as Record<string, { config: unknown; status: string }> | null;\n const channels: string[] = [];\n\n // Map known channel types to their Claude Code channel plugins\n const devChannels: string[] = []; // custom channels needing --dangerously-load-development-channels\n if (channelConfigs) {\n // Slack and Telegram use our custom per-agent channel MCP servers\n // (written into the provision .mcp.json by writeChannelCredentials). The\n // `server:<name>` form registers them with Claude Code's channel\n // notification pipeline. Telegram used to ride the\n // `plugin:telegram@claude-plugins-official` plugin, which fails with >1\n // agent per host because the plugin reads a single global bot-token .env\n // — see ENG-4437.\n //\n // Gate registration on the channel actually being provisioned — a config\n // row with status other than `active` / `pending` never triggers\n // writeChannelCredentials, so asking Claude Code to load a dev channel\n // with no MCP entry would fail with a confusing \"unknown channel\" error.\n const isChannelEnabled = (id: string): boolean => {\n const entry = channelConfigs[id];\n // Must also have a truthy config — a row with status=pending but\n // null config never triggers writeChannelCredentials upstream, so\n // registering `server:X` for it would point Claude at an absent MCP\n // entry (same failure mode as the unprovisioned-status case).\n return !!entry?.config && (entry.status === 'active' || entry.status === 'pending');\n };\n if (isChannelEnabled('slack')) {\n devChannels.push('server:slack');\n }\n if (isChannelEnabled('telegram')) {\n devChannels.push('server:telegram');\n }\n if (isChannelEnabled('discord')) {\n channels.push('plugin:discord@claude-plugins-official');\n }\n }\n\n // Always add direct-chat channel for persistent sessions.\n // This is provisioned in step 5b of processAgent() into .mcp-channels.json.\n devChannels.push('server:direct-chat');\n\n // Resolve auth mode + key from the exchange BEFORE any OAuth precheck —\n // otherwise api_key hosts get skipped for never having run `claude /login`\n // even though they don't need OAuth at all. Force-refresh bypasses the\n // ~50m JWT cache so mode/key changes on the webapp are picked up on the\n // next poll (ENG-4417 rotation acceptance criterion).\n //\n // Fail-closed on error: silently falling back to 'subscription' is exactly\n // the confused-deputy path ENG-4417 removes. If /host/exchange rejects with\n // anthropic_key_decrypt_failed, the host is misconfigured — refuse to\n // spawn (or stop a running session) so the operator sees the problem\n // instead of a session quietly booting under the wrong auth.\n let claudeAuthMode: 'subscription' | 'api_key';\n let anthropicApiKey: string | null;\n let anthropicApiKeyFingerprint: string | null;\n try {\n const apiKey = getApiKey();\n if (!apiKey) {\n log(`[persistent-session] Skipping '${codeName}' — AGT_API_KEY not set`);\n return;\n }\n const exchange = await exchangeApiKey(apiKey, false, { forceRefresh: true });\n claudeAuthMode = exchange.claudeAuthMode;\n anthropicApiKey = exchange.anthropicApiKey;\n anthropicApiKeyFingerprint = exchange.anthropicApiKeyFingerprint;\n } catch (err) {\n const msg = (err as Error).message;\n log(`[persistent-session] Failed to resolve auth for '${codeName}': ${msg} — refusing to spawn`);\n // Stop any running session: the auth config is unknown, so continuing is\n // a silent-fallback bug waiting to happen. Next successful poll respawns.\n if (isSessionHealthy(codeName)) {\n stopPersistentSessionAndForgetMcpBaseline(codeName);\n persistentSessionAgents.delete(codeName);\n claudeAuthTupleBySession.delete(codeName);\n }\n return;\n }\n\n // OAuth precheck only applies in subscription mode. api_key hosts have\n // their credentials in the exchange response above — no `claude /login`\n // required — so don't block them on a missing OAuth token. Hosts in\n // api_key mode with no key would already have failed the exchange\n // (503 anthropic_key_decrypt_failed or host_metadata_unavailable).\n if (claudeAuthMode === 'subscription') {\n if (!agentRuntimeAuthenticated) {\n // Re-check in case user logged in since last check\n agentRuntimeAuthenticated = await checkClaudeAuth();\n if (!agentRuntimeAuthenticated) {\n log(`[persistent-session] Skipping '${codeName}' — Claude Code not authenticated (subscription mode)`);\n return;\n }\n }\n } else if (!anthropicApiKey) {\n // Belt-and-braces: exchange should have returned a key when mode=api_key.\n // If it didn't, don't spawn — the session would just fail auth in tmux.\n log(`[persistent-session] Skipping '${codeName}' — api_key mode but no key returned from exchange`);\n return;\n }\n\n // Detect auth rotation: if the (mode, fingerprint) tuple differs from what\n // we last launched with, stop the session so it respawns with fresh auth\n // on the next poll. Fingerprint captures key rotation without exposing the\n // raw value. First launch (no recorded tuple) stamps the baseline only.\n const currentAuthTuple = `${claudeAuthMode}:${anthropicApiKeyFingerprint ?? 'none'}`;\n const recordedAuthTuple = claudeAuthTupleBySession.get(codeName);\n if (recordedAuthTuple && recordedAuthTuple !== currentAuthTuple && isSessionHealthy(codeName)) {\n log(`[persistent-session] Auth config changed for '${codeName}' (${recordedAuthTuple} → ${currentAuthTuple}) — restarting session`);\n stopPersistentSessionAndForgetMcpBaseline(codeName);\n persistentSessionAgents.delete(codeName);\n }\n\n // ENG-4642: scheduled day rollover. When the daily-session.json's\n // current.date no longer matches today's local date, the running\n // session is on yesterday's UUID. Kill it so the next pass mints a\n // fresh UUID via getOrCreateDailySession — but only when the agent\n // is between turns (JSONL mtime older than IDLE_THRESHOLD_S). If the\n // agent is mid-task, defer to the next tick rather than interrupt.\n // First-spawn agents (no current entry yet) and unhealthy sessions\n // (no tmux to kill) are no-ops.\n if (isStaleForToday(codeName) && isSessionHealthy(codeName)) {\n const current = peekCurrentSession(codeName);\n if (current) {\n const idle = isAgentIdle(projectDir, current.sessionId);\n if (idle) {\n log(\n `[persistent-session] Day rollover for '${codeName}' (yesterday=${current.date}) — agent idle, restarting to mint fresh session`,\n );\n stopPersistentSessionAndForgetMcpBaseline(codeName);\n persistentSessionAgents.delete(codeName);\n } else {\n log(\n `[persistent-session] Day rollover for '${codeName}' deferred — agent still active on session ${current.sessionId} (will retry next tick)`,\n );\n }\n }\n }\n\n // Start or maintain the persistent session\n if (!isSessionHealthy(codeName)) {\n if (persistentSessionAgents.has(codeName)) {\n // ENG-4659: include the actual claude error from the captured\n // pane log + a restart-cycle counter so silent restart loops are\n // visible at a glance. Run the recovery hook BEFORE the next\n // spawn so e.g. \"Session ID already in use\" rotates the UUID\n // instead of looping forever.\n //\n // CodeRabbit (PR #618): pane output is untrusted — it can echo\n // user prompts, tool output, secrets, etc. Per CLAUDE.md\n // (\"Default logging to hash-only or redacted in prod; never log\n // raw secrets\") we only embed the raw tail when the signature\n // is in a strict allowlist of known-safe Claude error messages.\n // For everything else we log only a sha256 prefix of the tail —\n // operators can correlate with the full file at\n // ~/.augmented/<codeName>/pane.log when they need it.\n const ctx = getLastFailureContext(codeName);\n const recovery = prepareForRespawn(codeName);\n const tailSummary = !ctx.tail\n ? ''\n : KNOWN_SAFE_TAIL_SIGNATURES.has(ctx.signature)\n ? `; last pane output (${PANE_TAIL_PREVIEW_LINES} of ~20 lines):\\n${truncateForLog(ctx.tail)}`\n : `; pane_tail_hash=sha256:${createHash('sha256').update(ctx.tail).digest('hex').slice(0, 12)} (raw at ~/.augmented/${codeName}/pane.log)`;\n const sigSummary = ctx.signature !== 'unknown' ? `; signature=${ctx.signature}` : '';\n const recoverySummary = recovery ? `; recovery=${recovery}` : '';\n log(\n `[persistent-session] Session for '${codeName}' is unhealthy ` +\n `(restart #${ctx.restartCount}${sigSummary}${recoverySummary}), will restart${tailSummary}`,\n );\n }\n\n // Provision Stop hook for result capture (idempotent)\n try {\n provisionStopHook(codeName);\n } catch (err) {\n log(`[persistent-session] Failed to provision Stop hook for '${codeName}': ${(err as Error).message}`);\n }\n\n // Provision isolation hook to prevent cross-agent file access (idempotent)\n try {\n provisionIsolationHook(codeName);\n } catch (err) {\n log(`[persistent-session] Failed to provision isolation hook for '${codeName}': ${(err as Error).message}`);\n }\n\n startPersistentSession({\n codeName,\n agentId: agent.agent_id,\n projectDir,\n mcpConfigPath,\n claudeMdPath,\n channels,\n devChannels,\n apiHost: requireHost(),\n claudeAuthMode,\n anthropicApiKey,\n log,\n });\n persistentSessionAgents.add(codeName);\n // Record the tuple we launched with so future polls can detect rotation.\n claudeAuthTupleBySession.set(codeName, currentAuthTuple);\n return; // Wait for next cycle to inject tasks (session needs time to boot)\n }\n\n // Session is healthy — reset restart counter.\n resetRestartCount(codeName);\n // Stamp the baseline tuple on first healthy observation (covers the\n // manager-restart case where the tmux session survived but our Map didn't).\n if (!claudeAuthTupleBySession.has(codeName)) {\n claudeAuthTupleBySession.set(codeName, currentAuthTuple);\n }\n\n // Sync scheduler state from API before checking for ready tasks.\n // This ensures deleted/disabled tasks don't fire and new tasks are picked up.\n // The scheduleChanged guard in syncTasksToScheduler preserves nextFireAt.\n const stableTasksHash = createHash('sha256')\n .update(JSON.stringify(tasks))\n .digest('hex')\n .slice(0, 16);\n const prevHash = knownTasksHashes.get(agent.agent_id);\n if (stableTasksHash !== prevHash) {\n const taskInputs: SchedulerTaskInput[] = tasks.map((t) => ({\n id: t.id as string,\n template_id: t.template_id as string,\n name: t.name as string,\n schedule_kind: t.schedule_kind as 'cron' | 'every' | 'at',\n schedule_expr: (t.schedule_expr as string) ?? null,\n schedule_every: (t.schedule_every as string) ?? null,\n schedule_at: (t.schedule_at as string) ?? null,\n timezone: (t.timezone as string) ?? 'UTC',\n prompt: (t.prompt as string) ?? '',\n session_target: (t.session_target as string) ?? 'isolated',\n delivery_mode: (t.delivery_mode as string) ?? 'none',\n delivery_channel: (t.delivery_channel as string) ?? null,\n delivery_to: (t.delivery_to as string) ?? null,\n enabled: (t.enabled as boolean) ?? true,\n triggered_at: (t.triggered_at as string) ?? null,\n }));\n const schedulerState = syncTasksToScheduler(codeName, agent.agent_id, taskInputs);\n claudeSchedulerStates.set(codeName, schedulerState);\n knownTasksHashes.set(agent.agent_id, stableTasksHash);\n log(`[persistent-session] Tasks synced for '${codeName}' (${taskInputs.length} task(s))`);\n } else if (!claudeSchedulerStates.has(codeName)) {\n claudeSchedulerStates.set(codeName, loadSchedulerState(codeName));\n }\n\n // Inject scheduled tasks that are due\n const state = claudeSchedulerStates.get(codeName);\n if (state) {\n // ENG-4675: pass inFlightClaudeTasks so getReadyTasks filters out\n // tasks whose subprocess is still running. Without this, every\n // supervisor tick logged \"N ready task(s)\" + \"Firing task ...\" for\n // the same trigger, even though the in-flight guard below correctly\n // skipped the duplicate spawn.\n const ready = getReadyTasks(state, inFlightClaudeTasks);\n if (ready.length > 0) {\n log(`[persistent-session] ${ready.length} ready task(s) for '${codeName}': ${ready.map(t => `${t.name}(next=${t.nextFireAt ? new Date(t.nextFireAt).toISOString() : 'null'})`).join(', ')}`);\n }\n for (const task of ready) {\n\n // ENG-4410: Skip kanban-work when board has no actionable items.\n if (KANBAN_WORK_TEMPLATES.has(task.templateId) && !hasActionableItems(boardItems)) {\n log(`[persistent-session] Skipping '${task.name}' for '${codeName}' — board is empty`);\n const updated = markTaskFired(codeName, task.taskId, 'ok');\n claudeSchedulerStates.set(codeName, updated);\n continue;\n }\n\n // Enrich prompt with board context\n let prompt = task.prompt;\n if (BOARD_INJECT_TEMPLATES.has(task.templateId) && boardItems.length > 0) {\n const template = PLAN_TEMPLATES.has(task.templateId) ? 'morning-plan' : 'follow-up';\n const boardPrefix = formatBoardForPrompt(boardItems, template);\n prompt = boardPrefix + prompt;\n }\n\n // For kanban-work: move top today item to in_progress\n if (KANBAN_WORK_TEMPLATES.has(task.templateId)) {\n const todayItem = boardItems.find((b) => b.status === 'todo');\n if (todayItem) {\n try {\n await api.post('/host/kanban', {\n agent_id: agent.agent_id,\n update: [{ id: todayItem.id, title: todayItem.title, status: 'in_progress' }],\n });\n log(`[persistent-session] Moved '${todayItem.title}' to in_progress for '${codeName}'`);\n } catch { /* non-fatal */ }\n }\n }\n\n // Guard against duplicate launches (same pattern as oneshot path).\n // ENG-4675: also defends against a stale in-flight set if getReadyTasks'\n // filter is ever bypassed.\n if (inFlightClaudeTasks.has(task.taskId)) {\n continue;\n }\n if ((claudeTaskConcurrency.get(codeName) ?? 0) >= MAX_CLAUDE_CONCURRENCY) {\n break;\n }\n inFlightClaudeTasks.add(task.taskId);\n claudeTaskConcurrency.set(codeName, (claudeTaskConcurrency.get(codeName) ?? 0) + 1);\n\n // ENG-4675: log AFTER the in-flight guard so it only fires when a\n // subprocess actually starts. Previously logged before the guard,\n // making manual triggers look like they fired multiple times.\n log(`[persistent-session] Firing task '${task.name}' for '${codeName}' via claude -p`);\n\n // Use the same oneshot claude -p path as non-persistent mode.\n // This captures output so we can deliver to Slack and process results.\n // The tmux session stays for interactive channels (Slack/Telegram).\n executeAndProcessClaudeTask(codeName, agent.agent_id, task, prompt)\n .catch(() => { /* errors logged inside executeAndProcessClaudeTask */ })\n .finally(() => {\n inFlightClaudeTasks.delete(task.taskId);\n claudeTaskConcurrency.set(codeName, Math.max(0, (claudeTaskConcurrency.get(codeName) ?? 1) - 1));\n });\n\n // Only inject one task per cycle in persistent mode\n break;\n }\n }\n\n}\n\n// ---------------------------------------------------------------------------\n// Supabase Realtime for direct chat (replaces 2s polling)\n// ---------------------------------------------------------------------------\n\nimport {\n startRealtimeChat,\n startRealtimeDrift,\n startRealtimeAssignments,\n startRealtimeConfig,\n startRealtimeKanban,\n startRealtimeIntegrationContext,\n stopRealtimeIntegrationContext,\n stopRealtimeChat,\n isRealtimeConnected,\n updateRealtimeToken,\n} from './realtime-chat.js';\n\nlet realtimeStarted = false;\nlet realtimeDriftStarted = false;\nlet realtimeKanbanStarted = false;\n// Snapshot of the agent_ids currently subscribed to the realtime\n// `plugin_context` channel. Empty set ⇒ not yet subscribed. Compared\n// against the current active-agent set each tick so an agent\n// activated after manager startup also gets realtime context updates\n// (ENG-4725 — previously this used a one-shot boolean and silently\n// missed any agents that came online later).\nlet subscribedIntegrationContextAgentIds: Set<string> = new Set();\nlet realtimeAssignStarted = false;\nlet realtimeConfigStarted = false;\nlet realtimeSubscribedAgentIds = new Set<string>();\n\nfunction ensureRealtimeStarted(agentStates: AgentState[]): void {\n // Check if new agents have appeared since last subscription\n const currentActiveIds = new Set(\n agentStates.filter((a) => a.status === 'active').map((a) => a.agentId),\n );\n if (realtimeStarted) {\n // Detect any change: additions or removals\n const sameSize = currentActiveIds.size === realtimeSubscribedAgentIds.size;\n const sameMembers = sameSize && [...currentActiveIds].every((id) => realtimeSubscribedAgentIds.has(id));\n if (sameMembers) return;\n // Agent set changed — tear down and reconnect with fresh set\n log('[realtime] Agent set changed — reconnecting subscriptions');\n stopRealtimeChat();\n realtimeStarted = false;\n realtimeDriftStarted = false;\n realtimeAssignStarted = false;\n realtimeConfigStarted = false;\n realtimeKanbanStarted = false;\n subscribedIntegrationContextAgentIds = new Set();\n }\n\n const activeAgentIds = agentStates\n .filter((a) => a.status === 'active')\n .map((a) => a.agentId);\n\n if (activeAgentIds.length === 0) return;\n\n const apiKey = process.env['AGT_API_KEY'];\n if (!apiKey) return;\n\n // Get Supabase config + token from the cached exchange result\n void exchangeApiKey(apiKey).then((exchange) => {\n if (!exchange.supabaseUrl || !exchange.supabaseAnonKey) {\n log('[realtime-chat] No Supabase URL/key from exchange — staying on polling');\n return;\n }\n\n startRealtimeChat({\n supabaseUrl: exchange.supabaseUrl,\n supabaseAnonKey: exchange.supabaseAnonKey,\n token: exchange.token,\n agentIds: activeAgentIds,\n onMessage: (msg) => {\n const agent = agentStates.find((a) => a.agentId === msg.agent_id);\n if (!agent) return;\n\n if (directChatInFlight.has(msg.id)) return;\n directChatInFlight.add(msg.id);\n\n processDirectChatMessage(agent, {\n id: msg.id,\n session_id: msg.session_id,\n content: msg.content,\n }).finally(() => {\n directChatInFlight.delete(msg.id);\n });\n },\n onError: (err) => {\n log(`[realtime-chat] Error: ${err.message}`);\n },\n onStatusChange: (status) => {\n if (status === 'disconnected' || status === 'error') {\n log('[realtime] Disconnected — falling back to polling, will reconnect next cycle');\n // Reset all subscription flags so they reconnect with a fresh client\n realtimeStarted = false;\n realtimeDriftStarted = false;\n realtimeAssignStarted = false;\n realtimeConfigStarted = false;\n realtimeKanbanStarted = false;\n subscribedIntegrationContextAgentIds = new Set();\n }\n },\n log,\n });\n\n realtimeStarted = true;\n realtimeSubscribedAgentIds = new Set(activeAgentIds);\n log(`[realtime-chat] Started for ${activeAgentIds.length} agent(s)`);\n }).catch((err) => {\n log(`[realtime-chat] Failed to start: ${(err as Error).message}`);\n });\n}\n\nfunction ensureRealtimeDriftStarted(agentStates: AgentState[]): void {\n if (realtimeDriftStarted) return;\n\n const activeAgentIds = agentStates.filter((a) => a.status === 'active').map((a) => a.agentId);\n if (activeAgentIds.length === 0) return;\n\n const apiKey = process.env['AGT_API_KEY'];\n if (!apiKey) return;\n\n void exchangeApiKey(apiKey).then((exchange) => {\n if (!exchange.supabaseUrl || !exchange.supabaseAnonKey) return;\n\n startRealtimeDrift({\n supabaseUrl: exchange.supabaseUrl,\n supabaseAnonKey: exchange.supabaseAnonKey,\n token: exchange.token,\n agentIds: activeAgentIds,\n onDrift: (doc) => {\n // Invalidate the known version so next poll cycle re-provisions\n const agentState = agentStates.find((a) => a.agentId === doc.agent_id);\n if (agentState) {\n knownVersions.delete(doc.agent_id);\n log(`[realtime] Drift invalidated for '${agentState.codeName}' — will re-provision next cycle`);\n }\n },\n log,\n });\n\n realtimeDriftStarted = true;\n log(`[realtime] Drift subscription started for ${activeAgentIds.length} agent(s)`);\n }).catch((err) => {\n log(`[realtime] Drift subscription failed: ${(err as Error).message}`);\n });\n}\n\nfunction ensureRealtimeAssignStarted(agentStates: AgentState[]): void {\n if (realtimeAssignStarted) return;\n\n const apiKey = process.env['AGT_API_KEY'];\n if (!apiKey) return;\n\n void exchangeApiKey(apiKey).then((exchange) => {\n if (!exchange.supabaseUrl || !exchange.supabaseAnonKey || !exchange.hostId) return;\n\n startRealtimeAssignments({\n supabaseUrl: exchange.supabaseUrl,\n supabaseAnonKey: exchange.supabaseAnonKey,\n token: exchange.token,\n hostId: exchange.hostId,\n onAssign: (payload) => {\n log(`[realtime] Agent ${payload.agent_id} assigned — will pick up next cycle`);\n // ENG-4643: stamp the agent for a fresh memory pull on its next\n // sync tick. Without this, a long-running manager that hosted\n // this agent before its current migration could still have\n // memoryFileHashes / lastDownloadHash entries from the prior\n // assignment and skip the catch-up download (the local-list-\n // hash short-circuit in syncMemories sees \"nothing changed\"\n // and returns). Clearing the caches + flagging for a forced\n // download-first pass keeps the new host in sync with the DB.\n //\n // Deliberately not invoking syncMemories from here. The\n // realtime payload only carries agent_id; syncMemories needs\n // {agent_id, code_name} plus the supervisor's configDir, none\n // of which are reachable from this callback's closure. The\n // next supervisor tick (interval default 10s) iterates the\n // freshly-loaded agent list and consumes the pending flag,\n // so the latency cost of waiting one tick is bounded and\n // the alternative (extra API call to resolve code_name from\n // here, plus duplicating the sync entrypoint plumbing) is\n // not worth the saved seconds.\n markAgentForFreshMemorySync(payload.agent_id);\n },\n onUnassign: (payload) => {\n log(`[realtime] Agent ${payload.agent_id} unassigned`);\n clearAgentCaches(payload.agent_id, '');\n },\n log,\n });\n\n realtimeAssignStarted = true;\n log(`[realtime] Assignment subscription started for host ${exchange.hostId}`);\n }).catch((err) => {\n log(`[realtime] Assignment subscription failed: ${(err as Error).message}`);\n });\n}\n\nfunction ensureRealtimeConfigStarted(agentStates: AgentState[]): void {\n if (realtimeConfigStarted) return;\n\n const activeAgentIds = agentStates.filter((a) => a.status === 'active').map((a) => a.agentId);\n if (activeAgentIds.length === 0) return;\n\n const apiKey = process.env['AGT_API_KEY'];\n if (!apiKey) return;\n\n void exchangeApiKey(apiKey).then((exchange) => {\n if (!exchange.supabaseUrl || !exchange.supabaseAnonKey) return;\n\n startRealtimeConfig({\n supabaseUrl: exchange.supabaseUrl,\n supabaseAnonKey: exchange.supabaseAnonKey,\n token: exchange.token,\n agentIds: activeAgentIds,\n onConfigChange: (agent) => {\n // Only invalidate status cache — version/provision caches are handled by drift subscription.\n // Don't invalidate knownVersions here as that triggers re-provision which updates\n // updated_at, creating a feedback loop with this Realtime subscription.\n knownStatuses.delete(agent.agent_id);\n },\n log,\n });\n\n realtimeConfigStarted = true;\n log(`[realtime] Config subscription started for ${activeAgentIds.length} agent(s)`);\n }).catch((err) => {\n log(`[realtime] Config subscription failed: ${(err as Error).message}`);\n });\n}\n\nfunction ensureRealtimeKanbanStarted(agentStates: AgentState[]): void {\n if (realtimeKanbanStarted) return;\n\n const activeAgentIds = agentStates.filter((a) => a.status === 'active').map((a) => a.agentId);\n if (activeAgentIds.length === 0) return;\n\n const apiKey = process.env['AGT_API_KEY'];\n if (!apiKey) return;\n\n void exchangeApiKey(apiKey).then((exchange) => {\n if (!exchange.supabaseUrl || !exchange.supabaseAnonKey) return;\n\n startRealtimeKanban({\n supabaseUrl: exchange.supabaseUrl,\n supabaseAnonKey: exchange.supabaseAnonKey,\n token: exchange.token,\n agentIds: activeAgentIds,\n onTodayItem: (item) => {\n // Trigger kanban-work immediately for this agent\n const agent = agentStates.find((a) => a.agentId === item.agent_id);\n if (!agent) return;\n\n const agentFw = agentFrameworkCache.get(agent.codeName) ?? 'openclaw';\n\n if (agentFw === 'claude-code') {\n // For persistent sessions, inject via tmux; for oneshot, fire claude -p\n const boardItems = kanbanBoardCache.get(agent.codeName) ?? [];\n if (isSessionHealthy(agent.codeName)) {\n injectMessage(agent.codeName, 'task', `New task added to your board: \"${item.title}\" (priority ${item.priority}). Pick it up — move to in_progress and start working.`, {\n task_name: 'kanban-work-trigger',\n }, log);\n log(`[realtime] Injected kanban-work trigger for '${agent.codeName}': \"${item.title}\"`);\n } else {\n fireClaudeWorkTrigger(agent.codeName, agent.agentId, boardItems);\n log(`[realtime] Fired kanban-work for '${agent.codeName}': \"${item.title}\"`);\n }\n }\n },\n // ENG-4507: kanban completion notification — surfaces user-driven\n // closures to the daemon log + telemetry surface. The agent-runtime\n // ingestion path is the kanban_list `closed_by` annotation (no live\n // injection in this slice — the agent reads its board between turns\n // and sees the new state naturally).\n onCompletion: (event) => {\n const agent = agentStates.find((a) => a.agentId === event.agent_id);\n const codeName = agent?.codeName ?? event.agent_id;\n log(\n `[realtime] Kanban completion forwarded for '${codeName}': ` +\n `item=${event.item_id} status=${event.status} actor=${event.last_actor_id ?? 'unknown'}`,\n );\n },\n log,\n });\n\n realtimeKanbanStarted = true;\n log(`[realtime] Kanban subscription started for ${activeAgentIds.length} agent(s)`);\n }).catch((err) => {\n log(`[realtime] Kanban subscription failed: ${(err as Error).message}`);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Integration context realtime (ENG-4342)\n//\n// Subscribes to plugin_context table changes for the manager's agents and\n// triggers an early poll cycle whenever a context row is inserted or\n// updated. The early poll is what actually re-renders SKILL.md — this\n// realtime channel just collapses the latency from \"next poll cycle\"\n// (~60s) to \"next event loop tick after the change\" (~100ms + RTT).\n// ---------------------------------------------------------------------------\n\nfunction activeAgentIdSetsEqual(current: string[], previous: Set<string>): boolean {\n if (current.length !== previous.size) return false;\n for (const id of current) if (!previous.has(id)) return false;\n return true;\n}\n\nfunction ensureRealtimeIntegrationContextStarted(agentStates: AgentState[]): void {\n const activeAgentIds = agentStates.filter((a) => a.status === 'active').map((a) => a.agentId);\n\n // No active agents → tear down any existing subscription so we don't\n // hold a channel filter for agents we no longer manage.\n if (activeAgentIds.length === 0) {\n if (subscribedIntegrationContextAgentIds.size > 0) {\n stopRealtimeIntegrationContext();\n subscribedIntegrationContextAgentIds = new Set();\n log('[realtime] Integration context subscription torn down (no active agents)');\n }\n return;\n }\n\n // Already subscribed to exactly this set → nothing to do.\n if (activeAgentIdSetsEqual(activeAgentIds, subscribedIntegrationContextAgentIds)) return;\n\n const apiKey = process.env['AGT_API_KEY'];\n if (!apiKey) return;\n\n // Active set changed (agent activated, deactivated, reassigned, etc.).\n // Tear down the existing channel so we can re-subscribe with the\n // updated `agent_id=in.(...)` filter — Supabase realtime filters are\n // immutable per channel, so a re-bind is the only path.\n const isResubscribe = subscribedIntegrationContextAgentIds.size > 0;\n if (isResubscribe) {\n stopRealtimeIntegrationContext();\n }\n\n // Snapshot the set we're about to subscribe to before the async work\n // so a concurrent tick sees the in-flight subscription and skips.\n // exchangeApiKey resolution happens off-tick; if it fails, we reset\n // below so the next tick retries.\n const targetSet = new Set(activeAgentIds);\n subscribedIntegrationContextAgentIds = targetSet;\n\n void exchangeApiKey(apiKey).then((exchange) => {\n // A newer tick may have replaced the snapshot (e.g. agent set changed\n // again, or the broader chat client disconnected and reset all flags).\n // Bail without mutating state or starting a now-stale subscription —\n // identity comparison is enough because we only ever assign fresh\n // `Set` instances to the module-level snapshot.\n if (subscribedIntegrationContextAgentIds !== targetSet) return;\n\n if (!exchange.supabaseUrl || !exchange.supabaseAnonKey) {\n subscribedIntegrationContextAgentIds = new Set();\n return;\n }\n\n startRealtimeIntegrationContext({\n supabaseUrl: exchange.supabaseUrl,\n supabaseAnonKey: exchange.supabaseAnonKey,\n token: exchange.token,\n agentIds: [...targetSet],\n onContextChange: (payload) => {\n // Invalidate the affected agent's cached plugin-skill hashes so the\n // next poll definitely re-renders SKILL.md (the hash check would\n // otherwise be a no-op if the LLM-rendered output happens to be\n // identical, which is rare but possible). Then trigger an early poll.\n const agent = agentStates.find((a) => a.agentId === payload.agent_id);\n if (agent) {\n // Match the cache key shape used when hashes are written in the\n // refresh loop: `plugin-skill:${agent.agent_id}:...`. Using\n // codeName here (as the original version did) silently failed to\n // invalidate anything, so plugin-context changes could be skipped\n // until some unrelated bundle change happened or the manager\n // restarted.\n for (const key of knownSkillHashes.keys()) {\n if (key.startsWith(`plugin-skill:${agent.agentId}:`)) {\n knownSkillHashes.delete(key);\n }\n }\n }\n triggerEarlyPoll(`integration context changed for agent ${payload.agent_id}`);\n },\n log,\n });\n\n log(\n `[realtime] Integration context subscription ${isResubscribe ? 're-bound' : 'started'} for ${targetSet.size} agent(s)`,\n );\n }).catch((err) => {\n // Reset so the next tick retries — but only if we're still the\n // owning snapshot. Otherwise a newer tick has already taken over\n // and clearing here would silently undo its in-flight work.\n if (subscribedIntegrationContextAgentIds === targetSet) {\n subscribedIntegrationContextAgentIds = new Set();\n }\n log(`[realtime] Integration context subscription failed: ${(err as Error).message}`);\n });\n}\n\n/**\n * Cancel the scheduled poll and run a new poll cycle on the next event-loop\n * tick. Used by realtime callbacks (plugin context, etc.) to collapse the\n * latency between an external change and the manager observing it.\n *\n * Idempotent — multiple triggers within the same tick coalesce into one\n * poll because pollCycle() is awaited and scheduleNext() only fires after\n * it completes.\n */\nfunction triggerEarlyPoll(reason: string): void {\n if (!running) return;\n if (pollTimer) {\n clearTimeout(pollTimer);\n pollTimer = null;\n }\n log(`[realtime] Triggering early poll: ${reason}`);\n // Schedule on the next tick so we don't run reentrantly inside a Realtime\n // callback. scheduleNext() will fire on completion.\n pollTimer = setTimeout(() => {\n void pollCycle().then(() => {\n // scheduleNext() is the normal post-poll re-arm. Forward declared at\n // the bottom of this file; safe to reference here because the function\n // declaration is hoisted.\n scheduleNext();\n });\n }, 0);\n}\n\n// ---------------------------------------------------------------------------\n// Direct chat — poll for pending messages (fallback when Realtime is down)\n// ---------------------------------------------------------------------------\n\n// Track in-flight direct chat messages to avoid double-processing\nconst directChatInFlight = new Set<string>();\n\nasync function pollDirectChatMessages(agentStates: AgentState[]): Promise<void> {\n for (const agent of agentStates) {\n if (agent.status !== 'active') continue;\n const fw = agentFrameworkCache.get(agent.codeName) ?? 'openclaw';\n // OpenClaw requires a running gateway; Claude Code does not\n if (fw === 'openclaw' && (!agent.gatewayRunning || !agent.gatewayPort)) continue;\n\n try {\n const data = await api.post<{\n messages: Array<{\n id: string;\n session_id: string;\n content: string;\n created_at: string;\n }>;\n }>('/host/direct-chat/poll', { agent_id: agent.agentId });\n\n for (const msg of data.messages) {\n if (directChatInFlight.has(msg.id)) continue;\n directChatInFlight.add(msg.id);\n\n // Process async so we don't block the poll cycle\n processDirectChatMessage(agent, msg).finally(() => {\n directChatInFlight.delete(msg.id);\n });\n }\n } catch (err) {\n log(`Direct chat poll failed for '${agent.codeName}': ${(err as Error).message}`);\n }\n }\n}\n\nasync function processDirectChatMessage(\n agent: AgentState,\n msg: { id: string; session_id: string; content: string },\n): Promise<void> {\n const fw = agentFrameworkCache.get(agent.codeName) ?? 'openclaw';\n log(`[direct-chat] Processing message for '${agent.codeName}' (fw=${fw}): id=${msg.id} len=${msg.content.length}`);\n\n // ENG-4778: when a persistent session is running for this agent, inject\n // the direct-chat message into it instead of spawning a fresh `claude -p`.\n // The fresh process has no conversation history — it can't pick up where\n // the running session left off (e.g. completing the AWS task that\n // triggered a broker resolution notification). Inject as a `<channel>`\n // tag so the agent's existing system-prompt instructions for direct-chat\n // (call `direct_chat.reply` with the session_id) still apply.\n //\n // For agents WITHOUT a running persistent session (oneshot Claude Code,\n // OpenClaw cron-only deployments) the original `claude -p` path below\n // still applies.\n if (isSessionHealthy(agent.codeName)) {\n // CodeRabbit (PR #748): escape session_id + content before splicing\n // into the XML-shaped envelope so a payload containing quotes,\n // angle brackets, or a literal `</channel>` can't break the\n // envelope structure (and possibly steer the agent's downstream\n // routing). Apply to both attribute values and body for safety —\n // escaped < / > in the body still render legibly to the agent.\n const escapeXml = (value: string): string =>\n value\n .replaceAll('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('\"', '"')\n .replaceAll(\"'\", ''');\n const channelEnvelope =\n `<channel source=\"direct-chat\" session_id=\"${escapeXml(msg.session_id)}\" user=\"webapp\">\\n` +\n `${escapeXml(msg.content)}\\n` +\n `</channel>`;\n const delivered = await injectMessage(\n agent.codeName,\n 'chat',\n channelEnvelope,\n { task_name: 'direct-chat' },\n log,\n );\n if (delivered) {\n log(`[direct-chat] Injected into persistent session for '${agent.codeName}' (msg=${msg.id}); agent will reply via direct_chat.reply tool`);\n return;\n }\n // CodeRabbit (PR #748): injectMessage returned false — acpx is\n // unavailable so it fell back to `tmux send-keys`, which doesn't\n // guarantee submission. Rather than strand the message in\n // 'processing' indefinitely, fall through to the original\n // `claude -p` path below so the user still gets a reply. Worst\n // case: a duplicate response if tmux send-keys DID land — the\n // user sees two answers, which is recoverable. Silent loss is\n // not.\n log(`[direct-chat] Inject reported unverified for '${agent.codeName}' (msg=${msg.id}) — falling back to one-shot direct-chat handling`);\n // Fall through to the existing claude -p / openclaw reply path below.\n }\n\n try {\n let reply: string;\n\n if (fw === 'claude-code') {\n // Always use claude -p for webapp direct chat so the reply comes back\n // through the API. The persistent session handles Slack/Telegram channels.\n const { getProjectDir: ccProjectDir } = await import('./claude-scheduler.js');\n const projDir = ccProjectDir(agent.codeName);\n\n // Build --allowedTools from the agent's configured MCP servers\n const mcpConfigPath = join(projDir, '.mcp.json');\n const serverNames: string[] = [];\n if (existsSync(mcpConfigPath)) {\n try {\n const d = JSON.parse(readFileSync(mcpConfigPath, 'utf-8'));\n if (d.mcpServers) serverNames.push(...Object.keys(d.mcpServers));\n } catch { /* non-fatal */ }\n }\n // ENG-4487: includes Skill + Agent via shared helper.\n const allowedTools = buildAllowedTools(serverNames);\n\n // ENG-4671: matches the scheduled-task fire path above. The webapp\n // direct-chat path didn't have the root-block (it doesn't use\n // --dangerously-skip-permissions), but it does share buildAllowedTools'\n // server-name mismatches (kanban-under-augmented), which empirically\n // caused tool calls to be denied in headless mode. --permission-mode\n // auto fixes both paths consistently.\n const chatArgs = [\n '-p', msg.content,\n '--output-format', 'text',\n '--mcp-config', mcpConfigPath,\n '--strict-mcp-config',\n '--permission-mode', 'auto',\n '--allowedTools', allowedTools,\n ];\n const chatClaudeMd = join(projDir, 'CLAUDE.md');\n if (existsSync(chatClaudeMd)) {\n chatArgs.push('--system-prompt-file', chatClaudeMd);\n }\n\n // Source .env.integrations for the claude process\n const envIntPath = join(projDir, '.env.integrations');\n const childEnv = { ...process.env };\n if (existsSync(envIntPath)) {\n try {\n for (const line of readFileSync(envIntPath, 'utf-8').split('\\n')) {\n if (!line || line.startsWith('#') || !line.includes('=')) continue;\n const eqIdx = line.indexOf('=');\n childEnv[line.slice(0, eqIdx)] = line.slice(eqIdx + 1);\n }\n } catch { /* non-fatal */ }\n }\n // Honor the host's configured auth mode (ENG-4417). Force-refresh +\n // fail-closed: if we can't confirm the auth config, don't spawn.\n try {\n await applyClaudeAuthToEnv(childEnv, 'direct-chat');\n } catch (err) {\n throw new Error(`Auth resolve failed for '${agent.codeName}': ${(err as Error).message}`);\n }\n\n const { stdout } = await execFilePromiseLong(resolveClaudeBinary(), chatArgs, { cwd: projDir, stdin: 'ignore', env: childEnv });\n reply = stdout.trim() || '[No response from agent]';\n } else {\n // OpenClaw: use profile-based agent invocation\n const { stdout } = await execFilePromiseLong('openclaw', [\n '--profile', agent.codeName,\n 'agent',\n '--local',\n '--agent', agent.codeName,\n '--message', msg.content,\n '--session-id', msg.session_id,\n '--json',\n ]);\n\n // OpenClaw agent --json returns either:\n // { payloads: [{ text }], meta: {...} } (success)\n // { result: { payloads: [{ text }] }, meta: {...} } (some versions)\n try {\n const parsed = JSON.parse(stdout);\n const payloads = (parsed?.payloads ?? parsed?.result?.payloads) as Array<{ text?: string }> | undefined;\n reply = payloads?.[0]?.text ?? parsed?.reply ?? parsed?.text ?? parsed?.message ?? stdout;\n } catch {\n reply = stdout.trim() || '[No response from agent]';\n }\n }\n\n // Post the reply back\n await api.post('/host/direct-chat/reply', {\n agent_id: agent.agentId,\n session_id: msg.session_id,\n content: reply,\n });\n\n log(`[direct-chat] Reply sent for '${agent.codeName}'`);\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n const errorId = createHash('sha256').update(errMsg).digest('hex').slice(0, 12);\n log(`[direct-chat] Failed to process message for '${agent.codeName}': error_id=${errorId} error=${errMsg.slice(0, 500)}`);\n\n // Post an error reply so the webapp doesn't poll forever\n try {\n await api.post('/host/direct-chat/reply', {\n agent_id: agent.agentId,\n session_id: msg.session_id,\n content: `[Error] Failed to process message (ref: ${errorId}). Please retry.`,\n });\n } catch {\n // Last resort — nothing more we can do\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Cron result harvesting — polls cron run history and updates agent status\n// ---------------------------------------------------------------------------\n\n// Template IDs that map to agent field updates\nconst STANDUP_TEMPLATES = new Set(['daily-standup', 'end-of-day-summary']);\nconst TASK_UPDATE_TEMPLATES = new Set(['hourly-status', 'task-update']);\nconst PLAN_TEMPLATES = new Set(['morning-plan']);\nconst KANBAN_WORK_TEMPLATES = new Set(['kanban-work']);\nconst BOARD_INJECT_TEMPLATES = new Set(['morning-plan', 'task-update', 'hourly-status', 'end-of-day-summary', 'kanban-work']);\n\n// ENG-4410: Check if board has actionable items (backlog, today, or in_progress).\n// Used to skip kanban-work LLM calls when the board is completely empty.\nconst ACTIONABLE_STATUSES = new Set(['backlog', 'todo', 'in_progress']);\nfunction hasActionableItems(items: BoardItem[]): boolean {\n return items.some((item) => ACTIONABLE_STATUSES.has(item.status));\n}\n\n// Throttle harvest — no need to check cron results every 30s cycle\nconst lastHarvestAt = new Map<string, number>();\nconst HARVEST_INTERVAL_MS = 3 * 60 * 1000; // 3 minutes\n\n// Cache kanban board per agent per poll cycle\ntype BoardItem = { id: string; title: string; status: string; priority: number; estimated_minutes?: number; deliverable?: string; result?: string; notify_channel?: string; notify_to?: string; updated_at?: string };\nconst kanbanBoardCache = new Map<string, BoardItem[]>();\n// Separate cache for notification board diff — not pre-populated by step 7d\nconst notifyBoardCache = new Map<string, Set<string>>();\n\ninterface CronRunEntry {\n ts: number;\n jobId: string;\n action: string;\n status: string;\n summary?: string;\n}\n\nasync function harvestCronResults(\n codeName: string,\n tasks: Array<{ id: string; template_id: string; name: string }>,\n gatewayPort: number,\n): Promise<void> {\n const token = readGatewayToken(codeName);\n const gwArgs = ['--url', `ws://127.0.0.1:${gatewayPort}`, ...(token ? ['--token', token] : [])];\n\n // List current jobs to map job IDs to template IDs\n let gatewayJobs: Array<{ id: string; name: string }> = [];\n try {\n const cliBin = resolveAgentFramework(codeName).cliBinary ?? 'openclaw';\n const { stdout } = await execFilePromise(cliBin, ['--profile', codeName, 'cron', 'list', '--json', ...gwArgs]);\n const parsed = JSON.parse(stdout);\n gatewayJobs = (parsed.jobs ?? []) as Array<{ id: string; name: string }>;\n } catch {\n return; // Gateway not ready\n }\n\n // Build a map from gateway job ID → DB task template_id\n const jobTemplateMap = new Map<string, string>();\n for (const job of gatewayJobs) {\n if (!job.name.startsWith('aug:')) continue;\n // Name format: aug:{template_id}:{task_uuid}\n const parts = job.name.split(':');\n if (parts.length >= 3) {\n jobTemplateMap.set(job.id, parts[1]!);\n }\n }\n\n // Check each relevant job for new completed runs\n for (const [jobId, templateId] of jobTemplateMap) {\n if (!STANDUP_TEMPLATES.has(templateId) && !TASK_UPDATE_TEMPLATES.has(templateId) && !PLAN_TEMPLATES.has(templateId) && !KANBAN_WORK_TEMPLATES.has(templateId)) continue;\n\n let runs: CronRunEntry[] = [];\n try {\n const cliBin2 = resolveAgentFramework(codeName).cliBinary ?? 'openclaw';\n const { stdout } = await execFilePromise(cliBin2, ['--profile', codeName, 'cron', 'runs', '--id', jobId, ...gwArgs]);\n const parsed = JSON.parse(stdout);\n runs = (parsed.entries ?? []) as CronRunEntry[];\n } catch {\n continue;\n }\n\n // Detect API key errors from failed runs\n const latestRun = runs.filter((r) => r.action === 'finished').sort((a, b) => b.ts - a.ts)[0];\n if (latestRun) {\n const agentId = codeNameToAgentId.get(codeName);\n const summary = latestRun.summary ?? '';\n const isKeyError = summary.includes('Key limit exceeded') || summary.includes('key limit') ||\n summary.includes('rate limit') || summary.includes('insufficient_quota') ||\n summary.includes('billing') || summary.includes('402');\n if (agentId) {\n if (latestRun.status === 'error' && isKeyError) {\n const errorMsg = summary.slice(0, 200);\n if (!apiKeyStatusCache.get(codeName)) {\n apiKeyStatusCache.set(codeName, true);\n api.post('/host/agent-api-key-status', { agent_id: agentId, status: 'rate_limited', error: errorMsg }).catch(() => {});\n log(`API key error detected for '${codeName}': ${errorMsg}`);\n }\n } else if (latestRun.status === 'ok' && apiKeyStatusCache.get(codeName)) {\n apiKeyStatusCache.delete(codeName);\n api.post('/host/agent-api-key-status', { agent_id: agentId, status: null, error: null }).catch(() => {});\n log(`API key status cleared for '${codeName}' — run succeeded`);\n }\n }\n }\n\n // Find the latest successful run\n const completed = runs\n .filter((r) => r.action === 'finished' && r.status === 'ok' && r.summary)\n .sort((a, b) => b.ts - a.ts);\n\n if (completed.length === 0) continue;\n\n const latest = completed[0]!;\n const lastSeen = lastCronRunTs.get(jobId) ?? 0;\n if (latest.ts <= lastSeen) continue; // Already processed\n\n lastCronRunTs.set(jobId, latest.ts);\n\n // POST result to API\n const statusUpdate: Record<string, unknown> = { agent_code_name: codeName };\n\n if (STANDUP_TEMPLATES.has(templateId)) {\n // Parse standup summary into structured fields\n const summary = latest.summary ?? '';\n statusUpdate.standup = parseStandupSummary(summary);\n }\n\n if (TASK_UPDATE_TEMPLATES.has(templateId)) {\n statusUpdate.current_tasks = latest.summary ?? '';\n }\n\n // Only POST agent-status if we have something to update (standup or current_tasks)\n if (statusUpdate.standup || statusUpdate.current_tasks !== undefined) {\n try {\n await api.post('/host/agent-status', statusUpdate);\n log(`Updated ${templateId} for '${codeName}' from cron result`);\n } catch (err) {\n log(`Failed to update ${templateId} for '${codeName}': ${(err as Error).message}`);\n }\n }\n\n // Kanban: Parse plan items from morning-plan template\n if (PLAN_TEMPLATES.has(templateId)) {\n const summary = latest.summary ?? '';\n const planItems = parsePlanItems(summary);\n if (planItems.length > 0) {\n // Look up agent_id from code_name\n try {\n const agentId = codeNameToAgentId.get(codeName);\n if (agentId) {\n await api.post('/host/kanban', {\n agent_id: agentId,\n add: planItems,\n archive_days: 7,\n });\n log(`Added ${planItems.length} kanban items for '${codeName}' from morning-plan`);\n }\n } catch (err) {\n log(`Failed to update kanban for '${codeName}': ${(err as Error).message}`);\n }\n }\n }\n\n // Kanban: Parse status updates from task-update/hourly/kanban-work templates\n if (TASK_UPDATE_TEMPLATES.has(templateId) || KANBAN_WORK_TEMPLATES.has(templateId)) {\n const summary = latest.summary ?? '';\n const kanbanUpdates = parseKanbanUpdates(summary);\n if (kanbanUpdates.length > 0) {\n try {\n const agentId = codeNameToAgentId.get(codeName);\n if (agentId) {\n await api.post('/host/kanban', {\n agent_id: agentId,\n update: kanbanUpdates,\n });\n log(`Updated ${kanbanUpdates.length} kanban items for '${codeName}'`);\n }\n } catch (err) {\n log(`Failed to update kanban for '${codeName}': ${(err as Error).message}`);\n }\n }\n\n // Board diff notification detection moved to step 12 in processAgent\n // (runs every poll cycle, not just on cron harvest)\n }\n }\n}\n\nfunction parseStandupSummary(summary: string): { yesterday: string; today: string; blockers: string } {\n // Try to extract sections from the standup text\n const lines = summary.split('\\n');\n let yesterday = '';\n let today = '';\n let blockers = '';\n let currentSection: 'yesterday' | 'todo' | 'blockers' | null = null;\n\n for (const line of lines) {\n const lower = line.toLowerCase();\n if (lower.includes('yesterday') || lower.includes('accomplished')) {\n currentSection = 'yesterday';\n continue;\n } else if (lower.includes('todo') || lower.includes('working on')) {\n currentSection = 'todo';\n continue;\n } else if (lower.includes('blocker')) {\n currentSection = 'blockers';\n continue;\n }\n\n const trimmed = line.replace(/^[-*•]\\s*/, '').trim();\n if (!trimmed) continue;\n\n switch (currentSection) {\n case 'yesterday': yesterday += (yesterday ? '\\n' : '') + trimmed; break;\n case 'todo': today += (today ? '\\n' : '') + trimmed; break;\n case 'blockers': blockers += (blockers ? '\\n' : '') + trimmed; break;\n }\n }\n\n // Fallback: if parsing didn't find sections, use full summary\n if (!yesterday && !today && !blockers) {\n today = summary;\n }\n\n return { yesterday, today, blockers };\n}\n\nfunction parsePlanItems(summary: string): Array<{\n title: string;\n description?: string;\n priority: number;\n estimated_minutes?: number;\n status: string;\n}> {\n const items: Array<{\n title: string;\n description?: string;\n priority: number;\n estimated_minutes?: number;\n status: string;\n }> = [];\n\n const lines = summary.split('\\n');\n let currentItem: (typeof items)[0] | null = null;\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // Match numbered/bulleted items: \"1. [HIGH] Task title (~30min)\" or \"- [MEDIUM] Task (~2hr)\"\n const itemMatch = trimmed.match(/^(?:\\d+[\\.\\)]\\s*|[-*•]\\s*)\\[?(HIGH|MEDIUM|LOW|MED)\\]?\\s*(.+)/i);\n\n if (itemMatch) {\n // Push previous item\n if (currentItem) items.push(currentItem);\n\n const priorityStr = itemMatch[1]!.toUpperCase();\n const rest = itemMatch[2]!;\n\n // Extract time estimate\n let estimatedMinutes: number | undefined;\n const timeMatch = rest.match(/\\(~?(\\d+)\\s*(min(?:utes?)?|hr?(?:ours?)?|h)\\)/i);\n if (timeMatch) {\n const val = parseInt(timeMatch[1]!, 10);\n const unit = timeMatch[2]!.toLowerCase();\n estimatedMinutes = unit.startsWith('h') ? val * 60 : val;\n }\n\n // Title is everything except the time estimate — sanitize LLM output\n const title = sanitizeKanbanString(\n rest.replace(/\\(~?\\d+\\s*(?:min(?:utes?)?|hr?(?:ours?)?|h)\\)/i, ''),\n MAX_KANBAN_TITLE_LENGTH,\n );\n if (!title) continue;\n\n const priorityMap: Record<string, number> = { HIGH: 1, MEDIUM: 2, MED: 2, LOW: 3 };\n const priority = priorityMap[priorityStr] ?? 2;\n if (![1, 2, 3].includes(priority)) continue;\n\n currentItem = {\n title,\n priority,\n estimated_minutes: estimatedMinutes,\n status: 'todo',\n };\n } else if (currentItem && trimmed && !trimmed.match(/^(?:PLAN|---)/i)) {\n // Non-numbered line after a plan item = description (length-limited)\n const descLine = sanitizeKanbanString(trimmed, MAX_KANBAN_NOTES_LENGTH);\n currentItem.description = currentItem.description\n ? sanitizeKanbanString(currentItem.description + '\\n' + descLine, MAX_KANBAN_NOTES_LENGTH)\n : descLine;\n }\n }\n\n if (currentItem) items.push(currentItem);\n return items;\n}\n\nconst VALID_KANBAN_STATUSES = new Set(['backlog', 'todo', 'in_progress', 'done']);\nconst MAX_KANBAN_TITLE_LENGTH = 500;\nconst MAX_KANBAN_NOTES_LENGTH = 2000;\n\n/** Sanitize a string from LLM output: trim + length limit */\nfunction sanitizeKanbanString(value: string, maxLen: number): string {\n if (!value) return '';\n return value\n .replace(/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]/g, '') // strip control chars (keep \\n, \\r, \\t)\n .replace(/\\{\\{.*?\\}\\}/g, '') // strip template injection markers\n .replace(/^(System|Assistant|Human):\\s*/gmi, '') // strip prompt-role prefixes\n .replace(/#{2,}/g, '') // strip markdown heading markers\n .replace(/\\s+/g, ' ') // collapse whitespace\n .trim()\n .slice(0, maxLen);\n}\n\n/** Sanitize all external fields on a board item before caching / prompt injection */\nfunction sanitizeBoardItem<T extends { title: string; status: string; deliverable?: string }>(item: T): T {\n return {\n ...item,\n title: sanitizeKanbanString(item.title, 200),\n status: sanitizeKanbanString(item.status, 50),\n ...(item.deliverable ? { deliverable: sanitizeKanbanString(item.deliverable, 500) } : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Built-in skill files — bundled for auto-deployment to all agents\n// ---------------------------------------------------------------------------\n\nconst builtInSkillCache = new Map<string, Array<{ relativePath: string; content: string }> | null>();\n\nfunction getBuiltInSkillContent(skillId: string): Array<{ relativePath: string; content: string }> | null {\n if (builtInSkillCache.has(skillId)) return builtInSkillCache.get(skillId)!;\n\n try {\n // Resolve skill directory relative to the CLI package root\n // In dev: skills/<skillId>/SKILL.md is in the monorepo root\n // When bundled: skills are embedded at build time or read from cwd\n const candidates = [\n join(process.cwd(), 'skills', skillId, 'SKILL.md'),\n join(new URL('.', import.meta.url).pathname, '..', '..', '..', '..', 'skills', skillId, 'SKILL.md'),\n ];\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n const content = readFileSync(candidate, 'utf-8');\n const files = [{ relativePath: 'SKILL.md', content }];\n builtInSkillCache.set(skillId, files);\n return files;\n }\n }\n\n builtInSkillCache.set(skillId, null);\n return null;\n } catch {\n builtInSkillCache.set(skillId, null);\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n\nfunction parseKanbanUpdates(summary: string): Array<{\n title: string;\n status: string;\n notes?: string;\n result?: string;\n}> {\n const updates: Array<{ title: string; status: string; notes?: string; result?: string }> = [];\n\n // Look for \"KANBAN UPDATE:\" section\n const kanbanIdx = summary.indexOf('KANBAN UPDATE:');\n if (kanbanIdx === -1) return updates;\n\n const kanbanSection = summary.slice(kanbanIdx + 'KANBAN UPDATE:'.length);\n const lines = kanbanSection.split('\\n');\n\n for (const line of lines) {\n const trimmed = line.trim();\n // Match: - \"item title\": status (optional notes/result)\n // Accept legacy 'today' as a synonym for 'todo' so prompts that haven't\n // been re-rendered post-rename still parse cleanly. Map to 'todo' before\n // validating against VALID_KANBAN_STATUSES (which only contains 'todo').\n const match = trimmed.match(/^[-*•]\\s*\"([^\"]+)\":\\s*(backlog|todo|today|in_progress|done)(?:\\s*\\((.+)\\))?/i);\n if (match) {\n const rawStatus = match[2]!.toLowerCase();\n const status = rawStatus === 'today' ? 'todo' : rawStatus;\n // Validate status is in the allowed set\n if (!VALID_KANBAN_STATUSES.has(status)) continue;\n\n const title = sanitizeKanbanString(match[1]!, MAX_KANBAN_TITLE_LENGTH);\n if (!title) continue;\n\n const parenthetical = match[3] ?? undefined;\n\n // Extract result from parenthetical when status is done\n // Pattern: \"result: <value>\" or just the whole parenthetical as notes\n let notes: string | undefined;\n let result: string | undefined;\n\n if (parenthetical && status === 'done') {\n const resultMatch = parenthetical.match(/^result:\\s*(.+)/i);\n if (resultMatch) {\n result = sanitizeKanbanString(resultMatch[1]!, MAX_KANBAN_NOTES_LENGTH);\n } else {\n notes = sanitizeKanbanString(parenthetical, MAX_KANBAN_NOTES_LENGTH);\n }\n } else if (parenthetical) {\n notes = sanitizeKanbanString(parenthetical, MAX_KANBAN_NOTES_LENGTH);\n }\n\n updates.push({\n title,\n status,\n notes,\n result,\n });\n }\n }\n\n return updates;\n}\n\nfunction formatBoardForPrompt(\n items: Array<{ title: string; status: string; priority: number; estimated_minutes?: number; deliverable?: string }>,\n template: 'morning-plan' | 'follow-up',\n): string {\n if (items.length === 0) return '';\n\n const priorityLabel = (p: number) => p === 1 ? 'HIGH' : p === 3 ? 'LOW' : 'MED';\n const timeLabel = (m?: number) => m ? ` (~${m >= 60 ? `${Math.round(m / 60)}hr` : `${m}min`})` : '';\n const deliverableLine = (d?: string) => d ? `\\n Deliverable: ${d}` : '';\n\n const grouped: Record<string, typeof items> = {};\n for (const item of items) {\n const key = item.status;\n if (!grouped[key]) grouped[key] = [];\n grouped[key]!.push(item);\n }\n\n const lines: string[] = [];\n\n if (template === 'morning-plan') {\n lines.push('=== CURRENT BOARD ===');\n\n for (const [status, label] of [['backlog', 'BACKLOG (carry-over)'], ['todo', 'TO DO'], ['in_progress', 'IN PROGRESS']] as const) {\n const statusItems = grouped[status];\n if (statusItems && statusItems.length > 0) {\n lines.push(`${label}:`);\n statusItems.forEach((item, i) => {\n lines.push(` ${i + 1}. [${priorityLabel(item.priority)}] ${item.title}${timeLabel(item.estimated_minutes)}${deliverableLine(item.deliverable)}`);\n });\n }\n }\n\n lines.push('=====================');\n lines.push('');\n lines.push('Create today\\'s plan. You may:');\n lines.push('- Move backlog items to \"todo\"');\n lines.push('- Add new items you\\'ve identified');\n lines.push('- Reprioritise existing items');\n lines.push('');\n } else {\n lines.push('=== YOUR KANBAN BOARD ===');\n\n for (const [status, label] of [['todo', 'TO DO'], ['in_progress', 'IN PROGRESS'], ['backlog', 'BACKLOG']] as const) {\n const statusItems = grouped[status];\n if (statusItems && statusItems.length > 0) {\n lines.push(`${label}:`);\n statusItems.forEach((item, i) => {\n lines.push(` ${i + 1}. [${priorityLabel(item.priority)}] ${item.title}${timeLabel(item.estimated_minutes)}${deliverableLine(item.deliverable)}`);\n });\n }\n }\n\n const doneItems = grouped['done'];\n if (doneItems && doneItems.length > 0) {\n lines.push('DONE TODAY:');\n doneItems.forEach((item, i) => {\n lines.push(` ${i + 1}. ${item.title}`);\n });\n }\n\n lines.push('=========================');\n lines.push('');\n lines.push('IMPORTANT: Use kanban MCP tools to update the board IN REAL TIME:');\n lines.push('1. FIRST call kanban.move to move your chosen item to in_progress BEFORE starting work');\n lines.push('2. Do the work');\n lines.push('3. Call kanban.done with a result summary when finished');\n lines.push('4. If blocked, call kanban.update with notes, then pick the next item');\n lines.push('');\n lines.push('SELF-MANAGEMENT: When you receive a request from a channel (Slack, Telegram)');\n lines.push('that takes more than a quick response, create a task first with kanban.add,');\n lines.push('move to in_progress, do the work, then kanban.done with a result summary.');\n lines.push('');\n lines.push('If MCP tools are unavailable, include a KANBAN UPDATE section in your output:');\n lines.push('KANBAN UPDATE:');\n lines.push('- \"item title\": new_status (optional notes)');\n lines.push('- \"item title\": done (result: <what you produced>)');\n lines.push('Statuses: backlog, today, in_progress, done');\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\nasync function execFilePromise(cmd: string, args: string[]): Promise<{ stdout: string; stderr: string }> {\n const { execFile: ef } = await import('node:child_process');\n return new Promise((resolve, reject) => {\n ef(cmd, args, { timeout: 15_000 }, (err, stdout, stderr) => {\n if (err) reject(err);\n else resolve({ stdout, stderr });\n });\n });\n}\n\n/**\n * Rejection shape for non-zero exits. Includes stdout AND stderr so\n * callers can log both — some tools (Claude CLI in particular) write\n * startup errors to stdout and leave stderr empty, which makes\n * stderr-only logging useless for debugging.\n */\nexport class ChildProcessError extends Error {\n public readonly code: number | null;\n public readonly stdout: string;\n public readonly stderr: string;\n constructor(code: number | null, stdout: string, stderr: string) {\n const stderrSnippet = stderr.trim().slice(0, 500);\n const stdoutSnippet = stdout.trim().slice(0, 500);\n // Prefer stderr in the message; fall back to stdout when stderr is empty.\n const detail = stderrSnippet || stdoutSnippet || '(no output)';\n super(`Exit code ${code}: ${detail}`);\n this.name = 'ChildProcessError';\n this.code = code;\n this.stdout = stdout;\n this.stderr = stderr;\n }\n}\n\nasync function execFilePromiseLong(\n cmd: string,\n args: string[],\n opts?: { cwd?: string; timeout?: number; stdin?: 'ignore'; env?: NodeJS.ProcessEnv },\n): Promise<{ stdout: string; stderr: string }> {\n const { spawn: sp } = await import('node:child_process');\n return new Promise((resolve, reject) => {\n const child = sp(cmd, args, {\n cwd: opts?.cwd,\n stdio: [opts?.stdin === 'ignore' ? 'ignore' : 'pipe', 'pipe', 'pipe'],\n ...(opts?.env ? { env: opts.env } : {}),\n });\n let stdout = '';\n let stderr = '';\n child.stdout?.on('data', (d: Buffer) => { stdout += d.toString(); });\n child.stderr?.on('data', (d: Buffer) => { stderr += d.toString(); });\n const timer = setTimeout(() => { child.kill(); reject(new Error(`Timed out after ${opts?.timeout ?? 120_000}ms`)); }, opts?.timeout ?? 120_000);\n child.on('close', (code) => {\n clearTimeout(timer);\n if (code !== 0) reject(new ChildProcessError(code, stdout, stderr));\n else resolve({ stdout, stderr });\n });\n child.on('error', (err) => { clearTimeout(timer); reject(err); });\n });\n}\n\n// ---------------------------------------------------------------------------\n// Cron health monitoring — detects late/failed jobs and sends alerts\n// ---------------------------------------------------------------------------\n\nconst LATE_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes grace period\n\ninterface CronAlert {\n type: 'late_standup' | 'cron_failure';\n agentCodeName: string;\n agentDisplayName: string;\n jobName: string;\n taskName: string;\n schedule: string;\n jobId: string;\n detail: string;\n}\n\nasync function monitorCronHealth(agentStates: AgentState[]): Promise<void> {\n const alerts: CronAlert[] = [];\n const now = Date.now();\n\n for (const agent of agentStates) {\n if (!agent.gatewayRunning || !agent.gatewayPort) continue;\n\n const token = readGatewayToken(agent.codeName);\n const gwArgs = ['--url', `ws://127.0.0.1:${agent.gatewayPort}`, ...(token ? ['--token', token] : [])];\n\n let jobs: Array<{\n id: string;\n name: string;\n enabled: boolean;\n state?: {\n nextRunAtMs?: number;\n lastRunAtMs?: number;\n lastRunStatus?: string;\n lastDelivered?: boolean;\n consecutiveErrors?: number;\n };\n }> = [];\n\n try {\n const cliBin = resolveAgentFramework(agent.codeName).cliBinary ?? 'openclaw';\n const { stdout } = await execFilePromise(cliBin, ['--profile', agent.codeName, 'cron', 'list', '--json', ...gwArgs]);\n const parsed = JSON.parse(stdout);\n jobs = parsed.jobs ?? [];\n } catch {\n continue;\n }\n\n for (const job of jobs) {\n if (!job.enabled || !job.name.startsWith('aug:')) continue;\n const alertKey = `${agent.codeName}:${job.id}`;\n\n // Look up human-readable task info\n const displayInfo = taskDisplayInfo.get(`${agent.codeName}:${job.name}`);\n const taskName = displayInfo?.taskName ?? job.name;\n const schedule = displayInfo?.schedule ?? '';\n const agentDisplayName = displayInfo?.agentDisplayName ?? agent.codeName;\n\n // Check for late jobs — nextRunAtMs is in the past by more than the threshold\n if (job.state?.nextRunAtMs && job.state.nextRunAtMs + LATE_THRESHOLD_MS < now) {\n if (!alertedJobs.has(`late:${alertKey}`)) {\n const minsLate = Math.round((now - job.state.nextRunAtMs) / 60_000);\n alerts.push({\n type: 'late_standup',\n agentCodeName: agent.codeName,\n agentDisplayName,\n jobName: job.name,\n taskName,\n schedule,\n jobId: job.id,\n detail: `Job is ${minsLate}m late (expected at ${new Date(job.state.nextRunAtMs).toISOString()})`,\n });\n alertedJobs.add(`late:${alertKey}`);\n }\n } else {\n // Clear the late alert if the job is no longer late\n alertedJobs.delete(`late:${alertKey}`);\n }\n\n // Check for failed runs\n if (job.state?.lastRunStatus === 'error' || (job.state?.consecutiveErrors && job.state.consecutiveErrors > 0)) {\n if (!alertedJobs.has(`fail:${alertKey}`)) {\n alerts.push({\n type: 'cron_failure',\n agentCodeName: agent.codeName,\n agentDisplayName,\n jobName: job.name,\n taskName,\n schedule,\n jobId: job.id,\n detail: `Last run failed (${job.state.consecutiveErrors ?? 1} consecutive error(s))`,\n });\n alertedJobs.add(`fail:${alertKey}`);\n }\n } else {\n alertedJobs.delete(`fail:${alertKey}`);\n }\n }\n }\n\n if (alerts.length === 0) return;\n\n // Log all alerts\n for (const alert of alerts) {\n log(`ALERT [${alert.type}] ${alert.agentCodeName}/${alert.jobName}: ${alert.detail}`);\n }\n\n // Send to Slack webhook if configured\n if (alertSlackWebhook) {\n await sendSlackAlert(alerts);\n }\n\n // Post to API for dashboard visibility\n try {\n await api.post('/host/cron-alerts', { alerts });\n } catch {\n // Non-fatal — API may not have this endpoint yet\n }\n}\n\n/**\n * Call Telegram Bot API using Node's https module (not fetch).\n * Node's native fetch (undici) can't reach api.telegram.org on some networks.\n */\nfunction telegramApiCall(botToken: string, method: string, body: unknown): Promise<{ ok: boolean; description?: string }> {\n return new Promise((resolve, reject) => {\n const postData = JSON.stringify(body);\n const req = https.request({\n hostname: 'api.telegram.org',\n port: 443,\n path: `/bot${botToken}/${method}`,\n method: 'POST',\n family: 4,\n timeout: 10_000,\n headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) },\n }, (res) => {\n let data = '';\n res.on('data', (d) => { data += d; });\n res.on('end', () => {\n try { resolve(JSON.parse(data)); } catch { reject(new Error('Invalid JSON from Telegram API')); }\n });\n });\n req.on('error', reject);\n req.on('timeout', () => { req.destroy(); reject(new Error('Telegram API timeout')); });\n req.write(postData);\n req.end();\n });\n}\n\nasync function sendSlackWebhookMessage(text: string): Promise<void> {\n if (!alertSlackWebhook) {\n log('sendSlackWebhookMessage: no alertSlackWebhook configured — message dropped');\n return;\n }\n try {\n const response = await fetch(alertSlackWebhook, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ text }),\n });\n if (!response.ok) {\n log(`Slack webhook failed: ${response.status} ${response.statusText}`);\n }\n } catch (err) {\n log(`Slack webhook error: ${(err as Error).message}`);\n }\n}\n\n/**\n * Post a message to a specific Slack channel using the agent's bot token\n * cached from channel_configs during refresh. Returns true if sent successfully.\n */\nasync function sendSlackChannelMessage(agentCodeName: string, channelId: string, text: string): Promise<boolean> {\n const result = await postSlackChannelMessage(agentCodeName, channelId, text);\n return result.ok;\n}\n\n/**\n * Like sendSlackChannelMessage, but returns the posted message's ts so the\n * caller can thread a follow-up reply under it. Accepts an optional\n * `threadTs` to post *as* a thread reply. ENG-4457 needs this to thread\n * the periodic discoverability hint under the primary scheduled delivery.\n */\nasync function postSlackChannelMessage(\n agentCodeName: string,\n channelId: string,\n text: string,\n threadTs?: string,\n): Promise<{ ok: boolean; ts?: string }> {\n const botToken = agentChannelTokens.get(agentCodeName)?.slack;\n if (!botToken) {\n log(`No Slack bot token cached for '${agentCodeName}' — cannot post to ${channelId}`);\n return { ok: false };\n }\n\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 5_000);\n try {\n const body: Record<string, unknown> = { channel: channelId, text };\n if (threadTs) body.thread_ts = threadTs;\n const response = await fetch('https://slack.com/api/chat.postMessage', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${botToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n clearTimeout(timeout);\n const data = await response.json() as { ok: boolean; error?: string; ts?: string };\n if (!data.ok) {\n log(`Slack chat.postMessage failed for '${agentCodeName}' to ${channelId}: ${data.error}`);\n return { ok: false };\n }\n return { ok: true, ts: data.ts };\n } finally {\n clearTimeout(timeout);\n }\n } catch (err) {\n log(`Slack channel message error for '${agentCodeName}': ${(err as Error).message}`);\n return { ok: false };\n }\n}\n\n// ENG-4457 — periodic discoverability hint. Rolled on each successful\n// scheduled delivery; on a hit, post one follow-up message (Slack as a\n// thread reply, Telegram as a second sendMessage) pointing the user at\n// \"you can change this schedule conversationally\". The core gating lives\n// in delivery-hint.ts so the decision is unit-testable.\n\nasync function maybePostSlackThreadHint(\n agentCodeName: string,\n channelId: string,\n primaryTs: string | undefined,\n): Promise<void> {\n if (!shouldIncludeHint(hintProbability(agentCodeName))) return;\n if (!primaryTs) {\n // No ts means we can't thread. Skip rather than post a loose second\n // message in the channel — that would look like noise.\n return;\n }\n const hint = pickHintVariant();\n const result = await postSlackChannelMessage(agentCodeName, channelId, hint, primaryTs);\n if (result.ok) {\n log(`[delivery-hint] Slack thread hint posted for '${agentCodeName}'`);\n }\n}\n\nasync function maybeSendTelegramFollowUpHint(\n agentCodeName: string,\n botToken: string,\n chatId: string | number,\n): Promise<void> {\n if (!shouldIncludeHint(hintProbability(agentCodeName))) return;\n const hint = pickHintVariant();\n try {\n const result = await telegramApiCall(botToken, 'sendMessage', { chat_id: chatId, text: hint });\n if (!result.ok) {\n // Bot API resolves `{ ok: false, description }` for app-level failures\n // (chat not found, bot blocked, rate limit). Without this branch the\n // hint drops silently despite the PR promising logged failures.\n log(`[delivery-hint] Telegram follow-up failed for '${agentCodeName}': ${result.description ?? 'unknown'}`);\n return;\n }\n log(`[delivery-hint] Telegram follow-up hint posted for '${agentCodeName}'`);\n } catch (err) {\n // Hint is a best-effort discoverability nudge — logging is enough.\n log(`[delivery-hint] Telegram follow-up failed for '${agentCodeName}': ${(err as Error).message}`);\n }\n}\n\nasync function sendSlackAlert(alerts: CronAlert[]): Promise<void> {\n const blocks = alerts.map((a) => {\n const emoji = a.type === 'late_standup' ? ':warning:' : ':x:';\n const label = a.type === 'late_standup' ? 'Late' : 'Failed';\n const scheduleInfo = a.schedule ? ` (${a.schedule})` : '';\n return `${emoji} *${label}* — *${a.agentDisplayName}* / ${a.taskName}${scheduleInfo}\\n${a.detail}`;\n });\n\n await sendSlackWebhookMessage(`:rotating_light: *Cron Health Alert*\\n\\n${blocks.join('\\n\\n')}`);\n}\n\n/**\n * Send a task notification to a specific channel (Slack or Telegram).\n */\n/** ENG-4422 §5–§6: deliver a scheduled task's output via a structured\n * DeliveryTarget (post-JSONB-migration) or a legacy `channel:<id>` /\n * `chat:<id>` opaque string (OpenClaw paths that still pre-date the\n * migration consumer-side).\n *\n * - Channel targets route via the existing Slack/Telegram send helpers.\n * - DM targets call @augmented/core/delivery.resolveDmTarget using the\n * agent's refresh payload (reports_to_slack_user_id / reports_to_person\n * ids, populated by host-runtime — see §8), then dispatch.\n * - Slack DMs open the IM via conversations.open and post with chat:write.\n * - Every DM body gets the attribution footer (§6).\n * - Failure status is posted to /host/schedules/delivery-status so the\n * server can update the observability columns (§10) + emit a\n * high-severity log line on 3+ consecutive failures.\n */\nasync function deliverScheduledTaskOutput(\n agentCodeName: string,\n agentId: string,\n rawTarget: unknown,\n body: string,\n taskId?: string,\n): Promise<void> {\n // ENG-4462: helper — wrap body with the schedule-edit link footer for the\n // given medium, honouring per-agent/host env kill switches. No-op when\n // taskId is absent or footer is disabled.\n const withLink = (b: string, medium: 'slack' | 'telegram'): string =>\n withScheduleLinkFooter({ body: b, medium, codeName: agentCodeName, agentId, taskId, log });\n\n // Legacy string form (OpenClaw -> manager-worker path, and any still-on-wire\n // rows we haven't flushed). Parse and route via sendTaskNotification.\n if (typeof rawTarget === 'string') {\n if (rawTarget.startsWith('channel:')) {\n const result = await sendTaskNotification(agentCodeName, 'slack', rawTarget, withLink(body, 'slack'));\n await reportDeliveryStatus(agentId, taskId, {\n status: result.ok ? 'ok' : 'failed',\n medium: 'slack',\n error_code: result.ok ? null : (result.error_code ?? 'SLACK_SEND_FAILED'),\n });\n return;\n }\n if (rawTarget.startsWith('chat:')) {\n const result = await sendTaskNotification(agentCodeName, 'telegram', rawTarget, withLink(body, 'telegram'));\n await reportDeliveryStatus(agentId, taskId, {\n status: result.ok ? 'ok' : 'failed',\n medium: 'telegram',\n error_code: result.ok ? null : (result.error_code ?? 'TELEGRAM_SEND_FAILED'),\n });\n return;\n }\n // Unknown string prefix — drop + log.\n log(`[delivery] Unrecognised legacy delivery_to string for '${agentCodeName}': ${rawTarget.slice(0, 60)}`);\n await reportDeliveryStatus(agentId, taskId, { status: 'failed', error_code: 'LEGACY_DELIVERY_TO_UNRECOGNISED' });\n return;\n }\n\n const parsed = parseDeliveryTarget(rawTarget);\n if (isParseError(parsed)) {\n log(`[delivery] Malformed delivery_to for '${agentCodeName}': ${parsed.code} — ${parsed.detail}`);\n await reportDeliveryStatus(agentId, taskId, { status: 'failed', error_code: parsed.code });\n return;\n }\n\n if (parsed.kind === 'channel') {\n if (parsed.provider === 'slack') {\n const channelId = parsed.channel_id ?? '';\n const sent = await postSlackChannelMessage(agentCodeName, channelId, withLink(body, 'slack'));\n await reportDeliveryStatus(agentId, taskId, {\n status: sent.ok ? 'ok' : 'failed',\n medium: 'slack',\n error_code: sent.ok ? null : 'SLACK_SEND_FAILED',\n });\n if (sent.ok) {\n await maybePostSlackThreadHint(agentCodeName, channelId, sent.ts);\n // ENG-4576 (PR #538): kick the API to post a threaded rating\n // prompt under the scheduled-task delivery. Server-side decides\n // whether to fire (block_kit_enabled, run outcome, idempotency).\n // Fire-and-forget — never block the delivery path on this.\n if (sent.ts && taskId) {\n await maybePostScheduledTaskRatingPrompt(agentId, taskId, channelId, sent.ts);\n }\n }\n return;\n }\n // Telegram channel-target\n const chatId = parsed.chat_id ?? '';\n const toStr = `chat:${chatId}`;\n const result = await sendTaskNotification(agentCodeName, 'telegram', toStr, withLink(body, 'telegram'));\n await reportDeliveryStatus(agentId, taskId, {\n status: result.ok ? 'ok' : 'failed',\n medium: 'telegram',\n error_code: result.ok ? null : (result.error_code ?? 'TELEGRAM_SEND_FAILED'),\n });\n if (result.ok) {\n const botToken = agentChannelTokens.get(agentCodeName)?.telegram;\n if (botToken) await maybeSendTelegramFollowUpHint(agentCodeName, botToken, chatId);\n }\n return;\n }\n\n // DM target — resolve person + medium, then dispatch with attribution footer.\n const agentRow = agentInfoForDelivery.get(agentCodeName);\n if (!agentRow) {\n log(`[delivery] No agent metadata cached for '${agentCodeName}' — dropping DM`);\n await reportDeliveryStatus(agentId, taskId, { status: 'failed', error_code: 'AGENT_METADATA_UNAVAILABLE' });\n return;\n }\n\n const resolved = resolveDmTarget(parsed, agentRow.resolverAgent, agentRow.peopleByPersonId);\n if (isResolveError(resolved)) {\n log(`[delivery] Cannot resolve DM target for '${agentCodeName}': ${resolved.code} — ${resolved.detail}`);\n await reportDeliveryStatus(agentId, taskId, { status: 'failed', error_code: resolved.code });\n return;\n }\n\n // Attribution footer first (§6), then the ENG-4462 schedule-edit link\n // underneath — order matters: the attribution identifies who sent the\n // message, the link lets the recipient act on it.\n const attributionBody = appendDmFooter(\n body,\n agentRow.ownerTeamName,\n agentRow.agentDisplayName,\n );\n const footeredBody =\n resolved.kind === 'dm'\n ? withLink(attributionBody, resolved.medium === 'slack' ? 'slack' : 'telegram')\n : attributionBody;\n\n if (resolved.kind === 'dm' && resolved.medium === 'slack') {\n const tokens = agentChannelTokens.get(agentCodeName);\n const botToken = tokens?.slack;\n if (!botToken) {\n await reportDeliveryStatus(agentId, taskId, { status: 'failed', error_code: 'SLACK_MISSING_SCOPE', medium: 'slack' });\n log(`[delivery] No Slack bot token for '${agentCodeName}' — DM dropped`);\n return;\n }\n // conversations.open → chat.postMessage. Slack accepts the user id for\n // chat.postMessage directly and will open/reuse the IM channel as long\n // as the bot has im:write.\n // CR #3108398187: 5-second abort on conversations.open matches the\n // timeout used by sendSlackChannelMessage; without it a stalled Slack\n // API call would tie up a scheduler slot indefinitely.\n try {\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => controller.abort(), 5_000);\n let openJson: { ok: boolean; error?: string; channel?: { id: string } };\n try {\n const openResp = await fetch('https://slack.com/api/conversations.open', {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${botToken}`,\n 'Content-Type': 'application/json; charset=utf-8',\n },\n body: JSON.stringify({ users: resolved.slack_user_id }),\n signal: controller.signal,\n });\n openJson = (await openResp.json()) as { ok: boolean; error?: string; channel?: { id: string } };\n } finally {\n clearTimeout(timeoutHandle);\n }\n if (!openJson.ok || !openJson.channel?.id) {\n const errCode = openJson.error === 'missing_scope' ? 'SLACK_MISSING_SCOPE' : `SLACK_OPEN_FAILED:${openJson.error ?? 'unknown'}`;\n log(`[delivery] conversations.open failed for '${agentCodeName}': ${openJson.error}`);\n await reportDeliveryStatus(agentId, taskId, { status: 'failed', error_code: errCode, medium: 'slack' });\n return;\n }\n const sent = await postSlackChannelMessage(agentCodeName, openJson.channel.id, footeredBody);\n await reportDeliveryStatus(agentId, taskId, {\n status: sent.ok ? 'ok' : 'failed',\n medium: 'slack',\n error_code: sent.ok ? null : 'SLACK_SEND_FAILED',\n });\n if (sent.ok) await maybePostSlackThreadHint(agentCodeName, openJson.channel.id, sent.ts);\n } catch (err) {\n const isAbort = (err as Error).name === 'AbortError';\n const errCode = isAbort ? 'SLACK_OPEN_TIMEOUT' : 'SLACK_EXCEPTION';\n log(`[delivery] Slack DM ${isAbort ? 'timeout' : 'failure'} for '${agentCodeName}': ${(err as Error).message}`);\n await reportDeliveryStatus(agentId, taskId, { status: 'failed', error_code: errCode, medium: 'slack' });\n }\n return;\n }\n\n if (resolved.kind === 'dm' && resolved.medium === 'telegram') {\n const tokens = agentChannelTokens.get(agentCodeName);\n const botToken = tokens?.telegram;\n if (!botToken) {\n log(`[delivery] No Telegram bot token for '${agentCodeName}' — DM dropped`);\n await reportDeliveryStatus(agentId, taskId, { status: 'failed', error_code: 'TELEGRAM_NO_TOKEN', medium: 'telegram' });\n return;\n }\n try {\n const result = await telegramApiCall(botToken, 'sendMessage', {\n chat_id: resolved.telegram_chat_id,\n text: footeredBody,\n });\n if (!result.ok) {\n log(`[delivery] Telegram DM failed for '${agentCodeName}': ${result.description}`);\n await reportDeliveryStatus(agentId, taskId, { status: 'failed', error_code: `TELEGRAM_SEND_FAILED:${result.description ?? 'unknown'}`, medium: 'telegram' });\n return;\n }\n await reportDeliveryStatus(agentId, taskId, { status: 'ok', medium: 'telegram' });\n await maybeSendTelegramFollowUpHint(agentCodeName, botToken, resolved.telegram_chat_id);\n } catch (err) {\n log(`[delivery] Telegram DM exception for '${agentCodeName}': ${(err as Error).message}`);\n await reportDeliveryStatus(agentId, taskId, { status: 'failed', error_code: 'TELEGRAM_EXCEPTION', medium: 'telegram' });\n }\n }\n}\n\n/** Per-agent metadata the delivery resolver needs. Populated during the\n * refresh cycle from the host-runtime payload (see §8). */\ninterface AgentDeliveryMetadata {\n agentDisplayName: string;\n ownerTeamName: string | null;\n resolverAgent: ResolverAgent;\n peopleByPersonId: Map<string, ResolverPerson>;\n}\nconst agentInfoForDelivery = new Map<string, AgentDeliveryMetadata>();\n\n/** Build `AgentDeliveryMetadata` from a refresh payload. Called whenever the\n * manager-worker syncs an agent so the resolver sees the latest reports_to /\n * owner_team / people-map state. */\nfunction cacheAgentDeliveryMetadata(codeName: string, refreshData: Record<string, unknown>): void {\n const agentRow = (refreshData['agent'] ?? {}) as Record<string, unknown>;\n const displayName = (agentRow['display_name'] as string | undefined) ?? codeName;\n const ownerTeamName = (agentRow['owner_team_name'] as string | null | undefined) ?? null;\n const framework = (agentRow['framework'] as string | undefined) ?? 'openclaw';\n const reportsToPersonId = (agentRow['reports_to'] as string | null | undefined) ?? null;\n const reportsToType = (agentRow['reports_to_type'] as 'person' | 'agent' | null | undefined) ?? null;\n\n // dm_capable_mediums: mirror the *same* \"active + credentialed\" predicate\n // the token cache uses (CR #3108333177). A channel config without a\n // bot_token isn't really DM-capable — with medium: 'auto', a pending or\n // broken Slack install would otherwise beat a healthy Telegram one.\n const channelConfigs = (refreshData['channel_configs'] ?? {}) as Record<string, { config?: Record<string, unknown> } | undefined>;\n const dmCapable: Array<'slack' | 'telegram'> = [];\n const slackBotToken = channelConfigs['slack']?.config?.['bot_token'];\n if (typeof slackBotToken === 'string' && slackBotToken.length > 0) {\n dmCapable.push('slack');\n }\n const telegramBotToken = channelConfigs['telegram']?.config?.['bot_token'];\n if (typeof telegramBotToken === 'string' && telegramBotToken.length > 0) {\n dmCapable.push('telegram');\n }\n\n const resolverAgent: ResolverAgent = {\n agent_id: (agentRow['agent_id'] as string) ?? '',\n framework,\n dm_capable_mediums: dmCapable,\n reports_to_person_id: reportsToType === 'person' ? reportsToPersonId : null,\n reports_to_type: reportsToType,\n };\n\n const peopleByPersonId = new Map<string, ResolverPerson>();\n // The reports_to person is carried directly on the agent row with its DM ids.\n if (reportsToPersonId && reportsToType === 'person') {\n peopleByPersonId.set(reportsToPersonId, {\n person_id: reportsToPersonId,\n display_name: (agentRow['reports_to_name'] as string | undefined) ?? 'Reports-To',\n slack_user_id: (agentRow['reports_to_slack_user_id'] as string | null | undefined) ?? null,\n telegram_chat_id: (agentRow['reports_to_telegram_chat_id'] as string | null | undefined) ?? null,\n });\n }\n // Other org people (for pinned dm:person:<id> targets) come via\n // refreshData.people. Keys may not be by person_id — guard and skip rows\n // without a person_id field.\n const people = (refreshData['people'] ?? []) as Array<Record<string, unknown>>;\n for (const p of people) {\n const personId = p['person_id'] as string | undefined;\n if (!personId) continue;\n const contactPrefs = (p['contact_preferences'] ?? {}) as Record<string, unknown>;\n peopleByPersonId.set(personId, {\n person_id: personId,\n display_name: (p['display_name'] as string | undefined) ?? 'person',\n slack_user_id: (contactPrefs['slack_user_id'] as string | null | undefined) ?? null,\n telegram_chat_id: (contactPrefs['telegram_chat_id'] as string | null | undefined) ?? null,\n });\n }\n\n agentInfoForDelivery.set(codeName, {\n agentDisplayName: displayName,\n ownerTeamName,\n resolverAgent,\n peopleByPersonId,\n });\n}\n\n/** POST dispatch status back to the API so the observability columns\n * (last_delivery_status / last_delivery_medium / last_delivery_at /\n * consecutive_failure_count) get updated. Best-effort — a transient API\n * failure here shouldn't take down the scheduler. */\nasync function reportDeliveryStatus(\n agentId: string,\n taskId: string | undefined,\n payload: { status: 'ok' | 'failed' | 'skipped'; medium?: 'slack' | 'telegram'; error_code?: string | null },\n): Promise<void> {\n if (!taskId) return;\n try {\n await api.post('/host/schedules/delivery-status', {\n agent_id: agentId,\n task_id: taskId,\n status: payload.status,\n medium: payload.medium ?? null,\n error_code: payload.error_code ?? null,\n });\n } catch (err) {\n log(`[delivery] Failed to report delivery status for ${agentId}/${taskId}: ${(err as Error).message}`);\n }\n}\n\n/** ENG-4581: dispatch any pending Claude Code OAuth pairing sessions\n * across this host's agents. Runs once per refresh cycle.\n *\n * Two states drive work:\n * - 'initiating' → run startClaudePair, report URL or error\n * - 'code_submitted' → run submitClaudePairCode, report success/failure\n *\n * Best-effort: a single bad session shouldn't block the others, and a\n * transient API failure just means we retry on the next refresh.\n */\ninterface PairableAgent { agentId: string; codeName: string }\n\n// Pair_ids the manager has spawned a tmux session for. Used to ask the\n// server \"of these, which are now terminal?\" so we can kill orphan\n// `agt-pair-<id>` tmux sessions left over from operator cancels or\n// connection drops.\nconst spawnedPairIds = new Set<string>();\n\nasync function processClaudePairSessions(agents: PairableAgent[]): Promise<void> {\n // Keep polling whenever we have orphan pair-tmux sessions to clean\n // up — even if the host temporarily has zero assigned agents.\n // Otherwise stale agt-pair-* sessions could linger until the manager\n // restarts.\n if (agents.length === 0 && spawnedPairIds.size === 0) return;\n const agentIds = agents.map((a) => a.agentId);\n const codeNameByAgentId = new Map(agents.map((a) => [a.agentId, a.codeName]));\n\n const pendingResp = await api.post<{\n pending: Array<{\n pair_id: string;\n agent_id: string;\n status: 'initiating' | 'code_submitted';\n code: string | null;\n created_at: string;\n updated_at: string;\n }>;\n cancelled_pair_ids?: string[];\n }>('/host/claude-pair/pending', {\n agent_ids: agentIds,\n spawned_pair_ids: Array.from(spawnedPairIds),\n });\n\n const {\n startClaudePair,\n submitClaudePairCode,\n spawnPairSession,\n killPairSession,\n pairTmuxSession,\n finalizeClaudePairOnboarding,\n } = await import('./claude-pair-runtime.js');\n\n // Sweep orphans the server flagged as terminal but still in our\n // spawned set (operator cancel, status reported via a different code\n // path, etc.). Kill the tmux session and forget about the id.\n for (const pairId of pendingResp.cancelled_pair_ids ?? []) {\n log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);\n const killed = await killPairSession(pairTmuxSession(pairId));\n if (killed) {\n spawnedPairIds.delete(pairId);\n } else {\n // Kill failed for a non-missing-session reason — leave the id in\n // spawnedPairIds so the next poll retries.\n log(`[claude-pair] kill-session failed for pair ${pairId.slice(0, 8)} — will retry on next poll`);\n }\n }\n\n if (!pendingResp.pending || pendingResp.pending.length === 0) return;\n\n // Terminal states cleared from the manager's perspective — once we\n // report any of these, the pair-scoped tmux session is no longer needed\n // and should be torn down.\n const TERMINAL: ReadonlySet<string> = new Set([\n 'success',\n 'failure',\n 'session_missing',\n 'timeout',\n ]);\n\n async function reportAndCleanup(\n pairId: string,\n body: Record<string, unknown>,\n ): Promise<void> {\n await api.post('/host/claude-pair/result', { pair_id: pairId, ...body });\n if (typeof body.status === 'string' && TERMINAL.has(body.status)) {\n // On success, give claude a beat to persist credentials to\n // ~/.claude/.credentials.json before we kill the tmux session.\n // Killing during the write window can leave the host with an\n // empty ~/.claude (claude shows the theme picker on next launch\n // as if no auth had ever happened).\n if (body.status === 'success') {\n await new Promise<void>((r) => setTimeout(r, 3_000));\n }\n const killed = await killPairSession(pairTmuxSession(pairId));\n if (killed) {\n spawnedPairIds.delete(pairId);\n }\n // If kill failed, the next poll's cancelled_pair_ids sweep will\n // retry — the row is now terminal so it'll be returned again.\n }\n }\n\n for (const session of pendingResp.pending) {\n // codeName is informational only — handy for log lines. The pair\n // flow drives a throwaway tmux session, NOT the agent's persistent\n // session, so we no longer require an entry in codeNameByAgentId.\n const codeName = codeNameByAgentId.get(session.agent_id) ?? '<unassigned>';\n const pairSession = pairTmuxSession(session.pair_id);\n\n try {\n if (session.status === 'initiating') {\n log(`[claude-pair] spawning pair session ${pairSession} for '${codeName}'`);\n const spawn = await spawnPairSession(pairSession);\n if (!spawn.ok) {\n await reportAndCleanup(session.pair_id, {\n status: 'failure',\n error_code: spawn.error.kind,\n error_message:\n spawn.error.kind === 'unknown' ? spawn.error.message : undefined,\n });\n continue;\n }\n spawnedPairIds.add(session.pair_id);\n\n log(`[claude-pair] dispatching /login (pair ${session.pair_id.slice(0, 8)})`);\n const result = await startClaudePair({ session: pairSession });\n if (result.kind === 'url') {\n await api.post('/host/claude-pair/result', {\n pair_id: session.pair_id,\n status: 'awaiting_code',\n url: result.url,\n });\n } else if (result.kind === 'timeout') {\n await reportAndCleanup(session.pair_id, {\n status: 'timeout',\n error_code: 'url_prompt_not_seen',\n error_message: 'Claude Code did not print the URL prompt within the deadline',\n });\n } else {\n const errKind = result.error.kind;\n // Forward the message for any kind that carries one. `oauth-retry-stuck`\n // ships actionable remediation text (\"clear ~/.claude/.credentials.json...\")\n // that operators need; the previous unknown-only filter dropped that\n // guidance and reduced the failure to a bare error_code.\n const errMessage =\n 'message' in result.error ? result.error.message : undefined;\n await reportAndCleanup(session.pair_id, {\n status: errKind === 'no-session' ? 'session_missing' : 'failure',\n error_code: errKind,\n error_message: errMessage,\n });\n }\n } else if (session.status === 'code_submitted' && session.code) {\n log(`[claude-pair] submitting code (pair ${session.pair_id.slice(0, 8)})`);\n const result = await submitClaudePairCode({\n session: pairSession,\n code: session.code,\n });\n if (result.kind === 'success') {\n // ENG-4633: drive Claude Code through its post-OAuth dialogs\n // so it persists ~/.claude.json before we kill the pair tmux\n // session. Without this step claude only wrote\n // ~/.claude/.credentials.json — the next agent launch hit\n // the login picker again. The result of this finalize is\n // logged but never gates the success report; even an\n // un-finalized pair still has working OAuth tokens, the\n // worst-case fallout is a one-time login picker on the\n // next agent launch (which ENG-4634 now surfaces).\n const finalize = await finalizeClaudePairOnboarding(pairSession, log);\n if (!finalize.finalized) {\n log(`[claude-pair] WARN: ~/.claude.json was not updated during onboarding (pair ${session.pair_id.slice(0, 8)}) — first agent launch may show the login picker`);\n }\n await reportAndCleanup(session.pair_id, { status: 'success' });\n } else if (result.kind === 'failure') {\n await reportAndCleanup(session.pair_id, {\n status: 'failure',\n error_code: 'invalid_code',\n error_message: result.rawMatch,\n });\n } else if (result.kind === 'timeout') {\n await reportAndCleanup(session.pair_id, {\n status: 'timeout',\n error_code: 'outcome_not_seen',\n error_message: 'Claude Code did not show a success/failure marker after submitting the code',\n });\n } else {\n const errKind = result.error.kind;\n // Forward the message for any kind that carries one. `oauth-retry-stuck`\n // ships actionable remediation text (\"clear ~/.claude/.credentials.json...\")\n // that operators need; the previous unknown-only filter dropped that\n // guidance and reduced the failure to a bare error_code.\n const errMessage =\n 'message' in result.error ? result.error.message : undefined;\n await reportAndCleanup(session.pair_id, {\n status: errKind === 'no-session' ? 'session_missing' : 'failure',\n error_code: errKind,\n error_message: errMessage,\n });\n }\n }\n } catch (err) {\n // Don't poison the loop. Log and let the next refresh retry —\n // pending rows stay in 'initiating' / 'code_submitted' until we\n // succeed or the operator gives up.\n log(`[claude-pair] dispatch failed for pair ${session.pair_id.slice(0, 8)}: ${(err as Error).message}`);\n }\n }\n}\n\n/** ENG-4576 (PR #538): kick the API to post a 1-5 rating prompt as a\n * threaded reply under a Slack scheduled-task delivery. Server-side\n * decides whether to fire (gated on block_kit_enabled, idempotent\n * per-run). Best-effort — never throws. */\nasync function maybePostScheduledTaskRatingPrompt(\n agentId: string,\n taskId: string,\n channelId: string,\n messageTs: string,\n): Promise<void> {\n try {\n await api.post('/host/scheduled-task/rating-prompt', {\n agent_id: agentId,\n task_id: taskId,\n channel: channelId,\n message_ts: messageTs,\n });\n } catch (err) {\n log(`[rating-prompt] Failed to post rating prompt for ${agentId}/${taskId}: ${(err as Error).message}`);\n }\n}\n\n/** Dispatch a message via the given channel.\n *\n * Returns `{ ok, error_code? }` so callers can faithfully report delivery\n * status to the observability columns rather than assuming a void call\n * succeeded (§10 / CR #3108333175). */\nasync function sendTaskNotification(\n agentCodeName: string,\n channel: string,\n to: string,\n text: string,\n): Promise<{ ok: boolean; error_code?: string }> {\n const tokens = agentChannelTokens.get(agentCodeName);\n\n if (channel === 'slack') {\n const botToken = tokens?.slack;\n const channelId = to.replace(/^channel:/, '');\n if (!botToken) {\n log(`No Slack bot token for '${agentCodeName}' — targeted notification dropped`);\n return { ok: false, error_code: 'SLACK_NO_TOKEN' };\n }\n const sent = await sendSlackChannelMessage(agentCodeName, channelId, text);\n return sent ? { ok: true } : { ok: false, error_code: 'SLACK_SEND_FAILED' };\n }\n\n if (channel === 'telegram') {\n const botToken = tokens?.telegram;\n const chatId = to.replace(/^chat:/, '');\n if (!botToken) {\n log(`No Telegram bot token for '${agentCodeName}' — notification dropped`);\n return { ok: false, error_code: 'TELEGRAM_NO_TOKEN' };\n }\n const allowedChats = tokens?.telegramAllowedChats;\n if (allowedChats && allowedChats.length > 0 && !allowedChats.includes(chatId)) {\n log(`Telegram chat ${chatId} not in allowed_chat_ids for '${agentCodeName}'`);\n return { ok: false, error_code: 'TELEGRAM_CHAT_NOT_ALLOWED' };\n }\n try {\n const result = await telegramApiCall(botToken, 'sendMessage', { chat_id: chatId, text });\n if (!result.ok) {\n log(`Telegram sendMessage failed for '${agentCodeName}': ${result.description}`);\n return { ok: false, error_code: `TELEGRAM_SEND_FAILED:${result.description ?? 'unknown'}` };\n }\n log(`Telegram notification sent for '${agentCodeName}' to chat ${chatId}`);\n return { ok: true };\n } catch (err) {\n log(`Telegram API error for '${agentCodeName}': ${(err as Error).message}`);\n return { ok: false, error_code: 'TELEGRAM_EXCEPTION' };\n }\n }\n\n log(`Unknown notify_channel '${channel}' for '${agentCodeName}'`);\n return { ok: false, error_code: `UNKNOWN_CHANNEL:${channel}` };\n}\n\n/**\n * Generate provision artifacts without writing to disk.\n * Returns the list of artifacts (relativePath + content) for comparison.\n */\nfunction generateArtifacts(\n agent: { agent_id: string; code_name: string; display_name: string; status: string; environment: string },\n refreshData: {\n agent: Record<string, unknown>;\n charter: { raw_content: string; version: string } | null;\n tools: { raw_content: string; version: string } | null;\n channel_configs: Record<string, { config: unknown; status: string }> | null;\n team_channel_policy: {\n team_id: string;\n allowed_channels: string[];\n denied_channels: string[];\n require_elevated_for_pii: boolean;\n } | null;\n team: { name: string; description: string | null; settings?: Record<string, unknown> } | null;\n scheduled_tasks?: unknown;\n knowledge?: Array<{ title: string; slug: string; content: string | null; scope: 'org' | 'team' }>;\n team_members?: Array<{ display_name: string; email?: string; role: string; title?: string; contact_channel?: string }>;\n people?: Array<{ display_name: string; email?: string; title?: string; department?: string; relationship?: string; contact_channel?: string }>;\n },\n adapter: FrameworkAdapter,\n): { relativePath: string; content: string }[] {\n if (!refreshData.charter || !refreshData.tools) {\n throw new Error('No charter/tools available');\n }\n\n const charterContent = refreshData.charter.raw_content;\n const toolsContent = refreshData.tools.raw_content;\n\n const charterParsed = extractFrontmatter(charterContent);\n if (!charterParsed.frontmatter) {\n throw new Error(`Failed to parse CHARTER.md frontmatter: ${charterParsed.error ?? 'unknown'}`);\n }\n\n const toolsParsed = extractFrontmatter(toolsContent);\n if (!toolsParsed.frontmatter) {\n throw new Error(`Failed to parse TOOLS.md frontmatter: ${toolsParsed.error ?? 'unknown'}`);\n }\n\n const charterFrontmatter = charterParsed.frontmatter as unknown as CharterFrontmatter;\n const toolsFrontmatter = toolsParsed.frontmatter as unknown as ToolsFrontmatter;\n\n const configuredChannels = Object.keys(refreshData.channel_configs ?? {}) as ChannelId[];\n\n const agentChannelPolicy: ChannelPolicy = {\n policy: 'allowlist',\n allowed: configuredChannels,\n denied: [],\n require_approval_to_change: true,\n };\n\n let orgChannelPolicy: OrgChannelPolicy | undefined;\n const policyData = refreshData.team_channel_policy;\n\n if (policyData) {\n orgChannelPolicy = {\n organization_id: policyData.team_id,\n allowed_channels: (policyData.allowed_channels ?? []) as ChannelId[],\n denied_channels: (policyData.denied_channels ?? []) as ChannelId[],\n require_elevated_for_pii: policyData.require_elevated_for_pii ?? false,\n };\n }\n\n const resolvedChannels = resolveChannels(agentChannelPolicy, orgChannelPolicy);\n const effectiveChannels = agent.status === 'paused' ? [] : resolvedChannels;\n\n // Resolve timezone deterministically — use unanimous task timezone, then team, then UTC\n const tasks = (refreshData as Record<string, unknown>).scheduled_tasks as Array<{ timezone?: string | null }> | null | undefined;\n const taskTimezones = [...new Set(\n (tasks ?? [])\n .map((t) => (typeof t.timezone === 'string' ? t.timezone.trim() : ''))\n .filter((tz): tz is string => tz.length > 0),\n )];\n const teamTimezoneRaw = (refreshData.team as { timezone?: unknown } | null | undefined)?.timezone;\n const teamTimezone = typeof teamTimezoneRaw === 'string' && teamTimezoneRaw.trim().length > 0 ? teamTimezoneRaw.trim() : undefined;\n const agentTimezone = (taskTimezones.length === 1 ? taskTimezones[0] : undefined) ?? teamTimezone ?? 'UTC';\n\n // Resolve personality seed: agent-level overrides org-level\n const agentPersonalitySeed = (refreshData.agent as Record<string, unknown>).personality_seed as string | undefined;\n const orgDefaults = (refreshData as Record<string, unknown>).model_defaults as { org?: { settings?: Record<string, unknown> } } | undefined;\n const personalitySeed = agentPersonalitySeed || orgDefaults?.org?.settings?.personality_seed as string | undefined;\n\n // Resolve reports_to reference (person or agent name)\n let reportsTo: ProvisionInput['reportsTo'];\n const agentData = refreshData.agent as Record<string, unknown>;\n const reportsToId = agentData.reports_to as string | null | undefined;\n const reportsToType = (agentData.reports_to_type as string | null | undefined) ?? 'agent';\n\n if (reportsToId) {\n const reportsToName = agentData.reports_to_name as string | undefined;\n const reportsToTitle = agentData.reports_to_title as string | undefined;\n const reportsToDesc = agentData.reports_to_description as string | undefined;\n if (reportsToName) {\n reportsTo = {\n name: reportsToName,\n type: reportsToType as 'agent' | 'person',\n title: reportsToTitle,\n description: reportsToDesc,\n };\n }\n }\n\n const provisionInput: ProvisionInput = {\n agent: refreshData.agent as any,\n charterFrontmatter,\n charterContent,\n toolsFrontmatter,\n toolsContent,\n resolvedChannels: effectiveChannels,\n deploymentTarget: 'local_docker' as DeploymentTarget,\n gatewayPort: 9000,\n team: refreshData.team ?? undefined,\n timezone: agentTimezone,\n reportsTo,\n personalitySeed,\n knowledge: (refreshData.knowledge ?? []).filter((k): k is { title: string; slug: string; content: string; scope: 'org' | 'team' } => !!k.content),\n knowledgeDelivery: ((refreshData.agent as Record<string, unknown>).knowledge_delivery as 'search' | 'files' | 'both' | undefined) ?? 'both',\n teamMembers: refreshData.team_members ?? undefined,\n people: refreshData.people ?? undefined,\n };\n\n const provisionOutput = provision(provisionInput, adapter.id);\n return provisionOutput.artifacts;\n}\n\n// ---------------------------------------------------------------------------\n// Memory sync — bidirectional sync between local files and agent_memories DB\n// ---------------------------------------------------------------------------\n\n/** Per-file content hashes to detect individual memory changes */\nconst memoryFileHashes = new Map<string, Map<string, string>>(); // agentId → (fileName → hash)\n/** Hash of DB memories response to skip redundant downloads */\nconst lastDownloadHash = new Map<string, string>(); // agentId → hash\n/** Hash of local file listing to skip redundant download checks */\nconst lastLocalFileHash = new Map<string, string>(); // agentId → hash\n\n/**\n * ENG-4643: agents that have just been assigned to this host (or have\n * otherwise been flagged as needing a clean re-pull from the API).\n * Set populated by the onAssign realtime callback; consumed at the\n * top of the next syncMemories run for that agent. The pending tick\n * skips the local-list-hash short-circuit, downloads first (so DB is\n * authoritative on the migration boundary, never overwritten by stale\n * local files), and clears the per-agent in-memory caches so a long-\n * running manager that previously hosted this agent doesn't carry\n * stale hashes that would suppress fresh writes.\n */\nconst pendingFreshMemorySync = new Set<string>(); // agent_id\n\nexport function markAgentForFreshMemorySync(agentId: string): void {\n pendingFreshMemorySync.add(agentId);\n memoryFileHashes.delete(agentId);\n lastDownloadHash.delete(agentId);\n lastLocalFileHash.delete(agentId);\n}\n\nfunction parseMemoryFile(raw: string, fallbackName: string): { name: string; content: string; type: string } | null {\n const trimmed = raw.trim();\n if (!trimmed) return null;\n\n const fmMatch = trimmed.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n([\\s\\S]*)$/);\n\n if (fmMatch) {\n // Has YAML frontmatter — parse structured fields\n const frontmatter = fmMatch[1] ?? '';\n const body = (fmMatch[2] ?? '').trim();\n if (!body) return null;\n\n const nameMatch = frontmatter.match(/^name:\\s*(.+)$/m);\n const typeMatch = frontmatter.match(/^type:\\s*(.+)$/m);\n const name = nameMatch?.[1]?.trim().replace(/^[\"']|[\"']$/g, '') ?? fallbackName;\n const rawType = typeMatch?.[1]?.trim().replace(/^[\"']|[\"']$/g, '') ?? 'user';\n\n const typeMap: Record<string, string> = { user: 'user', feedback: 'feedback', project: 'project', reference: 'reference', daily: 'daily' };\n const type = typeMap[rawType] ?? 'user';\n\n return { name, content: body, type };\n }\n\n // No frontmatter — treat the entire file as content.\n // Derive type from filename pattern: date-like names (YYYY-MM-DD) → daily, else project.\n const isDaily = /^\\d{4}-\\d{2}-\\d{2}$/.test(fallbackName);\n return {\n name: fallbackName,\n content: trimmed,\n type: isDaily ? 'daily' : 'project',\n };\n}\n\nasync function syncMemories(\n agent: { agent_id: string; code_name: string },\n configDir: string,\n log: (msg: string) => void,\n): Promise<void> {\n const projectDir = join(configDir, agent.code_name, 'project');\n const memoryDir = join(projectDir, 'memory');\n\n // ENG-4643: on a fresh-sync tick (post-migration / post-assignment) we\n // download first and let the DB be authoritative. Any stale local\n // file from a prior provision gets overwritten when its DB twin has\n // different content. Subsequent ticks fall through to the regular\n // upload-then-download bidirectional flow.\n //\n // Skip the upload path entirely when the download fails — without\n // a confirmed catch-up pull, any local .md files we'd upload risk\n // overwriting fresher DB content with stale local copies (the\n // exact scenario we're guarding against). Leave the pending flag\n // set so the next supervisor tick retries the download.\n const isFreshSync = pendingFreshMemorySync.has(agent.agent_id);\n if (isFreshSync) {\n log(`[memory-sync] Fresh-sync requested for '${agent.code_name}' — pulling DB first`);\n const ok = await downloadMemories(agent, memoryDir, log, { force: true });\n if (!ok) {\n log(`[memory-sync] Fresh-sync download failed for '${agent.code_name}' — skipping upload, will retry next tick`);\n return;\n }\n pendingFreshMemorySync.delete(agent.agent_id);\n }\n\n // --- Upload: local → DB (only changed files) ---\n if (existsSync(memoryDir)) {\n const prevHashes = memoryFileHashes.get(agent.agent_id) ?? new Map<string, string>();\n const currentHashes = new Map<string, string>();\n const changedMemories: Array<{ name: string; content: string; type: string; expires_at?: string }> = [];\n\n for (const file of readdirSync(memoryDir)) {\n if (!file.endsWith('.md')) continue;\n try {\n const raw = readFileSync(join(memoryDir, file), 'utf-8');\n const fileHash = createHash('sha256').update(raw).digest('hex').slice(0, 16);\n currentHashes.set(file, fileHash);\n\n // Only parse + upload if file content changed since last cycle\n if (prevHashes.get(file) === fileHash) continue;\n\n const parsed = parseMemoryFile(raw, file.replace(/\\.md$/, ''));\n if (parsed) {\n // Daily memories require expires_at (DB constraint)\n if (parsed.type === 'daily') {\n (parsed as { expires_at?: string }).expires_at = new Date(Date.now() + 48 * 60 * 60 * 1000).toISOString();\n }\n changedMemories.push(parsed);\n }\n } catch { /* skip unreadable */ }\n }\n\n memoryFileHashes.set(agent.agent_id, currentHashes);\n\n if (changedMemories.length > 0) {\n try {\n await api.post('/host/sync-memories', {\n agent_id: agent.agent_id,\n memories: changedMemories,\n });\n log(`Synced ${changedMemories.length} changed memories to DB for '${agent.code_name}'`);\n } catch (err) {\n // Reset hashes for changed files so they retry next cycle\n for (const mem of changedMemories) {\n for (const [file] of currentHashes) {\n const parsed = parseMemoryFile(readFileSync(join(memoryDir, file), 'utf-8'), file.replace(/\\.md$/, ''));\n if (parsed?.name === mem.name) currentHashes.delete(file);\n }\n }\n log(`Memory upload failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n }\n\n // --- Download: DB → local (skip if local files unchanged) ---\n // ENG-4643: skipped when isFreshSync ran above (we already pulled).\n if (!isFreshSync) {\n await downloadMemories(agent, memoryDir, log, { force: false });\n }\n}\n\n/**\n * Pull DB memories and write them to the local memory dir, reconciling\n * against existing files by CONTENT hash instead of slug presence.\n *\n * ENG-4643:\n * - Old behaviour: skip the DB version whenever a same-slug local\n * file existed, regardless of whether the local content matched.\n * Stale provision leftovers permanently shadowed DB updates.\n * - New behaviour: derive slug + render the canonical file body for\n * each DB memory; if the existing file's bytes match exactly,\n * skip; otherwise overwrite. Files with no DB twin are left alone\n * (the upload path handles those on the next pass).\n * - When `force` is true, the local-listing-hash short-circuit is\n * bypassed so the migration / fresh-assignment first sync always\n * reaches the network call.\n */\nasync function downloadMemories(\n agent: { agent_id: string; code_name: string },\n memoryDir: string,\n log: (msg: string) => void,\n { force }: { force: boolean },\n): Promise<boolean> {\n const localFiles = existsSync(memoryDir)\n ? readdirSync(memoryDir).filter((f) => f.endsWith('.md')).sort()\n : [];\n const localListHash = createHash('sha256').update(localFiles.join(',')).digest('hex').slice(0, 16);\n const prevLocalHash = lastLocalFileHash.get(agent.agent_id);\n const prevDownload = lastDownloadHash.get(agent.agent_id);\n\n try {\n const dbMemories = await api.post<{ memories: Array<{ name: string; type: string; content: string }> }>('/host/memories', {\n agent_id: agent.agent_id,\n });\n\n // Hash the response to detect changes server-side\n const responseHash = createHash('sha256')\n .update(JSON.stringify(dbMemories.memories ?? []))\n .digest('hex').slice(0, 16);\n\n // Skip processing if nothing changed (local files same + DB response same).\n // `force=true` callers (post-migration first sync) ignore this guard.\n if (\n !force &&\n prevDownload &&\n prevLocalHash === localListHash &&\n lastDownloadHash.get(agent.agent_id) === responseHash\n ) {\n return true;\n }\n\n lastDownloadHash.set(agent.agent_id, responseHash);\n lastLocalFileHash.set(agent.agent_id, localListHash);\n\n if (dbMemories.memories?.length) {\n mkdirSync(memoryDir, { recursive: true });\n\n let written = 0;\n let overwritten = 0;\n for (let i = 0; i < dbMemories.memories.length; i++) {\n const mem = dbMemories.memories[i]!;\n const rawSlug = mem.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '').slice(0, 60);\n const slug = rawSlug || `memory-${i}`;\n const filePath = join(memoryDir, `${slug}.md`);\n const desired = `---\\nname: ${JSON.stringify(mem.name)}\\ntype: ${mem.type}\\ndescription: ${JSON.stringify(mem.content.slice(0, 200))}\\n---\\n\\n${mem.content}\\n`;\n\n if (existsSync(filePath)) {\n // Content compare — DB wins on mismatch (the local file is\n // either a stale provision leftover or out-of-band edited\n // copy that the upload path didn't catch).\n let existing = '';\n try { existing = readFileSync(filePath, 'utf-8'); } catch { /* unreadable → overwrite */ }\n if (existing === desired) continue;\n writeFileSync(filePath, desired);\n overwritten++;\n } else {\n writeFileSync(filePath, desired);\n written++;\n }\n }\n\n if (written > 0 || overwritten > 0) {\n // Update local file hash since we just wrote new/changed files\n const updatedFiles = readdirSync(memoryDir).filter((f) => f.endsWith('.md')).sort();\n lastLocalFileHash.set(agent.agent_id, createHash('sha256').update(updatedFiles.join(',')).digest('hex').slice(0, 16));\n log(`Memory download for '${agent.code_name}': wrote ${written} new, overwrote ${overwritten} stale`);\n }\n }\n return true;\n } catch (err) {\n log(`Memory download failed for '${agent.code_name}': ${(err as Error).message}`);\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Cleanup helper (for revoked agents)\n// ---------------------------------------------------------------------------\n\nasync function cleanupAgentFiles(codeName: string, agentDir: string): Promise<void> {\n // Remove the provision directory\n if (existsSync(agentDir)) {\n try {\n rmSync(agentDir, { recursive: true, force: true });\n log(`Removed provision directory for '${codeName}'`);\n } catch (err) {\n log(`Failed to remove provision dir for '${codeName}': ${(err as Error).message}`);\n }\n }\n\n // Deregister from the framework runtime\n try {\n const adapter = resolveAgentFramework(codeName);\n const registered = await getOrCacheRegisteredAgents(adapter, codeName);\n if (registered.has(codeName)) {\n await adapter.deregisterAgent(codeName);\n registered.delete(codeName);\n log(`Deregistered '${codeName}' from ${adapter.label}`);\n }\n } catch (err) {\n log(`Failed to deregister '${codeName}': ${(err as Error).message}`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// IPC + lifecycle\n// ---------------------------------------------------------------------------\n\nfunction startGatewayPool(): void {\n if (gatewayPool) return;\n\n gatewayPool = new GatewayClientPool();\n\n gatewayPool.on('connected', (codeName: string) => {\n log(`Gateway WebSocket connected for '${codeName}'`);\n });\n\n gatewayPool.on('disconnected', (codeName: string) => {\n log(`Gateway WebSocket disconnected for '${codeName}'`);\n });\n\n gatewayPool.on('error', (err: Error, codeName: string) => {\n log(`Gateway WebSocket error for '${codeName}': ${err.message}`);\n });\n\n gatewayPool.on('event', (evt: PooledGatewayEvent) => {\n // Forward to parent via IPC\n send({ type: 'gateway-event', event: evt.event, payload: evt.payload, agentCodeName: evt.agentCodeName });\n\n // Forward to API (fire and forget)\n getHostId().then((hostId) => {\n if (!hostId) return;\n api.post('/host/events', {\n host_id: hostId,\n agent_code_name: evt.agentCodeName,\n event_type: evt.event,\n payload: evt.payload,\n timestamp: new Date().toISOString(),\n }).catch((err) => {\n log(`Failed to forward gateway event: ${(err as Error).message}`);\n });\n }).catch(() => {\n // Ignore — host ID resolution is best-effort here\n });\n });\n}\n\nfunction stopGatewayPool(): void {\n if (gatewayPool) {\n gatewayPool.disconnectAll();\n gatewayPool = null;\n }\n}\n\n// Caffeinate subprocess — prevents macOS sleep while manager is running\nlet caffeinateProc: import('node:child_process').ChildProcess | null = null;\n\nasync function startCaffeinate(): Promise<void> {\n if (process.platform !== 'darwin') return;\n try {\n const { spawn } = await import('node:child_process');\n caffeinateProc = spawn('caffeinate', ['-dims'], {\n stdio: 'ignore',\n detached: false,\n });\n caffeinateProc.on('error', (err) => {\n log(`caffeinate failed: ${err.message} — Mac may sleep during operation`);\n caffeinateProc = null;\n });\n caffeinateProc.on('exit', () => { caffeinateProc = null; });\n if (caffeinateProc.pid) {\n log(`caffeinate started (PID ${caffeinateProc.pid}) — Mac sleep prevented while manager is running`);\n }\n } catch (err) {\n log(`caffeinate not available: ${(err as Error).message} — Mac may sleep during operation`);\n }\n}\n\nfunction stopCaffeinate(): void {\n if (caffeinateProc) {\n caffeinateProc.kill();\n caffeinateProc = null;\n log('caffeinate stopped');\n }\n}\n\nfunction startPolling(): void {\n if (!config || running) return;\n running = true;\n\n void startCaffeinate();\n // ENG-4712: rehydrate the channel-config hash cache from disk so a\n // fresh manager process recognises credentials it (or a sibling\n // manager) already wrote on a previous run, and skips the noisy\n // `reason=first-write` rewrite for unchanged configs.\n loadChannelHashCache();\n log(`Starting poll loop (interval=${config.intervalMs}ms, configDir=${config.configDir})`);\n\n // Kill any orphaned agt-* tmux sessions from a previous manager run,\n // then start the poll loop. Must await to avoid race with session spawn.\n void killAllAgtTmuxSessions().catch(() => {}).then(() => migrateToProfiles()).then(() => {\n startGatewayPool();\n return pollCycle();\n }).then(() => {\n scheduleNext();\n });\n}\n\nfunction scheduleNext(): void {\n if (!running || !config) return;\n\n // ENG-4488: if the self-update flipped this during the poll we just\n // finished, exit cleanly instead of scheduling another cycle. Also\n // re-check inside the timer callback — `checkAndUpdateCli()` is kicked\n // off without await, so the flag can flip between scheduleNext() and\n // the next fire. Without the second check we'd run one more pollCycle\n // on the old Cellar path, exactly the failure this is meant to avoid.\n // forcedExitCode: 0 so the supervisor treats this as a clean respawn\n // even if graceful cleanup hits its 15s backstop.\n const restartNow = (): void => {\n log(`[self-update] Restarting manager to load upgraded binary (${pendingUpgradeVersion ?? 'unknown version'})`);\n // Exit with the dedicated restart code so the supervisor distinguishes\n // \"please respawn me\" from \"the operator asked me to stop\" — overloading\n // exit code 0 would make `agt manager stop` unable to actually stop\n // a supervised manager.\n void stopPolling({ forcedExitCode: SUPERVISOR_RESTART_EXIT_CODE }).then(() => {\n process.exit(SUPERVISOR_RESTART_EXIT_CODE);\n });\n };\n\n if (restartAfterUpgrade) {\n restartNow();\n return;\n }\n pollTimer = setTimeout(() => {\n if (restartAfterUpgrade) {\n restartNow();\n return;\n }\n void pollCycle().then(scheduleNext);\n }, config.intervalMs);\n}\n\nasync function killAllAgtTmuxSessions(): Promise<void> {\n try {\n const { stdout: output } = await execFilePromiseLong(\n 'tmux',\n ['list-sessions', '-F', '#{session_name}'],\n { timeout: 2_000, stdin: 'ignore' },\n );\n const sessions = output.trim().split('\\n').filter((s) => s.startsWith('agt-'));\n for (const session of sessions) {\n try {\n await execFilePromiseLong('tmux', ['kill-session', '-t', session], {\n timeout: 2_000,\n stdin: 'ignore',\n });\n log(`Killed tmux session '${session}'`);\n } catch { /* session may have already exited */ }\n }\n if (sessions.length > 0) {\n log(`Cleaned up ${sessions.length} agt-* tmux session(s)`);\n }\n } catch {\n // tmux not installed or no server running — nothing to clean up\n }\n}\n\nasync function stopPolling(opts: { forcedExitCode?: number } = {}): Promise<void> {\n running = false;\n if (pollTimer) {\n clearTimeout(pollTimer);\n pollTimer = null;\n }\n\n // Hard backstop: force exit after 15s if graceful cleanup hangs. Default\n // exit code is 1 (crash-signal) so a SIGTERM handler that can't drain\n // surfaces as a failure to any supervisor. The self-update restart path\n // passes forcedExitCode: 0 so a stuck cleanup after `brew upgrade`\n // doesn't poison the respawn — the supervisor still sees clean exit and\n // re-spawns onto the new binary.\n const shutdownTimer = setTimeout(() => {\n log('Shutdown timeout exceeded (15s), forcing exit');\n process.exit(opts.forcedExitCode ?? 1);\n }, 15000);\n shutdownTimer.unref();\n\n stopCaffeinate();\n stopRealtimeChat();\n stopGatewayPool();\n\n // Kill all agt-* tmux sessions FIRST — this is the most critical cleanup\n // Do it before graceful session stop to ensure sessions are killed even if\n // the graceful path hangs or times out.\n log('Killing tmux sessions...');\n await killAllAgtTmuxSessions();\n\n // Then gracefully stop persistent sessions (no-op if already killed)\n log('Stopping persistent sessions...');\n await stopAllSessionsAndWait(log, { timeoutMs: 4_000 });\n\n // Stop all per-agent gateway processes\n log('Stopping gateway processes...');\n await stopAllGateways();\n\n clearTimeout(shutdownTimer);\n}\n\n// ---------------------------------------------------------------------------\n// Public API — called directly by the CLI command (no fork/IPC)\n// ---------------------------------------------------------------------------\n\n/**\n * Start the manager poll loop. Call this directly from the CLI command.\n * The process stays alive running the poll loop until stopManager() is called\n * or a signal is received.\n */\nexport function startManager(opts: { intervalMs: number; configDir: string }): void {\n config = opts;\n // ENG-4658: First log line on every fresh worker. Two purposes:\n // 1. Proves the on-disk log destination is writable from this worker\n // (the bug was a silent write to a stdio fd that pointed nowhere).\n // 2. Gives operators a \"manager process boundary\" marker so a long\n // manager.log can be split into per-worker segments by grepping\n // for `[startup] worker pid=`.\n // If this line doesn't appear in manager.log shortly after a fresh\n // spawn, the worker can't write to the file and the operator should\n // check ~/.augmented permissions.\n log(\n `[startup] worker pid=${process.pid} ppid=${process.ppid} ` +\n `node=${process.version} log=${join(homedir(), '.augmented', 'manager.log')}`,\n );\n deployMcpAssets();\n // ENG-4808: reap any channel-MCP processes that were left orphaned by\n // a prior manager crash or unclean exit. The per-restart reap inside\n // stopPersistentSession handles the in-process case; this catches the\n // pre-existing-orphan case where the manager process itself died with\n // children still attached. Idempotent: a clean restart with no orphans\n // is a no-op.\n reapOrphanChannelMcps({ log });\n // Fetch the host's own framework from /host/exchange and ensure the\n // framework's binaries are installed. Runs async so startup isn't blocked\n // on a brew install — the binary is required only when agents are launched,\n // not for the polling loop itself. Failures are logged, never fatal.\n void ensureHostFrameworkBinaries();\n startPolling();\n}\n\nasync function ensureHostFrameworkBinaries(): Promise<void> {\n const apiKey = getApiKey();\n if (!apiKey) {\n log('[framework-install] AGT_API_KEY not set — skipping host framework binary check');\n return;\n }\n try {\n const exchange = await exchangeApiKey(apiKey);\n if (!exchange.framework) {\n log('[framework-install] host has no framework set — skipping');\n return;\n }\n log(`[framework-install] host framework = ${exchange.framework}`);\n await ensureFrameworkBinary(exchange.framework);\n } catch (err) {\n log(`[framework-install] failed: ${(err as Error).message}`);\n }\n}\n\n/**\n * Deploy bundled MCP server files to ~/.augmented/_mcp/ if they don't exist\n * or are outdated. This ensures prod hosts have the Slack channel server\n * and augmented MCP server without manual file copying.\n */\n/**\n * SIGTERM every running instance of the given channel MCP basenames so they\n * respawn with the newly-deployed bundle on next message.\n *\n * Channel MCPs are stdio children of each agent's Claude Code tmux session —\n * when they exit, Claude Code auto-respawns them via the entry in .mcp.json.\n * That respawn reads the freshly-written bundle from ~/.augmented/_mcp/, so\n * all we need is a death signal.\n *\n * Matches by command line containing the basename — `ps` output on macOS +\n * Linux both include the full path, so `/path/to/slack-channel.js` grep-hits\n * reliably. Not SIGKILL: the channel servers already handle SIGTERM cleanly\n * (ENG-4452) to drain in-flight Socket Mode events.\n */\nfunction restartRunningChannelMcps(basenames: string[]): void {\n try {\n const psOutput = syncExecFile(\n 'ps',\n ['-eo', 'pid=,command='],\n { encoding: 'utf-8', timeout: 5_000 },\n );\n const lines = psOutput.split('\\n').filter((l) => l.trim());\n const selfPid = process.pid;\n let signalled = 0;\n for (const line of lines) {\n const match = line.match(/^\\s*(\\d+)\\s+(.*)$/);\n if (!match) continue;\n const pid = Number(match[1]);\n const command = match[2]!;\n if (pid === selfPid) continue;\n // Only match JS basenames in the command path; env-var substrings like\n // SOMEPATH=/fake/slack-channel.js don't count because the basename\n // token is followed by whitespace or end-of-line when it's the actual\n // script being executed.\n const hit = basenames.find((b) => new RegExp(`/${b}\\\\.js(\\\\s|$)`).test(command));\n if (!hit) continue;\n try {\n process.kill(pid, 'SIGTERM');\n signalled++;\n log(`[manager] sent SIGTERM to pid=${pid} (${hit}.js) — will respawn on next message`);\n } catch (err) {\n // ESRCH = already dead; others are \"not our process\" (EPERM). Ignore.\n const code = (err as NodeJS.ErrnoException).code;\n if (code && code !== 'ESRCH') {\n log(`[manager] failed to signal pid=${pid}: ${code}`);\n }\n }\n }\n if (signalled === 0) {\n log(`[manager] no running instances to restart (deploy still applies to future spawns)`);\n }\n } catch (err) {\n log(`[manager] restartRunningChannelMcps failed: ${(err as Error).message}`);\n }\n}\n\nfunction deployMcpAssets(): void {\n const targetDir = join(homedir(), '.augmented', '_mcp');\n mkdirSync(targetDir, { recursive: true });\n\n // Resolve MCP files from the CLI package's mcp/ directory\n const moduleDir = dirname(fileURLToPath(import.meta.url));\n // Dev: src/lib → ../../mcp, Built: dist/lib → ../../mcp, npm global: dist/lib → ../../mcp\n let mcpSourceDir = '';\n let dir = moduleDir;\n for (let i = 0; i < 6; i++) {\n const candidate = join(dir, 'mcp');\n if (existsSync(join(candidate, 'index.js'))) {\n mcpSourceDir = candidate;\n break;\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n\n if (!mcpSourceDir) {\n log('[manager] MCP assets not found in CLI package — skipping deployment');\n return;\n }\n\n // Track which channel basenames got a new bundle so we can signal running\n // instances to restart. Without this, already-spawned channel MCP children\n // (parented to each agent's Claude Code tmux session) keep running the\n // previous bundle from memory — `deployMcpAssets` only updates the file\n // on disk. That's exactly the scenario where a merged branch like ENG-4503\n // (Slack image auto-download) or ENG-4504 (Telegram image auto-download)\n // silently \"doesn't work\" until the operator manually restarts every agent.\n const changedBasenames: string[] = [];\n const fileHash = (p: string): string | null => {\n try {\n if (!existsSync(p)) return null;\n return createHash('sha256').update(readFileSync(p)).digest('hex');\n } catch {\n return null;\n }\n };\n\n // Only these basenames are eligible for deploy-triggered SIGTERM. `index.js`\n // is deployed alongside the channels but must NEVER trigger the restart path\n // because countless unrelated Node processes on the host have an `index.js`\n // entrypoint — matching on `/index.js` would SIGTERM any of them. The list\n // below is the closed set of channel bundles whose process commands reliably\n // end in `/<basename>.js` with no collisions.\n const RESTARTABLE_CHANNEL_FILES = new Set([\n 'slack-channel.js',\n 'direct-chat-channel.js',\n 'telegram-channel.js',\n ]);\n\n for (const file of ['index.js', 'slack-channel.js', 'direct-chat-channel.js', 'telegram-channel.js']) {\n const src = join(mcpSourceDir, file);\n const dst = join(targetDir, file);\n if (!existsSync(src)) continue;\n const before = fileHash(dst);\n try {\n // Always overwrite to ensure latest version\n copyFileSync(src, dst);\n const after = fileHash(dst);\n if (before !== after && RESTARTABLE_CHANNEL_FILES.has(file)) {\n // file is 'slack-channel.js' → basename 'slack-channel' (matches the\n // ChannelType enum in channel-sweep.ts)\n changedBasenames.push(file.replace(/\\.js$/, ''));\n }\n } catch (err) {\n log(`[manager] Failed to deploy ${file}: ${(err as Error).message}`);\n }\n }\n log(`[manager] MCP assets deployed to ${targetDir}`);\n\n if (changedBasenames.length > 0) {\n log(`[manager] Bundle(s) updated: ${changedBasenames.join(', ')} — signalling running instances to restart`);\n restartRunningChannelMcps(changedBasenames);\n }\n\n // Patch any agent .mcp.json files that still reference the npx fallback\n // (which was never published). Replace with the local node path.\n const localMcpPath = join(targetDir, 'index.js');\n try {\n const agentsDir = join(homedir(), '.augmented', 'agents');\n if (existsSync(agentsDir)) {\n for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue;\n // Check both provision and project dirs\n for (const subdir of ['provision', 'project']) {\n const mcpJsonPath = join(agentsDir, entry.name, subdir, '.mcp.json');\n try {\n const raw = readFileSync(mcpJsonPath, 'utf-8');\n if (!raw.includes('@integrity-labs/augmented-mcp')) continue;\n const mcpConfig = JSON.parse(raw) as { mcpServers?: Record<string, Record<string, unknown>> };\n const augServer = mcpConfig.mcpServers?.['augmented'];\n if (!augServer) continue;\n augServer.command = 'node';\n augServer.args = [localMcpPath];\n writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2));\n log(`[manager] Patched ${entry.name}/${subdir}/.mcp.json: npx → node`);\n } catch {\n // File doesn't exist or isn't valid JSON — skip\n }\n }\n }\n }\n } catch (err) {\n log(`[manager] Failed to patch agent .mcp.json files: ${(err as Error).message}`);\n }\n}\n\n/**\n * Stop the manager poll loop gracefully.\n */\nexport async function stopManager(): Promise<void> {\n await stopPolling();\n}\n\n// Graceful shutdown on signals\nlet shuttingDown = false;\nfor (const sig of ['SIGTERM', 'SIGINT'] as const) {\n process.on(sig, () => {\n if (shuttingDown) {\n log(`Received ${sig} again during shutdown — ignoring (cleanup in progress)`);\n return;\n }\n shuttingDown = true;\n log(`Received ${sig}, shutting down`);\n void stopPolling().then(() => {\n process.exit(0);\n });\n });\n}\n\n// If parent disconnects, exit\nprocess.on('disconnect', () => {\n log('Parent disconnected, exiting');\n void stopPolling().then(() => {\n process.exit(0);\n });\n});\n","/**\n * ENG-4897 — pure decision helper for `.mcp.json` drift detection.\n *\n * Extracted from manager-worker.ts so unit tests can exercise the\n * decision tree without importing the entire worker (which has heavy\n * side effects at module load).\n *\n * The manager polls every 10s. After it deploys the latest `.mcp.json`\n * to a persistent claude session's project directory, it asks: has the\n * file changed since the running claude was launched? Three outcomes\n * matter:\n * - No file on disk yet → nothing to compare.\n * - First observation (no recorded baseline) → adopt current as baseline.\n * - Mismatch → schedule a tmux respawn so claude reads the fresh config.\n *\n * The optimistic baseline (we adopt whatever's on disk on first poll\n * after manager start) is the trade-off documented in the issue. Worst\n * case is one missed drift event after a manager restart; the next\n * change is caught.\n */\n\nexport type McpDriftAction =\n | { kind: 'no-config' }\n | { kind: 'baseline'; hash: string }\n | { kind: 'no-drift' }\n | { kind: 'drift'; previous: string; current: string };\n\nexport function decideMcpDriftAction(\n currentHash: string | null,\n knownHash: string | undefined,\n): McpDriftAction {\n if (!currentHash) return { kind: 'no-config' };\n if (!knownHash) return { kind: 'baseline', hash: currentHash };\n if (knownHash === currentHash) return { kind: 'no-drift' };\n return { kind: 'drift', previous: knownHash, current: currentHash };\n}\n","/**\n * ENG-4911 — pure helper for the integration-change hash used by the\n * `/host/refresh` reconciliation loop in manager-worker.\n *\n * Originally the hash covered only `definition_id + credentials`, which\n * meant a config-only change in the database (e.g. switching\n * `xero_tenant_id` from one tenant to another without rotating the\n * access token) silently failed to propagate: the hash matched the\n * stored value, the manager short-circuited before `writeIntegrations`,\n * `.env.integrations` was never rewritten, and the running MCP child\n * kept calling Xero with the old tenant id. This was a real\n * cross-tenant data exposure path on Sterling 2026-05-10.\n *\n * The fix is to also fold `config` into the hash payload. We use a\n * stable, ordered representation (sorted keys) so that two semantically\n * equivalent config blobs hash identically — without that, JSON's\n * insertion-order serialization could spuriously bump the hash on every\n * poll.\n */\n\nimport { createHash } from 'node:crypto';\n\ninterface HashableIntegration {\n definition_id: string;\n credentials?: Record<string, unknown> | null;\n config?: Record<string, unknown> | null;\n}\n\nfunction canonicalize(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(canonicalize);\n if (value && typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(obj).sort()) {\n out[key] = canonicalize(obj[key]);\n }\n return out;\n }\n return value;\n}\n\nexport function computeIntegrationsHash(integrations: HashableIntegration[]): string {\n const payload = integrations.map((i) =>\n `${i.definition_id}:${JSON.stringify(canonicalize(i.credentials ?? {}))}:${JSON.stringify(canonicalize(i.config ?? {}))}`,\n );\n return createHash('sha256')\n .update(JSON.stringify(payload))\n .digest('hex')\n .slice(0, 16);\n}\n","// ENG-4832: reap MCP child processes whose env references a credential\n// that's just been rotated, so the next call respawns them with the\n// fresh value.\n//\n// Why this exists: the manager polls /host/refresh, gets new OAuth\n// tokens, writes them into the agent's `.env.integrations`. But the\n// MCP children (xero-mcp-server, gmail-mcp, etc.) were spawned by\n// Claude Code at session start and hold their **spawn-time** env. They\n// never re-read .env.integrations, so once the access_token in their\n// env block expires (typically 30 min for Xero), every upstream call\n// 401s — and the agent reports \"refresh failed\" because its view of\n// the token is stale, regardless of what the DB row says. See\n// ENG-4832 for the full triage (Stirling, Day 3).\n//\n// The fix: when integration credentials rotate, identify which MCP\n// children reference the rotated env var(s) AND belong to this agent,\n// SIGTERM them. Claude Code's MCP transport detects the dead child\n// and respawns it on next request — fresh env, fresh token, agent\n// keeps working. Conversation context is preserved.\n//\n// Companion to orphan-channel-mcp-reaper.ts (ENG-4808). Same shape\n// (parse ps, walk ppid chain, kill with grace) but a different\n// targeting rule: that one finds children whose parent claude is\n// dead; this one finds children whose env is stale.\n//\n// Pure helpers exposed for tests:\n// • parseEnvIntegrationsVars() — extract var names from a\n// .env.integrations file\n// • findMcpServersUsingVars() — given the parsed .mcp.json and\n// a set of changed var names, which server keys are affected?\n// • findMcpChildrenForAgent() — walk ps rows, return PIDs\n// belonging to <codeName>'s claude AND matching one of the\n// server-key argv signatures\n//\n// Side-effecting:\n// • reapStaleMcpChildren() — orchestrates the above + SIGTERM\n// / grace / SIGKILL. Idempotent — calling repeatedly is safe.\n\nimport { execFileSync } from 'node:child_process';\nimport { parsePsRows, type PsRow } from './orphan-channel-mcp-reaper.js';\n\n/**\n * Parse a `.env.integrations` file body into a Map of var name → raw\n * value (the value is captured verbatim, including any shell-quote\n * wrapping the writer added). Pure — operate on file contents, not\n * paths. Robust to:\n * - leading/trailing whitespace\n * - shell-quote forms (`X='value'`, `X=\"value\"`, `X=raw`)\n * - comments (lines starting with `#`)\n * - blank lines\n * - duplicates (later wins, matching shell-source semantics)\n *\n * Values are NOT dequoted on purpose — for the diff use case we just\n * need byte-for-byte equality between two writes of the same value,\n * and writeIntegrations is deterministic (always emits the same\n * shellQuote form for the same input). Skipping dequoting also keeps\n * this immune to quote-handling bugs.\n */\nexport function parseEnvIntegrationsEntries(content: string): Map<string, string> {\n const entries = new Map<string, string>();\n for (const raw of content.split(/\\r?\\n/)) {\n const line = raw.trim();\n if (!line || line.startsWith('#')) continue;\n const eq = line.indexOf('=');\n if (eq <= 0) continue;\n const name = line.slice(0, eq).trim();\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) continue;\n const value = line.slice(eq + 1);\n entries.set(name, value);\n }\n return entries;\n}\n\n/**\n * Just the SET of var names declared in a `.env.integrations` body.\n * Convenience wrapper over parseEnvIntegrationsEntries — preserves\n * the original ENG-4832 API for callers that only need names.\n */\nexport function parseEnvIntegrationsVars(content: string): string[] {\n return [...parseEnvIntegrationsEntries(content).keys()];\n}\n\n/**\n * Diff two `.env.integrations` parses. Returns the names whose value\n * changed (rotated), was added, or was removed between the two\n * snapshots.\n *\n * Used by the manager-worker integration-rotation hook to pass the\n * **precisely-rotated** subset to findMcpServersUsingVars rather than\n * \"every var currently present in the file\", which would over-reap\n * unrelated MCP children on every refresh tick (CodeRabbit on PR #797).\n */\nexport function diffEnvIntegrations(\n oldContent: string | undefined,\n newContent: string,\n): string[] {\n const oldEntries = oldContent === undefined ? new Map<string, string>() : parseEnvIntegrationsEntries(oldContent);\n const newEntries = parseEnvIntegrationsEntries(newContent);\n const changed = new Set<string>();\n for (const [name, value] of newEntries) {\n if (oldEntries.get(name) !== value) changed.add(name);\n }\n // Removed vars also count — a server whose env was depending on a\n // now-absent var has effectively been rotated to \"no value\", which\n // is just as breaking as a value change.\n for (const name of oldEntries.keys()) {\n if (!newEntries.has(name)) changed.add(name);\n }\n return [...changed];\n}\n\n/**\n * Pure: given a parsed `.mcp.json` and a set of env-var names that\n * have just rotated, return the MCP server keys whose `env` block\n * references any of those vars via `${VAR}` substitution.\n *\n * Only the `${VAR}` form is checked because that's the only form\n * Claude Code substitutes at MCP launch time from the parent claude's\n * env. A literal value-equals-name (e.g. `\"TOKEN\": \"XERO_ACCESS_TOKEN\"`\n * with no `${}`) is just a constant string the MCP child sees as-is —\n * the rotated value never reaches it, so reaping the child wouldn't\n * fix anything (the child would just respawn with the same broken\n * literal). CodeRabbit on PR #797 caught a doc-vs-impl drift that\n * previously claimed both forms were checked; this comment is now\n * aligned with the implementation, and the test\n * `does NOT match servers whose env contains the var name as a\n * literal value` pins the correct behaviour.\n */\nexport interface McpServerEntry {\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n}\n\nexport interface McpConfig {\n mcpServers?: Record<string, McpServerEntry>;\n}\n\nexport function findMcpServersUsingVars(\n mcp: McpConfig | null | undefined,\n changedVars: Iterable<string>,\n): string[] {\n const changedSet = new Set(changedVars);\n if (!mcp?.mcpServers || changedSet.size === 0) return [];\n\n const result: string[] = [];\n for (const [serverKey, entry] of Object.entries(mcp.mcpServers)) {\n if (!entry || typeof entry !== 'object') continue;\n const env = entry.env;\n if (!env || typeof env !== 'object') continue;\n let matches = false;\n for (const value of Object.values(env)) {\n if (typeof value !== 'string') continue;\n // Match ${VAR} substitution form first — the common case.\n const placeholderMatches = value.match(/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g);\n if (placeholderMatches) {\n for (const ph of placeholderMatches) {\n const name = ph.slice(2, -1);\n if (changedSet.has(name)) {\n matches = true;\n break;\n }\n }\n }\n if (matches) break;\n }\n if (matches) result.push(serverKey);\n }\n return result;\n}\n\n/**\n * Compile MCP-server-key signatures into argv match patterns. Each MCP\n * server is spawned as some variant of `npx -y <package>` or\n * `node <path>` — the running child's argv contains the package\n * binary name (e.g. `xero-mcp-server`) which is stable enough to\n * match against. Falling back to the raw server-key as a literal\n * (e.g. `xero`) catches the cases where the package name and the\n * mcpServers key match.\n */\n/**\n * Build precise argv-matchers from each server's actual `.mcp.json`\n * `command + args`, NOT from the bare server key.\n *\n * Pre-fix this function used `\\b<serverKey>\\b` against argv, which on\n * a server key like `xero` would also match `xero-other-mcp-server` —\n * any sibling package that happens to share a prefix would get bounced\n * along with the intended target (CodeRabbit on PR #797). The fix:\n * extract a precise per-server signature from the entry's command +\n * args. For an `npx -y @xeroapi/xero-mcp-server@latest` entry we end\n * up matching against:\n * 1. the full package spec (`@xeroapi/xero-mcp-server`) — matches\n * the npm-exec wrapper process\n * 2. the bin basename (`xero-mcp-server`) — matches the actual node\n * child whose argv is `node /path/to/.bin/xero-mcp-server`\n *\n * Both forms are needed because the parent npm-exec process and the\n * child node process have different argv shapes but share the same\n * package identity.\n *\n * Falls back to a `(?<![A-Za-z0-9_])${key}(?![A-Za-z0-9_])` matcher\n * (alnum/underscore boundary, but `-` and `/` allowed) when the entry\n * has no resolvable package signature — the bare-key form is still\n * tighter than the original `\\b...\\b` because `_` is now part of the\n * \"name continuation\" set, so a server key `xero` doesn't match\n * `xero_thing` either.\n */\nfunction buildArgvMatchersForEntry(key: string, entry: McpServerEntry | undefined): RegExp[] {\n const escapeRe = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const patterns: RegExp[] = [];\n\n // Try to extract a precise package signature from the args. The\n // common shapes we provision through buildMcpJson:\n // npx -y @scope/package@version\n // npx -y package@version\n // node /absolute/path/to/.bin/package-name\n // Walk args from last to first, looking for the first token that\n // looks like a package spec or an absolute path with a bin basename.\n const args = entry?.args ?? [];\n for (let i = args.length - 1; i >= 0; i--) {\n const arg = args[i];\n if (typeof arg !== 'string' || !arg) continue;\n // Skip flags\n if (arg.startsWith('-')) continue;\n\n // Strip @version suffix from npm package specs.\n const stripped = arg.replace(/@[^/@]*$/, (m) =>\n // @latest, @1.2.3, etc. — drop. But @scope at the START of a\n // package spec must NOT be stripped, so we only strip a tail\n // `@...` if it doesn't itself start with a slash inside.\n // The regex above only matches a single trailing `@...`\n // segment after the last `/`, so this is safe for `@scope/pkg`.\n m.includes('/') ? m : '',\n );\n\n // Tail-boundary that disallows the package-name continuation\n // characters: alnum, `_`, `-`. Without this, matching\n // `@integrity-labs/cloud-broker` would also match\n // `@integrity-labs/cloud-broker-experimental` — exactly the\n // overmatch class CodeRabbit's review on PR #797 flagged.\n const tail = '(?![A-Za-z0-9_-])';\n\n // Looks like an npm package spec? (`@scope/pkg` or `pkg-name`)\n if (/^@?[a-z0-9]([a-z0-9._-]*\\/)?[a-z0-9._-]+$/i.test(stripped)) {\n patterns.push(new RegExp(`${escapeRe(stripped)}${tail}`));\n // Also push the basename if the spec is scoped — matches the\n // node child whose argv only has the bin name.\n const basename = stripped.split('/').pop();\n if (basename && basename !== stripped) {\n patterns.push(new RegExp(`${escapeRe(basename)}${tail}`));\n }\n break;\n }\n\n // Looks like a path? Match the basename (the bin name) — the\n // running child's argv typically has the full path.\n if (stripped.includes('/')) {\n const basename = stripped.split('/').pop();\n if (basename) {\n patterns.push(new RegExp(`${escapeRe(basename)}${tail}`));\n break;\n }\n }\n }\n\n if (patterns.length === 0) {\n // Fallback: bare server key with tighter boundaries than `\\b`.\n // Disallow alphanumerics + `_` either side; `-` and `/` are\n // permitted as separators (so e.g. `cloud-broker` matches\n // `@integrity-labs/cloud-broker` correctly).\n const safe = escapeRe(key);\n patterns.push(new RegExp(`(?<![A-Za-z0-9_])${safe}(?![A-Za-z0-9_])`));\n if (safe.includes('_')) {\n const dashed = safe.replace(/_/g, '-');\n patterns.push(new RegExp(`(?<![A-Za-z0-9_])${dashed}(?![A-Za-z0-9_])`));\n }\n }\n return patterns;\n}\n\n/** Match the parent claude bound to <codeName> via `--name agt-<codeName>`. */\nexport function buildClaudeAgentMatcher(codeName: string): RegExp {\n // Restrict to alnum/dash so a malicious codeName can't widen the\n // match (codeName is validated upstream but defence in depth).\n const safe = codeName.replace(/[^A-Za-z0-9_-]/g, '');\n // Use a tail boundary that disallows hyphen-as-word-character —\n // `\\b` between `g` and `-` matches in regex (since `-` is not a\n // word char), so `\\\\b` would let `agt-stirling-other` match the\n // matcher for `stirling`. Require an actual argument boundary\n // (whitespace) or end-of-string instead. CodeRabbit-detected\n // regression class.\n return new RegExp(`\\\\bclaude\\\\b.*--name\\\\s+agt-${safe}(?=\\\\s|$)`);\n}\n\n/**\n * Pure: given parsed ps rows, find MCP child PIDs that belong to\n * <codeName>'s session AND match one of the rotated-env server keys.\n *\n * \"Belong to <codeName>'s session\" = ppid chain (up to maxDepth)\n * reaches a `claude --name agt-<codeName>` process. Same walk shape\n * as the orphan reaper, but the success direction is inverted: that\n * one collects MCPs whose ancestry does NOT reach a live claude\n * (orphans); this one collects MCPs whose ancestry DOES reach a\n * specific live claude (this agent's claude).\n *\n * Empty `serverKeys` returns []. Empty `psRows` returns [].\n * maxDepth defaults to 8 — same as the orphan reaper.\n */\nexport function findMcpChildrenForAgent(args: {\n rows: PsRow[];\n codeName: string;\n serverKeys: string[];\n /**\n * Parsed `.mcp.json` so the matcher can derive precise per-server\n * argv signatures from each entry's `command + args`. When omitted,\n * matching falls back to the bare-key form which is tighter than\n * the original `\\b...\\b` but can still overmatch for short keys.\n * Production callers pass this; some tests pass only `serverKeys`\n * to exercise the fallback path.\n */\n mcpJson?: McpConfig | null;\n maxDepth?: number;\n}): number[] {\n const { rows, codeName, serverKeys, mcpJson } = args;\n const maxDepth = args.maxDepth ?? 8;\n if (serverKeys.length === 0 || rows.length === 0) return [];\n\n const claudeMatcher = buildClaudeAgentMatcher(codeName);\n // Build precise per-server matchers from each entry's command + args\n // when mcpJson is provided. Falls back to the bare-key form when\n // a key has no resolvable entry (e.g. it was just removed from\n // mcp.json mid-tick or the test passed only serverKeys).\n const argvMatchers: RegExp[] = [];\n for (const key of serverKeys) {\n const entry = mcpJson?.mcpServers?.[key];\n argvMatchers.push(...buildArgvMatchersForEntry(key, entry));\n }\n const byPid = new Map<number, PsRow>(rows.map((r) => [r.pid, r]));\n\n const matched: number[] = [];\n for (const row of rows) {\n // Must match at least one of the server-key argv patterns.\n if (!argvMatchers.some((re) => re.test(row.args))) continue;\n // Must NOT itself be a claude (we want children, not the agent's\n // claude process).\n if (/\\bclaude\\b/.test(row.args) && row.args.includes(`--name agt-${codeName}`)) continue;\n\n // Walk parents looking for the agent's claude.\n let cur: PsRow | undefined = byPid.get(row.ppid);\n let belongs = false;\n for (let depth = 0; depth < maxDepth && cur; depth++) {\n if (claudeMatcher.test(cur.args)) {\n belongs = true;\n break;\n }\n if (cur.pid === 1) break;\n cur = byPid.get(cur.ppid);\n }\n if (belongs) matched.push(row.pid);\n }\n return matched;\n}\n\n/**\n * Run `ps`, find the stale MCP children for <codeName> belonging to\n * the listed serverKeys, and SIGTERM them. After `graceMs`, SIGKILL\n * any that didn't exit. Returns the list of pids that were sent\n * SIGTERM (informational — tests use this without mocking\n * process.kill).\n *\n * Idempotent. Cheap enough to call on every integration-rotation\n * tick — when nothing matches, the work is one ps invocation + a\n * regex pass.\n */\nexport function reapStaleMcpChildren(args: {\n log: (msg: string) => void;\n codeName: string;\n serverKeys: string[];\n /**\n * Parsed `.mcp.json` so the matcher can derive precise per-server\n * argv signatures from each entry's command + args. Production\n * callers always pass this; tests can omit to exercise the\n * fallback (bare-key with tightened boundaries).\n */\n mcpJson?: McpConfig | null;\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, codeName, serverKeys, mcpJson, graceMs = 5_000 } = args;\n if (serverKeys.length === 0) return [];\n\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 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(`[stale-mcp-reaper] ps invocation failed for '${codeName}': ${(err as Error).message} — skipping reap`);\n return [];\n }\n\n const rows = parsePsRows(psOutput);\n const targets = findMcpChildrenForAgent({ rows, codeName, serverKeys, mcpJson });\n if (targets.length === 0) return [];\n\n const byPid = new Map<number, PsRow>(rows.map((r) => [r.pid, r]));\n const describe = (pid: number): string => {\n const argv = byPid.get(pid)?.args ?? '';\n // Pull the first identifiable npm package or script name from argv.\n const pkgMatch = argv.match(/(@[a-z0-9_-]+\\/[a-z0-9_-]+|[a-z0-9_-]+-mcp-server|[a-z0-9_-]+-mcp\\b)/i);\n return pkgMatch ? `${pkgMatch[1]} (pid ${pid})` : `pid ${pid}`;\n };\n\n log(\n `[stale-mcp-reaper] '${codeName}': rotating ${targets.length} stale MCP child(ren) for [${serverKeys.join(', ')}]: ${targets.map(describe).join(', ')}`,\n );\n for (const pid of targets) {\n killProcess(pid, 'SIGTERM');\n }\n\n setTimeout(() => {\n try {\n // CodeRabbit on PR #797: re-resolve ownership before SIGKILL.\n // 5s grace is long enough for the kernel to recycle a freed\n // PID into an entirely unrelated process — `targets.filter(isAlive)`\n // alone would happily kill that bystander. Re-running the\n // original ps + match pipeline against fresh process state\n // confirms the PID is still one of *our* MCP children for this\n // agent. Anything that doesn't survive that re-check is either\n // already dead (good) or has been recycled (do NOT kill).\n let freshPsOutput: string;\n try {\n freshPsOutput = runPs();\n } catch (err) {\n log(`[stale-mcp-reaper] '${codeName}': fresh ps for SIGKILL re-verify failed: ${(err as Error).message} — skipping SIGKILL pass`);\n return;\n }\n const stillOwned = new Set(\n findMcpChildrenForAgent({\n rows: parsePsRows(freshPsOutput),\n codeName,\n serverKeys,\n mcpJson,\n }),\n );\n const stragglers = targets.filter((pid) => isAlive(pid) && stillOwned.has(pid));\n if (stragglers.length === 0) return;\n log(\n `[stale-mcp-reaper] '${codeName}': ${stragglers.length} child(ren) survived SIGTERM; sending SIGKILL: ${stragglers.map(describe).join(', ')}`,\n );\n for (const pid of stragglers) {\n killProcess(pid, 'SIGKILL');\n }\n } catch (err) {\n log(`[stale-mcp-reaper] '${codeName}': error in SIGKILL pass: ${(err as Error).message}`);\n }\n }, graceMs).unref();\n\n return targets;\n}\n","// ENG-4807: pure decision function — should the manager restart an agent's\n// persistent session because its channel set changed?\n//\n// Why this exists: when a channel is added or removed for a live persistent\n// claude-code session, the parent claude was launched with stale flags\n// (`--dangerously-load-development-channels` and `--allowedTools`). Those\n// flags are baked at spawn time and don't pick up the new channel. The\n// only fix is to kill the tmux session so the manager respawns it.\n// See ENG-4807 for the full diagnosis.\n//\n// Pulled out as a pure function so the gating logic is unit-testable\n// without mocking processAgent's dozens of dependencies.\n\nexport interface ChannelRestartDecisionInput {\n /**\n * The channel ids the manager saw on the *previous* refresh tick for\n * this agent, or `undefined` if this is the first time we've seen the\n * agent (first poll, or fresh manager process). On first poll the\n * persistent session is either not started yet or about to be started\n * with the fresh launch flags, so a restart would be a noisy no-op.\n */\n previousChannelIds: Set<string> | undefined;\n /** The channel ids in this refresh tick's response. */\n currentChannelIds: Set<string>;\n /**\n * Agent's `session_mode` from the refresh response. Restart only\n * applies to `persistent` sessions — oneshot agents don't have a\n * tmux session whose launch flags need refreshing.\n */\n sessionMode: string | undefined;\n /**\n * Agent's framework. Channel listener flags are claude-code-specific;\n * other frameworks (openclaw, nemoclaw) don't use the\n * `--dangerously-load-development-channels` mechanism so the restart\n * doesn't apply.\n */\n framework: string;\n /**\n * Whether the agent's tmux session is currently alive. If it isn't,\n * there's nothing to kill — the manager will spawn a fresh one\n * downstream with the right flags.\n */\n sessionHealthy: boolean;\n}\n\nexport interface ChannelRestartDecision {\n /** True iff the manager should kill the tmux session. */\n restart: boolean;\n /** Channels that appeared in this tick (informational, for log line). */\n added: string[];\n /** Channels that disappeared in this tick (informational, for log line). */\n removed: string[];\n}\n\n/**\n * ENG-4807 (CodeRabbit on PR #773 round 4): the channel set we care about\n * for restart purposes is NOT every key in `channel_configs` — it's only\n * the channels that the running session would actually load as channel\n * listeners. Those are the ones the manager calls\n * `writeChannelCredentials` for, gated on `status ∈ {active, pending}`\n * AND a non-null `config`. A status-only flip (e.g. active → revoked)\n * removes the channel from the launchable set even though its row stays\n * in `channel_configs`, and therefore should trigger a restart so the\n * launch flags drop the now-unauthorised channel. Inverse: a row that\n * never becomes launchable (e.g. always errored) should NOT trigger a\n * restart.\n *\n * Pure helper so the launchable filter has the same unit-test coverage\n * as the rest of the decision logic.\n */\nexport interface ChannelConfigEntry {\n status?: string;\n config?: unknown;\n}\n\nexport function launchableChannelIds(\n channelConfigs: Record<string, ChannelConfigEntry | undefined> | null | undefined,\n): Set<string> {\n if (!channelConfigs) return new Set();\n const result = new Set<string>();\n for (const [channelId, entry] of Object.entries(channelConfigs)) {\n if (!entry) continue;\n if (entry.config == null) continue;\n if (entry.status !== 'active' && entry.status !== 'pending') continue;\n result.add(channelId);\n }\n return result;\n}\n\nexport function decideChannelRestart(input: ChannelRestartDecisionInput): ChannelRestartDecision {\n const { previousChannelIds, currentChannelIds, sessionMode, framework, sessionHealthy } = input;\n\n // First poll for this agent — nothing to restart, the session (if any)\n // either doesn't exist yet or was just spawned with the right flags.\n if (previousChannelIds === undefined) {\n return { restart: false, added: [], removed: [] };\n }\n\n // Compute the diff regardless — it's used in the log line even when we\n // ultimately don't restart, and it's cheap.\n const added = [...currentChannelIds].filter((c) => !previousChannelIds.has(c));\n const removed = [...previousChannelIds].filter((c) => !currentChannelIds.has(c));\n\n // No actual set change — nothing to do (debounces same-set writes that\n // happen on every refresh tick once the cache is warm).\n if (added.length === 0 && removed.length === 0) {\n return { restart: false, added, removed };\n }\n\n // Restart only applies to live persistent claude-code sessions.\n if (sessionMode !== 'persistent') return { restart: false, added, removed };\n if (framework !== 'claude-code') return { restart: false, added, removed };\n if (!sessionHealthy) return { restart: false, added, removed };\n\n return { restart: true, added, removed };\n}\n","/**\n * ENG-4341: Integration context rendering.\n *\n * Substitutes `{{context.foo}}` placeholders in a SKILL.md body with values\n * from the resolved plugin context, and appends a \"## Team Overrides\"\n * section if the freeform overrides text is non-empty.\n *\n * Substitution rules (matching the RFC §4 + §4b contracts):\n * - String / boolean / number values are interpolated as-is.\n * - Arrays and maps are JSON-stringified (no per-type formatter in the\n * initial slice; skill authors can use JSON syntax in their prose).\n * - A placeholder that references a missing key is replaced with the\n * empty string and a warning is emitted via the warn callback. Never\n * leave a literal {{context.foo}} in the rendered output.\n *\n * Lives in its own module so tests can import it without pulling in the\n * full manager-worker entry-point side effects.\n */\n\nconst PLUGIN_CONTEXT_PLACEHOLDER_RE = /\\{\\{\\s*context\\.([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\}\\}/g;\n\nconst TEAM_OVERRIDES_HEADER =\n '## Team Overrides\\n\\n> **The following overrides anything you\\'ve read above.** ' +\n 'If any rule here conflicts with earlier instructions in this skill, follow what is ' +\n 'written here. These are user-supplied directives that take precedence over the ' +\n 'plugin\\'s default guidance.\\n';\n\nfunction formatContextValue(value: unknown): string {\n if (value === null || value === undefined) return '';\n if (typeof value === 'string') return value;\n if (typeof value === 'boolean' || typeof value === 'number') return String(value);\n // Arrays and objects: JSON-stringify so the agent sees a stable shape.\n // Skill authors who want a different format should describe the field\n // in their skill prose, not transform it here.\n try {\n return JSON.stringify(value);\n } catch {\n return '';\n }\n}\n\nexport function renderIntegrationSkillContent(\n raw: string,\n values: Record<string, unknown>,\n overrides: string,\n warn: (message: string) => void = () => {},\n): string {\n // 1. Substitute {{context.<field>}} placeholders.\n const substituted = raw.replace(PLUGIN_CONTEXT_PLACEHOLDER_RE, (_match, fieldName: string) => {\n if (!(fieldName in values)) {\n warn(`unresolved placeholder {{context.${fieldName}}} — substituting empty string`);\n return '';\n }\n return formatContextValue(values[fieldName]);\n });\n\n // 2. Append the freeform overrides section if non-empty.\n const trimmedOverrides = overrides.trim();\n if (!trimmedOverrides) return substituted;\n\n const separator = substituted.endsWith('\\n') ? '\\n' : '\\n\\n';\n return `${substituted}${separator}---\\n\\n${TEAM_OVERRIDES_HEADER}\\n${trimmedOverrides}\\n`;\n}\n","/**\n * Integration skill folder-layout builder (ENG-4502).\n *\n * Historically every integration scope landed as its own top-level skill folder:\n *\n * .claude/skills/integration-google-sheets-skill-googlesheets-read/SKILL.md\n * .claude/skills/integration-google-sheets-skill-googlesheets-write/SKILL.md\n * .claude/skills/integration-google-sheets-skill-googlesheets-delete/SKILL.md\n * .claude/skills/integration-google-sheets-skill-googlesheets-other/SKILL.md\n *\n * Confirmed via the claude-code-guide skill: Claude Code's skill discovery\n * does NOT recurse, so `integration-google-sheets/read/SKILL.md` style\n * nesting would be invisible. This module implements the compromise that\n * does work:\n *\n * .claude/skills/integration-google-sheets/\n * SKILL.md ← single umbrella Claude sees; references scopes\n * scopes/read.md ← scope-specific reference, loaded on demand\n * scopes/write.md\n * scopes/delete.md\n * scopes/other.md\n *\n * The umbrella SKILL.md is synthesized from the per-scope `skill_name`s and\n * an extracted description from each scope's frontmatter. Original scope\n * content (frontmatter and body) lands at `scopes/<slug>.md`.\n *\n * Pure functions here so the layout logic can be unit-tested without\n * involving the filesystem.\n */\n\nimport type { CapabilitySkillFile } from '@augmented/core';\n\nexport interface IntegrationSkillInput {\n /**\n * Wire-contract field — matches the `plugin_slug` key in the\n * /host/refresh payload. Renaming the field would break the host adapter\n * contract; the type alias here keeps the rename surface bounded.\n */\n plugin_slug: string;\n skill_id: string;\n skill_name: string;\n /** Rendered scope content including YAML frontmatter. */\n content: string;\n}\n\nexport interface IntegrationBundle {\n /** Slug used as the top-level folder name (`integration-<slug>`). */\n integrationSlug: string;\n /** Files relative to the integration folder root. */\n files: CapabilitySkillFile[];\n /**\n * Stable set of scope-file relative paths (e.g. `scopes/read.md`) included\n * in this bundle. Exposed for per-file hashing / migration cleanup.\n */\n scopePaths: string[];\n}\n\n/** Extract the `description:` line from a skill frontmatter block. */\nexport function extractDescription(content: string): string | null {\n const match = content.match(/^---\\s*([\\s\\S]*?)---/);\n if (!match) return null;\n const frontmatter = match[1] ?? '';\n // Match either a double-quoted string with escaped quotes / backslashes, or an\n // unquoted scalar up to end-of-line. The quoted branch captures the raw inner\n // content so escapes can be normalised below.\n const descMatch = frontmatter.match(/^\\s*description:\\s*(?:\"((?:\\\\.|[^\"\\\\])*)\"|([^\\n]+))\\s*$/m);\n if (!descMatch) return null;\n const quoted = descMatch[1];\n if (quoted !== undefined) {\n return quoted.replace(/\\\\([\"\\\\])/g, '$1').trim();\n }\n return descMatch[2]?.trim() ?? null;\n}\n\n/** Sanitize a scope slug so it's safe as a filename. */\nfunction sanitizeScopeSlug(skillId: string, integrationSlug: string): string {\n // Strip the integration slug prefix if present — `googlesheets-read` under\n // `integration-google-sheets` reads better as just `read`.\n const normalized = skillId.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');\n const prefix = integrationSlug.replace(/-/g, '');\n const stripped =\n normalized.startsWith(`${prefix}-`) ? normalized.slice(prefix.length + 1) : normalized;\n return stripped.replace(/^-|-$/g, '') || normalized || 'scope';\n}\n\n/**\n * Build the umbrella integration bundle from one or more scope skills.\n *\n * Contract:\n * - The umbrella SKILL.md always exists, even for a single-scope integration,\n * so the on-disk shape is predictable.\n * - Its frontmatter uses an integration-wide `name` derived from the slug\n * and a `description` that concatenates scope descriptions with \" \", so\n * Claude can match any scope keyword against a single skill entry.\n * - The body lists every scope with a link to its scope doc, giving Claude\n * a deterministic way to load the right reference on demand.\n * - Scope content is written verbatim under `scopes/<slug>.md` — including\n * its own frontmatter — so an agent following a link sees the full\n * original doc.\n */\nexport function buildIntegrationBundle(skills: IntegrationSkillInput[]): IntegrationBundle {\n if (skills.length === 0) {\n throw new Error('buildIntegrationBundle: empty skills list');\n }\n const integrationSlug = skills[0]!.plugin_slug;\n // Deterministic scope order so the bundle content (and its hash) doesn't\n // flap when the DB returns rows in a different order between polls.\n const ordered = [...skills].sort((a, b) => a.skill_id.localeCompare(b.skill_id));\n\n const entries = ordered.map((s) => {\n const scopeSlug = sanitizeScopeSlug(s.skill_id, integrationSlug);\n const description = extractDescription(s.content);\n return {\n skillId: s.skill_id,\n skillName: s.skill_name,\n description,\n scopeSlug,\n scopePath: `scopes/${scopeSlug}.md`,\n content: s.content,\n };\n });\n\n // Two different skill_ids can normalise to the same scopeSlug (e.g. `Read`\n // and `read!`). Without a guard the later file silently overwrites the\n // earlier one at install time while the umbrella links both bullets to the\n // same doc — fail fast so the collision is visible.\n const scopePathGroups = new Map<string, typeof entries>();\n for (const entry of entries) {\n const bucket = scopePathGroups.get(entry.scopePath);\n if (bucket) bucket.push(entry);\n else scopePathGroups.set(entry.scopePath, [entry]);\n }\n for (const [scopePath, group] of scopePathGroups) {\n if (group.length > 1) {\n const conflicts = group.map((e) => `${e.skillId} (${e.skillName})`).join(', ');\n throw new Error(\n `buildIntegrationBundle: duplicate scope path '${scopePath}' for integration '${integrationSlug}' — conflicting skills: ${conflicts}`,\n );\n }\n }\n\n const integrationName = slugToTitle(integrationSlug);\n // Description: join scope descriptions so Claude has broad keyword coverage\n // across the integration. Fall back to joined scope names when a scope's\n // frontmatter is missing a description line (defensive — content is\n // expected to always have frontmatter, but this keeps the umbrella\n // renderable even on weird input).\n const umbrellaDescription = entries\n .map((e) => e.description ?? `Use for ${e.skillName}.`)\n .join(' ');\n\n const scopeList = entries\n .map((e) => {\n const label = e.skillName || slugToTitle(e.scopeSlug);\n const desc = e.description ? ` — ${e.description}` : '';\n return `- **${label}** ([${e.scopePath}](${e.scopePath}))${desc}`;\n })\n .join('\\n');\n\n const umbrella = [\n '---',\n `name: \"${integrationName}\"`,\n `description: \"${escapeYamlDouble(umbrellaDescription)}\"`,\n '---',\n '',\n `# ${integrationName}`,\n '',\n `This skill bundles ${entries.length === 1 ? 'one scope' : `${entries.length} scopes`} for the ${integrationSlug} integration. Each scope has a dedicated reference under \\`scopes/\\` that you should load on demand when the user's intent maps to it.`,\n '',\n '## Scopes',\n '',\n scopeList,\n '',\n '## How to use',\n '',\n `Identify which scope matches the user's request, then read the matching file under \\`scopes/\\` for the exact tool names and usage details. Do not guess tool names from this umbrella file alone.`,\n '',\n ].join('\\n');\n\n const files: CapabilitySkillFile[] = [\n { relativePath: 'SKILL.md', content: umbrella },\n ...entries.map((e) => ({\n relativePath: e.scopePath,\n content: e.content,\n })),\n ];\n\n return {\n integrationSlug,\n files,\n scopePaths: entries.map((e) => e.scopePath),\n };\n}\n\n/** `google-sheets` → `Google Sheets` */\nfunction slugToTitle(slug: string): string {\n return slug\n .split(/[-_]/)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(' ');\n}\n\n/** Escape characters that would break a double-quoted YAML scalar. */\nfunction escapeYamlDouble(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n\n/** Group integration skills by `plugin_slug` (wire-contract field), preserving insertion order of slugs. */\nexport function groupSkillsByIntegration(skills: IntegrationSkillInput[]): Map<string, IntegrationSkillInput[]> {\n const map = new Map<string, IntegrationSkillInput[]>();\n for (const s of skills) {\n const bucket = map.get(s.plugin_slug);\n if (bucket) bucket.push(s);\n else map.set(s.plugin_slug, [s]);\n }\n return map;\n}\n\n/**\n * Hash the umbrella-bundle file set into a single value so the manager only\n * re-writes when something actually changed. Hashing the concatenated\n * (relativePath, content) pairs catches new scope additions, scope\n * reorderings, and content tweaks without false positives.\n */\nexport function bundleFingerprint(files: CapabilitySkillFile[]): string {\n // Sort so the fingerprint is order-independent — buildIntegrationBundle already\n // sorts scopes, but a defensive re-sort here protects callers that build\n // bundles differently (tests, future shortcuts).\n const sorted = [...files].sort((a, b) => a.relativePath.localeCompare(b.relativePath));\n return sorted.map((f) => `${f.relativePath}\\x00${f.content}`).join('\\x01');\n}\n","/**\n * Gateway WebSocket Client — connects to the OpenClaw gateway event stream\n * and emits typed events for consumption by the manager worker.\n */\n\nimport { EventEmitter } from 'node:events';\nimport WebSocket from 'ws';\n\nconst DEFAULT_PORT = 18789;\nconst RECONNECT_INITIAL_MS = 1_000;\nconst RECONNECT_BASE_MS = 5_000;\nconst RECONNECT_MAX_MS = 30_000;\nconst HEARTBEAT_INTERVAL_MS = 30_000;\n\nexport interface GatewayEvent {\n type: 'event';\n event: string;\n payload: unknown;\n seq?: number;\n stateVersion?: number;\n}\n\nexport interface GatewayClientOptions {\n port?: number;\n token?: string;\n}\n\nexport interface PooledGatewayEvent extends GatewayEvent {\n agentCodeName: string;\n}\n\nexport class GatewayClient extends EventEmitter {\n private readonly port: number;\n private readonly token: string | undefined;\n private ws: WebSocket | null = null;\n private _connected = false;\n private _everConnected = false;\n private reconnectAttempts = 0;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private pongReceived = true;\n private intentionalClose = false;\n private pendingRpc = new Map<string, { resolve: (v: unknown) => void; reject: (e: Error) => void; timer: ReturnType<typeof setTimeout> }>();\n private rpcSeq = 0;\n\n constructor(options: GatewayClientOptions = {}) {\n super();\n this.port = options.port ?? Number(process.env['OPENCLAW_GATEWAY_PORT']) ?? DEFAULT_PORT;\n this.token = options.token ?? process.env['OPENCLAW_GATEWAY_TOKEN'];\n }\n\n get connected(): boolean {\n return this._connected;\n }\n\n connect(): void {\n this.intentionalClose = false;\n this.doConnect();\n }\n\n disconnect(): void {\n this.intentionalClose = true;\n this.clearTimers();\n\n if (this.ws) {\n this.ws.removeAllListeners();\n this.ws.close(1000);\n this.ws = null;\n }\n\n if (this._connected) {\n this._connected = false;\n this.emit('disconnected');\n }\n }\n\n // ── Private ──────────────────────────────────────────────────────────────\n\n private doConnect(): void {\n const url = `ws://127.0.0.1:${this.port}`;\n\n try {\n const headers: Record<string, string> = {};\n if (this.token) {\n headers['Authorization'] = `Bearer ${this.token}`;\n }\n\n this.ws = new WebSocket(url, { headers });\n } catch (err) {\n this.emit('error', err);\n this.scheduleReconnect();\n return;\n }\n\n this.ws.on('open', () => {\n // Wait for challenge message from gateway — connection established, auth pending\n });\n\n this.ws.on('message', (data: WebSocket.Data) => {\n try {\n const msg = JSON.parse(data.toString());\n this.handleMessage(msg);\n } catch {\n // Ignore non-JSON messages\n }\n });\n\n this.ws.on('close', (code: number, reason: Buffer) => {\n this.clearHeartbeat();\n if (this._connected) {\n this._connected = false;\n this.emit('disconnected');\n } else if (!this._everConnected) {\n // Never connected — log the close reason for debugging\n this.emit('error', new Error(`WebSocket closed before auth (code=${code}, reason=${reason?.toString() || 'none'})`));\n }\n if (!this.intentionalClose) {\n this.scheduleReconnect();\n }\n });\n\n this.ws.on('error', (err: Error) => {\n // Suppress ECONNREFUSED before first successful connection — the gateway\n // may still be booting. The reconnect loop will retry with backoff.\n if (!this._everConnected && (err as NodeJS.ErrnoException).code === 'ECONNREFUSED') {\n return;\n }\n this.emit('error', err);\n });\n\n this.ws.on('pong', () => {\n this.pongReceived = true;\n });\n }\n\n private handleMessage(msg: Record<string, unknown>): void {\n switch (msg.type) {\n case 'challenge':\n // Respond to challenge handshake\n this.sendJson({\n type: 'connect',\n nonce: msg.nonce,\n role: 'operator',\n scopes: ['events:read'],\n });\n break;\n\n case 'hello-ok':\n this._connected = true;\n this._everConnected = true;\n this.reconnectAttempts = 0;\n this.startHeartbeat();\n this.emit('connected');\n break;\n\n case 'event':\n this.emit('event', {\n type: 'event',\n event: msg.event,\n payload: msg.payload,\n seq: msg.seq,\n stateVersion: msg.stateVersion,\n } as GatewayEvent);\n break;\n\n case 'heartbeat':\n case 'tick':\n // Gateway-initiated heartbeat — no action needed\n break;\n\n case 'rpc-result':\n case 'rpc-error': {\n const rpcId = msg.id as string;\n const pending = this.pendingRpc.get(rpcId);\n if (pending) {\n this.pendingRpc.delete(rpcId);\n clearTimeout(pending.timer);\n if (msg.type === 'rpc-error') {\n pending.reject(new Error((msg.error as string) ?? 'RPC error'));\n } else {\n pending.resolve(msg.result);\n }\n }\n break;\n }\n\n default:\n // Unknown message type — check if it's an RPC response with a matching id\n if (msg.id && typeof msg.id === 'string') {\n const pending = this.pendingRpc.get(msg.id);\n if (pending) {\n this.pendingRpc.delete(msg.id);\n clearTimeout(pending.timer);\n if (msg.error) {\n pending.reject(new Error(String(msg.error)));\n } else {\n pending.resolve(msg);\n }\n }\n }\n break;\n }\n }\n\n /**\n * Send an RPC call to the gateway and wait for a response.\n * Returns the response payload or throws on timeout/error.\n */\n sendRpc(method: string, params: Record<string, unknown> = {}, timeoutMs = 10_000): Promise<unknown> {\n return new Promise((resolve, reject) => {\n if (!this._connected || this.ws?.readyState !== WebSocket.OPEN) {\n reject(new Error('Gateway not connected'));\n return;\n }\n const id = `rpc-${++this.rpcSeq}-${Date.now()}`;\n const timer = setTimeout(() => {\n this.pendingRpc.delete(id);\n reject(new Error(`RPC ${method} timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n this.pendingRpc.set(id, { resolve, reject, timer });\n this.sendJson({ type: 'rpc', id, method, params });\n });\n }\n\n private sendJson(obj: unknown): void {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify(obj));\n }\n }\n\n private startHeartbeat(): void {\n this.clearHeartbeat();\n this.pongReceived = true;\n\n this.heartbeatTimer = setInterval(() => {\n if (!this.pongReceived) {\n // Stale connection — force reconnect\n this.ws?.terminate();\n return;\n }\n this.pongReceived = false;\n this.ws?.ping();\n }, HEARTBEAT_INTERVAL_MS);\n }\n\n private clearHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private scheduleReconnect(): void {\n if (this.intentionalClose) return;\n\n // First retry uses a short delay (gateway may still be booting), then exponential backoff\n const delay = this.reconnectAttempts === 0\n ? RECONNECT_INITIAL_MS\n : Math.min(RECONNECT_BASE_MS * Math.pow(2, this.reconnectAttempts - 1), RECONNECT_MAX_MS);\n this.reconnectAttempts++;\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.doConnect();\n }, delay);\n }\n\n private clearTimers(): void {\n this.clearHeartbeat();\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// GatewayClientPool — manages multiple per-agent gateway connections\n// ---------------------------------------------------------------------------\n\nexport class GatewayClientPool extends EventEmitter {\n private clients = new Map<string, GatewayClient>();\n\n addAgent(codeName: string, port: number, token?: string): void {\n if (this.clients.has(codeName)) {\n this.removeAgent(codeName);\n }\n\n const client = new GatewayClient({ port, token });\n\n client.on('connected', () => {\n this.emit('connected', codeName);\n });\n\n client.on('disconnected', () => {\n this.emit('disconnected', codeName);\n });\n\n client.on('error', (err: Error) => {\n this.emit('error', err, codeName);\n });\n\n client.on('event', (evt: GatewayEvent) => {\n const pooledEvent: PooledGatewayEvent = {\n ...evt,\n agentCodeName: codeName,\n };\n this.emit('event', pooledEvent);\n });\n\n this.clients.set(codeName, client);\n client.connect();\n }\n\n removeAgent(codeName: string): void {\n const client = this.clients.get(codeName);\n if (client) {\n client.disconnect();\n this.clients.delete(codeName);\n }\n }\n\n hasAgent(codeName: string): boolean {\n return this.clients.has(codeName);\n }\n\n isConnected(codeName: string): boolean {\n return this.clients.get(codeName)?.connected ?? false;\n }\n\n /**\n * Send an RPC call to a specific agent's gateway.\n * Returns the response or throws on timeout/error/not connected.\n */\n async sendRpc(codeName: string, method: string, params: Record<string, unknown> = {}, timeoutMs = 10_000): Promise<unknown> {\n const client = this.clients.get(codeName);\n if (!client) throw new Error(`No gateway client for agent '${codeName}'`);\n return client.sendRpc(method, params, timeoutMs);\n }\n\n disconnectAll(): void {\n for (const [, client] of this.clients) {\n client.disconnect();\n }\n this.clients.clear();\n }\n\n get size(): number {\n return this.clients.size;\n }\n}\n","import { readFile, readdir } from 'node:fs/promises';\nimport { homedir, platform } from 'node:os';\nimport { join } from 'node:path';\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { ClaudeAuthReport, ClaudeAuthStatus } from '@augmented/core';\n\nconst execFileAsync = promisify(execFile);\n\n// Anything less than this is reported as \"expiring_soon\" — gives the operator\n// enough runway to re-pair before the host goes offline.\nconst EXPIRING_SOON_MS = 48 * 60 * 60 * 1000; // 48 hours\n\n/**\n * Best-effort detection of the host's Claude Code authentication state.\n *\n * Precedence:\n * 1. ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN env var → mode=api_key, valid forever\n * 2. macOS: Keychain entry \"Claude Code-credentials\"\n * 3. Linux: ~/.claude/.credentials.json (or credentials.json)\n *\n * Reported in the host heartbeat so the console can surface a \"re-pair\"\n * prompt before Claude Code goes silently broken.\n *\n * Returns null if no authentication state can be detected at all (e.g. the\n * host has never logged in and has no API key set).\n */\nexport async function detectClaudeAuth(): Promise<ClaudeAuthReport | null> {\n if (process.env['ANTHROPIC_API_KEY'] || process.env['ANTHROPIC_AUTH_TOKEN']) {\n return { mode: 'api_key', status: 'valid', expires_at: null };\n }\n\n const creds = await readOauthCredentials();\n if (!creds) return null;\n\n return computeSubscriptionStatus(creds);\n}\n\ntype OauthCreds = {\n expiresAt?: number | string;\n accessToken?: string;\n refreshToken?: string;\n};\n\n/**\n * List every path where Claude Code OAuth credentials might live on this\n * host, ordered by preference (calling user's home first, then /home/* on\n * Linux-root). Useful for other CLI code that wants to e.g. copy the creds\n * into the manager's HOME before spawning claude.\n */\nexport async function findClaudeCredentialsPaths(): Promise<string[]> {\n const candidates: string[] = [\n join(homedir(), '.claude', '.credentials.json'),\n join(homedir(), '.claude', 'credentials.json'),\n ];\n\n const isLinuxRoot =\n platform() === 'linux'\n && typeof process.getuid === 'function'\n && process.getuid() === 0;\n\n if (isLinuxRoot) {\n try {\n const entries = await readdir('/home', { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n candidates.push(join('/home', entry.name, '.claude', '.credentials.json'));\n candidates.push(join('/home', entry.name, '.claude', 'credentials.json'));\n }\n } catch {\n // /home doesn't exist or isn't readable\n }\n }\n\n return candidates;\n}\n\nasync function readOauthCredentials(): Promise<OauthCreds | null> {\n if (platform() === 'darwin') {\n const fromKeychain = await readMacKeychain();\n if (fromKeychain) return fromKeychain;\n }\n\n const candidates = await findClaudeCredentialsPaths();\n\n for (const path of candidates) {\n const parsed = await readJsonSilently(path);\n if (parsed) {\n // Claude Code has historically used several keys at the top level.\n return (parsed.claudeAiOauth ?? parsed.oauth ?? parsed) as OauthCreds;\n }\n }\n\n return null;\n}\n\nasync function readMacKeychain(): Promise<OauthCreds | null> {\n try {\n const { stdout } = await execFileAsync(\n 'security',\n ['find-generic-password', '-s', 'Claude Code-credentials', '-w'],\n { timeout: 3000 }\n );\n const parsed = JSON.parse(stdout.trim()) as Record<string, unknown>;\n return (parsed.claudeAiOauth ?? parsed) as OauthCreds;\n } catch {\n return null;\n }\n}\n\nasync function readJsonSilently(path: string): Promise<Record<string, unknown> | null> {\n try {\n const raw = await readFile(path, 'utf-8');\n return JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nfunction computeSubscriptionStatus(creds: OauthCreds): ClaudeAuthReport {\n const expiresAtMs = parseExpiresAt(creds.expiresAt);\n\n // `expiresAt` on Claude Code subscription creds is the short-lived access\n // token (~8 hours). When a refresh token is present, Claude Code auto-\n // refreshes it silently in the background — the access-token expiry is\n // not a meaningful signal for \"needs human action\". Only warn on access-\n // token expiry when there's no refresh token to fall back on.\n //\n // Caveat: we don't track refresh-token expiry because Claude Code's\n // credentials format doesn't expose one. Refresh tokens are typically\n // long-lived (months). If one does expire, Claude Code surfaces the\n // failure at the next auto-refresh attempt, which the operator will\n // see in their manager.log. Trading off a rare silent-fail scenario\n // for a much worse \"warn on every login\" experience.\n const hasRefreshToken = typeof creds.refreshToken === 'string' && creds.refreshToken.trim().length > 0;\n\n if (expiresAtMs === null) {\n return {\n mode: 'subscription',\n status: 'unknown',\n expires_at: null,\n };\n }\n\n const msUntilExpiry = expiresAtMs - Date.now();\n let status: ClaudeAuthStatus;\n if (hasRefreshToken) {\n // Even past the access-token expiry we expect auto-refresh to succeed.\n // Only report 'expired' if we're past expiry AND don't have a refresh\n // token — we can't reach that branch here, so always 'valid' when\n // refresh token is present.\n status = 'valid';\n } else {\n status =\n msUntilExpiry <= 0 ? 'expired'\n : msUntilExpiry <= EXPIRING_SOON_MS ? 'expiring_soon'\n : 'valid';\n }\n\n return {\n mode: 'subscription',\n status,\n expires_at: new Date(expiresAtMs).toISOString(),\n };\n}\n\nfunction parseExpiresAt(raw: unknown): number | null {\n if (typeof raw === 'number' && Number.isFinite(raw)) {\n // Ms-since-epoch or seconds-since-epoch — disambiguate: anything below\n // year 2100 in seconds is <= 4e9, while the same instant in ms is 4e12.\n return raw < 1e12 ? raw * 1000 : raw;\n }\n if (typeof raw === 'string') {\n const parsed = Date.parse(raw);\n return Number.isNaN(parsed) ? null : parsed;\n }\n return null;\n}\n","/**\n * Deterministic JSON serialisation for use as a hash key (ENG-4468).\n *\n * The manager caches channel-config hashes to decide whether to rewrite\n * `.mcp.json` each poll. The previous implementation hashed\n * `JSON.stringify(entry.config)` directly, but Postgres JSONB doesn't\n * preserve key order — each `SELECT config FROM ...` can return the same\n * semantic object with different key ordering. `JSON.stringify` then\n * produces a different string, the hash mismatches, and the cache is\n * missed every poll. For agents with nested config objects (e.g. Slack's\n * `manifest`) the churn was constant: credentials rewritten every\n * ~1–2 min → drift detection fires → MCP subprocess gets re-read and\n * sometimes fails to respawn cleanly.\n *\n * `canonicalJson` walks the value and emits keys in sorted order at every\n * object level so two semantically-identical configs produce identical\n * output regardless of the input key order.\n */\n\nexport function canonicalJson(value: unknown): string {\n return JSON.stringify(normalize(value));\n}\n\n/**\n * Recursively rebuild the value with every object's keys in alphabetic\n * order. Arrays preserve their order (semantic). Primitives pass\n * through. Circular references would throw here the same way they\n * would in JSON.stringify, which is fine — this codepath is fed by\n * plain DB JSONB so cycles are impossible.\n */\nfunction normalize(value: unknown): unknown {\n if (value === null || typeof value !== 'object') return value;\n if (Array.isArray(value)) return value.map(normalize);\n const sorted: Record<string, unknown> = {};\n const keys = Object.keys(value as Record<string, unknown>).sort();\n for (const k of keys) {\n sorted[k] = normalize((value as Record<string, unknown>)[k]);\n }\n return sorted;\n}\n","// ENG-4712: persist the manager's channel-config hash cache across\n// process restarts.\n//\n// The manager keeps an in-memory Map of `agentId:channelId → sha256` so\n// that consecutive polls don't re-write `.mcp.json` channel entries\n// that haven't changed. That Map lives at module scope, so every fresh\n// manager process (post self-update on Linux EC2, post launchd respawn\n// on macOS, post SIGTERM, etc.) starts empty. With an empty cache the\n// very next poll re-emits a `Channel credentials written … reason=first-write`\n// log line for every active agent × channel pair, even though the\n// on-disk content already matches the desired config — a noisy and\n// misleading signal that operators read as \"credentials keep churning\".\n//\n// Round-tripping the Map through a JSON sidecar at the configDir root\n// turns those churn lines into silent no-ops on restart while leaving\n// the existing in-memory cache contract unchanged.\n\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nconst CACHE_FILENAME = 'channel-hash-cache.json';\n\nexport function getChannelHashCacheFile(configDir: string): string {\n return join(configDir, CACHE_FILENAME);\n}\n\n/**\n * Hydrate `target` with persisted entries. Missing / unreadable /\n * malformed files are treated as \"empty cache\" — the next poll will\n * rewrite credentials and repopulate, which costs at most one extra\n * round of `first-write` log lines but never breaks correctness.\n */\nexport function loadChannelHashCache(\n target: Map<string, string>,\n configDir: string,\n): void {\n const path = getChannelHashCacheFile(configDir);\n if (!existsSync(path)) return;\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(path, 'utf-8'));\n } catch {\n return;\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return;\n for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {\n if (typeof value === 'string' && value.length > 0) target.set(key, value);\n }\n}\n\n/**\n * Persist `source` to disk. Failures are non-fatal — the cache stays\n * in memory and the next save attempt may succeed. We accept one extra\n * `first-write` round on the next manager restart over crashing the\n * poll loop.\n */\nexport function saveChannelHashCache(\n source: Map<string, string>,\n configDir: string,\n): void {\n const path = getChannelHashCacheFile(configDir);\n const obj: Record<string, string> = {};\n for (const [key, value] of source) obj[key] = value;\n try {\n writeFileSync(path, JSON.stringify(obj, null, 2));\n } catch {\n // intentional no-op; see doc comment\n }\n}\n","/**\n * Channel MCP process sweep (ENG-4453 / ENG-4448).\n *\n * Defense-in-depth: kill surplus per-agent channel MCP processes that escaped\n * the stdin-close + SIGTERM/SIGINT handlers added in ENG-4434. Slack and\n * Telegram load-balance inbound events across every connected long-poll /\n * Socket Mode waiter, so orphan channel children silently swallow ~85% of\n * traffic when they accumulate.\n *\n * The sweep runs on a throttle (default every 5 min) from the manager poll\n * loop. For each configured agent, it looks for `slack-channel.js`,\n * `direct-chat-channel.js`, `telegram-channel.js` processes whose env includes\n * `AGT_AGENT_CODE_NAME=<code_name>` (all three servers set this, starting\n * ENG-4436). Expected count per agent per channel type is 1.\n *\n * Live-process determination — three signals, in priority order:\n * 1. **PPID chain anchored on the agent's tmux pane** (authoritative). The\n * caller passes `livePidsByAgent`, populated by `tmux list-panes`. Any\n * MCP whose ancestor chain reaches one of those pids is live; everything\n * else for that agent is zombie. This deterministically resolves the\n * \"two non-orphans\" case that older sweep versions punted on.\n * 2. **PPID === 1** (definite orphan). Used as a fallback when the tmux\n * anchor is unavailable (no live session, lookup failed).\n * 3. **Singleton in group**. If exactly one process exists for an agent's\n * channel and it's not visibly orphaned, leave it — might be mid-spawn.\n *\n * Kill selection is pure — see `pickKillTargets` — so it can be unit-tested\n * without spawning processes. The ps-query, tmux query, and actual SIGTERM\n * live behind `sweepChannelProcesses`.\n */\n\nimport { execFileSync } from 'node:child_process';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type ChannelType = 'slack-channel' | 'direct-chat-channel' | 'telegram-channel';\n\n/** Basenames the MCP channel servers publish in their bin field — keep in\n * sync with packages/mcp/package.json. */\nconst CHANNEL_BASENAMES: ChannelType[] = [\n 'slack-channel',\n 'direct-chat-channel',\n 'telegram-channel',\n];\n\nexport interface ChannelProcessInfo {\n pid: number;\n ppid: number;\n channelType: ChannelType;\n codeName: string;\n /** Monotonically increasing elapsed-time seconds. Higher = older. */\n etimeSeconds: number;\n /** Original ps command field, truncated. Kept for log messages. */\n command: string;\n}\n\nexport interface SweepKill {\n pid: number;\n codeName: string;\n channelType: ChannelType;\n etimeSeconds: number;\n /**\n * - `orphan`: PPID === 1 (parent died, reparented to init/launchd).\n * - `not-in-live-chain`: PPID chain doesn't reach any of the agent's\n * known tmux pane pids — i.e. it belongs to a previous Claude Code\n * session that didn't clean up its children.\n */\n reason: 'orphan' | 'not-in-live-chain';\n}\n\nexport interface SweepResult {\n /** Kill targets that were actually signalled (or would be in dry-run). */\n kills: SweepKill[];\n /** How many channel-matching processes the sweep inspected. */\n inspected: number;\n /** Agents the sweep considered (for log correlation). */\n scannedAgents: string[];\n dryRun: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// etime parsing — `[[dd-]hh:]mm:ss`\n// ---------------------------------------------------------------------------\n\n/** Convert a ps `etime` field (macOS + Linux format) to seconds. */\nexport function parseEtime(s: string): number {\n const trimmed = s.trim();\n if (!trimmed) return 0;\n\n let days = 0;\n let rest = trimmed;\n const dashIdx = rest.indexOf('-');\n if (dashIdx >= 0) {\n days = parseInt(rest.slice(0, dashIdx), 10) || 0;\n rest = rest.slice(dashIdx + 1);\n }\n const parts = rest.split(':').map((p) => parseInt(p, 10) || 0);\n // Normalise to [h, m, s] right-aligned (ps omits leading zero fields).\n let h = 0;\n let m = 0;\n let sec = 0;\n if (parts.length === 3) {\n [h, m, sec] = parts as [number, number, number];\n } else if (parts.length === 2) {\n [m, sec] = parts as [number, number];\n } else if (parts.length === 1) {\n [sec] = parts as [number];\n }\n return days * 86_400 + h * 3_600 + m * 60 + sec;\n}\n\n// ---------------------------------------------------------------------------\n// ps output parsing\n// ---------------------------------------------------------------------------\n\n/**\n * Parse `ps eww -o pid=,ppid=,etime=,command=` output. `eww` appends the\n * process's env to the command field (`KEY=val KEY=val ...`), which is how\n * we scope kills to a specific agent via AGT_AGENT_CODE_NAME.\n *\n * Exported for unit tests — the live path calls this from sweepChannelProcesses.\n */\nexport function parsePsOutput(psOutput: string): ChannelProcessInfo[] {\n const results: ChannelProcessInfo[] = [];\n const lines = psOutput.split('\\n');\n\n for (const rawLine of lines) {\n const line = rawLine.trimStart();\n if (!line) continue;\n\n // Columns: PID PPID ETIME COMMAND...\n // etime has no spaces, so splitting on whitespace for the first 3 fields\n // is safe. Everything after field 3 is the command + env.\n const firstSpace = line.search(/\\s+/);\n if (firstSpace < 0) continue;\n const pid = parseInt(line.slice(0, firstSpace), 10);\n if (!Number.isFinite(pid)) continue;\n\n const afterPid = line.slice(firstSpace).trimStart();\n const secondSpace = afterPid.search(/\\s+/);\n if (secondSpace < 0) continue;\n const ppid = parseInt(afterPid.slice(0, secondSpace), 10);\n if (!Number.isFinite(ppid)) continue;\n\n const afterPpid = afterPid.slice(secondSpace).trimStart();\n const thirdSpace = afterPpid.search(/\\s+/);\n if (thirdSpace < 0) continue;\n const etime = afterPpid.slice(0, thirdSpace);\n const command = afterPpid.slice(thirdSpace).trimStart();\n\n // Match the script token at a word boundary — `ps eww` appends the whole\n // process env to the command field, so a plain `.includes()` can\n // false-positive when an env value happens to contain `slack-channel.js`\n // or similar. Path prefix excludes `=` to rule out `KEY=/path/…-channel.js`\n // env-value tokens.\n const channelMatch = command.match(\n new RegExp(`(?:^|\\\\s)(?:[^\\\\s=]*/)?(${CHANNEL_BASENAMES.join('|')})\\\\.js(?:\\\\s|$)`),\n );\n const channelType = channelMatch?.[1] as ChannelType | undefined;\n if (!channelType) continue;\n\n // Extract AGT_AGENT_CODE_NAME from the env-suffix on the command line.\n // Matches on a word boundary so `AGT_AGENT_CODE_NAME_FOO` can't spoof it.\n const match = command.match(/(?:^|\\s)AGT_AGENT_CODE_NAME=([^\\s]+)/);\n if (!match) continue;\n const codeName = match[1]!;\n\n results.push({\n pid,\n ppid,\n channelType,\n codeName,\n etimeSeconds: parseEtime(etime),\n command: command.slice(0, 500),\n });\n }\n\n return results;\n}\n\n/**\n * Parse the same `ps eww` output to build a PID → PPID map covering EVERY\n * process on the host (not just channel children). Used to walk the parent\n * chain of an MCP up to the agent's tmux pane pid.\n */\nexport function parseAllPids(psOutput: string): Map<number, number> {\n const map = new Map<number, number>();\n for (const rawLine of psOutput.split('\\n')) {\n const line = rawLine.trimStart();\n if (!line) continue;\n const firstSpace = line.search(/\\s+/);\n if (firstSpace < 0) continue;\n const pid = parseInt(line.slice(0, firstSpace), 10);\n if (!Number.isFinite(pid)) continue;\n const afterPid = line.slice(firstSpace).trimStart();\n const secondSpace = afterPid.search(/\\s+/);\n if (secondSpace < 0) continue;\n const ppid = parseInt(afterPid.slice(0, secondSpace), 10);\n if (!Number.isFinite(ppid)) continue;\n map.set(pid, ppid);\n }\n return map;\n}\n\n/**\n * Walk the PPID chain from `startPid` upward until we hit pid 0/1 or visit a\n * pid we've already seen (cycle guard). Returns the set of ancestors visited\n * INCLUDING `startPid` itself.\n *\n * Bounded to 64 hops — process trees on real hosts never approach that.\n */\nexport function walkPpidChain(startPid: number, parentMap: Map<number, number>): Set<number> {\n const visited = new Set<number>();\n let cur = startPid;\n for (let i = 0; i < 64; i++) {\n if (cur <= 1 || visited.has(cur)) break;\n visited.add(cur);\n const parent = parentMap.get(cur);\n if (parent === undefined) break;\n cur = parent;\n }\n return visited;\n}\n\n// ---------------------------------------------------------------------------\n// Kill target selection — pure\n// ---------------------------------------------------------------------------\n\nexport interface PickKillTargetsArgs {\n processes: ChannelProcessInfo[];\n agentCodeNames: Set<string>;\n /** PID → PPID map for the entire host, used for chain walks. */\n parentMap: Map<number, number>;\n /** codeName → set of pids identified as live anchors (typically tmux pane\n * pids for the agent's `agt-<codeName>` session). When a key is missing or\n * the set is empty, the sweep falls back to PPID===1 orphan detection only\n * for that agent. */\n livePidsByAgent: Map<string, Set<number>>;\n}\n\nexport interface PickKillTargetsResult {\n kills: SweepKill[];\n /** Reserved for future use. The chain-walk eliminates the previous\n * \"ambiguous\" case entirely — when livePidsByAgent has an entry for the\n * agent, every process is decisively classified. Kept on the result type\n * for backward compatibility with logging code. */\n ambiguousGroups: string[];\n}\n\n/**\n * Given every channel process on the host, group by (codeName, channelType)\n * and identify zombies via PPID-chain walk against the agent's known live\n * anchor pids.\n *\n * Kill rules:\n * 1. **PPID chain reaches the agent's live pid set** → live (keep).\n * 2. **PPID === 1** → orphan (kill).\n * 3. **Live anchor known but chain doesn't reach it** → not-in-live-chain\n * (kill). This catches the previously-ambiguous case where two non-orphan\n * processes both claim to be the agent's MCP — the one parented under the\n * dead-but-not-yet-reaped Claude Code session gets killed.\n * 4. **Live anchor unknown** (no tmux session, lookup failed) → fall back\n * to PPID===1 orphan detection only; never kill non-orphans without\n * positive evidence.\n * 5. **Singleton non-orphan with no live-anchor info** → leave alone (might\n * be mid-spawn).\n *\n * Only considers agents in `agentCodeNames` — a host shared with another\n * manager instance must not cross-kill into that instance's agents.\n */\nexport function pickKillTargets(args: PickKillTargetsArgs): PickKillTargetsResult {\n const { processes, agentCodeNames, parentMap, livePidsByAgent } = args;\n const kills: SweepKill[] = [];\n\n // Seed the parent map with each channel process's known ppid so a sparse\n // input map (or a unit-test fixture that omits these) still walks correctly.\n const fullParentMap = new Map(parentMap);\n for (const proc of processes) {\n if (!fullParentMap.has(proc.pid)) fullParentMap.set(proc.pid, proc.ppid);\n }\n\n const groups = new Map<string, ChannelProcessInfo[]>();\n for (const proc of processes) {\n if (!agentCodeNames.has(proc.codeName)) continue;\n const key = `${proc.codeName}:${proc.channelType}`;\n const bucket = groups.get(key);\n if (bucket) bucket.push(proc);\n else groups.set(key, [proc]);\n }\n\n for (const [, bucket] of groups) {\n if (bucket.length === 0) continue;\n const codeName = bucket[0]!.codeName;\n const liveAnchors = livePidsByAgent.get(codeName);\n const haveAnchors = !!liveAnchors && liveAnchors.size > 0;\n\n for (const proc of bucket) {\n // Definite orphan — always a kill target regardless of anchor info.\n if (proc.ppid === 1) {\n // …unless it's the only process for this group and we have no\n // anchor info: keep it as a last-resort listener (mid-respawn case).\n if (!haveAnchors && bucket.length === 1) continue;\n kills.push({\n pid: proc.pid,\n codeName: proc.codeName,\n channelType: proc.channelType,\n etimeSeconds: proc.etimeSeconds,\n reason: 'orphan',\n });\n continue;\n }\n\n // Non-orphan: classify via PPID-chain walk if we have anchor info.\n if (!haveAnchors) continue;\n\n const chain = walkPpidChain(proc.pid, fullParentMap);\n let isLive = false;\n for (const anchor of liveAnchors!) {\n if (chain.has(anchor)) { isLive = true; break; }\n }\n if (!isLive) {\n kills.push({\n pid: proc.pid,\n codeName: proc.codeName,\n channelType: proc.channelType,\n etimeSeconds: proc.etimeSeconds,\n reason: 'not-in-live-chain',\n });\n }\n }\n }\n\n return { kills, ambiguousGroups: [] };\n}\n\n// ---------------------------------------------------------------------------\n// Live sweep — wraps ps + kill, throttled by the caller\n// ---------------------------------------------------------------------------\n\nexport interface SweepOptions {\n agentCodeNames: Set<string>;\n /** When true, log kill targets but do not send SIGTERM. */\n dryRun: boolean;\n /** Injected for testability; defaults to process-level SIGTERM. */\n killFn?: (pid: number) => void;\n log: (msg: string) => void;\n}\n\nfunction defaultKill(pid: number): void {\n try {\n process.kill(pid, 'SIGTERM');\n } catch {\n // Process may have exited between ps and kill — race is harmless.\n }\n}\n\n/**\n * Resolve the live tmux pane pid for each agent — the authoritative anchor for\n * \"this MCP belongs to the currently-running Claude Code session\". Returns an\n * empty set for agents whose tmux session isn't found (no live anchor → sweep\n * falls back to orphan-only detection for that agent).\n *\n * Each agent's session name is `agt-<codeName>`; the pane pid is the program\n * running directly in that pane (Claude Code), and every MCP child has it\n * somewhere on its parent chain.\n */\nexport function resolveLiveAnchorPids(agentCodeNames: Iterable<string>): Map<string, Set<number>> {\n const result = new Map<string, Set<number>>();\n for (const codeName of agentCodeNames) {\n const pids = new Set<number>();\n try {\n const out = execFileSync('tmux', ['list-panes', '-t', `agt-${codeName}`, '-F', '#{pane_pid}'], {\n encoding: 'utf-8',\n timeout: 2_000,\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n for (const line of out.split('\\n')) {\n const pid = parseInt(line.trim(), 10);\n if (Number.isFinite(pid) && pid > 1) pids.add(pid);\n }\n } catch {\n // tmux session doesn't exist (paused/never started/crashed) — leave\n // pids empty so the sweep falls back to orphan-only detection.\n }\n result.set(codeName, pids);\n }\n return result;\n}\n\n/**\n * Run `ps eww`, classify surplus per-agent channel processes, and (unless\n * dry-run) SIGTERM them.\n *\n * The poll loop calls this; it's responsible for the throttling interval.\n */\nexport async function sweepChannelProcesses(opts: SweepOptions): Promise<SweepResult> {\n const { agentCodeNames, dryRun, log } = opts;\n const kill = opts.killFn ?? defaultKill;\n\n let psOutput = '';\n try {\n // -E on macOS, `e` on Linux — both append the process environment to the\n // command field. macOS accepts `-E`, Linux's procps-ng does not, but both\n // accept `e` as a BSD-style option when passed without the leading dash.\n psOutput = execFileSync('ps', ['eww', '-o', 'pid=,ppid=,etime=,command='], {\n encoding: 'utf-8',\n timeout: 5_000,\n maxBuffer: 10 * 1024 * 1024,\n });\n } catch (err) {\n log(`[channel-sweep] ps failed: ${(err as Error).message}`);\n return { kills: [], inspected: 0, scannedAgents: [...agentCodeNames], dryRun };\n }\n\n const processes = parsePsOutput(psOutput);\n const parentMap = parseAllPids(psOutput);\n const livePidsByAgent = resolveLiveAnchorPids(agentCodeNames);\n const { kills } = pickKillTargets({ processes, agentCodeNames, parentMap, livePidsByAgent });\n\n for (const target of kills) {\n const ageMin = Math.round(target.etimeSeconds / 60);\n log(\n `[channel-sweep]${dryRun ? '[dry-run]' : ''} surplus ${target.channelType} for ${target.codeName}: ` +\n `killing pid=${target.pid} (${target.reason}, age=${ageMin}m)`,\n );\n if (!dryRun) kill(target.pid);\n }\n\n return {\n kills,\n inspected: processes.length,\n scannedAgents: [...agentCodeNames],\n dryRun,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Targeted teardown — kill every channel MCP for one (de-provisioned) agent\n// ---------------------------------------------------------------------------\n\n/**\n * Pure: from a list of channel processes, return every pid whose\n * AGT_AGENT_CODE_NAME matches `codeName`. Exported for unit tests.\n *\n * Used by the de-provision path: when an agent is unassigned/deleted from a\n * host, the periodic `sweepChannelProcesses` will *not* clean up its slack /\n * telegram / direct-chat MCPs because that sweep is scoped to currently-known\n * agent code names. Without targeted teardown, the slack-channel and\n * telegram-channel processes survive (often reparented to init when the tmux\n * session dies), and Telegram's getUpdates load-balances inbound traffic to\n * whichever poller is alive — including the orphan on the old host. That's\n * exactly the conflict that took Scout offline after migrating from one host\n * to another.\n */\nexport function pickTeardownTargets(\n processes: ChannelProcessInfo[],\n codeName: string,\n): ChannelProcessInfo[] {\n return processes.filter((p) => p.codeName === codeName);\n}\n\nexport interface KillAgentChannelProcessesOptions {\n /** Injected for testability; defaults to process.kill SIGTERM/SIGKILL. */\n killFn?: (pid: number, signal: NodeJS.Signals) => void;\n /** Injected for testability; defaults to ps eww. */\n psFn?: () => string;\n log: (msg: string) => void;\n /** When true, log targets but do not signal them. */\n dryRun?: boolean;\n}\n\nfunction defaultKillSignal(pid: number, signal: NodeJS.Signals): void {\n try {\n process.kill(pid, signal);\n } catch {\n // Already exited — race is harmless.\n }\n}\n\nfunction defaultPs(): string {\n return execFileSync('ps', ['eww', '-o', 'pid=,ppid=,etime=,command='], {\n encoding: 'utf-8',\n timeout: 5_000,\n maxBuffer: 10 * 1024 * 1024,\n });\n}\n\n/**\n * SIGTERM every slack/telegram/direct-chat MCP for `codeName`. Best-effort —\n * failures are logged and swallowed so the de-provision path can continue.\n *\n * Returns the pids it signalled (useful for tests + log correlation).\n */\nexport function killAgentChannelProcesses(\n codeName: string,\n opts: KillAgentChannelProcessesOptions,\n): number[] {\n const { log, dryRun = false } = opts;\n const kill = opts.killFn ?? defaultKillSignal;\n const ps = opts.psFn ?? defaultPs;\n\n let psOutput = '';\n try {\n psOutput = ps();\n } catch (err) {\n log(`[channel-teardown] ps failed for '${codeName}': ${(err as Error).message}`);\n return [];\n }\n\n const targets = pickTeardownTargets(parsePsOutput(psOutput), codeName);\n if (targets.length === 0) return [];\n\n const pids = targets.map((t) => t.pid);\n log(\n `[channel-teardown]${dryRun ? '[dry-run]' : ''} de-provision '${codeName}': ` +\n `killing ${targets.length} channel MCP(s) — ` +\n targets.map((t) => `${t.channelType}#${t.pid}`).join(', '),\n );\n\n if (!dryRun) {\n for (const pid of pids) kill(pid, 'SIGTERM');\n }\n return pids;\n}\n","/**\n * ENG-4705: Channel input watchdog.\n *\n * Symptom: an inbound Slack/Telegram/Direct-Chat message lands in the Claude\n * Code TUI input buffer (visible as `❯ <text>` between the input-box rule\n * lines) but the channel server's auto-submit doesn't fire — the text just\n * sits there until something sends Enter manually.\n *\n * The dispatcher pattern landed in ENG-4684 reduces the *frequency* (slow\n * requests get fanned out to a background subagent so the parent's listener\n * turn returns immediately), but doesn't fix the underlying race: when\n * triage decides a request is \"fast\" and the parent handles it inline, the\n * main turn still occupies the TUI for several seconds, and any channel\n * message that arrives during that window stacks in the input buffer\n * un-submitted.\n *\n * Workaround the ticket itself documents: `tmux send-keys -t <session>\n * Enter`. This watchdog automates that — every poll cycle it captures the\n * pane for each managed claude-code agent, looks for un-submitted text in\n * the input box, and fires Enter once the same text has been sitting there\n * unchanged for STUCK_THRESHOLD_MS.\n *\n * Safety:\n * - Skip the agent if a tmux client is attached (a human might be typing).\n * - Require the text to be unchanged for STUCK_THRESHOLD_MS — short windows\n * avoid racing the channel server's own (eventual) submit.\n * - Single-shot per stuck buffer: once we fire Enter for a hash we don't\n * fire again until the input changes.\n * - Skip if Claude is actively rendering a spinner (`✻ <verb>ing…`); a\n * fresh send-keys Enter into a busy TUI is harmless but noisy in logs.\n */\n\nconst STUCK_THRESHOLD_MS = 5_000;\n// ENG-4716: when a tmux client is attached we don't skip the agent\n// outright (the original safety-first carve-out swallowed the steady-\n// state \"monitoring\" case operators care about). Instead we widen the\n// stable-buffer window so a human typing has more than 5s to finish\n// before the watchdog steps in. Active typing changes the buffer hash\n// every keystroke and keeps resetting `firstSeenAt`, so this only\n// matters when the buffer is *truly* stable.\nconst ATTACHED_STUCK_THRESHOLD_MS = 15_000;\nconst INPUT_BOX_DIVIDER = /^[─━]{10,}/;\nconst PROMPT_PREFIX = '❯ ';\n\nexport interface AgentInputState {\n /** Hash of the input-box text observed last poll. */\n lastInputHash: string;\n /** Wall-clock ms when this hash was first seen. */\n firstSeenAt: number;\n /** Have we already sent Enter for this hash? */\n resolved: boolean;\n}\n\nexport interface WatchdogIo {\n /** Snapshot of the agent's tmux pane (multiline). Empty / null if the session doesn't exist. */\n capturePane: (codeName: string) => string | null;\n /** Whether any tmux client is attached to the session right now. */\n isClientAttached: (codeName: string) => boolean;\n /** Send a single Enter keystroke to the agent's tmux session. */\n sendEnter: (codeName: string) => void;\n /** Logger. */\n log: (msg: string) => void;\n /** Current wall-clock ms (injectable for tests). */\n now: () => number;\n}\n\nexport interface WatchdogConfig {\n /** ms a stuck buffer must persist before we fire Enter. Defaults to STUCK_THRESHOLD_MS. */\n stuckThresholdMs?: number;\n /**\n * ms a stuck buffer must persist before we fire Enter when a tmux\n * client is attached to the session. Higher than `stuckThresholdMs`\n * to give a human typer extra headroom. Defaults to\n * ATTACHED_STUCK_THRESHOLD_MS.\n */\n attachedStuckThresholdMs?: number;\n}\n\n/**\n * Single-agent decision step — pure given the pane text + state. Returns\n * the next state and whether to fire Enter.\n *\n * Exported for unit testing.\n */\nexport function decide(\n pane: string,\n prev: AgentInputState | undefined,\n now: number,\n config: WatchdogConfig = {},\n): { fire: boolean; next: AgentInputState | undefined } {\n const threshold = config.stuckThresholdMs ?? STUCK_THRESHOLD_MS;\n\n const inputText = extractInputBoxText(pane);\n if (!inputText) {\n return { fire: false, next: undefined };\n }\n\n if (isActivelyProcessing(pane)) {\n // Don't reset the timer — the input might still be stuck once Claude\n // finishes; we just don't fire while a spinner is live.\n return { fire: false, next: prev };\n }\n\n const hash = simpleHash(inputText);\n if (!prev || prev.lastInputHash !== hash) {\n return {\n fire: false,\n next: { lastInputHash: hash, firstSeenAt: now, resolved: false },\n };\n }\n\n if (prev.resolved) return { fire: false, next: prev };\n if (now - prev.firstSeenAt < threshold) return { fire: false, next: prev };\n\n return {\n fire: true,\n next: { ...prev, resolved: true },\n };\n}\n\n/**\n * Extract the contents of the Claude Code input box from a pane snapshot.\n * Returns null when the input box is empty or absent.\n *\n * The TUI bracketed input area looks like:\n * ────────── agt-bob ──\n * ❯ ping repro 2\n * ──────────────────────\n *\n * We accept any line starting with `❯ ` whose previous non-empty line is a\n * row of `─` characters (the top divider) — that's robust to width changes\n * and to the optional ` agt-<codeName> ` label embedded in the divider.\n *\n * Exported for unit testing.\n */\nexport function extractInputBoxText(pane: string): string | null {\n const lines = pane.split('\\n');\n for (let i = 1; i < lines.length; i++) {\n const line = lines[i] ?? '';\n if (!line.startsWith(PROMPT_PREFIX)) continue;\n // Walk back to the most recent non-empty line; it must be a divider row.\n let j = i - 1;\n while (j >= 0 && (lines[j] ?? '').trim() === '') j--;\n if (j < 0) continue;\n if (!INPUT_BOX_DIVIDER.test((lines[j] ?? '').trim())) continue;\n const text = line.slice(PROMPT_PREFIX.length).trim();\n return text.length > 0 ? text : null;\n }\n return null;\n}\n\n/**\n * True when the pane shows a live spinner (`✻ Cogitating…`,\n * `✻ Crunching…`, etc.). Past tense forms like `✻ Cogitated for 7s` mean\n * the work has finished and we treat the agent as idle.\n *\n * Exported for unit testing.\n */\nexport function isActivelyProcessing(pane: string): boolean {\n // Search bottom-up for the most recent spinner line.\n const lines = pane.split('\\n');\n for (let i = lines.length - 1; i >= 0; i--) {\n const line = (lines[i] ?? '').trim();\n if (!line.startsWith('✻')) continue;\n // Past tense: \"✻ Cogitated for 7s\", \"✻ Crunched for 51s\" — work done.\n if (/\\bfor\\s+\\d+s\\s*$/.test(line)) return false;\n // Present participle: \"✻ Cogitating…\", \"✻ Crunching…\" — still working.\n if (/\\b\\w+ing[…\\.]{0,3}\\s*$/i.test(line)) return true;\n // Ambiguous spinner line — don't block on it.\n return false;\n }\n return false;\n}\n\nfunction simpleHash(s: string): string {\n let h = 0;\n for (let i = 0; i < s.length; i++) {\n h = ((h << 5) - h + s.charCodeAt(i)) | 0;\n }\n return h.toString(16);\n}\n\n/**\n * Run one watchdog pass over the given agents. Stateful: keeps an internal\n * map of per-agent input state across calls.\n */\nexport function checkChannelInputs(\n codeNames: readonly string[],\n io: WatchdogIo,\n config: WatchdogConfig = {},\n states: Map<string, AgentInputState> = sharedStates,\n): void {\n const live = new Set(codeNames);\n for (const codeName of codeNames) {\n try {\n checkOne(codeName, io, config, states);\n } catch (err) {\n io.log(`[channel-input-watchdog] '${codeName}': ${(err as Error).message}`);\n }\n }\n // Drop state for agents that are no longer in scope.\n for (const key of [...states.keys()]) {\n if (!live.has(key)) states.delete(key);\n }\n}\n\nfunction checkOne(\n codeName: string,\n io: WatchdogIo,\n config: WatchdogConfig,\n states: Map<string, AgentInputState>,\n): void {\n const pane = io.capturePane(codeName);\n if (!pane) {\n states.delete(codeName);\n return;\n }\n\n // ENG-4716: don't skip outright when a client is attached — operators\n // routinely keep a tmux client open just to monitor agents, and the\n // original blanket skip ate the steady-state case. Widen the stable-\n // buffer threshold instead. Active typing changes the buffer hash on\n // every keystroke, so the unchanged-hash gate already covers the\n // \"don't fight the human\" concern.\n const attached = io.isClientAttached(codeName);\n const effectiveConfig: WatchdogConfig = attached\n ? {\n ...config,\n stuckThresholdMs:\n config.attachedStuckThresholdMs ?? ATTACHED_STUCK_THRESHOLD_MS,\n }\n : config;\n\n const prev = states.get(codeName);\n const { fire, next } = decide(pane, prev, io.now(), effectiveConfig);\n\n if (next === undefined) {\n states.delete(codeName);\n } else {\n states.set(codeName, next);\n }\n\n if (fire) {\n // Log hash + length only — channel input may contain PII / secrets, so\n // prod logging stays hash-only per the project's logging policy.\n const text = extractInputBoxText(pane) ?? '';\n const hash = next?.lastInputHash ?? simpleHash(text);\n io.log(\n `[channel-input-watchdog] '${codeName}': stuck channel input — firing Enter (input_hash=${hash}, len=${text.length})`,\n );\n io.sendEnter(codeName);\n }\n}\n\nconst sharedStates = new Map<string, AgentInputState>();\n\n/** Test seam — clear the singleton map between tests. */\nexport function _resetSharedStatesForTests(): void {\n sharedStates.clear();\n}\n","/**\n * Periodic discoverability hint for scheduled task deliveries (ENG-4457).\n *\n * Roughly every 10th scheduled delivery gets a follow-up message that\n * reminds the user their schedule is editable conversationally — Slack as\n * a thread reply, DM platforms as a second message. Uses a randomized\n * gate rather than a persisted counter because:\n * - state survives nothing (no DB writes / per-task schedule counters)\n * - sums to the right rate over time\n * - no race between concurrent deliveries\n *\n * Behaviour is purely a function of `(random, probability, env)` so the\n * decision logic is unit-testable without spawning a delivery.\n */\n\nconst DEFAULT_PROBABILITY = 0.1;\n\n/** Normalise an agent code_name to the env-var suffix form. Code names are\n * kebab-case; env vars use UPPER_SNAKE. Non-ident chars become `_`. */\nfunction envSuffixFor(codeName: string): string {\n return codeName.replace(/[^A-Za-z0-9]+/g, '_').toUpperCase();\n}\n\n/**\n * Configurable via env so operators can tune cadence or kill it entirely.\n *\n * Per-agent override takes precedence over host-global. For an agent with\n * code_name `phil-shout`:\n * AGT_DELIVERY_HINT_DISABLED__PHIL_SHOUT=1 → 0 (disabled for this agent)\n * AGT_DELIVERY_HINT_PROBABILITY__PHIL_SHOUT=0 → 0\n * AGT_DELIVERY_HINT_PROBABILITY__PHIL_SHOUT=0.5 → 0.5\n *\n * Fallback chain: per-agent disable → per-agent probability → host disable\n * → host probability → default (0.1). This lets an operator dial down a\n * noisy agent without affecting the rest of the host.\n */\nexport function hintProbability(\n codeName?: string,\n env: NodeJS.ProcessEnv = process.env,\n): number {\n const suffix = codeName ? `__${envSuffixFor(codeName)}` : '';\n\n // Per-agent disable wins outright — if an operator flagged a specific\n // agent off, a global probability override shouldn't re-enable it.\n if (suffix && env[`AGT_DELIVERY_HINT_DISABLED${suffix}`] === '1') return 0;\n\n const perAgent = suffix ? env[`AGT_DELIVERY_HINT_PROBABILITY${suffix}`] : undefined;\n if (perAgent != null) return clampProbability(perAgent);\n\n if (env['AGT_DELIVERY_HINT_DISABLED'] === '1') return 0;\n\n const host = env['AGT_DELIVERY_HINT_PROBABILITY'];\n if (host != null) return clampProbability(host);\n\n return DEFAULT_PROBABILITY;\n}\n\nfunction clampProbability(raw: string): number {\n const parsed = parseFloat(raw);\n if (!Number.isFinite(parsed)) return DEFAULT_PROBABILITY;\n if (parsed < 0) return 0;\n if (parsed > 1) return 1;\n return parsed;\n}\n\n/**\n * Pure decision gate. `rng` defaults to Math.random but is injectable so\n * tests can pin the outcome.\n */\nexport function shouldIncludeHint(\n probability: number,\n rng: () => number = Math.random,\n): boolean {\n if (probability <= 0) return false;\n if (probability >= 1) return true;\n return rng() < probability;\n}\n\n/**\n * The hint text. 10 variants to avoid \"I've seen this one before\" fatigue\n * when a heavy user catches a couple in the same week. Each variant must\n * surface at least two concrete example prompts so the reader has\n * something specific to try.\n */\nexport const HINT_VARIANTS: ReadonlyArray<string> = Object.freeze([\n \"Quick note: you can change this scheduled task just by asking me — e.g. \\\"Change the schedule for this task\\\" or \\\"Make this report less verbose in future\\\".\",\n \"By the way, this is on a schedule — if you'd like to tweak it, just say something like \\\"run this weekly instead of daily\\\" or \\\"make this report less verbose in future\\\" and I'll handle it.\",\n \"Heads up: I deliver this on a schedule you can edit conversationally. Try \\\"Change the schedule for this task\\\" or \\\"Make this report shorter\\\" whenever you want to adjust it.\",\n \"PS — no UI needed to tune this. Just tell me \\\"Send this at 9am instead\\\" or \\\"Skip weekends for this report\\\" and I'll update the schedule.\",\n \"FYI this is a scheduled delivery. You can reshape it in plain English — e.g. \\\"Make this fortnightly\\\" or \\\"Only include items from the last 24 hours\\\".\",\n \"You're the boss of this schedule — ask me things like \\\"Pause this for two weeks\\\" or \\\"Include a summary at the top next time\\\" and I'll apply it.\",\n \"Side note: you can say \\\"Change this to Mondays only\\\" or \\\"Drop the preamble in future reports\\\" and I'll update the task. No config screen required.\",\n \"Reminder: I run this on a schedule you can edit by talking. Good openers: \\\"Change when this fires\\\" or \\\"Make future reports more concise\\\".\",\n \"Small tip — this delivery is editable on the fly. Try \\\"Move this to afternoons\\\" or \\\"Cut the detail down in future runs\\\" and I'll retune it.\",\n \"If this cadence or format isn't quite right, just ask — \\\"Only run this on weekdays\\\" or \\\"Shorter summaries from now on\\\" both work, no form to fill out.\",\n]);\n\nexport function pickHintVariant(rng: () => number = Math.random): string {\n const idx = Math.floor(rng() * HINT_VARIANTS.length) % HINT_VARIANTS.length;\n return HINT_VARIANTS[idx]!;\n}\n","/**\n * Schedule-edit deep-link footer for scheduled-task deliveries (ENG-4462).\n *\n * ENG-4444 added the `/agents/:agentId/schedules/:taskId` route and a\n * `build_schedule_edit_link` MCP tool; ENG-4456 added a prompt-preamble line\n * telling the agent to embed kanban deep links. Neither guarantees the user\n * sees a clickable edit link on *every* scheduled delivery — the agent has\n * to opt in. This module closes that gap delivery-side by appending a\n * schedule-edit link to the body before it hits Slack/Telegram.\n *\n * All functions are pure so behaviour is unit-testable without spawning a\n * delivery. The manager-worker wrapper calls into these from\n * deliverScheduledTaskOutput.\n */\n\n/** Media whose footer format differs. Fallback is a plain URL. */\nexport type FooterMedium = 'slack' | 'telegram' | 'plain';\n\n/** Normalise an agent code_name to the env-var suffix form. Kebab → snake,\n * uppercased. Mirrors the pattern in delivery-hint.ts for consistency. */\nfunction envSuffixFor(codeName: string): string {\n return codeName.replace(/[^A-Za-z0-9]+/g, '_').toUpperCase();\n}\n\n/**\n * Is the schedule-link footer enabled for this delivery?\n *\n * Precedence (matches delivery-hint.ts):\n * per-agent disable → host disable → default (enabled).\n *\n * Per-agent env: `AGT_SCHEDULE_LINK_FOOTER_DISABLED__<CODE_NAME>=1`\n * Host-global env: `AGT_SCHEDULE_LINK_FOOTER_DISABLED=1`\n */\nexport function scheduleLinkFooterEnabled(\n codeName?: string,\n env: NodeJS.ProcessEnv = process.env,\n): boolean {\n if (codeName) {\n const perAgent = env[`AGT_SCHEDULE_LINK_FOOTER_DISABLED__${envSuffixFor(codeName)}`];\n if (perAgent === '1') return false;\n }\n if (env['AGT_SCHEDULE_LINK_FOOTER_DISABLED'] === '1') return false;\n return true;\n}\n\n/**\n * Resolve the webapp console URL from env. Manager-worker runs client-side,\n * so we can't derive from a request Origin — we rely on the operator\n * configuring `AGT_CONSOLE_URL` (the canonical name per the SST setup) or\n * `NEXT_PUBLIC_APP_URL` (local-dev parity with the rest of the stack).\n * Returns null if neither is set; callers should skip footer emission.\n */\nexport function getConsoleUrl(env: NodeJS.ProcessEnv = process.env): string | null {\n // Evaluate each candidate trimmed and fall back past whitespace-only\n // values. Plain `AGT_CONSOLE_URL || NEXT_PUBLIC_APP_URL` would treat a\n // whitespace-only AGT_CONSOLE_URL as truthy and suppress the fallback,\n // returning null despite a valid NEXT_PUBLIC_APP_URL being set.\n const canonical = env['AGT_CONSOLE_URL']?.trim();\n if (canonical) return canonical.replace(/\\/+$/, '');\n\n const fallback = env['NEXT_PUBLIC_APP_URL']?.trim();\n if (fallback) return fallback.replace(/\\/+$/, '');\n\n return null;\n}\n\n/**\n * Build the canonical schedule-edit URL. Mirrors\n * packages/openclaw-plugin-augmented/src/tools/schedule-link-url.ts so the\n * CLI isn't pulling an openclaw-plugin dep it doesn't otherwise use.\n * agentId / taskId are encodeURIComponent'd defensively.\n */\nexport function buildScheduleEditLink(\n consoleUrl: string,\n agentId: string,\n taskId: string,\n): string {\n const base = consoleUrl.replace(/\\/+$/, '');\n return `${base}/agents/${encodeURIComponent(agentId)}/schedules/${encodeURIComponent(taskId)}`;\n}\n\n/**\n * Format the footer line for a given medium.\n * - Slack: `<url|Edit schedule>` — Slack's native link syntax, keeps the\n * URL out of the visible text.\n * - Telegram: `Edit schedule: <url>` — Telegram's text-mode sendMessage\n * auto-linkifies bare URLs, so a plain label + URL reads cleanly.\n * - plain: just the URL, for callers that aren't sure which medium it is.\n */\nexport function formatScheduleLinkFooter(url: string, medium: FooterMedium): string {\n if (medium === 'slack') return `<${url}|Edit schedule>`;\n if (medium === 'telegram') return `Edit schedule: ${url}`;\n return url;\n}\n\n/**\n * Append the schedule-edit footer to a body. Separated from the body by a\n * blank line, and idempotent against the identical footer — retries /\n * re-deliveries won't stack multiple copies.\n */\nexport function appendScheduleLinkFooter(\n body: string,\n url: string,\n medium: FooterMedium,\n): string {\n const footer = formatScheduleLinkFooter(url, medium);\n const trimmed = body.replace(/\\s+$/, '');\n if (trimmed.endsWith(footer)) return trimmed;\n return `${trimmed}\\n\\n${footer}`;\n}\n\n/**\n * ENG-4495: one-time warning guard. We used to silently drop the footer\n * when AGT_CONSOLE_URL was unset, which is how Scout's prod host went\n * months without deep links before anyone noticed. Emit exactly one log\n * line per process so misconfigured hosts leave a breadcrumb in\n * ~/.augmented/manager.log without spamming it on every delivery tick.\n */\nlet warnedNullConsoleUrl = false;\n\n/** Test helper — reset the once-per-process warning latch between tests. */\nexport function resetConsoleUrlWarning(): void {\n warnedNullConsoleUrl = false;\n}\n\n/**\n * Convenience wrapper — the thing the manager-worker will call on each\n * successful delivery. Returns the body unchanged if any precondition fails\n * (disabled, missing console URL, missing taskId, etc.) so callers don't\n * need a pile of guards at every integration site.\n *\n * `log` is optional so tests / tooling can call this without a logger;\n * manager-worker passes its persistent `log()` so the missing-console-URL\n * warning lands in manager.log alongside other delivery diagnostics.\n */\nexport function withScheduleLinkFooter(opts: {\n body: string;\n medium: FooterMedium;\n codeName?: string;\n agentId: string;\n taskId?: string;\n env?: NodeJS.ProcessEnv;\n log?: (msg: string) => void;\n}): string {\n if (!opts.taskId) return opts.body;\n if (!scheduleLinkFooterEnabled(opts.codeName, opts.env)) return opts.body;\n const consoleUrl = getConsoleUrl(opts.env);\n if (!consoleUrl) {\n if (!warnedNullConsoleUrl && opts.log) {\n warnedNullConsoleUrl = true;\n try {\n opts.log(\n '[schedule-link] AGT_CONSOLE_URL unset and NEXT_PUBLIC_APP_URL unset — schedule-edit deep-link footer disabled. Run `agt setup` again or export AGT_CONSOLE_URL (e.g. https://app.augmented.team) to restore it.',\n );\n } catch {\n // Diagnostic log must never break delivery fallback — swallow.\n }\n }\n return opts.body;\n }\n const url = buildScheduleEditLink(consoleUrl, opts.agentId, opts.taskId);\n return appendScheduleLinkFooter(opts.body, url, opts.medium);\n}\n","/**\n * Restart-flag IPC between channel MCP subprocesses (e.g. telegram-channel)\n * and the manager. A channel handler that observes a `/restart` command for\n * its agent writes a flag file here; the manager poll loop scans this dir\n * each cycle, kills the agent's tmux session (which lets the existing\n * \"ensure session\" path respawn it), and posts an ack back to the\n * originating channel.\n *\n * Filename: `<codeName>.flag` so concurrent /restart commands collapse to\n * one (idempotent — repeated taps don't queue multiple restarts).\n *\n * Body: JSON record describing where the request came from so the manager\n * can ack to the right place.\n */\n\nimport { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { randomUUID } from 'node:crypto';\n\nexport interface RestartFlag {\n /** Agent code_name. Matches the filename stem. */\n codeName: string;\n /** Channel source for the ack reply, e.g. \"telegram\". */\n source: string;\n /** Unix epoch ms when the flag was written. */\n ts: number;\n /** Channel-specific routing for the ack reply (chat id, thread id, etc). */\n reply?: Record<string, string | number>;\n}\n\nexport function restartFlagsDir(): string {\n return join(homedir(), '.augmented', 'restart-flags');\n}\n\nfunction flagPath(codeName: string): string {\n return join(restartFlagsDir(), `${codeName}.flag`);\n}\n\nexport function writeRestartFlag(flag: RestartFlag): string {\n const dir = restartFlagsDir();\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const path = flagPath(flag.codeName);\n // Atomic write: write to a sibling temp path then rename. Without this,\n // a manager poll that fires during the writeFileSync window would read a\n // partial flag and fall into the corrupt-file fallback — losing source +\n // reply routing, which means the restart fires but the operator never gets\n // the final ack.\n // Include a per-call randomUUID so concurrent writes (overlapping channel\n // handlers, retries) can't collide on the same tmp path and have one\n // renameSync remove the file the other is about to rename.\n const tmpPath = `${path}.${process.pid}.${randomUUID()}.tmp`;\n writeFileSync(tmpPath, JSON.stringify(flag) + '\\n', 'utf8');\n renameSync(tmpPath, path);\n return path;\n}\n\nexport function readRestartFlags(): RestartFlag[] {\n const dir = restartFlagsDir();\n if (!existsSync(dir)) return [];\n const out: RestartFlag[] = [];\n for (const entry of readdirSync(dir)) {\n if (!entry.endsWith('.flag')) continue;\n try {\n const raw = readFileSync(join(dir, entry), 'utf8');\n const parsed = JSON.parse(raw) as RestartFlag;\n // Defensive: derive codeName from filename if the body is malformed,\n // so a stray flag file always identifies an agent.\n if (typeof parsed.codeName !== 'string' || parsed.codeName.length === 0) {\n parsed.codeName = entry.replace(/\\.flag$/, '');\n }\n out.push(parsed);\n } catch {\n // Corrupt flag — treat as a bare restart request for the agent in\n // the filename. The manager will still kick the session.\n out.push({\n codeName: entry.replace(/\\.flag$/, ''),\n source: 'unknown',\n ts: Date.now(),\n });\n }\n }\n return out;\n}\n\nexport function deleteRestartFlag(codeName: string): void {\n const path = flagPath(codeName);\n if (existsSync(path)) {\n rmSync(path, { force: true });\n }\n}\n","/**\n * ENG-4568: Process restart flags written by channel MCP subprocesses\n * (telegram-channel etc) when an authorised user posts `/restart`. Each\n * cycle, the manager poll loop calls processRestartFlags() to:\n *\n * 1. Read flag files in ~/.augmented/restart-flags/\n * 2. For each flag, verify the agent is one we manage and is on the\n * Claude Code framework (out of scope for v1: openclaw, nemoclaw)\n * 3. Stop the agent's tmux session via stopPersistentSession() — the\n * ensure-session path on the same poll respawns it\n * 4. Post an ack back to the originating channel\n * 5. Delete the flag so we don't re-process it\n *\n * The handler is intentionally framework-aware but channel-agnostic in its\n * core: each `source` gets a small dispatcher for the post-restart ack.\n * Today only Telegram is wired up.\n */\n\nimport { deleteRestartFlag, readRestartFlags, type RestartFlag } from './restart-flags.js';\n\nexport interface ProcessRestartFlagsOpts {\n /** Logger function from manager-worker */\n log: (msg: string) => void;\n /**\n * Returns the framework code for an agent's code_name, or null if the\n * agent is unknown to this manager. Used to gate /restart to claude-code\n * only per the ENG-4568 v1 scope.\n */\n resolveFramework: (codeName: string) => string | null;\n /**\n * Stop helper from persistent-session. Kills the tmux session and clears\n * internal state so the next ensure-session pass respawns. Only invoked\n * for claude-code agents.\n */\n stopSession: (codeName: string) => void;\n /**\n * Returns the Telegram bot token + chat allowlist for an agent, or null\n * if the agent has no telegram channel binding. The handler uses this to\n * post the post-restart confirmation back to the originating chat.\n */\n getTelegramTokens: (codeName: string) => { token: string; allowedChats?: string[] } | null;\n /**\n * Send a Telegram message via the manager's existing `telegramApiCall`\n * helper. Kept as an injected callback so this module doesn't have to\n * reach into manager-worker's https client.\n */\n sendTelegram: (\n botToken: string,\n method: 'sendMessage',\n body: Record<string, unknown>,\n ) => Promise<{ ok: boolean; description?: string }>;\n /**\n * Returns the Slack bot token for an agent, or null if no slack binding\n * exists.\n */\n getSlackToken: (codeName: string) => string | null;\n /**\n * Post a Slack message (chat.postMessage). Returns ok flag and an\n * optional error string mirroring the Slack Web API shape.\n */\n sendSlack: (\n botToken: string,\n body: Record<string, unknown>,\n ) => Promise<{ ok: boolean; error?: string }>;\n}\n\nexport async function processRestartFlags(opts: ProcessRestartFlagsOpts): Promise<void> {\n const flags = readRestartFlags();\n if (flags.length === 0) return;\n\n for (const flag of flags) {\n try {\n await processOne(flag, opts);\n } catch (err) {\n opts.log(`[restart-handler] Failed to process flag for '${flag.codeName}': ${(err as Error).message}`);\n // Surface the failure to the operator instead of leaving them with\n // just the \"⏳ queued\" ack and silence (CodeRabbit feedback): the\n // resolveFramework / stopSession call paths can throw, and silent\n // log-only handling hides the real failure mode.\n try {\n await sendError(flag, opts, `❌ Restart failed for \\`${flag.codeName}\\`: internal error. Check manager logs.`);\n } catch (ackErr) {\n opts.log(`[restart-handler] Error-ack send failed for '${flag.codeName}': ${(ackErr as Error).message}`);\n }\n } finally {\n // Always delete — a stuck flag would otherwise loop the manager into\n // restarting the same agent every cycle. If processing legitimately\n // failed, the user will see the error reply (or absence of an ack)\n // and can re-issue /restart.\n try {\n deleteRestartFlag(flag.codeName);\n } catch (err) {\n opts.log(`[restart-handler] Failed to delete flag for '${flag.codeName}': ${(err as Error).message}`);\n }\n }\n }\n}\n\nasync function processOne(flag: RestartFlag, opts: ProcessRestartFlagsOpts): Promise<void> {\n const framework = opts.resolveFramework(flag.codeName);\n if (framework === null) {\n opts.log(`[restart-handler] Ignoring /restart for unknown agent '${flag.codeName}'`);\n await sendError(flag, opts, `Agent \\`${flag.codeName}\\` is not managed by this host.`);\n return;\n }\n\n if (framework !== 'claude-code') {\n opts.log(`[restart-handler] Ignoring /restart for '${flag.codeName}' — framework is '${framework}', not claude-code`);\n await sendError(flag, opts, `\\`/restart\\` is only supported for Claude Code agents (this agent runs on \\`${framework}\\`).`);\n return;\n }\n\n opts.log(`[restart-handler] Restarting tmux session for '${flag.codeName}' (source: ${flag.source})`);\n opts.stopSession(flag.codeName);\n\n // Downgraded from \"✅ Restarted\" — at this point we've only killed the\n // session. The respawn happens on the same poll's ensure-session pass and\n // persistent-session.ts doesn't expose a \"session healthy\" signal we can\n // wait on without restructuring that module. Saying \"Restarted\" before\n // the new session is up is a false positive (CodeRabbit feedback).\n await sendAck(flag, opts, `🔄 Restart initiated for \\`${flag.codeName}\\` — the replacement session is being created.`);\n}\n\nasync function sendAck(flag: RestartFlag, opts: ProcessRestartFlagsOpts, text: string): Promise<void> {\n if (flag.source === 'telegram') {\n const tokens = opts.getTelegramTokens(flag.codeName);\n if (!tokens) {\n opts.log(`[restart-handler] No telegram tokens cached for '${flag.codeName}' — skipping ack`);\n return;\n }\n const chatId = flag.reply?.['chat_id'];\n if (chatId == null) return;\n const messageId = flag.reply?.['message_id'];\n const body: Record<string, unknown> = { chat_id: chatId, text, parse_mode: 'Markdown' };\n if (messageId != null) body.reply_to_message_id = Number(messageId);\n try {\n const resp = await opts.sendTelegram(tokens.token, 'sendMessage', body);\n if (!resp.ok) {\n opts.log(`[restart-handler] Telegram ack failed for '${flag.codeName}': ${resp.description ?? 'unknown'}`);\n }\n } catch (err) {\n opts.log(`[restart-handler] Telegram ack threw for '${flag.codeName}': ${(err as Error).message}`);\n }\n return;\n }\n\n if (flag.source === 'slack') {\n const token = opts.getSlackToken(flag.codeName);\n if (!token) {\n opts.log(`[restart-handler] No slack token cached for '${flag.codeName}' — skipping ack`);\n return;\n }\n const channel = flag.reply?.['channel'];\n if (channel == null) return;\n const threadTs = flag.reply?.['thread_ts'];\n const body: Record<string, unknown> = { channel, text };\n // Reply in-thread when we have one so the ack stays scoped to the\n // restart command's conversation rather than spamming the channel.\n if (threadTs != null) body.thread_ts = String(threadTs);\n try {\n const resp = await opts.sendSlack(token, body);\n if (!resp.ok) {\n opts.log(`[restart-handler] Slack ack failed for '${flag.codeName}': ${resp.error ?? 'unknown'}`);\n }\n } catch (err) {\n opts.log(`[restart-handler] Slack ack threw for '${flag.codeName}': ${(err as Error).message}`);\n }\n return;\n }\n\n opts.log(`[restart-handler] Unknown ack source '${flag.source}' for '${flag.codeName}' — skipping`);\n}\n\nasync function sendError(flag: RestartFlag, opts: ProcessRestartFlagsOpts, text: string): Promise<void> {\n // Same shape as sendAck — separate function for clarity at the call site.\n await sendAck(flag, opts, text);\n}\n","/**\n * Supabase Realtime subscriptions for the manager.\n *\n * Replaces polling with WebSocket subscriptions:\n * - direct_chat_messages INSERT → instant chat delivery (~100ms vs 2s)\n * - agent_doc_versions INSERT → instant drift detection (~100ms vs 5min)\n * - host_agents INSERT/DELETE → instant agent assignment changes\n *\n * Falls back to polling if the Realtime connection drops.\n */\n\nimport { createClient, type SupabaseClient, type RealtimeChannel } from '@supabase/supabase-js';\nimport { formatActorId, isSelfCompletion, type KanbanCompletionEvent } from '@augmented/core';\n\n// ---------------------------------------------------------------------------\n// Shared client\n// ---------------------------------------------------------------------------\n\nlet client: SupabaseClient | null = null;\nlet chatChannel: RealtimeChannel | null = null;\nlet driftChannel: RealtimeChannel | null = null;\nlet assignChannel: RealtimeChannel | null = null;\nlet configChannel: RealtimeChannel | null = null;\nlet kanbanChannel: RealtimeChannel | null = null;\nlet integrationContextChannel: RealtimeChannel | null = null;\nlet connected = false;\nlet tearingDown = false; // Guard against re-entrant teardown\n\nexport interface RealtimeConfig {\n supabaseUrl: string;\n supabaseAnonKey: string;\n token: string;\n log: (msg: string) => void;\n}\n\nfunction ensureClient(config: RealtimeConfig): SupabaseClient {\n if (client) return client;\n\n client = createClient(config.supabaseUrl, config.supabaseAnonKey, {\n global: {\n headers: { Authorization: `Bearer ${config.token}` },\n },\n realtime: {\n params: { apikey: config.supabaseAnonKey },\n },\n });\n\n client.realtime.setAuth(config.token);\n return client;\n}\n\n// ---------------------------------------------------------------------------\n// Direct chat subscription\n// ---------------------------------------------------------------------------\n\nexport interface ChatMessagePayload {\n id: string;\n agent_id: string;\n session_id: string;\n content: string;\n status: string;\n created_at: string;\n}\n\nexport function startRealtimeChat(\n config: RealtimeConfig & {\n agentIds: string[];\n onMessage: (msg: ChatMessagePayload) => void;\n onError: (err: Error) => void;\n onStatusChange: (status: 'connected' | 'disconnected' | 'error') => void;\n },\n): void {\n const { agentIds, onMessage, onStatusChange, log } = config;\n if (agentIds.length === 0) return;\n\n const sb = ensureClient(config);\n const filterStr = agentIds.length === 1\n ? `agent_id=eq.${agentIds[0]}`\n : `agent_id=in.(${agentIds.join(',')})`;\n\n chatChannel = sb\n .channel('direct-chat-realtime')\n .on('postgres_changes', {\n event: 'INSERT',\n schema: 'public',\n table: 'direct_chat_messages',\n filter: filterStr,\n }, (payload) => {\n const msg = payload.new as ChatMessagePayload;\n if (msg.status !== 'pending') return;\n log(`[realtime] Chat message for agent ${msg.agent_id}: id=${msg.id}`);\n onMessage(msg);\n })\n .subscribe((status) => {\n if (status === 'SUBSCRIBED') {\n connected = true;\n log('[realtime] Chat channel connected');\n onStatusChange('connected');\n } else if (status === 'CLOSED' || status === 'CHANNEL_ERROR' || status === 'TIMED_OUT') {\n if (tearingDown) return; // Prevent re-entrant teardown from recursive close events\n connected = false;\n log(`[realtime] Chat channel: ${status} — will reconnect next cycle`);\n // Don't call stopRealtimeChat here — it triggers more CLOSED events.\n // Just null out references and let the manager reset flags for fresh reconnect.\n chatChannel = null;\n driftChannel = null;\n assignChannel = null;\n configChannel = null;\n kanbanChannel = null;\n integrationContextChannel = null;\n if (client) { try { client.removeAllChannels(); } catch { /* ignore */ } client = null; }\n onStatusChange(status === 'TIMED_OUT' ? 'error' : 'disconnected');\n }\n });\n\n log(`[realtime] Subscribing to direct_chat_messages for ${agentIds.length} agent(s)`);\n}\n\n// ---------------------------------------------------------------------------\n// Drift detection subscription\n// ---------------------------------------------------------------------------\n\nexport interface DriftPayload {\n version_id: string;\n agent_id: string;\n doc_type: string;\n version: string;\n created_at: string;\n}\n\nexport function startRealtimeDrift(\n config: RealtimeConfig & {\n agentIds: string[];\n onDrift: (payload: DriftPayload) => void;\n },\n): void {\n const { agentIds, onDrift, log } = config;\n if (agentIds.length === 0) return;\n\n const sb = ensureClient(config);\n const filterStr = agentIds.length === 1\n ? `agent_id=eq.${agentIds[0]}`\n : `agent_id=in.(${agentIds.join(',')})`;\n\n driftChannel = sb\n .channel('drift-realtime')\n .on('postgres_changes', {\n event: 'INSERT',\n schema: 'public',\n table: 'agent_doc_versions',\n filter: filterStr,\n }, (payload) => {\n const doc = payload.new as DriftPayload;\n log(`[realtime] Doc version change for agent ${doc.agent_id}: ${doc.doc_type} v${doc.version}`);\n onDrift(doc);\n })\n .subscribe((status) => {\n if (status === 'SUBSCRIBED') {\n log('[realtime] Drift channel connected');\n } else if (status === 'CLOSED' || status === 'CHANNEL_ERROR') {\n log(`[realtime] Drift channel: ${status}`);\n }\n });\n\n log(`[realtime] Subscribing to agent_doc_versions for ${agentIds.length} agent(s)`);\n}\n\n// ---------------------------------------------------------------------------\n// Agent assignment subscription\n// ---------------------------------------------------------------------------\n\nexport interface AssignmentPayload {\n host_id: string;\n agent_id: string;\n assigned_by: string;\n}\n\nexport function startRealtimeAssignments(\n config: RealtimeConfig & {\n hostId: string;\n onAssign: (payload: AssignmentPayload) => void;\n onUnassign: (payload: { agent_id: string }) => void;\n },\n): void {\n const { hostId, onAssign, onUnassign, log } = config;\n\n const sb = ensureClient(config);\n\n assignChannel = sb\n .channel('assignment-realtime')\n .on('postgres_changes', {\n event: 'INSERT',\n schema: 'public',\n table: 'host_agents',\n filter: `host_id=eq.${hostId}`,\n }, (payload) => {\n const row = payload.new as AssignmentPayload;\n log(`[realtime] Agent assigned: ${row.agent_id} to host ${row.host_id}`);\n onAssign(row);\n })\n .on('postgres_changes', {\n event: 'DELETE',\n schema: 'public',\n table: 'host_agents',\n }, (payload) => {\n // DELETE events can't be filtered by Supabase Realtime\n // We receive all DELETEs and filter client-side\n const old = payload.old as { host_id?: string; agent_id?: string };\n if (old.host_id === hostId && old.agent_id) {\n log(`[realtime] Agent unassigned: ${old.agent_id} from host ${hostId}`);\n onUnassign({ agent_id: old.agent_id });\n }\n })\n .subscribe((status) => {\n if (status === 'SUBSCRIBED') {\n log('[realtime] Assignment channel connected');\n } else if (status === 'CLOSED' || status === 'CHANNEL_ERROR') {\n log(`[realtime] Assignment channel: ${status}`);\n }\n });\n\n log(`[realtime] Subscribing to host_agents for host ${hostId}`);\n}\n\n// ---------------------------------------------------------------------------\n// Agent config change subscription\n// ---------------------------------------------------------------------------\n\nexport interface AgentConfigPayload {\n agent_id: string;\n code_name: string;\n status: string;\n framework: string;\n session_mode: string;\n primary_model: string | null;\n updated_at: string;\n}\n\nexport function startRealtimeConfig(\n config: RealtimeConfig & {\n agentIds: string[];\n onConfigChange: (payload: AgentConfigPayload) => void;\n },\n): void {\n const { agentIds, onConfigChange, log } = config;\n if (agentIds.length === 0) return;\n\n const sb = ensureClient(config);\n const filterStr = agentIds.length === 1\n ? `agent_id=eq.${agentIds[0]}`\n : `agent_id=in.(${agentIds.join(',')})`;\n\n // Track last known values to filter out timestamp-only updates\n const lastKnown = new Map<string, string>();\n\n configChannel = sb\n .channel('config-realtime')\n .on('postgres_changes', {\n event: 'UPDATE',\n schema: 'public',\n table: 'agents',\n filter: filterStr,\n }, (payload) => {\n const agent = payload.new as AgentConfigPayload;\n\n // Build a fingerprint of meaningful fields (ignore timestamps like updated_at, last_heartbeat_at)\n const fingerprint = `${agent.status}|${agent.framework}|${agent.session_mode}|${agent.primary_model}`;\n const prev = lastKnown.get(agent.agent_id);\n\n if (prev === fingerprint) return; // timestamp-only change, ignore\n lastKnown.set(agent.agent_id, fingerprint);\n\n log(`[realtime] Agent config changed: ${agent.code_name} (status=${agent.status})`);\n onConfigChange(agent);\n })\n .subscribe((status) => {\n if (status === 'SUBSCRIBED') {\n log('[realtime] Config channel connected');\n } else if (status === 'CLOSED' || status === 'CHANNEL_ERROR') {\n log(`[realtime] Config channel: ${status}`);\n }\n });\n\n log(`[realtime] Subscribing to agents table for ${agentIds.length} agent(s)`);\n}\n\n// ---------------------------------------------------------------------------\n// Kanban item subscription — trigger work when items hit 'todo'\n// ---------------------------------------------------------------------------\n\nexport interface KanbanItemPayload {\n id: string;\n agent_id: string;\n title: string;\n status: string;\n priority: number;\n last_actor_id?: string | null;\n completed_at?: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Kanban delivery telemetry (ENG-4514)\n//\n// Tracks DB→manager latency for completion events and how many we suppress\n// (self-actor) or drop (subscriber error). Surfaced via getKanbanMetrics()\n// so an operator-facing status command can render p50/p95 + counters\n// without having to scrape logs.\n// ---------------------------------------------------------------------------\n\ninterface KanbanMetrics {\n delivered: number;\n suppressedSelfActor: number;\n dropped: number;\n /** Last 200 latencies (ms) — bounded ring; old samples roll off. */\n latencies: number[];\n}\n\nconst METRICS_WINDOW = 200;\nconst kanbanMetrics: KanbanMetrics = {\n delivered: 0,\n suppressedSelfActor: 0,\n dropped: 0,\n latencies: [],\n};\n\nfunction recordLatency(ms: number): void {\n kanbanMetrics.latencies.push(ms);\n if (kanbanMetrics.latencies.length > METRICS_WINDOW) {\n kanbanMetrics.latencies.shift();\n }\n}\n\n/**\n * Visible for testing — nearest-rank percentile on a pre-sorted array.\n * Returns 0 when the array is empty so callers don't need a guard.\n */\nexport function percentile(sorted: number[], p: number): number {\n if (sorted.length === 0) return 0;\n const idx = Math.min(sorted.length - 1, Math.floor((sorted.length - 1) * p));\n return sorted[idx] ?? 0;\n}\n\nexport interface KanbanMetricsSnapshot {\n delivered: number;\n suppressedSelfActor: number;\n dropped: number;\n sampleSize: number;\n p50LatencyMs: number;\n p95LatencyMs: number;\n}\n\nexport function getKanbanMetrics(): KanbanMetricsSnapshot {\n const sorted = [...kanbanMetrics.latencies].sort((a, b) => a - b);\n return {\n delivered: kanbanMetrics.delivered,\n suppressedSelfActor: kanbanMetrics.suppressedSelfActor,\n dropped: kanbanMetrics.dropped,\n sampleSize: sorted.length,\n p50LatencyMs: percentile(sorted, 0.5),\n p95LatencyMs: percentile(sorted, 0.95),\n };\n}\n\n/**\n * Visible for testing — reset the in-memory counters between runs.\n */\nexport function resetKanbanMetrics(): void {\n kanbanMetrics.delivered = 0;\n kanbanMetrics.suppressedSelfActor = 0;\n kanbanMetrics.dropped = 0;\n kanbanMetrics.latencies.length = 0;\n}\n\nexport function startRealtimeKanban(\n config: RealtimeConfig & {\n agentIds: string[];\n onTodayItem: (payload: KanbanItemPayload) => void;\n /**\n * ENG-4514: invoked when a kanban item transitions to `done` or\n * `failed` and the actor was NOT the agent itself. Forwarding into\n * the agent runtime (Claude Code MCP surface) lands in ENG-4515.\n */\n onCompletion?: (event: KanbanCompletionEvent) => void;\n },\n): void {\n const { agentIds, onTodayItem, onCompletion, log } = config;\n if (agentIds.length === 0) return;\n\n const sb = ensureClient(config);\n const filterStr = agentIds.length === 1\n ? `agent_id=eq.${agentIds[0]}`\n : `agent_id=in.(${agentIds.join(',')})`;\n\n kanbanChannel = sb\n .channel('kanban-realtime')\n .on('postgres_changes', {\n event: 'INSERT',\n schema: 'public',\n table: 'agent_kanban_items',\n filter: filterStr,\n }, (payload) => {\n const item = payload.new as KanbanItemPayload;\n if (item.status === 'todo') {\n // Don't log raw titles — task content can be sensitive. Identifiers only.\n log(`[realtime] New kanban item in 'todo': item_id=${item.id} agent=${item.agent_id}`);\n onTodayItem(item);\n }\n })\n .on('postgres_changes', {\n event: 'UPDATE',\n schema: 'public',\n table: 'agent_kanban_items',\n filter: filterStr,\n }, (payload) => {\n const item = payload.new as KanbanItemPayload;\n const old = payload.old as KanbanItemPayload;\n // Only trigger when status changes TO 'todo' (not from todo to something else)\n if (item.status === 'todo' && old.status !== 'todo') {\n log(`[realtime] Kanban item moved to 'todo': item_id=${item.id} agent=${item.agent_id}`);\n onTodayItem(item);\n }\n // ENG-4514: completion events. Fire only on the *transition* into\n // done/failed so we don't double-emit when a downstream UPDATE (e.g. an\n // embedding write) touches an already-completed row.\n if (\n onCompletion &&\n (item.status === 'done' || item.status === 'failed') &&\n old.status !== item.status\n ) {\n const event: KanbanCompletionEvent = {\n agent_id: item.agent_id,\n item_id: item.id,\n status: item.status,\n last_actor_id: item.last_actor_id ?? null,\n completed_at: item.completed_at ?? null,\n title: item.title,\n };\n if (isSelfCompletion(event)) {\n kanbanMetrics.suppressedSelfActor++;\n // Don't log per-event for the suppressed path — the agent's own\n // kanban_done log already covered it. Counter is enough.\n return;\n }\n // Latency is best-effort: postgres_changes payloads carry\n // `commit_timestamp`; subtract from now() for an approximate\n // DB→manager delivery time. Falls back to 0 if missing.\n const commitTs = (payload as unknown as { commit_timestamp?: string }).commit_timestamp;\n const latencyMs = commitTs ? Math.max(0, Date.now() - new Date(commitTs).getTime()) : 0;\n try {\n // Only count as delivered when the handler returns successfully —\n // otherwise the same event would land in both `delivered` and\n // `dropped`, overstating success.\n onCompletion(event);\n recordLatency(latencyMs);\n kanbanMetrics.delivered++;\n log(\n `[realtime] Kanban completion: agent=${item.agent_id} item=${item.id} ` +\n `status=${item.status} actor=${item.last_actor_id ?? 'unknown'} ` +\n `latency_ms=${latencyMs}`,\n );\n } catch (err) {\n // The handler threw — count as a drop and keep the subscription\n // healthy. Without this guard a runtime adapter bug would knock\n // the manager off the channel for every agent.\n kanbanMetrics.dropped++;\n log(`[realtime] Kanban completion handler error: ${(err as Error).message}`);\n }\n }\n })\n .subscribe((status) => {\n if (status === 'SUBSCRIBED') {\n log('[realtime] Kanban channel connected');\n } else if (status === 'CLOSED' || status === 'CHANNEL_ERROR') {\n log(`[realtime] Kanban channel: ${status}`);\n }\n });\n\n log(`[realtime] Subscribing to agent_kanban_items for ${agentIds.length} agent(s)`);\n // Suppress unused-import warnings — formatActorId is exported from core\n // for symmetry with isSelfCompletion and used by tests.\n void formatActorId;\n}\n\n// ---------------------------------------------------------------------------\n// Integration context subscription (ENG-4342)\n//\n// Subscribes to INSERT/UPDATE on `plugin_context` filtered by the manager's\n// agent IDs. Fires `onContextChange(agentId)` whenever a row touches an\n// agent we manage. The handler is responsible for triggering an early poll\n// or re-rendering — this module just delivers the event.\n// ---------------------------------------------------------------------------\n\nexport interface IntegrationContextChangePayload {\n agent_id: string;\n plugin_id: string;\n}\n\nexport function startRealtimeIntegrationContext(\n config: RealtimeConfig & {\n agentIds: string[];\n onContextChange: (payload: IntegrationContextChangePayload) => void;\n },\n): void {\n const { agentIds, onContextChange, log } = config;\n if (agentIds.length === 0) return;\n\n const sb = ensureClient(config);\n const filterStr = agentIds.length === 1\n ? `agent_id=eq.${agentIds[0]}`\n : `agent_id=in.(${agentIds.join(',')})`;\n\n integrationContextChannel = sb\n .channel('plugin-context-realtime')\n .on('postgres_changes', {\n event: 'INSERT',\n schema: 'public',\n table: 'plugin_context',\n filter: filterStr,\n }, (payload) => {\n const row = payload.new as IntegrationContextChangePayload;\n log(`[realtime] plugin_context INSERT for agent ${row.agent_id} (plugin ${row.plugin_id})`);\n onContextChange(row);\n })\n .on('postgres_changes', {\n event: 'UPDATE',\n schema: 'public',\n table: 'plugin_context',\n filter: filterStr,\n }, (payload) => {\n const row = payload.new as IntegrationContextChangePayload;\n log(`[realtime] plugin_context UPDATE for agent ${row.agent_id} (plugin ${row.plugin_id})`);\n onContextChange(row);\n })\n .subscribe((status) => {\n if (status === 'SUBSCRIBED') {\n log('[realtime] Integration context channel connected');\n } else if (status === 'CLOSED' || status === 'CHANNEL_ERROR') {\n log(`[realtime] Integration context channel: ${status}`);\n }\n });\n\n log(`[realtime] Subscribing to plugin_context for ${agentIds.length} agent(s)`);\n}\n\n/**\n * Tear down only the integration-context channel — used when the active\n * agent set changes and we need to re-subscribe with an updated filter\n * (ENG-4725). Leaves the rest of the realtime client + other channels\n * untouched.\n */\nexport function stopRealtimeIntegrationContext(): void {\n if (!integrationContextChannel) return;\n try { integrationContextChannel.unsubscribe(); } catch {}\n integrationContextChannel = null;\n}\n\n// ---------------------------------------------------------------------------\n// Shared utilities\n// ---------------------------------------------------------------------------\n\nexport function updateRealtimeToken(token: string): void {\n if (client) {\n client.realtime.setAuth(token);\n }\n}\n\nexport function isRealtimeConnected(): boolean {\n return connected;\n}\n\nexport function stopRealtimeChat(): void {\n if (tearingDown) return;\n tearingDown = true;\n try {\n if (chatChannel) { try { chatChannel.unsubscribe(); } catch {} chatChannel = null; }\n if (driftChannel) { try { driftChannel.unsubscribe(); } catch {} driftChannel = null; }\n if (assignChannel) { try { assignChannel.unsubscribe(); } catch {} assignChannel = null; }\n if (configChannel) { try { configChannel.unsubscribe(); } catch {} configChannel = null; }\n if (kanbanChannel) { try { kanbanChannel.unsubscribe(); } catch {} kanbanChannel = null; }\n if (integrationContextChannel) { try { integrationContextChannel.unsubscribe(); } catch {} integrationContextChannel = null; }\n if (client) {\n try { client.removeAllChannels(); } catch {}\n client = null;\n }\n connected = false;\n } finally {\n tearingDown = false;\n }\n}\n"],"mappings":"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","names":["createHash","readFileSync","writeFileSync","mkdirSync","existsSync","rmSync","readdirSync","join","homedir","log","client","join","execFileSync","log","config","envSuffixFor","existsSync","readFileSync","writeFileSync","homedir","join","existsSync","readFileSync","join","config","log","config","log","join","createHash","readFileSync","saveChannelHashCache","execFileSync","existsSync","join","homedir","rmSync","readFileSync","mkdirSync","writeFileSync","loadChannelHashCache","saveChannelHashCache","createHash","log","readdirSync","platform","state","trackedFiles","primaryModel","sessionMode","frameworkId","agentFw","getProjectDir"]}
|
|
1
|
+
{"version":3,"sources":["../../src/lib/manager-worker.ts","../../src/lib/mcp-config-drift.ts","../../src/lib/integration-hash.ts","../../src/lib/stale-mcp-reaper.ts","../../src/lib/channel-restart-decision.ts","../../src/lib/integration-context-render.ts","../../src/lib/integration-skill-layout.ts","../../src/lib/gateway-client.ts","../../src/lib/claude-auth-detect.ts","../../src/lib/canonical-json.ts","../../src/lib/channel-hash-cache.ts","../../src/lib/channel-sweep.ts","../../src/lib/channel-input-watchdog.ts","../../src/lib/delivery-hint.ts","../../src/lib/delivery-schedule-link.ts","../../src/lib/restart-flags.ts","../../src/lib/restart-handler.ts","../../src/lib/realtime-chat.ts"],"sourcesContent":["/**\n * Manager Worker — forked child process that polls the API for agent config\n * changes and local file drift. Communicates with the watchdog parent via IPC.\n *\n * This file is a standalone entry point built by tsup so `fork()` can target it.\n */\n\nimport { createHash } from 'node:crypto';\nimport { readFileSync, writeFileSync, appendFileSync, mkdirSync, chmodSync, existsSync, rmSync, readdirSync, statSync, unlinkSync, copyFileSync } from 'node:fs';\nimport https from 'node:https';\nimport { execFileSync as syncExecFile } from 'node:child_process';\nimport { join, dirname } from 'node:path';\nimport { homedir } from 'node:os';\nimport { fileURLToPath } from 'node:url';\nimport {\n extractFrontmatter,\n resolveChannels,\n getFramework,\n parseDeliveryTarget,\n isParseError,\n resolveDmTarget,\n isResolveError,\n appendDmFooter,\n wrapScheduledTaskPrompt,\n classifyOutput,\n type PriorRun,\n type FrameworkAdapter,\n type CharterFrontmatter,\n type ToolsFrontmatter,\n type ChannelId,\n type ChannelPolicy,\n type OrgChannelPolicy,\n type DeploymentTarget,\n type ProvisionInput,\n type DeliveryTarget,\n type ResolverAgent,\n type ResolverPerson,\n} from '@augmented/core';\nimport { provision } from '@augmented/core/provisioning/provisioner.js';\nimport { extractCommandNotFound } from '@augmented/core/provisioning/hook-env.js';\nimport { getIntegration } from '@augmented/core/integrations/registry.js';\nimport { reapOrphanChannelMcps } from './orphan-channel-mcp-reaper.js';\nimport { decideMcpDriftAction } from './mcp-config-drift.js';\nimport { computeIntegrationsHash } from './integration-hash.js';\nimport {\n diffEnvIntegrations,\n findMcpServersUsingVars,\n reapStaleMcpChildren,\n} from './stale-mcp-reaper.js';\nimport { decideChannelRestart, launchableChannelIds } from './channel-restart-decision.js';\n\n// Register framework adapters (side-effect imports)\nimport '@augmented/core/provisioning/frameworks/openclaw/index.js';\nimport '@augmented/core/provisioning/frameworks/nemoclaw/index.js';\nimport { provisionStopHook, provisionIsolationHook } from '@augmented/core/provisioning/frameworks/claudecode/index.js';\nimport '@augmented/core/provisioning/frameworks/managed-agents/index.js';\n\nimport { api, getHostId, exchangeApiKey } from './api-client.js';\nimport { getApiKey, requireHost } from './config.js';\nimport { sanitizeMcpJson } from './mcp-sanitize.js';\nimport { renderIntegrationSkillContent } from './integration-context-render.js';\nimport {\n buildIntegrationBundle,\n bundleFingerprint,\n groupSkillsByIntegration,\n type IntegrationSkillInput,\n} from './integration-skill-layout.js';\nimport { GatewayClientPool, type PooledGatewayEvent } from './gateway-client.js';\nimport { detectClaudeAuth } from './claude-auth-detect.js';\nimport { canonicalJson } from './canonical-json.js';\nimport {\n loadChannelHashCache as loadChannelHashCacheFromDisk,\n saveChannelHashCache as saveChannelHashCacheToDisk,\n} from './channel-hash-cache.js';\nimport { buildAllowedTools } from './claude-tools.js';\nimport { SUPERVISOR_RESTART_EXIT_CODE } from '../commands/manager.js';\nimport { sweepChannelProcesses, killAgentChannelProcesses } from './channel-sweep.js';\nimport { checkChannelInputs } from './channel-input-watchdog.js';\nimport { hintProbability, pickHintVariant, shouldIncludeHint } from './delivery-hint.js';\nimport { withScheduleLinkFooter } from './delivery-schedule-link.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface WorkerConfig {\n intervalMs: number;\n configDir: string;\n gatewayPort?: number;\n gatewayToken?: string;\n}\n\ninterface AcpSessionSummary {\n sessionId: string;\n agentCommand: string;\n sessionName?: string;\n queueState: string;\n turnCount: number;\n startedAt: string;\n}\n\ninterface AgentState {\n agentId: string;\n codeName: string;\n status: string;\n charterVersion: string;\n toolsVersion: string;\n secretsHash: string | null;\n lastRefreshAt: string | null;\n lastProvisionAt: string | null;\n lastDriftCheckAt: string | null;\n lastSecretsProvisionAt: string | null;\n gatewayPort: number | null;\n gatewayPid: number | null;\n gatewayRunning: boolean;\n acpSessions: AcpSessionSummary[];\n // ENG-4947: latest `restart_requested_at` (from /host/agents) that this\n // manager has already acted on. Persists across manager restarts so a\n // dashboard-issued restart fires exactly once even if we crash between\n // poll and respawn.\n lastRestartProcessedAt?: string | null;\n}\n\ninterface ManagerState {\n pid: number;\n startedAt: string;\n lastPollAt: string | null;\n pollCount: number;\n errorCount: number;\n agents: AgentState[];\n}\n\n// Event types (formerly IPC messages, now just internal events)\ntype ManagerEvent =\n | { type: 'ready' }\n | { type: 'state-update'; state: ManagerState }\n | { type: 'provisioned'; agentId: string; codeName: string }\n | { type: 'drift-detected'; agentId: string; codeName: string; files: string[] }\n | { type: 'gateway-event'; event: string; payload: unknown; agentCodeName?: string }\n | { type: 'error'; message: string }\n | { type: 'shutdown' };\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n// OpenClaw gateway binds the specified port + a few additional ports above it,\n// so we space allocations 10 apart to avoid collisions.\nconst GATEWAY_PORT_BASE = 18800;\nconst GATEWAY_PORT_STEP = 10;\nconst GATEWAY_PORT_MAX = 18899;\nconst AUGMENTED_DIR = join(process.env['HOME'] ?? '/tmp', '.augmented');\nconst GATEWAY_PORTS_FILE = join(AUGMENTED_DIR, 'gateway-ports.json');\n\n// Channel-process zombie sweep (ENG-4453). Throttled to avoid shelling out\n// to `ps eww` every poll. Default 5 min, overridable via env for tuning or\n// stress tests. AGT_CHANNEL_SWEEP_DRY_RUN=1 logs kill targets without sending\n// SIGTERM — operators should flip this on first and verify the targets before\n// letting the sweep act.\nconst CHANNEL_SWEEP_INTERVAL_MS = (() => {\n const raw = parseInt(process.env['AGT_CHANNEL_SWEEP_INTERVAL_MS'] ?? '', 10);\n if (!Number.isFinite(raw)) return 5 * 60 * 1000;\n // Clamp overrides up to the 30s floor rather than falling back to default\n // — an operator who sets =10000 clearly intends a short interval, not the\n // five-minute silent fallback.\n return Math.max(raw, 30_000);\n})();\nconst CHANNEL_SWEEP_DRY_RUN = process.env['AGT_CHANNEL_SWEEP_DRY_RUN'] === '1';\nlet lastChannelSweepAt = 0;\n\n// ---------------------------------------------------------------------------\n// State\n// ---------------------------------------------------------------------------\n\nlet config: WorkerConfig | null = null;\nlet running = false;\nlet pollTimer: ReturnType<typeof setTimeout> | null = null;\n\n// ENG-4659: when persistent-session reports a session unhealthy, the\n// pane log tail is included in the operator-facing log line. Keep the\n// inline preview short — operators reading the log don't need a full\n// 20-line dump on every restart, just enough to identify the failure\n// pattern. The full tail is still on disk at ~/.augmented/<codeName>/pane.log.\nconst PANE_TAIL_PREVIEW_LINES = 5;\n/**\n * ENG-4923: extract `multi_agent.telegram_peers[]` from a CHARTER's raw\n * frontmatter so the manager can pass the resolved roster as the\n * TELEGRAM_PEERS env var on the per-agent MCP child. The shape on\n * disk is `{ code_name, bot_id }`; we add an empty `agent_id` field\n * because the classifier's parsePeersEnv requires it (downstream\n * runtime — ENG-4904 — can resolve agent_id from the API when\n * building the peer preamble).\n *\n * Lenient: any parse / shape error returns []. The CHARTER lint\n * rule from ENG-4901 already validates the shape at write time;\n * a runtime parse failure here just means the classifier sees no\n * declared peers, which falls through to its existing\n * `unknown_peer` drop path. Better than crashing the manager tick.\n */\n/**\n * ENG-4935 / ENG-4929 §4.0: gate-path metadata for each CHARTER-declared\n * peer. The classifier in packages/mcp admits a peer message only when\n * the peer resolves AND `gate_path` is non-null; a null gate path\n * surfaces in telemetry as `cross_team_grant_missing` (cross-org pair\n * without a live grant, or an org in `consent_required` mode with a\n * peer that has neither same-team membership nor a grant).\n *\n * - `same_team` — peer's bot_id is on this team's roster.\n * - `intra_org_unrestricted` — peer is foreign-team, but the org's\n * cross_team_peer_intra_org toggle is\n * `unrestricted`, so no grant required.\n * - `grant:<grant_id>` — peer is foreign-team and a live grant\n * authorises it. Validated against the\n * inbound grants snapshot.\n * - `null` — gate missing: cross-org without grant\n * or charter references a stale grant.\n * Charter still lists it (so the UI can\n * tell the user) but classifier drops.\n */\ntype PeerGatePath = 'same_team' | 'intra_org_unrestricted' | `grant:${string}` | null;\n\nexport interface CharterPeerWithGate {\n code_name: string;\n bot_id: number;\n agent_id: string;\n gate_path: PeerGatePath;\n}\n\nexport interface CharterPeerGateContext {\n /** Bot_ids of same-team agents (from /host/refresh team_peer_bot_ids). */\n teamPeerBotIds?: ReadonlyArray<number>;\n /**\n * Inbound grants snapshot (from /host/refresh cross_team_grants.inbound).\n * `granted_agent_bot_id` is denormalised so we can match a grant by the\n * bot_id the CHARTER cites without a second lookup.\n */\n crossTeamGrants?: ReadonlyArray<{\n grant_id: string;\n granted_agent_bot_id: number | null;\n revoked_at: string | null;\n expires_at: string | null;\n }>;\n /** Org-level toggle from organization.cross_team_peer_intra_org. */\n crossTeamPeerIntraOrg?: 'unrestricted' | 'consent_required';\n /** Time source for expiry comparison. Defaults to new Date(). */\n now?: () => Date;\n}\n\n/**\n * ENG-4923 / ENG-4935: extract `multi_agent.telegram_peers[]` from a\n * CHARTER's raw frontmatter and resolve a gate_path per peer.\n *\n * Lenient parsing — any shape error returns []. The CHARTER lint rules\n * (ENG-4901 + ENG-4938) already validate at write time; a runtime parse\n * failure here just means the classifier sees no declared peers, which\n * falls through to its existing `unknown_peer` drop path.\n *\n * When `gateContext` is omitted (e.g. older /host/refresh response that\n * predates ENG-4935), every peer's gate_path is set to `same_team` —\n * which preserves the pre-ENG-4935 behaviour where every declared peer\n * was admitted unconditionally.\n */\nexport function extractCharterTelegramPeers(\n rawContent: string,\n gateContext?: CharterPeerGateContext,\n): CharterPeerWithGate[] {\n if (!rawContent || rawContent.length === 0) return [];\n try {\n const parsed = extractFrontmatter(rawContent);\n const frontmatter = parsed.frontmatter as\n | { multi_agent?: { telegram_peers?: Array<{ code_name?: unknown; bot_id?: unknown; cross_team_grant_id?: unknown }> } }\n | null;\n const peers = frontmatter?.multi_agent?.telegram_peers;\n if (!peers || !Array.isArray(peers)) return [];\n const teamBotIds = new Set(gateContext?.teamPeerBotIds ?? []);\n const grants = gateContext?.crossTeamGrants ?? [];\n const intraOrg = gateContext?.crossTeamPeerIntraOrg ?? 'unrestricted';\n const now = (gateContext?.now ?? (() => new Date()))();\n const out: CharterPeerWithGate[] = [];\n for (const p of peers) {\n if (\n !p ||\n typeof p !== 'object' ||\n typeof p.code_name !== 'string' ||\n typeof p.bot_id !== 'number' ||\n !Number.isInteger(p.bot_id) ||\n p.bot_id <= 0\n ) {\n continue;\n }\n const grantId =\n typeof p.cross_team_grant_id === 'string' && p.cross_team_grant_id.length > 0\n ? p.cross_team_grant_id\n : null;\n let gate: PeerGatePath = null;\n\n if (gateContext === undefined) {\n // Backwards-compat: no gate context = pre-ENG-4935 manager; admit\n // every declared peer as same-team to preserve the old contract.\n gate = 'same_team';\n } else if (teamBotIds.has(p.bot_id)) {\n // CodeRabbit on PR #868: same-team wins over a CHARTER-cited\n // grant_id. Otherwise a stale cross_team_grant_id (left over from\n // when the peer was on another team) would downgrade a current\n // same-team peer to `null` on revoke/expiry. Team membership is\n // the strongest signal — trust it.\n gate = 'same_team';\n } else if (grantId) {\n // Charter cites a cross-team grant — must match a live grant in the\n // inbound snapshot, and the grant must authorise the bot_id the\n // charter declared. Lint covers this at write time; the runtime\n // re-checks because the grant may have been revoked since.\n const grant = grants.find((g) => g.grant_id === grantId);\n if (\n grant &&\n !grant.revoked_at &&\n (!grant.expires_at || new Date(grant.expires_at) > now) &&\n grant.granted_agent_bot_id === p.bot_id\n ) {\n gate = `grant:${grantId}` as const;\n } else {\n gate = null;\n }\n } else if (intraOrg === 'unrestricted') {\n // Foreign-team peer without a grant — admitted only because the\n // org is in unrestricted mode. Flipping the toggle to\n // consent_required will reduce this to `null` and the classifier\n // will start dropping with `cross_team_grant_missing`.\n gate = 'intra_org_unrestricted';\n } else {\n gate = null;\n }\n out.push({ code_name: p.code_name, bot_id: p.bot_id, agent_id: '', gate_path: gate });\n }\n return out;\n } catch {\n return [];\n }\n}\n\nfunction truncateForLog(s: string): string {\n const lines = s.split('\\n').filter((l) => l.length > 0);\n return lines.slice(-PANE_TAIL_PREVIEW_LINES).map((l) => ` | ${l}`).join('\\n');\n}\n// CodeRabbit (PR #618): only embed the raw pane tail in manager.log\n// when the failure signature is one we recognise as a safe-to-quote\n// Claude error string. For anything else (including 'unknown' tails)\n// log only the sha256 prefix and point operators at the on-disk file.\n// Add new signatures here only after auditing what Claude actually\n// prints for that failure mode.\nconst KNOWN_SAFE_TAIL_SIGNATURES = new Set<string>(['session_id_in_use']);\n\n// Track last-known versions + hashes per agent to detect changes\nconst knownVersions = new Map<string, { charterVersion: string; toolsVersion: string }>();\nconst knownStatuses = new Map<string, string>();\nconst knownChannels = new Map<string, Set<string>>();\n\n// ENG-4807 / CodeRabbit on PR #773: coalesce per-agent session-restart\n// timers so two hot-reload triggers in the same tick (e.g. channel set\n// changed AND MCP toolkit changed) don't schedule two stops back-to-back.\n// Without this, the first stop kills the original session and the second\n// stop fires a few seconds later — by which time the persistent-session\n// healthcheck has already respawned, and the second stop kills the\n// freshly-respawned session.\nconst pendingSessionRestarts = new Map<string, ReturnType<typeof setTimeout>>();\n\nfunction scheduleSessionRestart(codeName: string, delayMs: number, reason: string): void {\n const existing = pendingSessionRestarts.get(codeName);\n if (existing) {\n clearTimeout(existing);\n log(`[hot-reload] Coalesced restart for '${codeName}': replacing pending timer with ${reason}`);\n }\n const timer = setTimeout(() => {\n pendingSessionRestarts.delete(codeName);\n stopPersistentSession(codeName, log);\n // ENG-4897: drop the hash baseline when the session actually stops.\n // Without this, the next poll compares the fresh session against the\n // dead one's hash and schedules another spurious restart. Covers the\n // existing hot-reload-restart callers (channel-set change, new MCP\n // servers) that already pre-date the drift check.\n runningMcpHashes.delete(codeName);\n log(`[hot-reload] Session stopped for '${codeName}' — will respawn with ${reason}`);\n }, delayMs);\n // Don't keep the manager alive solely on a pending restart timer.\n timer.unref?.();\n pendingSessionRestarts.set(codeName, timer);\n}\n\n// CodeRabbit on PR #773 (round 2): cancel any pending hot-reload restart\n// timer for an agent so a non-hot-reload teardown path (pause / revoke /\n// unassign / auth-rotation stop / day-rollover / shutdown / restart-flags)\n// doesn't get followed seconds later by a stale timer firing and killing\n// the freshly respawned replacement. Idempotent: safe to call when no\n// timer is pending. Call this BEFORE every direct stopPersistentSession\n// call in this file (the single in-timer call is already self-cleaning\n// via the pendingSessionRestarts.delete above).\nfunction cancelPendingSessionRestart(codeName: string): void {\n const existing = pendingSessionRestarts.get(codeName);\n if (!existing) return;\n clearTimeout(existing);\n pendingSessionRestarts.delete(codeName);\n log(`[hot-reload] Cancelled pending restart timer for '${codeName}' (another teardown path is handling it)`);\n}\nconst writtenHashes = new Map<string, Map<string, string>>();\nconst knownSecretsHashes = new Map<string, string>();\n\n// ENG-4897: per-agent hash of the project `.mcp.json` that the running\n// claude session was launched with. The existing stale-MCP reaper\n// (ENG-4832) only watches env-var-keyed integration changes; structural\n// changes to .mcp.json (new server entries, removed entries, inline env\n// changes for tokens that aren't ${VAR}-substituted) slip past it.\n//\n// Sterling/agt-demo (2026-05-09): operator added Telegram, the manager\n// wrote the new .mcp.json, but the running claude session — launched\n// with --strict-mcp-config — had only the pre-Telegram view. Its\n// `--dangerously-load-development-channels server:telegram` flag then\n// errored \"no MCP server configured with that name\" until manual\n// `tmux kill-session` triggered a respawn.\n//\n// On each poll we hash the project .mcp.json and compare to the value\n// recorded for this agent. If they differ, schedule a session restart\n// (the existing hot-reload coalescer takes care of the rest). On first\n// observation post-manager-start we adopt the current hash as the\n// optimistic baseline — worst case is one missed drift event after a\n// manager restart, the next change is caught.\nconst runningMcpHashes = new Map<string, string>();\n\nfunction projectMcpHash(codeName: string, projectDir: string): string | null {\n try {\n return createHash('sha256').update(readFileSync(join(projectDir, '.mcp.json'))).digest('hex');\n } catch {\n return null;\n }\n}\n\n/**\n * ENG-4897 follow-up (CodeRabbit on PR #829): drop the `runningMcpHashes`\n * entry whenever a session is stopped for ANY reason (auth rotation,\n * day rollover, pause, terminate, hot-reload restart). Without this,\n * the post-`ensurePersistentSession()` drift check on the NEXT poll\n * compares the freshly-respawned session against the dead session's\n * hash, immediately scheduling a spurious second restart.\n *\n * `cancelPendingSessionRestart` is called before every direct\n * `stopPersistentSession` (see its docstring), but the cancel doesn't\n * clear the hash. Call this wrapper instead — it bundles the cancel +\n * the hash drop, so every stop path stays consistent.\n */\nfunction stopPersistentSessionAndForgetMcpBaseline(codeName: string): void {\n cancelPendingSessionRestart(codeName);\n stopPersistentSession(codeName, log);\n runningMcpHashes.delete(codeName);\n}\n\nfunction checkMcpConfigDriftAndScheduleRestart(codeName: string, projectDir: string): void {\n const currentHash = projectMcpHash(codeName, projectDir);\n const action = decideMcpDriftAction(currentHash, runningMcpHashes.get(codeName));\n switch (action.kind) {\n case 'no-config':\n case 'no-drift':\n return;\n case 'baseline':\n runningMcpHashes.set(codeName, action.hash);\n return;\n case 'drift':\n log(\n `[hot-reload] .mcp.json content changed for '${codeName}' ` +\n `(${action.previous.slice(0, 12)} → ${action.current.slice(0, 12)}); scheduling restart (ENG-4897)`,\n );\n scheduleSessionRestart(codeName, 0, '.mcp.json content change (ENG-4897)');\n // Drop the entry — the next poll after respawn captures the new\n // hash as baseline. Keeping the old hash would re-fire every poll\n // until the respawn timer triggers.\n runningMcpHashes.delete(codeName);\n return;\n }\n}\n// Persisted to disk via saveChannelHashCache() so a fresh manager process\n// (post self-update, post launchd restart, etc.) doesn't re-emit\n// `reason=first-write` for credentials that are already on disk and\n// match the desired config (ENG-4712).\nconst knownChannelConfigHashes = new Map<string, string>();\nconst knownModels = new Map<string, string>();\nconst knownTasksHashes = new Map<string, string>();\nconst knownIntegrationHashes = new Map<string, string>();\n// ENG-4481: desired-state hash of managed-toolkit MCP servers (provider/id/url/header-keys).\n// Used to converge `.mcp.json` every poll instead of only when credentials change,\n// so plugin-added toolkits self-heal when the previous write never happened.\nconst knownManagedMcpHashes = new Map<string, string>();\nconst knownSkillHashes = new Map<string, string>();\n// Track last-seen cron run timestamps per job to avoid re-processing\nconst lastCronRunTs = new Map<string, number>();\n// Track last work_trigger_at per agent to avoid duplicate triggers\nconst lastWorkTriggerAt = new Map<string, number>();\n// Track which in_progress items we've already alerted as stale\nconst alertedStaleItems = new Set<string>();\n// Track agents with known API key errors (avoid spamming the API)\nconst apiKeyStatusCache = new Map<string, boolean>();\nconst STALE_TASK_THRESHOLD_MS = 30 * 60 * 1000; // 30 minutes\n\n// Alert webhook URL — cached from first agent's team settings\nlet alertSlackWebhook: string | null = null;\n// Track alerted job IDs to avoid duplicate alerts within a session\nconst alertedJobs = new Set<string>();\n// Map cron job name (aug:template:uuid) → human-readable task info\nconst taskDisplayInfo = new Map<string, { taskName: string; schedule: string; agentDisplayName: string }>();\n// Map agent code_name → display_name for alerts\nconst agentDisplayNames = new Map<string, string>();\n// Map code_name → agent_id for kanban updates\nconst codeNameToAgentId = new Map<string, string>();\n// Map code_name → channel bot tokens (from channel_configs)\nconst agentChannelTokens = new Map<string, { slack?: string; telegram?: string; telegramAllowedChats?: string[] }>();\n\n// Per-cycle channel tracking — populated by processAgent, reconciled after all agents\nconst activeChannels = new Map<string, Set<string>>(); // channelId -> Set<codeName>\n\n// Gateways started this poll cycle — skip health-check restarts for these\nconst gatewaysStartedThisCycle = new Set<string>();\n\n// Cumulative state for the parent\nlet state: ManagerState = {\n pid: process.pid,\n startedAt: new Date().toISOString(),\n lastPollAt: null,\n pollCount: 0,\n errorCount: 0,\n agents: [],\n};\n\n// Cache registered agents per framework per poll cycle\nconst registeredAgentsCache = new Map<string, Set<string>>();\n\n// Per-agent framework ID cache (populated from API response and refresh data)\nconst agentFrameworkCache = new Map<string, string>();\n\n// Track which framework binaries we've already checked/installed this session\nconst frameworkBinaryChecked = new Set<string>();\n\n// Claude Code auth status — set during ensureFrameworkBinary, checked before launching sessions\nlet agentRuntimeAuthenticated = false;\n\n/** Resolve the framework adapter for a given agent, using the cache or defaulting to 'openclaw'. */\nfunction resolveAgentFramework(codeName: string): FrameworkAdapter {\n const frameworkId = agentFrameworkCache.get(codeName) ?? 'openclaw';\n return getFramework(frameworkId);\n}\n\n// Gateway client pool — replaces single GatewayClient\nlet gatewayPool: GatewayClientPool | null = null;\n\n/**\n * Clear all per-agent caches when an agent is unassigned, revoked, or needs\n * a full reprovision. Keeps the cleanup in one place instead of scattering\n * .delete() calls for every new Map we add.\n */\nfunction clearAgentCaches(agentId: string, codeName: string): void {\n // ID-keyed caches\n knownVersions.delete(agentId);\n knownStatuses.delete(agentId);\n knownChannels.delete(agentId);\n writtenHashes.delete(agentId);\n knownSecretsHashes.delete(agentId);\n knownModels.delete(agentId);\n knownTasksHashes.delete(agentId);\n knownIntegrationHashes.delete(agentId);\n knownManagedMcpHashes.delete(agentId);\n\n // codeName-keyed caches\n agentDisplayNames.delete(codeName);\n codeNameToAgentId.delete(codeName);\n agentFrameworkCache.delete(codeName);\n kanbanBoardCache.delete(codeName);\n lastHarvestAt.delete(codeName);\n claudeSchedulerStates.delete(codeName);\n claudeTaskConcurrency.delete(codeName);\n\n // Memory sync caches\n memoryFileHashes.delete(agentId);\n lastDownloadHash.delete(agentId);\n lastLocalFileHash.delete(agentId);\n\n // Compound-keyed caches (agentId:suffix)\n let channelCacheMutated = false;\n for (const key of knownChannelConfigHashes.keys()) {\n if (key.startsWith(`${agentId}:`)) {\n knownChannelConfigHashes.delete(key);\n channelCacheMutated = true;\n }\n }\n if (channelCacheMutated) saveChannelHashCache();\n for (const key of knownSkillHashes.keys()) {\n if (key.startsWith(`${agentId}:`)) knownSkillHashes.delete(key);\n }\n for (const key of taskDisplayInfo.keys()) {\n if (key.startsWith(`${codeName}:`)) taskDisplayInfo.delete(key);\n }\n}\n\n// Cached framework version (detected once at startup, refreshed periodically)\nlet cachedFrameworkVersion: string | null = null;\nlet lastVersionCheckAt = 0;\nconst VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1000; // Re-check every 5 minutes\n\n// agt CLI version, injected by tsup at build time. Reported on heartbeat\n// so the host page can surface \"which agt is actually running here?\".\n// In dev (tsx), the constant isn't defined — fall back to 'dev'.\ndeclare const __CLI_VERSION__: string;\nconst agtCliVersion = typeof __CLI_VERSION__ !== 'undefined' ? __CLI_VERSION__ : 'dev';\n\n// ---------------------------------------------------------------------------\n// Framework binary install / upgrade\n// ---------------------------------------------------------------------------\n\n/**\n * Ensure a Homebrew formula is installed. Used for framework dependencies\n * like tmux that are required but not part of the framework itself.\n * Returns true if the binary is available after the check.\n */\nasync function ensureBrewDependency(binary: string, formula: string): Promise<boolean> {\n if (frameworkBinaryChecked.has(`dep:${binary}`)) return true;\n\n const { execFileSync } = await import('node:child_process');\n\n try {\n execFileSync('which', [binary], { timeout: 5_000 });\n frameworkBinaryChecked.add(`dep:${binary}`);\n return true;\n } catch {\n // Binary not found — try to install\n }\n\n let brewPath: string;\n try {\n brewPath = execFileSync('which', ['brew'], { timeout: 5_000 }).toString().trim();\n } catch {\n log(`${binary} not found and Homebrew not available — install manually: brew install ${formula}`);\n frameworkBinaryChecked.add(`dep:${binary}`);\n return false;\n }\n\n log(`${binary} not found — installing via Homebrew...`);\n try {\n execFileSync(brewPath, ['install', formula], { timeout: 120_000, stdio: 'pipe' });\n } catch (err) {\n log(`Failed to install ${formula}: ${(err as Error).message}`);\n frameworkBinaryChecked.add(`dep:${binary}`);\n return false;\n }\n\n // Prepend brew's bin dir to PATH so the post-install verification (and any\n // downstream subprocesses) can find the binary. Brew installs to a custom\n // prefix that isn't on the manager's PATH when spawned by cloud-init.\n const brewBinDir = dirname(brewPath);\n if (!process.env.PATH?.split(':').includes(brewBinDir)) {\n process.env.PATH = `${brewBinDir}:${process.env.PATH ?? ''}`;\n }\n\n try {\n execFileSync('which', [binary], { timeout: 5_000 });\n log(`${binary} installed successfully`);\n frameworkBinaryChecked.add(`dep:${binary}`);\n return true;\n } catch {\n log(`${formula} install completed but ${binary} not found on PATH`);\n frameworkBinaryChecked.add(`dep:${binary}`);\n return false;\n }\n}\n\n/**\n * Ensure the CLI binary for a framework is available. Currently only handles\n * `claude-code` via Homebrew. Called once per session when we first see an\n * agent using that framework.\n */\n/**\n * Resolve the brew binary. `which brew` fails when PATH doesn't include the\n * Homebrew bin dir (common when the manager is spawned by cloud-init with a\n * minimal env). Fall back to the canonical Linuxbrew install location.\n */\nfunction resolveBrewPath(execFileSync: typeof import('node:child_process').execFileSync): string | null {\n try {\n const out = execFileSync('which', ['brew'], { timeout: 5_000 }).toString().trim();\n if (out) return out;\n } catch {\n // fall through to absolute path check\n }\n // Canonical install locations across the three platforms we run on.\n // Linuxbrew first (the AL2023 hosts the manager runs on most often),\n // then Apple-silicon macOS (`/opt/homebrew`), then Intel macOS\n // (`/usr/local`). Without the macOS entries the launchd-spawned\n // manager bails out of self-update on Macs whose sparse PATH doesn't\n // include the brew bin dir.\n const fallbacks = [\n '/home/linuxbrew/.linuxbrew/bin/brew',\n '/opt/homebrew/bin/brew',\n '/usr/local/bin/brew',\n ];\n for (const path of fallbacks) {\n if (existsSync(path)) return path;\n }\n return null;\n}\n\n/**\n * ENG-4421: resolve a toolkit → install its CLI if missing.\n *\n * Plugins declare `required_toolkits: [...]`. The manager unions these across\n * an agent's installed plugins and calls this per toolkit. Idempotent: a hit\n * on `command -v <binary>` short-circuits; the in-memory guard makes repeat\n * polls cheap.\n *\n * Installer strategies are declared in the catalog (`IntegrationCliTool.installer`):\n * - 'npm' : `npm install -g <package>`\n * - 'brew' : `brew install <package>` (proxied via ec2-user when running as root)\n * - 'script' : runs `cli_tool.script` verbatim. The catalog is the trust boundary —\n * never take the script from plugin-side data.\n * - 'manual' : skip — logged as a hint for the operator.\n *\n * All failures are non-fatal: the manager logs and moves on so a missing CLI\n * doesn't block the rest of the agent's provisioning.\n *\n * Caching semantics:\n * - `toolkitCliEnsured` caches *permanent* outcomes: success, 'manual'\n * installer (operator's responsibility), or catalog authoring errors\n * (missing package/script). Those states won't change poll-to-poll.\n * - `toolkitCliRetryAfter` caches *transient* failures (network, brew\n * refused root, npm 500, post-install verify fail) with a 5-min\n * cooldown. This lets a retry recover automatically instead of\n * requiring a manager restart.\n */\nconst toolkitCliEnsured = new Set<string>();\nconst toolkitCliRetryAfter = new Map<string, number>();\nconst toolkitCliFailureCount = new Map<string, number>();\nconst TOOLKIT_INSTALL_RETRY_MS = 5 * 60_000;\n// After this many consecutive failures, mark the toolkit permanently\n// failed for the lifetime of the manager process. Stops the log spam\n// when an upstream package is broken (e.g. linear-cli's musl fallback\n// doesn't exist) or the host is missing some prereq we can't auto-\n// install. Operator restarts the manager to retry.\nconst TOOLKIT_INSTALL_MAX_FAILURES = 3;\n\nfunction recordToolkitFailure(toolkitSlug: string, reason: string): void {\n const count = (toolkitCliFailureCount.get(toolkitSlug) ?? 0) + 1;\n toolkitCliFailureCount.set(toolkitSlug, count);\n if (count >= TOOLKIT_INSTALL_MAX_FAILURES) {\n log(`[toolkit-install] ${toolkitSlug}: ${reason} (giving up after ${count} attempts — restart the manager to retry)`);\n toolkitCliEnsured.add(toolkitSlug);\n toolkitCliRetryAfter.delete(toolkitSlug);\n } else {\n log(`[toolkit-install] ${toolkitSlug}: ${reason} (attempt ${count}/${TOOLKIT_INSTALL_MAX_FAILURES}, retrying in ${TOOLKIT_INSTALL_RETRY_MS / 60_000}m)`);\n toolkitCliRetryAfter.set(toolkitSlug, Date.now() + TOOLKIT_INSTALL_RETRY_MS);\n }\n}\n\nasync function ensureToolkitCli(toolkitSlug: string): Promise<void> {\n if (toolkitCliEnsured.has(toolkitSlug)) return;\n const retryAfter = toolkitCliRetryAfter.get(toolkitSlug) ?? 0;\n if (retryAfter > Date.now()) return;\n\n const integration = getIntegration(toolkitSlug);\n if (!integration?.cli_tool) {\n // Unknown toolkit or no CLI declared — permanent, won't change between polls.\n toolkitCliEnsured.add(toolkitSlug);\n return;\n }\n\n const { binary, installer, package: pkg, script } = integration.cli_tool;\n const resolvedInstaller = installer ?? 'manual';\n\n const { execFileSync, execSync } = await import('node:child_process');\n\n // Fast path: already on PATH.\n try {\n execFileSync('which', [binary], { timeout: 5_000, stdio: 'pipe' });\n toolkitCliEnsured.add(toolkitSlug);\n toolkitCliRetryAfter.delete(toolkitSlug);\n toolkitCliFailureCount.delete(toolkitSlug);\n return;\n } catch { /* not on PATH — attempt install */ }\n\n if (resolvedInstaller === 'manual') {\n log(`[toolkit-install] ${toolkitSlug}: binary '${binary}' missing, installer=manual — operator must install`);\n // Not going to change without operator action; mark permanent to avoid log spam.\n toolkitCliEnsured.add(toolkitSlug);\n return;\n }\n\n let brewBinDir: string | null = null;\n try {\n if (resolvedInstaller === 'npm') {\n if (!pkg) {\n log(`[toolkit-install] ${toolkitSlug}: installer=npm but no package declared`);\n // Catalog authoring mistake — permanent, retrying won't fix it.\n toolkitCliEnsured.add(toolkitSlug);\n return;\n }\n log(`[toolkit-install] ${toolkitSlug}: installing via npm (${pkg})…`);\n execFileSync('npm', ['install', '-g', pkg], { timeout: 180_000, stdio: 'pipe' });\n } else if (resolvedInstaller === 'brew') {\n if (!pkg) {\n log(`[toolkit-install] ${toolkitSlug}: installer=brew but no package declared`);\n toolkitCliEnsured.add(toolkitSlug);\n return;\n }\n const brewPath = resolveBrewPath(execFileSync);\n if (!brewPath) {\n log(`[toolkit-install] ${toolkitSlug}: installer=brew but Homebrew not available — install manually: brew install ${pkg}`);\n // Env-level problem — unlikely to self-heal; mark permanent.\n toolkitCliEnsured.add(toolkitSlug);\n return;\n }\n brewBinDir = dirname(brewPath);\n const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;\n log(`[toolkit-install] ${toolkitSlug}: installing via brew (${pkg})…`);\n if (isRoot) {\n // cwd: '/tmp' — brew refuses if the cwd isn't readable to\n // ec2-user; the manager's cwd is /root (mode 0700) by default.\n execFileSync('sudo', ['-u', 'ec2-user', '-H', brewPath, 'install', pkg], { timeout: 180_000, stdio: 'pipe', cwd: '/tmp' });\n } else {\n execFileSync(brewPath, ['install', pkg], { timeout: 180_000, stdio: 'pipe' });\n }\n } else if (resolvedInstaller === 'script') {\n if (!script) {\n log(`[toolkit-install] ${toolkitSlug}: installer=script but no script declared`);\n toolkitCliEnsured.add(toolkitSlug);\n return;\n }\n log(`[toolkit-install] ${toolkitSlug}: running declared install script…`);\n // The script comes from the catalog (source-controlled) — never from\n // runtime plugin data. execSync is safe here because the catalog is\n // the trust boundary.\n execSync(script, { timeout: 180_000, stdio: 'pipe' });\n }\n } catch (err) {\n const msg = (err as Error).message.slice(0, 200);\n recordToolkitFailure(toolkitSlug, `installer=${resolvedInstaller} failed — ${msg}`);\n return;\n }\n\n // Prepend brew's bin dir to PATH so the post-install `which` + any downstream\n // spawns can actually find the new binary. Brew installs to a custom prefix\n // (e.g. /home/linuxbrew/.linuxbrew/bin) that isn't on the manager's PATH when\n // spawned by cloud-init with a minimal env.\n if (brewBinDir && !process.env.PATH?.split(':').includes(brewBinDir)) {\n process.env.PATH = `${brewBinDir}:${process.env.PATH ?? ''}`;\n }\n\n // Verify the binary landed on PATH after install.\n try {\n execFileSync('which', [binary], { timeout: 5_000, stdio: 'pipe' });\n log(`[toolkit-install] ${toolkitSlug}: installed — ${binary} now on PATH`);\n toolkitCliEnsured.add(toolkitSlug);\n toolkitCliRetryAfter.delete(toolkitSlug);\n toolkitCliFailureCount.delete(toolkitSlug);\n } catch {\n recordToolkitFailure(toolkitSlug, `installer=${resolvedInstaller} completed but ${binary} still not on PATH`);\n }\n}\n\n/**\n * Run a command and capture stdout, without blocking the Node event loop.\n *\n * `execFileSync` blocks the entire process until the child returns, which on\n * a slow `brew upgrade --cask claude-code` can be 30–120s. Every other\n * manager poll task — HTTP, WS heartbeats, agent processing — stalls for\n * that whole window. Switching to `spawn` + a Promise wrapper keeps the\n * event loop alive while brew runs.\n */\nfunction runAsync(\n cmd: string,\n args: string[],\n opts: { timeout: number; cwd?: string },\n): Promise<{ code: number; stdout: string; stderr: string }> {\n return new Promise((resolve, reject) => {\n import('node:child_process').then(({ spawn }) => {\n const child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'], cwd: opts.cwd });\n let stdout = '';\n let stderr = '';\n // 'close' only fires after stdio streams drain, which can hang\n // indefinitely if the child ignores SIGTERM. Settle the promise the\n // moment the deadline hits so callers don't block past `opts.timeout`,\n // then escalate to SIGKILL as a backstop.\n let settled = false;\n const timer = setTimeout(() => {\n child.kill('SIGTERM');\n if (settled) return;\n settled = true;\n reject(new Error(`${cmd} ${args.join(' ')} timed out after ${opts.timeout}ms`));\n setTimeout(() => { try { child.kill('SIGKILL'); } catch { /* already gone */ } }, 5_000).unref();\n }, opts.timeout);\n\n child.stdout?.on('data', (b) => { stdout += b.toString(); });\n child.stderr?.on('data', (b) => { stderr += b.toString(); });\n child.on('error', (err) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n reject(err);\n });\n child.on('close', (code) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve({ code: code ?? -1, stdout, stderr });\n });\n }).catch(reject);\n });\n}\n\nasync function ensureFrameworkBinary(frameworkId: string): Promise<void> {\n if (frameworkId !== 'claude-code') return;\n if (frameworkBinaryChecked.has(frameworkId)) return;\n frameworkBinaryChecked.add(frameworkId);\n\n const { execFileSync } = await import('node:child_process');\n\n const brewPath = resolveBrewPath(execFileSync);\n if (!brewPath) {\n log('Homebrew not found (no `brew` on PATH, no /home/linuxbrew/.linuxbrew/bin/brew). Cannot auto-install Claude Code. Install manually: https://claude.ai/download');\n return;\n }\n log(`Using brew at ${brewPath}`);\n\n // Brew refuses to run as root. When the manager is spawned by the EC2\n // bootstrap (user-data runs as root), proxy the brew call through ec2-user.\n // Otherwise invoke brew directly.\n //\n // The cwd matters: brew refuses to run if the cwd isn't readable to the\n // user it'll run as. The manager's cwd is typically /root (mode 0700)\n // when launched by cloud-init, which ec2-user can't read — so the\n // sudo'd brew bails out with \"current working directory must be\n // readable to ec2-user\". /tmp is world-readable and has no other\n // semantic meaning to brew.\n const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;\n const runBrew = (args: string[], opts: { timeout: number }) => {\n if (isRoot) {\n return runAsync('sudo', ['-u', 'ec2-user', '-H', brewPath, ...args], { ...opts, cwd: '/tmp' });\n }\n return runAsync(brewPath, args, opts);\n };\n\n // Check if claude binary exists. Check the canonical Linuxbrew path first\n // (since `which claude` can fail when PATH is minimal) then fall back to PATH.\n // `which` is fast (<5ms typical) so a sync call here is acceptable.\n let claudeExists = existsSync('/home/linuxbrew/.linuxbrew/bin/claude');\n if (!claudeExists) {\n try {\n execFileSync('which', ['claude'], { timeout: 5_000 });\n claudeExists = true;\n } catch { /* not installed */ }\n }\n\n if (!claudeExists) {\n log(`Claude Code binary not found — installing via Homebrew${isRoot ? ' (as ec2-user via sudo)' : ''}...`);\n try {\n const r = await runBrew(['install', '--cask', 'claude-code'], { timeout: 120_000 });\n if (r.code !== 0) {\n log(`Claude Code install failed (exit ${r.code}): ${r.stderr.trim() || r.stdout.trim()}`);\n return;\n }\n } catch (err) {\n log(`Claude Code install failed: ${(err as Error).message}`);\n return;\n }\n\n // Prepend brew's bin dir to PATH so subsequent `which claude` /\n // resolveClaudeBinary calls find the new binary on minimal-PATH hosts\n // (e.g. EC2 cloud-init root env). Mirrors ensureBrewDependency().\n const brewBinDir = dirname(brewPath);\n if (!process.env.PATH?.split(':').includes(brewBinDir)) {\n process.env.PATH = `${brewBinDir}:${process.env.PATH ?? ''}`;\n }\n\n // Verify\n if (existsSync('/home/linuxbrew/.linuxbrew/bin/claude')) {\n log('Claude Code installed successfully');\n } else {\n log('Claude Code install completed but binary not found at expected path — check brew logs');\n }\n } else {\n // Upgrade — fire-and-forget. The binary already exists, so agents can run\n // on it immediately; we don't gate the poll loop on a network-bound\n // `brew upgrade`. The new version, if any, is picked up the next time the\n // manager (or a tmux session) starts.\n log(`Checking for Claude Code updates in background${isRoot ? ' (as ec2-user via sudo)' : ''}...`);\n runBrew(['upgrade', '--cask', 'claude-code'], { timeout: 120_000 })\n .then((r) => {\n const combined = `${r.stdout}\\n${r.stderr}`;\n if (r.code === 0) {\n if (combined.includes('already installed') || combined.includes('up-to-date')) {\n log('Claude Code is already up to date');\n } else {\n log('Claude Code upgraded successfully (will apply on next session start)');\n }\n } else if (combined.includes('already installed') || combined.includes('up-to-date') || combined.includes('not upgraded')) {\n // brew upgrade exits non-zero when already up to date on some versions.\n log('Claude Code is already up to date');\n } else {\n log(`Claude Code upgrade failed (exit ${r.code}): ${r.stderr.trim() || r.stdout.trim()}`);\n }\n })\n .catch((err) => log(`Claude Code upgrade failed: ${(err as Error).message}`));\n }\n\n // Check if Claude Code is authenticated. Don't await the upgrade — auth is\n // independent of upgrade state and the binary that's already on disk works.\n agentRuntimeAuthenticated = await checkClaudeAuth();\n}\n\n// ---------------------------------------------------------------------------\n// CLI self-update via Homebrew (throttled to once every 5 minutes)\n// ---------------------------------------------------------------------------\n\nconst UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes\n// ENG-4485: only log \"up to date\" once per manager process so the periodic\n// check doesn't spam the log every 5 minutes. Errors, \"update available\",\n// and \"upgraded to X\" always log.\nlet selfUpdateUpToDateLogged = false;\n// ENG-4488: set after a successful `brew upgrade` so the poll loop exits\n// cleanly and a supervisor can re-spawn onto the new binary. Without this,\n// the running process keeps executing from the old Cellar path whose lazy-\n// imported hashed chunks brew has already removed — the first dynamic\n// import after the swap throws `Cannot find module`.\nlet restartAfterUpgrade = false;\nlet pendingUpgradeVersion: string | null = null;\n// In-flight lock for the self-update path. pollCycle() kicks\n// checkAndUpdateCli() off fire-and-forget, so without this a slow\n// `brew update` or `npm install -g` lets later polls re-enter and\n// launch a second updater concurrently. brew/npm both lock-file\n// internally and would error on the collision; worse, both could\n// flip restartAfterUpgrade and double-schedule a manager respawn.\nlet selfUpdateInFlight = false;\nasync function checkAndUpdateCli(): Promise<void> {\n if (selfUpdateInFlight) return;\n selfUpdateInFlight = true;\n try {\n const cliPath = process.argv[1] ?? '';\n const isDevMode = cliPath.includes('/src/') || cliPath.includes('tsx');\n if (isDevMode) return;\n\n // Distinguish brew formula installs from npm-global installs. A bare\n // `/home/linuxbrew/` or `/opt/homebrew/` prefix isn't enough — Linuxbrew's\n // hosted Node lays npm-global packages under `.../lib/node_modules/...`,\n // so we match on the canonical formula path `/Cellar/<formula>/`.\n let resolvedPath = cliPath;\n try {\n const { realpathSync } = await import('node:fs');\n resolvedPath = realpathSync(cliPath);\n } catch { /* fall back to argv[1] verbatim */ }\n const isBrewFormula = /\\/Cellar\\/[^/]+\\//.test(resolvedPath);\n const isNpmGlobal = !isBrewFormula && resolvedPath.includes('node_modules');\n if (!isBrewFormula && !isNpmGlobal) return; // Unknown install method\n\n // Throttle: check last update time from disk.\n const { readFileSync: readF, writeFileSync: writeF } = await import('node:fs');\n const markerPath = join(homedir(), '.augmented', '.last-update-check');\n try {\n const lastCheck = parseInt(readF(markerPath, 'utf-8').trim(), 10);\n if (Date.now() - lastCheck < UPDATE_CHECK_INTERVAL_MS) return;\n } catch { /* no marker yet — first run */ }\n\n // Stamp the marker BEFORE running the heavy updater so a long\n // brew/npm run doesn't let the same poll cycle re-enter past the\n // throttle as soon as the lock releases. Update again at the end\n // so the next throttle window measures from completion.\n try {\n writeF(markerPath, String(Date.now()));\n } catch { /* non-fatal */ }\n\n if (isBrewFormula) {\n await checkAndUpdateCliViaBrew();\n } else {\n await checkAndUpdateCliViaNpm();\n }\n\n // Update marker regardless of outcome — failures here shouldn't trigger\n // a tight retry loop on every poll cycle.\n try {\n writeF(markerPath, String(Date.now()));\n } catch { /* non-fatal */ }\n } finally {\n selfUpdateInFlight = false;\n }\n}\n\nasync function checkAndUpdateCliViaBrew(): Promise<void> {\n const { execFileSync } = await import('node:child_process');\n // Use the shared resolveBrewPath helper rather than a bare `which brew`.\n // The manager runs with cloud-init's minimal env on EC2 (and a similarly\n // sparse PATH under launchd on macOS), so `which brew` returns nothing\n // even on hosts where the CLI clearly lives in a Cellar — without this,\n // self-update on those hosts silently no-ops.\n const brewPath = resolveBrewPath(execFileSync);\n if (!brewPath) return; // No Homebrew on PATH or canonical install dirs.\n\n // Refresh tap metadata first. `brew outdated` consults the LOCAL formula\n // cache, so without this it will happily report no updates even when the\n // tap has a newer version on GitHub. Hitting this was the reason self-\n // updates silently stopped working for hours after a version bump.\n try {\n execFileSync(brewPath, ['update', '--quiet'], { timeout: 60_000, stdio: 'pipe' });\n } catch (err) {\n log(`[self-update] brew update failed (continuing with stale cache): ${(err as Error).message}`);\n }\n\n try {\n const outdated = execFileSync(brewPath, ['outdated', '--json=v2'], {\n timeout: 30_000,\n encoding: 'utf-8',\n });\n const data = JSON.parse(outdated) as { formulae?: Array<{ name: string; installed_versions: string[]; current_version: string }> };\n const agtOutdated = data.formulae?.find((f) => f.name === 'agt' || f.name === 'integrity-labs/tap/agt');\n\n if (agtOutdated) {\n const installed = agtOutdated.installed_versions?.[0] ?? 'unknown';\n const latest = agtOutdated.current_version ?? 'unknown';\n log(`[self-update] agt CLI update available: ${installed} → ${latest}. Upgrading via brew...`);\n\n try {\n execFileSync(brewPath, ['upgrade', 'integrity-labs/tap/agt'], {\n timeout: 120_000,\n stdio: 'pipe',\n });\n log(`[self-update] agt CLI upgraded to ${latest}. Scheduling manager restart so the new binary takes effect.`);\n restartAfterUpgrade = true;\n pendingUpgradeVersion = latest;\n } catch (err) {\n log(`[self-update] brew upgrade failed: ${(err as Error).message}`);\n }\n } else if (!selfUpdateUpToDateLogged) {\n log(`[self-update] agt CLI is up to date (brew, ${agtCliVersion})`);\n selfUpdateUpToDateLogged = true;\n }\n } catch (err) {\n log(`[self-update] brew outdated failed: ${(err as Error).message}`);\n }\n}\n\n/**\n * npm-global install upgrade path — used on AWS EC2 hosts (the bootstrap\n * runs `npm install -g @integrity-labs/agt-cli`). Queries the npm registry\n * directly (the source of truth that the API also reads from), compares\n * against the build-time `__CLI_VERSION__`, and re-runs `npm install -g`\n * when behind. Requires `npm` on PATH; on AL2023 hosts the manager runs as\n * ec2-user with passwordless sudo so we shell out via `sudo -n` to write\n * into /usr/lib/node_modules (owned by root).\n */\nasync function checkAndUpdateCliViaNpm(): Promise<void> {\n const { execFileSync } = await import('node:child_process');\n\n // Skip the comparison entirely in dev where __CLI_VERSION__ wasn't injected.\n if (agtCliVersion === 'dev') return;\n\n // Query the npm registry's `latest` dist-tag. Direct fetch — same source\n // the /cli/version API endpoint reads from — so the manager doesn't need\n // any extra wiring beyond outbound HTTPS. AbortSignal.timeout keeps a\n // hung registry from blocking the poll loop.\n let latest: string;\n try {\n const res = await fetch(\n 'https://registry.npmjs.org/@integrity-labs/agt-cli/latest',\n {\n signal: AbortSignal.timeout(10_000),\n headers: { Accept: 'application/json' },\n },\n );\n if (!res.ok) {\n log(`[self-update] npm registry returned ${res.status}`);\n return;\n }\n const body = (await res.json()) as { version?: string };\n if (!body.version) {\n log(`[self-update] npm registry response missing version field`);\n return;\n }\n latest = body.version;\n } catch (err) {\n log(`[self-update] npm registry fetch failed: ${(err as Error).message}`);\n return;\n }\n\n if (!isNewerSemver(agtCliVersion, latest)) {\n if (!selfUpdateUpToDateLogged) {\n log(`[self-update] agt CLI is up to date (npm, ${agtCliVersion})`);\n selfUpdateUpToDateLogged = true;\n }\n return;\n }\n\n log(`[self-update] agt CLI update available: ${agtCliVersion} → ${latest}. Upgrading via npm...`);\n\n // /usr/lib/node_modules is root-owned on EC2; the manager runs as\n // ec2-user. `sudo -n` non-interactively elevates — succeeds on hosts\n // where ec2-user has NOPASSWD sudo (the AL2023 default), fails fast\n // otherwise so we don't hang waiting for a TTY password prompt.\n // When already running as root (older bootstraps where the manager\n // never switched to ec2-user) we skip sudo entirely.\n const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;\n const cmd = isRoot ? 'npm' : 'sudo';\n const args = isRoot\n ? [\n 'install',\n '-g',\n `@integrity-labs/agt-cli@${latest}`,\n '--registry=https://registry.npmjs.org',\n ]\n : [\n '-n',\n 'npm',\n 'install',\n '-g',\n `@integrity-labs/agt-cli@${latest}`,\n '--registry=https://registry.npmjs.org',\n ];\n\n try {\n execFileSync(cmd, args, { timeout: 180_000, stdio: 'pipe' });\n log(`[self-update] agt CLI upgraded to ${latest}. Scheduling manager restart so the new binary takes effect.`);\n restartAfterUpgrade = true;\n pendingUpgradeVersion = latest;\n } catch (err) {\n log(`[self-update] npm upgrade failed: ${(err as Error).message}`);\n }\n}\n\n/** Compare two semver strings; returns true when `remote` is strictly newer. */\nfunction isNewerSemver(local: string, remote: string): boolean {\n const parse = (v: string) => v.replace(/^v/, '').split('.').map(Number);\n const l = parse(local);\n const r = parse(remote);\n for (let i = 0; i < 3; i++) {\n const lv = l[i] ?? 0;\n const rv = r[i] ?? 0;\n if (rv !== lv) return rv > lv;\n }\n return false;\n}\n\n/**\n * Check Claude Code auth and return true if logged in.\n *\n * Uses the same file/keychain probe as `detectClaudeAuth` (our heartbeat\n * reporter) instead of shelling out to `claude auth status`. On a root-owned\n * manager that's spawned by cloud-init, `claude` isn't on PATH and even if\n * it were, running it as root would look at `/root/.claude/` — but the\n * operator logs in as `ssm-user` or `ec2-user`, so the creds live under\n * their home. `detectClaudeAuth` probes every user home in `/home` when\n * running as root on Linux, so it finds subscription creds regardless of\n * which user completed `claude /login`.\n */\nasync function checkClaudeAuth(): Promise<boolean> {\n try {\n const report = await detectClaudeAuth();\n if (!report) {\n log('⚠️ Claude Code is not authenticated. Run `claude /login` (or set `ANTHROPIC_API_KEY`) on the host, then the manager will pick it up on the next cycle.');\n return false;\n }\n if (report.status === 'expired') {\n log(`⚠️ Claude Code auth expired (${report.mode}). Re-run \\`claude /login\\` to refresh.`);\n return false;\n }\n if (report.status === 'expiring_soon') {\n log(`[auth] Claude Code token expiring soon (expires_at=${report.expires_at}) — still valid, proceeding`);\n }\n return true;\n } catch (err) {\n log(`⚠️ Claude Code auth probe failed: ${(err as Error).message}`);\n return false;\n }\n}\n\n/**\n * Prepare a child-process env for non-tmux one-shot Claude spawns\n * (claude-scheduler + direct-chat). Mirrors what spawnSession() in\n * persistent-session does for the tmux path.\n *\n * - Force-refreshes /host/exchange so mode flips and key rotations are\n * picked up immediately instead of lagging the ~50m JWT cache.\n * - When mode=api_key, injects the decrypted key AND purges any OAuth\n * credential files under homedir()/.claude so Claude can't silently\n * fall back to a stale subscription session.\n * - When mode=subscription, strips ANTHROPIC_API_KEY from the env\n * (a key inherited from the manager's shell would override OAuth).\n * - Fails closed: throws if mode=api_key but no decrypted key is in\n * the exchange response, or if the exchange itself errors. Callers\n * must catch and skip the spawn — never spawn Claude with ambiguous\n * auth under the wrong mode.\n */\nasync function applyClaudeAuthToEnv(\n childEnv: Record<string, string | undefined>,\n label: string,\n): Promise<void> {\n const apiKey = getApiKey();\n if (!apiKey) {\n throw new Error('AGT_API_KEY is not set');\n }\n const exchange = await exchangeApiKey(apiKey, false, { forceRefresh: true });\n\n if (exchange.claudeAuthMode === 'api_key') {\n if (!exchange.anthropicApiKey) {\n throw new Error('claude_auth_mode=api_key but /host/exchange returned no decrypted key');\n }\n childEnv.ANTHROPIC_API_KEY = exchange.anthropicApiKey;\n // Purge OAuth creds for the active user so Claude doesn't silently\n // prefer a stale subscription session. Matches the persistent-session\n // purge (ENG-4417); use homedir() not a hardcoded /root path so this\n // works for non-root and macOS dev setups.\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(`[${label}] Removed ${p} (api_key mode — preventing OAuth fallback)`);\n } catch { /* non-fatal */ }\n }\n }\n } else {\n // subscription mode — strip any inherited key so Claude uses its OAuth login\n delete childEnv.ANTHROPIC_API_KEY;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Port allocation\n// ---------------------------------------------------------------------------\n\nfunction loadGatewayPorts(): Record<string, number> {\n try {\n return JSON.parse(readFileSync(GATEWAY_PORTS_FILE, 'utf-8'));\n } catch {\n return {};\n }\n}\n\nfunction saveGatewayPorts(ports: Record<string, number>): void {\n mkdirSync(AUGMENTED_DIR, { recursive: true });\n writeFileSync(GATEWAY_PORTS_FILE, JSON.stringify(ports, null, 2));\n}\n\nfunction allocatePort(codeName: string): number {\n const ports = loadGatewayPorts();\n\n // Already allocated\n if (ports[codeName]) return ports[codeName];\n\n // Find next free port (spaced by GATEWAY_PORT_STEP to avoid OpenClaw's extra port binds)\n const usedPorts = new Set(Object.values(ports));\n for (let port = GATEWAY_PORT_BASE; port <= GATEWAY_PORT_MAX; port += GATEWAY_PORT_STEP) {\n if (!usedPorts.has(port)) {\n ports[codeName] = port;\n saveGatewayPorts(ports);\n return port;\n }\n }\n\n throw new Error(`No free gateway ports in range ${GATEWAY_PORT_BASE}-${GATEWAY_PORT_MAX}`);\n}\n\nfunction freePort(codeName: string): void {\n const ports = loadGatewayPorts();\n if (ports[codeName]) {\n delete ports[codeName];\n saveGatewayPorts(ports);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n// State file lives under the manager's configDir so a non-default dir\n// doesn't split manager artifacts across locations.\nfunction getStateFile(): string {\n return join(config?.configDir ?? join(process.env['HOME'] ?? '/tmp', '.augmented'), 'manager-state.json');\n}\n\n// ENG-4712: see channel-hash-cache.ts for the rationale. We keep the\n// load/save call sites inline here so every cache mutation pairs with a\n// persist; the module holds the file format + IO and stays trivially\n// testable.\nfunction channelHashCacheDir(): string {\n return config?.configDir ?? join(process.env['HOME'] ?? '/tmp', '.augmented');\n}\n\nfunction loadChannelHashCache(): void {\n loadChannelHashCacheFromDisk(knownChannelConfigHashes, channelHashCacheDir());\n}\n\nfunction saveChannelHashCache(): void {\n saveChannelHashCacheToDisk(knownChannelConfigHashes, channelHashCacheDir());\n}\n\nfunction send(msg: ManagerEvent): void {\n // Write state updates to file for `agt manager status` to read\n if (msg.type === 'state-update') {\n try {\n writeFileSync(getStateFile(), JSON.stringify(msg.state, null, 2));\n } catch { /* non-fatal */ }\n }\n // Log notable events\n if (msg.type === 'provisioned') {\n log(`Provisioned ${msg.codeName}`);\n } else if (msg.type === 'drift-detected') {\n log(`Drift detected: ${msg.codeName} (${msg.files?.join(', ')})`);\n } else if (msg.type === 'error') {\n log(`Error: ${msg.message}`);\n }\n}\n\n// Path initialized lazily on first log() call so we don't need a second\n// explicit init step. The manager always has access to ~/.augmented.\nlet managerLogPath: string | null = null;\n// Whether log() should mirror to manager.log. If the first appendFileSync\n// throws, flip to false and stay stderr-only for the rest of this worker\n// process — no per-poll append-failure spam, no retry. The next worker\n// (after a respawn) starts fresh with managerLogWritable=true and gets\n// its own one-shot opportunity. The first failure also writes a single\n// reason line to stderr so operators see what's wrong.\nlet managerLogWritable = true;\n\n// Redact secret-shaped tokens from log messages before they hit any durable\n// sink. Applied to both stderr and the on-disk mirror — stderr gets captured\n// by journald / cloud-init / supervisor logs in production, so \"raw on\n// stderr\" is not actually safe. No raw-secret escape hatch: if you need to\n// inspect a live token, read it from its source of truth (env, secrets\n// manager) rather than scraping it from a log stream.\n//\n// Patterns:\n// - host API keys tlk_…\n// - Slack tokens xox[baprs]-…\n// - Anthropic keys sk-ant-…\n// - Telegram bot tokens <digits>:<base64url>\n// - Bearer JWTs Bearer …\n// - env-var assignments *_TOKEN=, *_SECRET=, *_API_KEY=, *_PASSWORD=\n// (broad shape match so future secrets we forget to enumerate are\n// still redacted).\nfunction redactForDiskLog(value: string): string {\n try {\n return value\n .replace(/\\b(Bearer\\s+)[A-Za-z0-9._-]+\\b/gi, '$1[REDACTED]')\n .replace(/\\bxox[baprs]-[A-Za-z0-9-]+\\b/g, '[REDACTED-SLACK]')\n .replace(/\\btlk_[A-Za-z0-9._-]+\\b/g, '[REDACTED-HOST]')\n .replace(/\\bsk-ant-[A-Za-z0-9_-]+\\b/g, '[REDACTED-ANTHROPIC]')\n .replace(/\\b\\d{8,12}:[A-Za-z0-9_-]{30,}\\b/g, '[REDACTED-TELEGRAM]')\n .replace(\n /\\b([A-Z0-9_]*(?:TOKEN|SECRET|API[_-]?KEY|PASSWORD)[A-Z0-9_]*)=(?:\"[^\"\\r\\n]*\"|'[^'\\r\\n]*'|[^\\s\\r\\n]+)/gi,\n '$1=[REDACTED]',\n );\n } catch {\n return '[REDACTED]';\n }\n}\n\nfunction log(msg: string): void {\n const ts = new Date().toISOString();\n const safeMsg = redactForDiskLog(msg);\n const line = `[manager-worker ${ts}] ${safeMsg}\\n`;\n\n // ENG-4658: Write directly to ~/.augmented/manager.log via O_APPEND\n // rather than relying on the supervisor having inherited a redirected\n // stderr fd. The previous behaviour assumed `agt manager start\n // --supervise` was launched with `nohup ... >> manager.log 2>&1` (or\n // a launchd/systemd unit doing the equivalent), and the spawned\n // worker would inherit those file descriptors. When systemd respawns\n // the supervisor without that shell redirection — or any time\n // logrotate moves the file out from under an inherited fd — the\n // worker's stderr silently goes to the journal / /dev/null and\n // operators see manager.log freeze.\n //\n // Direct append makes the worker self-sufficient: it always writes to\n // the configured path with O_APPEND, regardless of how stdio was set\n // up by whichever process spawned us. We still echo to stderr so\n // foreground runs (interactive `agt manager start` without\n // `--supervise`) and any supervisor that DOES capture stdio get the\n // same lines for free.\n if (!managerLogPath) {\n try {\n managerLogPath = join(homedir(), '.augmented', 'manager.log');\n mkdirSync(dirname(managerLogPath), { recursive: true });\n if (existsSync(managerLogPath)) {\n chmodSync(managerLogPath, 0o600);\n }\n } catch { /* non-fatal — first-touch perm hardening is best-effort */ }\n }\n let appendedToFile = false;\n if (managerLogPath && managerLogWritable) {\n try {\n // O_APPEND on every call: cheap (no persistent fd to drift), and\n // each append is atomic for writes <= PIPE_BUF on Linux which\n // these single-line entries always are.\n appendFileSync(managerLogPath, line, { encoding: 'utf-8', mode: 0o600 });\n appendedToFile = true;\n } catch (err) {\n // First failure flips the gate so we don't spam appendFileSync\n // attempts every poll. Print the reason to stderr once so an\n // operator knows the on-disk log is gone.\n managerLogWritable = false;\n process.stderr.write(\n `[manager-worker ${ts}] [log] manager.log append failed; falling back to stderr-only: ${(err as Error).message}\\n`,\n );\n }\n }\n // Echo to stderr only when:\n // - we couldn't write to the file (fallback diagnostic), OR\n // - stderr is a real TTY (foreground operator running interactively)\n // Under supervision, launchd / systemd / nohup all redirect stderr at\n // manager.log already (apps/cli/src/__tests__/manager-supervisor.test.ts\n // confirms launchd routes both StandardOutPath and StandardErrorPath\n // there). With the direct append above, an unconditional stderr write\n // would land each line in the file twice — once via our append, once\n // via the supervisor's redirect. CodeRabbit catch on PR #619.\n if (!appendedToFile || process.stderr.isTTY === true) {\n process.stderr.write(line);\n }\n}\n\nfunction sha256(content: string): string {\n return createHash('sha256').update(content, 'utf8').digest('hex');\n}\n\nfunction hashFile(filePath: string): string | null {\n try {\n const content = readFileSync(filePath, 'utf-8');\n return sha256(content);\n } catch {\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Skills index — refresh the \"## Available Skills\" section in CLAUDE.md by\n// scanning the agent's .claude/skills/ directory and parsing frontmatter.\n// ---------------------------------------------------------------------------\n\nconst SKILLS_INDEX_START = '<!-- AGT:SKILLS_INDEX_START -->';\nconst SKILLS_INDEX_END = '<!-- AGT:SKILLS_INDEX_END -->';\n\nfunction sanitizeSkillsIndexText(value: string): string {\n return value\n .replaceAll(SKILLS_INDEX_START, '')\n .replaceAll(SKILLS_INDEX_END, '')\n .replace(/<!--[\\s\\S]*?-->/g, '')\n .replace(/\\r?\\n+/g, ' ')\n .trim();\n}\n\nfunction parseSkillFrontmatter(content: string): { name?: string; description?: string } {\n if (!content.startsWith('---')) return {};\n const end = content.indexOf('\\n---', 3);\n if (end === -1) return {};\n const block = content.slice(3, end);\n const out: { name?: string; description?: string } = {};\n for (const line of block.split('\\n')) {\n const m = line.match(/^(name|description)\\s*:\\s*(.*)$/);\n if (m && m[1] && m[2] !== undefined) out[m[1] as 'name' | 'description'] = m[2].trim().replace(/^[\"']|[\"']$/g, '');\n }\n return out;\n}\n\nasync function refreshSkillsIndexInClaudeMd(configDir: string, codeName: string, log: (msg: string) => void): Promise<void> {\n const { readdirSync, readFileSync: rfs, existsSync: ex, writeFileSync } = await import('node:fs');\n const skillsDir = join(configDir, codeName, 'project', '.claude', 'skills');\n const claudeMdPath = join(configDir, codeName, 'project', 'CLAUDE.md');\n if (!ex(skillsDir) || !ex(claudeMdPath)) return;\n\n const entries: { id: string; name: string; description: string }[] = [];\n for (const dir of readdirSync(skillsDir).sort()) {\n const skillFile = join(skillsDir, dir, 'SKILL.md');\n if (!ex(skillFile)) continue;\n try {\n const { name, description } = parseSkillFrontmatter(rfs(skillFile, 'utf-8'));\n entries.push({\n id: dir,\n name: sanitizeSkillsIndexText(name ?? dir),\n description: sanitizeSkillsIndexText(description ?? '(no description)'),\n });\n } catch { /* skip unreadable */ }\n }\n\n const body = entries.length\n ? entries.map((e) => `- **${e.name}** — ${e.description}`).join('\\n')\n : '_(no skills installed)_';\n const section = `${SKILLS_INDEX_START}\n## Available Skills\n\nThe following skills are installed in \\`.claude/skills/\\`. Claude Code auto-activates them when relevant — you don't need to read them manually, but you should know they exist.\n\n${body}\n\n## Updating Integrations (ENG-4341)\n\nIntegration skills under \\`.claude/skills/integration-*/SKILL.md\\` are **read-only** and managed by the platform. They are derived from the integration's database row plus any per-agent context overrides, and are re-rendered every time the manager polls or a context change is broadcast over Supabase Realtime.\n\n**Never edit \\`.claude/skills/integration-*/SKILL.md\\` files directly.** If you do, your edit will be silently overwritten on the next manager refresh, AND it won't propagate to other agents using the same integration.\n\nTo change an integration's behavior (add a rule, update a default, tell the integration not to do something), call the **\\`plugin.improve\\`** MCP tool with the user's request. The tool calls the platform API, which uses an LLM to translate the request into a structured update of the integration's typed context fields and/or freeform overrides text. You'll get a diff back to show the user; on confirmation, call the tool again with \\`auto_apply: true\\` to apply.\n\nExamples of when to use \\`plugin.improve\\`:\n- *\"Update the Coding integration so we always use trunk instead of main\"*\n- *\"Add a rule to the Coding integration: never merge directly to main, always open a PR\"*\n- *\"Tell the Knowledge Base integration not to return results below 70% relevance\"*\n- *\"The Slack integration should post incidents to #oncall, not #general\"*\n${SKILLS_INDEX_END}`;\n\n const current = rfs(claudeMdPath, 'utf-8');\n let next: string;\n if (current.includes(SKILLS_INDEX_START) && current.includes(SKILLS_INDEX_END)) {\n next = current.replace(new RegExp(`${SKILLS_INDEX_START}[\\\\s\\\\S]*?${SKILLS_INDEX_END}`), section);\n } else {\n next = current.trimEnd() + '\\n\\n' + section + '\\n';\n }\n\n if (next !== current) {\n writeFileSync(claudeMdPath, next, 'utf-8');\n log(`Refreshed skills index in CLAUDE.md for '${codeName}' (${entries.length} skills)`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Migration: shared config → per-agent profiles\n// ---------------------------------------------------------------------------\n\nasync function migrateToProfiles(): Promise<void> {\n const homeDir = process.env['HOME'] ?? '/tmp';\n const sharedConfigPath = join(homeDir, '.openclaw', 'openclaw.json');\n\n // Check if shared config exists\n let sharedConfig: Record<string, unknown>;\n try {\n sharedConfig = JSON.parse(readFileSync(sharedConfigPath, 'utf-8'));\n } catch {\n return; // No shared config — nothing to migrate\n }\n\n // Check if there are multiple agents registered in shared config\n const agents = sharedConfig['agents'] as Record<string, unknown> | undefined;\n const agentList = (agents?.['list'] as Array<Record<string, unknown>>) ?? [];\n if (agentList.length === 0) return;\n\n const adapter = getFramework('openclaw');\n let migrated = 0;\n\n for (const agentEntry of agentList) {\n const codeName = agentEntry['id'] as string;\n if (!codeName) continue;\n\n // Skip 'main' — that's the interactive CLI agent, not managed by Augmented\n if (codeName === 'main') continue;\n\n const profileDir = join(homeDir, `.openclaw-${codeName}`);\n\n // Skip agents that already have profile dirs\n if (existsSync(join(profileDir, 'openclaw.json'))) continue;\n\n log(`Migrating agent '${codeName}' to per-agent profile`);\n\n // Seed profile config from shared config\n if (adapter.seedProfileConfig) {\n adapter.seedProfileConfig(codeName);\n }\n\n // Copy auth profiles from shared to profile dir\n const sharedAuthDir = join(homeDir, '.openclaw', 'agents', codeName, 'agent');\n const profileAuthDir = join(profileDir, 'agents', codeName, 'agent');\n const authFile = join(sharedAuthDir, 'auth-profiles.json');\n if (existsSync(authFile)) {\n mkdirSync(profileAuthDir, { recursive: true });\n const authContent = readFileSync(authFile, 'utf-8');\n writeFileSync(join(profileAuthDir, 'auth-profiles.json'), authContent);\n }\n\n // Allocate a gateway port\n allocatePort(codeName);\n\n migrated++;\n }\n\n if (migrated > 0) {\n log(`Migration complete: ${migrated} agent(s) migrated to per-agent profiles`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Gateway lifecycle\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve model tiers through the inheritance chain:\n * Agent override → Org default → Platform default\n */\nfunction resolveModelChain(refreshData: Record<string, unknown>): { primary?: string; secondary?: string; tertiary?: string } {\n const agent = refreshData.agent as Record<string, unknown> | undefined;\n const modelDefaults = refreshData.model_defaults as { platform?: Record<string, unknown> | null; org?: Record<string, unknown> | null } | undefined;\n const platform = modelDefaults?.platform ?? {};\n const org = modelDefaults?.org ?? {};\n\n function resolve(tier: 'primary' | 'secondary' | 'tertiary'): string | undefined {\n const agentField = `${tier}_model`;\n const platformField = `default_${tier}_model`;\n\n // 1. Agent override\n const agentVal = agent?.[agentField] as string | undefined;\n if (agentVal) return agentVal;\n\n // 2. Org default\n const orgVal = org?.[platformField] as string | undefined;\n if (orgVal) return orgVal;\n\n // 3. Platform default\n const platformVal = platform?.[platformField] as string | undefined;\n if (platformVal) return platformVal;\n\n return undefined;\n }\n\n return {\n primary: resolve('primary'),\n secondary: resolve('secondary'),\n tertiary: resolve('tertiary'),\n };\n}\n\nfunction readGatewayToken(codeName: string): string | undefined {\n const adapter = resolveAgentFramework(codeName);\n if (adapter.readGatewayToken) {\n return adapter.readGatewayToken(codeName);\n }\n // Fallback for adapters without readGatewayToken\n const homeDir = process.env['HOME'] ?? '/tmp';\n try {\n const cfg = JSON.parse(readFileSync(join(homeDir, `.openclaw-${codeName}`, 'openclaw.json'), 'utf-8'));\n return cfg?.gateway?.auth?.token as string | undefined;\n } catch {\n return undefined;\n }\n}\n\nconst GATEWAY_HUNG_TIMEOUT_MS = 5 * 60_000; // 5 minutes\n\n/**\n * Detect if a gateway is hung by checking for cron jobs stuck in \"running\" state.\n * Reads the cron jobs.json directly (no gateway communication needed since the\n * gateway itself may be unresponsive).\n */\nfunction isGatewayHung(codeName: string): boolean {\n const homeDir = process.env['HOME'] ?? '/tmp';\n const jobsPath = join(homeDir, `.openclaw-${codeName}`, 'cron', 'jobs.json');\n if (!existsSync(jobsPath)) return false;\n\n try {\n const data = JSON.parse(readFileSync(jobsPath, 'utf-8')) as Record<string, unknown>;\n const jobs = (data.jobs ?? data) as Array<Record<string, unknown>>;\n if (!Array.isArray(jobs)) return false;\n\n const now = Date.now();\n for (const job of jobs) {\n const state = job.state as Record<string, unknown> | undefined;\n if (!state) continue;\n const runStartedAt = state.runStartedAtMs as number | undefined;\n const isRunning = state.status === 'running' || state.running === true;\n if (isRunning && runStartedAt && (now - runStartedAt) > GATEWAY_HUNG_TIMEOUT_MS) {\n return true;\n }\n }\n } catch { /* non-fatal */ }\n\n return false;\n}\n\nasync function ensureGatewayRunning(codeName: string, adapter: FrameworkAdapter): Promise<{ pid: number | null; port: number | null; running: boolean }> {\n if (!adapter.isGatewayRunning || !adapter.startGateway) {\n return { pid: null, port: null, running: false };\n }\n\n const status = await adapter.isGatewayRunning(codeName);\n if (status.running) {\n // Check if gateway is hung — if a cron has been \"running\" for too long,\n // the gateway is likely stuck and needs a restart\n if (await isGatewayHung(codeName)) {\n log(`Gateway for '${codeName}' appears hung (cron stuck >5min) — restarting`);\n if (adapter.stopGateway) {\n try { await adapter.stopGateway(codeName); } catch { /* force kill below */ }\n }\n // Give it a moment to die\n await new Promise((r) => setTimeout(r, 2000));\n // Clear stale cron state so it doesn't immediately re-hang\n const homeDir = process.env['HOME'] ?? '/tmp';\n const cronJobsPath = join(homeDir, `.openclaw-${codeName}`, 'cron', 'jobs.json');\n clearStaleCronRunState(cronJobsPath);\n // Fall through to start a new gateway below\n } else {\n // Update config port in case it changed (e.g. after manual restart)\n if (status.port) {\n try {\n const homeDir = process.env['HOME'] ?? '/tmp';\n const configPath = join(homeDir, `.openclaw-${codeName}`, 'openclaw.json');\n if (existsSync(configPath)) {\n const cfg = JSON.parse(readFileSync(configPath, 'utf-8'));\n if (cfg.gateway?.port !== status.port) {\n if (!cfg.gateway) cfg.gateway = {};\n cfg.gateway.port = status.port;\n writeFileSync(configPath, JSON.stringify(cfg, null, 2));\n }\n }\n } catch { /* Non-fatal */ }\n }\n // Ensure pool has a connection to this gateway\n if (gatewayPool && status.port && !gatewayPool.hasAgent(codeName)) {\n const token = readGatewayToken(codeName);\n gatewayPool.addAgent(codeName, status.port, token);\n }\n return { pid: status.pid ?? null, port: status.port ?? null, running: true };\n }\n }\n\n // Allocate port and start\n const port = allocatePort(codeName);\n try {\n const result = await adapter.startGateway(codeName, port);\n log(`Gateway started for '${codeName}' on port ${port} (PID ${result.pid})`);\n gatewaysStartedThisCycle.add(codeName);\n\n // Write port to profile config so `openclaw cron run` can find the gateway\n try {\n const homeDir = process.env['HOME'] ?? '/tmp';\n const configPath = join(homeDir, `.openclaw-${codeName}`, 'openclaw.json');\n if (existsSync(configPath)) {\n const cfg = JSON.parse(readFileSync(configPath, 'utf-8'));\n if (!cfg.gateway) cfg.gateway = {};\n cfg.gateway.port = port;\n writeFileSync(configPath, JSON.stringify(cfg, null, 2));\n }\n } catch { /* Non-fatal */ }\n\n // Connect gateway client pool — the client silently handles ECONNREFUSED\n // on first attempt and retries with backoff while the gateway boots.\n if (gatewayPool) {\n const token = readGatewayToken(codeName);\n gatewayPool.addAgent(codeName, port, token);\n }\n\n return { pid: result.pid, port, running: true };\n } catch (err) {\n log(`Failed to start gateway for '${codeName}': ${(err as Error).message}`);\n return { pid: null, port, running: false };\n }\n}\n\nasync function stopGatewayIfRunning(codeName: string, adapter: FrameworkAdapter): Promise<void> {\n if (!adapter.stopGateway) return;\n\n try {\n const stopped = await adapter.stopGateway(codeName);\n if (stopped) {\n log(`Gateway stopped for '${codeName}'`);\n }\n } catch (err) {\n log(`Failed to stop gateway for '${codeName}': ${(err as Error).message}`);\n }\n\n // Disconnect from pool\n if (gatewayPool) {\n gatewayPool.removeAgent(codeName);\n }\n}\n\nasync function stopAllGateways(): Promise<void> {\n const ports = loadGatewayPorts();\n\n for (const codeName of Object.keys(ports)) {\n const adapter = resolveAgentFramework(codeName);\n await stopGatewayIfRunning(codeName, adapter);\n }\n\n if (gatewayPool) {\n gatewayPool.disconnectAll();\n }\n}\n\nasync function healthCheckGateways(agentStates: AgentState[]): Promise<void> {\n for (const agentState of agentStates) {\n if (agentState.status !== 'active' || !agentState.gatewayPort) continue;\n\n const adapter = resolveAgentFramework(agentState.codeName);\n if (!adapter.isGatewayRunning || !adapter.startGateway) continue;\n\n // Skip gateways that were just started this cycle — they may still be booting\n if (gatewaysStartedThisCycle.has(agentState.codeName)) continue;\n\n const status = await adapter.isGatewayRunning(agentState.codeName);\n if (!status.running && agentState.gatewayRunning) {\n // Gateway crashed — alert and restart\n const displayName = agentDisplayNames.get(agentState.codeName) ?? agentState.codeName;\n log(`Gateway for '${agentState.codeName}' crashed, restarting...`);\n sendSlackWebhookMessage(\n `:red_circle: *Host Down* — *${displayName}* (\\`${agentState.codeName}\\`)\\nOpenClaw gateway crashed. Attempting automatic restart...`,\n ).catch(() => {});\n\n try {\n const result = await adapter.startGateway(agentState.codeName, agentState.gatewayPort);\n agentState.gatewayPid = result.pid;\n agentState.gatewayRunning = true;\n log(`Gateway restarted for '${agentState.codeName}' (PID ${result.pid})`);\n\n // Give the gateway process time to bind the port before connecting WebSocket\n await new Promise((resolve) => setTimeout(resolve, 2000));\n\n // Reconnect pool client\n if (gatewayPool) {\n const token = readGatewayToken(agentState.codeName);\n gatewayPool.addAgent(agentState.codeName, agentState.gatewayPort, token);\n }\n\n sendSlackWebhookMessage(\n `:large_green_circle: *Host Recovered* — *${displayName}* (\\`${agentState.codeName}\\`)\\nGateway restarted successfully (PID ${result.pid}).`,\n ).catch(() => {});\n } catch (err) {\n agentState.gatewayRunning = false;\n log(`Failed to restart gateway for '${agentState.codeName}': ${(err as Error).message}`);\n sendSlackWebhookMessage(\n `:x: *Host Restart Failed* — *${displayName}* (\\`${agentState.codeName}\\`)\\nAutomatic restart failed: ${(err as Error).message}`,\n ).catch(() => {});\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Poll cycle\n// ---------------------------------------------------------------------------\n\nasync function pollCycle(): Promise<void> {\n if (!config) return;\n\n // ENG-4488: short-circuit every entrypoint into pollCycle — not just the\n // normal scheduleNext timer. triggerEarlyPoll() (fired by Realtime\n // events, webapp-direct-chat, gateway pool signals, etc.) queues\n // pollCycle() with zero delay, which could otherwise sneak one more\n // cycle in on the old Cellar path after restartAfterUpgrade flipped.\n if (restartAfterUpgrade) return;\n\n // ENG-4568: process /restart flag files written by channel MCPs. Run\n // before the rest of the cycle so a kill takes effect immediately and\n // the ensure-session pass below respawns the killed agent in the same\n // poll. Errors are logged inside the handler — never let a flag take\n // down the whole cycle.\n try {\n await processRestartFlags({\n log,\n resolveFramework: (codeName) => agentFrameworkCache.get(codeName) ?? null,\n stopSession: (codeName) => {\n // CodeRabbit on PR #773 (round 2): cancel any pending hot-reload\n // restart timer so it can't fire after this teardown and kill\n // the replacement session.\n stopPersistentSessionAndForgetMcpBaseline(codeName);\n persistentSessionAgents.delete(codeName);\n claudeAuthTupleBySession.delete(codeName);\n },\n getTelegramTokens: (codeName) => {\n const t = agentChannelTokens.get(codeName);\n if (!t?.telegram) return null;\n return { token: t.telegram, allowedChats: t.telegramAllowedChats };\n },\n sendTelegram: (botToken, method, body) => telegramApiCall(botToken, method, body),\n getSlackToken: (codeName) => agentChannelTokens.get(codeName)?.slack ?? null,\n sendSlack: async (botToken, body) => {\n // Match the timeout pattern other chat.postMessage call sites in\n // this file already use (lines ~5543, ~5762) — without this, a\n // hung Slack API would block pollCycle and stall every agent's\n // ensure-session pass behind the restart ack (CodeRabbit feedback).\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 5_000);\n try {\n const res = await fetch('https://slack.com/api/chat.postMessage', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json; charset=utf-8',\n Authorization: `Bearer ${botToken}`,\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n const data = (await res.json()) as { ok: boolean; error?: string };\n return data;\n } catch (err) {\n const isAbort = (err as Error).name === 'AbortError';\n return { ok: false, error: isAbort ? 'timeout' : (err as Error).message };\n } finally {\n clearTimeout(timer);\n }\n },\n });\n } catch (err) {\n log(`[restart-handler] processRestartFlags threw: ${(err as Error).message}`);\n }\n\n // ENG-4485: Kick off the CLI self-update check on every poll. The function\n // throttles the actual `brew outdated` call via an on-disk marker\n // (`.last-update-check`) + `UPDATE_CHECK_INTERVAL_MS`, so calling it each\n // poll is cheap — most invocations return immediately without shelling out.\n // Fire-and-forget: we don't want poll latency coupled to brew.\n checkAndUpdateCli().catch((err) => log(`[self-update] Check failed: ${(err as Error).message}`));\n\n try {\n // Clear per-cycle caches\n registeredAgentsCache.clear();\n gatewaysStartedThisCycle.clear();\n\n // 1. Discover assigned agents\n const hostId = await getHostId();\n if (!hostId) {\n send({ type: 'error', message: 'Could not resolve host ID from API key' });\n return;\n }\n\n // 1b. Detect framework version (cached, refreshed every 5 min)\n const now = Date.now();\n if (now - lastVersionCheckAt > VERSION_CHECK_INTERVAL_MS) {\n try {\n // Use the first known agent's framework, or default to openclaw\n const firstAgent = state.agents[0];\n const versionAdapter = firstAgent ? resolveAgentFramework(firstAgent.codeName) : getFramework('openclaw');\n if (versionAdapter.getVersion) {\n cachedFrameworkVersion = await versionAdapter.getVersion();\n }\n } catch {\n // Non-fatal — version detection is supplementary\n }\n lastVersionCheckAt = now;\n }\n\n // 1c. Heartbeat — update last_seen_at, framework_version, host security, diagnostics, and hostname\n try {\n const { detectHostSecurity } = await import('./host-security.js');\n const { collectDiagnostics } = await import('./persistent-session.js');\n\n // Collect diagnostics for all known persistent session agents\n const diagCodeNames = [...persistentSessionAgents];\n const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : undefined;\n\n // Detect Tailscale hostname for remote SSH attach\n let tailscaleHostname: string | undefined;\n try {\n const { execSync: es } = await import('node:child_process');\n const tsJson = es('tailscale status --self --json 2>/dev/null', {\n encoding: 'utf-8', timeout: 3000,\n }).trim();\n const ts = JSON.parse(tsJson);\n tailscaleHostname = ts.Self?.DNSName?.replace(/\\.$/, '') || undefined;\n } catch {\n // Tailscale not installed or not connected — fall back to hostname\n try {\n const { execSync: es } = await import('node:child_process');\n tailscaleHostname = es('hostname', { encoding: 'utf-8', timeout: 1000 }).trim();\n } catch { /* non-fatal */ }\n }\n\n // Get OS username for SSH command\n let osUsername: string | undefined;\n try {\n const { userInfo } = await import('node:os');\n osUsername = userInfo().username;\n } catch { /* non-fatal */ }\n\n // Claude Code auth freshness — console uses this to show a re-pair prompt\n // before the OAuth token expires silently. Returns null if no auth is\n // detectable (e.g. a fresh host with no Claude Code login yet).\n let claudeAuth: Awaited<ReturnType<typeof detectClaudeAuth>> = null;\n try {\n claudeAuth = await detectClaudeAuth();\n } catch (err) {\n // Per coding guidelines: default to hash-only/redacted logging in prod.\n // Auth detection errors can surface credential file paths — hash\n // instead of logging raw.\n const errText = err instanceof Error ? err.message : String(err);\n const errId = createHash('sha256').update(errText).digest('hex').slice(0, 12);\n log(`Claude auth detection failed (error_id=${errId})`);\n }\n\n await api.post('/host/heartbeat', {\n host_id: hostId,\n framework_version: cachedFrameworkVersion ?? undefined,\n agt_version: agtCliVersion,\n host_security: detectHostSecurity() ?? undefined,\n agent_runtime_authenticated: agentRuntimeAuthenticated,\n agent_diagnostics: agentDiagnostics,\n hostname: tailscaleHostname,\n os_username: osUsername,\n claude_auth: claudeAuth ?? undefined,\n });\n } catch (err) {\n log(`Heartbeat failed: ${(err as Error).message}`);\n }\n\n const data = await api.post<{\n agents: Array<{\n agent_id: string;\n code_name: string;\n display_name: string;\n status: string;\n environment: string;\n framework?: string;\n // ENG-4947: dashboard-issued restart signal. The manager kills+\n // respawns the agent's tmux session when this advances past\n // AgentState.lastRestartProcessedAt.\n restart_requested_at?: string | null;\n }>;\n }>('/host/agents', { host_id: hostId });\n\n const agents = data.agents ?? [];\n\n // ENG-4947: act on dashboard-issued restart signals before the normal\n // refresh pass. Killing the tmux session here means processAgent below\n // sees the agent as needing a fresh respawn rather than a no-op refresh.\n // We track the acks in a per-cycle Map and apply them to the\n // AgentState rows produced by processAgent below, so the timestamp\n // survives even when the agent's first appearance on this host\n // already carries a pending signal (no `prev` state to mutate).\n const restartAcks = new Map<string, string>();\n for (const agent of agents) {\n const requested = agent.restart_requested_at ?? null;\n if (!requested) continue;\n const prev = state.agents.find((a) => a.agentId === agent.agent_id);\n const lastProcessed = prev?.lastRestartProcessedAt ?? null;\n // Same timestamp (or older) → already handled, skip.\n if (lastProcessed && Date.parse(lastProcessed) >= Date.parse(requested)) continue;\n\n log(`[restart] Dashboard requested restart for '${agent.code_name}' at ${requested}`);\n try {\n const { execSync: es } = await import('node:child_process');\n es(`tmux kill-session -t agt-${agent.code_name} 2>/dev/null`, { stdio: 'ignore' });\n } catch {\n /* no session to kill — the respawn below will start a fresh one */\n }\n stopPersistentSessionAndForgetMcpBaseline(agent.code_name);\n restartAcks.set(agent.agent_id, requested);\n }\n\n // Ensure framework binaries are installed/upgraded for any new frameworks\n const frameworksThisCycle = new Set(agents.map((a) => a.framework).filter(Boolean));\n for (const fw of frameworksThisCycle) {\n await ensureFrameworkBinary(fw!);\n }\n\n // Track which channels have at least one active agent per profile\n activeChannels.clear();\n\n // Update state agents list\n const agentStates: AgentState[] = [];\n\n for (const agent of agents) {\n try {\n await processAgent(agent, agentStates);\n } catch (err) {\n log(`Error processing agent '${agent.code_name}': ${(err as Error).message}`);\n // Preserve existing state for this agent on error\n const existing = state.agents.find((a) => a.agentId === agent.agent_id);\n if (existing) {\n agentStates.push(existing);\n } else {\n agentStates.push({\n agentId: agent.agent_id,\n codeName: agent.code_name,\n status: agent.status,\n charterVersion: '',\n toolsVersion: '',\n secretsHash: null,\n lastRefreshAt: null,\n lastProvisionAt: null,\n lastDriftCheckAt: null,\n lastSecretsProvisionAt: null,\n gatewayPort: null,\n gatewayPid: null,\n gatewayRunning: false,\n acpSessions: [],\n });\n }\n }\n }\n\n // ENG-4947: stamp lastRestartProcessedAt on the AgentState rows produced\n // above so the next poll doesn't replay the same restart. Done after\n // processAgent so the timestamp applies even when this was the agent's\n // first appearance on the host (no prior state to mutate up-front).\n // Persist to disk IMMEDIATELY (don't wait for end of cycle) so a crash\n // between here and the cycle's end-of-cycle state-update can't replay\n // the restart on the next manager boot.\n if (restartAcks.size > 0) {\n for (const ack of agentStates) {\n const requested = restartAcks.get(ack.agentId);\n if (requested) ack.lastRestartProcessedAt = requested;\n }\n try {\n const ackedState: ManagerState = { ...state, agents: agentStates };\n writeFileSync(getStateFile(), JSON.stringify(ackedState, null, 2));\n } catch (err) {\n // Non-fatal — the end-of-cycle state-update will retry the write.\n // Worst case: a crash between here and end-of-cycle replays the\n // restart on next boot, which is the pre-fix behaviour anyway.\n log(`[restart] failed to persist ack immediately: ${(err as Error).message}`);\n }\n }\n\n // Reconcile channel enabled state per profile.\n // Each agent has its own profile config, so enable/disable within that profile.\n try {\n for (const [channelId, codeNames] of activeChannels) {\n for (const codeName of codeNames) {\n const adapter = resolveAgentFramework(codeName);\n if (adapter.setChannelEnabled) {\n adapter.setChannelEnabled(channelId, true, codeName);\n }\n }\n }\n } catch { /* non-fatal */ }\n\n // Detect unassigned/deleted agents (were in state but not in current list)\n const currentIds = new Set(agents.map((a) => a.agent_id));\n for (const prev of state.agents) {\n if (!currentIds.has(prev.agentId)) {\n log(`Agent '${prev.codeName}' removed from host (deleted or unassigned)`);\n const adapter = resolveAgentFramework(prev.codeName);\n await stopGatewayIfRunning(prev.codeName, adapter);\n // Stop the persistent tmux session first — that takes the Claude Code\n // process and most of its MCP children with it. The explicit kill-session\n // mirrors the revoked branch and covers sessions started by a previous\n // manager run that this manager's `sessions` Map doesn't track.\n stopPersistentSessionAndForgetMcpBaseline(prev.codeName);\n try {\n const { execSync: es } = await import('node:child_process');\n es(`tmux kill-session -t agt-${prev.codeName} 2>/dev/null`, { stdio: 'ignore' });\n } catch { /* no session to kill */ }\n // ENG: tear down slack/telegram/direct-chat MCPs explicitly. Channel\n // children sometimes reparent to init when their Claude Code parent\n // exits, and the periodic channel-sweep won't touch them after this\n // point because their codeName is no longer in the manager's active\n // set. Without this, a Telegram poller for the de-provisioned agent\n // can keep running and conflict with whichever new host now owns it.\n killAgentChannelProcesses(prev.codeName, { log });\n freePort(prev.codeName);\n const agentDir = join(adapter.getAgentDir(prev.codeName), 'provision');\n await cleanupAgentFiles(prev.codeName, agentDir);\n clearAgentCaches(prev.agentId, prev.codeName);\n }\n }\n\n // ENG-4581: poll for pending Claude Code OAuth pairing sessions across\n // all agents on this host and dispatch each one. Best-effort —\n // failures are logged and the next refresh will retry.\n try {\n await processClaudePairSessions(agents.map((a) => ({\n agentId: a.agent_id,\n codeName: a.code_name,\n })));\n } catch (err) {\n log(`[claude-pair] poll failed: ${(err as Error).message}`);\n }\n\n // Health check: restart crashed gateways\n await healthCheckGateways(agentStates);\n\n // Channel MCP zombie sweep (ENG-4453) — every CHANNEL_SWEEP_INTERVAL_MS,\n // kill surplus per-agent channel MCP processes (orphans reparented to\n // init after a parent Claude Code died). ENG-4434 added signal handlers\n // that *should* prevent this, but on hosts we've still seen channel\n // zombies accumulate; this is the belt-and-braces net.\n if (Date.now() - lastChannelSweepAt >= CHANNEL_SWEEP_INTERVAL_MS) {\n lastChannelSweepAt = Date.now();\n const agentCodeNames = new Set(agentStates.map((a) => a.codeName));\n sweepChannelProcesses({\n agentCodeNames,\n dryRun: CHANNEL_SWEEP_DRY_RUN,\n log,\n }).catch((err) => {\n log(`[channel-sweep] sweep error: ${(err as Error).message}`);\n });\n }\n\n // ENG-4705: channel input watchdog. Detect channel-server-injected\n // messages that landed in the Claude Code TUI input buffer but never\n // got their auto-submit Enter (the bug: text sits as `❯ <message>` and\n // never becomes a user turn). Fire Enter for any input that's been\n // unchanged for >5s on a session with no human attached. Cheap — one\n // tmux capture-pane per claude-code agent per poll.\n {\n const claudeCodeAgents = agentStates\n .filter((a) => a.status === 'active')\n .filter((a) => (agentFrameworkCache.get(a.codeName) ?? 'openclaw') === 'claude-code')\n .map((a) => a.codeName);\n // Always call checkChannelInputs — when the list is empty it still\n // performs the per-cycle state cleanup pass for agents that have\n // been paused/revoked since last check.\n checkChannelInputs(claudeCodeAgents, {\n capturePane: (codeName) => {\n try {\n return syncExecFile('tmux', ['capture-pane', '-t', `agt-${codeName}`, '-p'], {\n stdio: ['ignore', 'pipe', 'ignore'],\n timeout: 2_000,\n }).toString();\n } catch {\n return null;\n }\n },\n isClientAttached: (codeName) => {\n try {\n const out = syncExecFile('tmux', ['list-clients', '-t', `agt-${codeName}`], {\n stdio: ['ignore', 'pipe', 'ignore'],\n timeout: 2_000,\n }).toString();\n return out.trim().length > 0;\n } catch {\n // Fail closed: if we can't tell whether a human is attached,\n // assume they are and skip the watchdog action. Better to\n // miss a stuck buffer than to fire Enter while someone is\n // typing.\n return true;\n }\n },\n sendEnter: (codeName) => {\n try {\n syncExecFile('tmux', ['send-keys', '-t', `agt-${codeName}`, 'Enter'], {\n stdio: 'ignore',\n timeout: 2_000,\n });\n } catch {\n // Best-effort watchdog action — never fail the poll cycle on\n // a tmux hiccup. Next cycle will retry.\n }\n },\n log,\n now: () => Date.now(),\n });\n }\n\n // Monitor cron health — detect late/failed jobs and alert (throttled)\n const lastHealthCheck = lastHarvestAt.get('__cron_health__') ?? 0;\n if (Date.now() - lastHealthCheck >= HARVEST_INTERVAL_MS) {\n lastHarvestAt.set('__cron_health__', Date.now());\n monitorCronHealth(agentStates).catch((err) => {\n log(`Cron health monitor error: ${(err as Error).message}`);\n });\n }\n\n // Direct chat: use Realtime if connected, fall back to polling\n if (!isRealtimeConnected()) {\n pollDirectChatMessages(agentStates).catch((err) => {\n log(`Direct chat poll error: ${(err as Error).message}`);\n });\n }\n\n // Start Realtime subscription if not yet started and we have Supabase config\n ensureRealtimeStarted(agentStates);\n ensureRealtimeDriftStarted(agentStates);\n ensureRealtimeAssignStarted(agentStates);\n ensureRealtimeConfigStarted(agentStates);\n ensureRealtimeKanbanStarted(agentStates);\n ensureRealtimeIntegrationContextStarted(agentStates);\n\n // Spawn due recurring kanban templates\n try {\n const spawnData = await api.post<{ spawned: number }>('/host/kanban/recurring/spawn');\n if (spawnData.spawned > 0) {\n log(`Spawned ${spawnData.spawned} recurring kanban item(s)`);\n }\n } catch { /* non-fatal */ }\n\n state = {\n ...state,\n lastPollAt: new Date().toISOString(),\n pollCount: state.pollCount + 1,\n agents: agentStates,\n };\n\n send({ type: 'state-update', state });\n } catch (err) {\n state.errorCount++;\n const message = (err as Error).message;\n log(`Poll error: ${message}`);\n send({ type: 'error', message });\n\n // Fatal auth error — exit so watchdog can restart with backoff\n if (message.includes('exchange failed') || message.includes('401')) {\n log('Fatal auth error, exiting for watchdog restart');\n send({ type: 'shutdown' });\n process.exit(1);\n }\n }\n}\n\nasync function getOrCacheRegisteredAgents(adapter: FrameworkAdapter, profile?: string): Promise<Set<string>> {\n const cacheKey = profile ? `${adapter.id}:${profile}` : adapter.id;\n let cached = registeredAgentsCache.get(cacheKey);\n if (!cached) {\n cached = await adapter.getRegisteredAgents(profile);\n registeredAgentsCache.set(cacheKey, cached);\n }\n return cached;\n}\n\nasync function processAgent(\n agent: { agent_id: string; code_name: string; display_name: string; status: string; environment: string; framework?: string },\n agentStates: AgentState[],\n): Promise<void> {\n if (!config) return;\n\n log(`==================== ${agent.display_name} (${agent.code_name}) ====================`);\n agentDisplayNames.set(agent.code_name, agent.display_name);\n codeNameToAgentId.set(agent.code_name, agent.agent_id);\n\n // Cache the framework from the agent list response (refreshAgentConfig may override with more specific data)\n if (agent.framework) {\n agentFrameworkCache.set(agent.code_name, agent.framework);\n }\n\n const now = new Date().toISOString();\n const adapter = resolveAgentFramework(agent.code_name);\n // ENG-4418: agentDir resolves through the adapter so manager + framework\n // share a single provision tree. Initial value uses the cached framework\n // from /host/agents; it is RE-resolved once /host/refresh returns the\n // authoritative framework below (see `agentDir = join(frameworkAdapter...)`\n // after agentFrameworkCache.set). Without the re-resolution, a stale\n // cache would trigger claudecodeAdapter.getAgentDir's migration on the\n // wrong framework's tree.\n let agentDir = join(adapter.getAgentDir(agent.code_name), 'provision');\n\n // 1a. Status-aware branching\n if (agent.status === 'draft' || agent.status === 'paused') {\n log(`Agent '${agent.code_name}' is ${agent.status}, skipping provisioning`);\n await stopGatewayIfRunning(agent.code_name, adapter);\n // Kill persistent tmux session if it exists (prevents paused agents responding on channels)\n // Must use tmux kill-session directly — the sessions Map doesn't track sessions from previous manager runs\n stopPersistentSessionAndForgetMcpBaseline(agent.code_name);\n try {\n const { execSync: es } = await import('node:child_process');\n es(`tmux kill-session -t agt-${agent.code_name} 2>/dev/null`, { stdio: 'ignore' });\n log(`Killed tmux session for paused agent '${agent.code_name}'`);\n } catch { /* no session to kill */ }\n agentStates.push({\n agentId: agent.agent_id,\n codeName: agent.code_name,\n status: agent.status,\n charterVersion: '',\n toolsVersion: '',\n secretsHash: null,\n lastRefreshAt: now,\n lastProvisionAt: null,\n lastDriftCheckAt: null,\n lastSecretsProvisionAt: null,\n gatewayPort: null,\n gatewayPid: null,\n gatewayRunning: false,\n acpSessions: [],\n });\n return;\n }\n\n if (agent.status === 'revoked') {\n log(`Agent '${agent.code_name}' is revoked, cleaning up`);\n await stopGatewayIfRunning(agent.code_name, adapter);\n stopPersistentSessionAndForgetMcpBaseline(agent.code_name);\n try { const { execSync: es } = await import('node:child_process'); es(`tmux kill-session -t agt-${agent.code_name} 2>/dev/null`, { stdio: 'ignore' }); } catch { /* no session */ }\n killAgentChannelProcesses(agent.code_name, { log });\n freePort(agent.code_name);\n await cleanupAgentFiles(agent.code_name, agentDir);\n clearAgentCaches(agent.agent_id, agent.code_name);\n knownStatuses.set(agent.agent_id, agent.status);\n agentStates.push({\n agentId: agent.agent_id,\n codeName: agent.code_name,\n status: agent.status,\n charterVersion: '',\n toolsVersion: '',\n secretsHash: null,\n lastRefreshAt: now,\n lastProvisionAt: null,\n lastDriftCheckAt: null,\n lastSecretsProvisionAt: null,\n gatewayPort: null,\n gatewayPid: null,\n gatewayRunning: false,\n acpSessions: [],\n });\n return;\n }\n\n // 1b. Detect status change → force reprovision and channel re-evaluation\n const previousStatus = knownStatuses.get(agent.agent_id);\n if (previousStatus && previousStatus !== agent.status) {\n log(`Agent '${agent.code_name}' status changed: ${previousStatus} → ${agent.status}`);\n knownVersions.delete(agent.agent_id); // Force reprovision\n // Invalidate channel config hashes so credentials/bindings/enabled state get rewritten\n let channelCacheMutated = false;\n for (const key of knownChannelConfigHashes.keys()) {\n if (key.startsWith(`${agent.agent_id}:`)) {\n knownChannelConfigHashes.delete(key);\n channelCacheMutated = true;\n }\n }\n if (channelCacheMutated) saveChannelHashCache();\n }\n knownStatuses.set(agent.agent_id, agent.status);\n\n // 2. Refresh — get latest charter/tools from API\n let refreshData: {\n agent: Record<string, unknown>;\n charter: { raw_content: string; version: string } | null;\n tools: { raw_content: string; version: string } | null;\n channel_configs: Record<string, { config: unknown; status: string }> | null;\n team_channel_policy: {\n team_id: string;\n allowed_channels: string[];\n denied_channels: string[];\n require_elevated_for_pii: boolean;\n } | null;\n team: { name: string; description: string | null; settings?: Record<string, unknown> } | null;\n scheduled_tasks: Array<{\n id: string;\n template_id: string;\n name: string;\n schedule_kind: string;\n schedule_expr: string | null;\n schedule_every: string | null;\n schedule_at: string | null;\n timezone: string;\n prompt: string;\n session_target: string;\n delivery_mode: string;\n delivery_channel: string;\n delivery_to: string | null;\n enabled: boolean;\n }> | null;\n plugin_skills?: Array<{\n plugin_slug: string;\n skill_id: string;\n skill_name: string;\n content: string;\n }>;\n plugin_install_hooks?: Array<{\n plugin_slug: string;\n script: string;\n }>;\n /** ENG-4421: toolkits required by installed plugins — manager resolves CLI installs from these. */\n plugin_toolkits?: Array<{\n plugin_slug: string;\n toolkits: string[];\n }>;\n /** ENG-4341: pre-resolved plugin contexts (defaults applied, agent-scope row merged in). */\n plugin_contexts?: Array<{\n plugin_id: string;\n plugin_slug: string;\n values: Record<string, unknown>;\n overrides: string;\n }>;\n /** Org + team knowledge items for skill generation. */\n knowledge?: Array<{ title: string; slug: string; content: string | null; scope: 'org' | 'team' }>;\n /** Team members for CLAUDE.md directory. */\n team_members?: Array<{ display_name: string; email?: string; role: string; title?: string; contact_channel?: string }>;\n /** People/contacts for CLAUDE.md directory. */\n people?: Array<{ display_name: string; email?: string; title?: string; department?: string; relationship?: string; contact_channel?: string }>;\n };\n\n try {\n refreshData = await api.post<typeof refreshData>('/host/refresh', {\n agent_id: agent.agent_id,\n });\n } catch (err) {\n log(`Refresh failed for '${agent.code_name}': ${(err as Error).message}`);\n const existing = state.agents.find((a) => a.agentId === agent.agent_id);\n agentStates.push(existing ?? {\n agentId: agent.agent_id,\n codeName: agent.code_name,\n status: agent.status,\n charterVersion: '',\n toolsVersion: '',\n secretsHash: null,\n lastRefreshAt: null,\n lastProvisionAt: null,\n lastDriftCheckAt: null,\n lastSecretsProvisionAt: null,\n gatewayPort: null,\n gatewayPid: null,\n gatewayRunning: false,\n acpSessions: [],\n });\n return;\n }\n\n // Cache alert webhook from team settings (first agent wins)\n if (!alertSlackWebhook && refreshData.team?.settings) {\n const webhook = refreshData.team.settings['alert_slack_webhook'];\n if (typeof webhook === 'string' && webhook.startsWith('https://')) {\n alertSlackWebhook = webhook;\n }\n }\n\n if (!refreshData.charter || !refreshData.tools) {\n log(`No charter/tools for '${agent.code_name}', skipping`);\n agentStates.push({\n agentId: agent.agent_id,\n codeName: agent.code_name,\n status: agent.status,\n charterVersion: '',\n toolsVersion: '',\n secretsHash: null,\n lastRefreshAt: now,\n lastProvisionAt: null,\n lastDriftCheckAt: null,\n lastSecretsProvisionAt: null,\n gatewayPort: null,\n gatewayPid: null,\n gatewayRunning: false,\n acpSessions: [],\n });\n return;\n }\n\n // Resolve framework adapter from agent data and update cache\n const frameworkId = (refreshData.agent.framework as string) ?? 'openclaw';\n agentFrameworkCache.set(agent.code_name, frameworkId);\n const frameworkAdapter = getFramework(frameworkId);\n // ENG-4418: re-resolve agentDir now that /host/refresh has given us the\n // authoritative framework. If the cache was stale (agent switched\n // framework since last poll), the initial agentDir would have pointed at\n // the wrong tree — recalculate before any writes or adapter calls use it.\n agentDir = join(frameworkAdapter.getAgentDir(agent.code_name), 'provision');\n\n // ENG-4422 §5: cache the agent delivery metadata so the DM resolver has\n // what it needs without an extra API call at fire time.\n // The host-runtime payload carries reports_to_* + owner_team_name (see §8).\n cacheAgentDeliveryMetadata(agent.code_name, refreshData);\n\n // Ensure per-agent profile config exists\n if (frameworkAdapter.seedProfileConfig) {\n frameworkAdapter.seedProfileConfig(agent.code_name);\n }\n\n const charterVersion = refreshData.charter.version;\n const toolsVersion = refreshData.tools.version;\n const known = knownVersions.get(agent.agent_id);\n\n let lastProvisionAt = state.agents.find((a) => a.agentId === agent.agent_id)?.lastProvisionAt ?? null;\n\n // CodeRabbit on PR #773 (round 4): diff against the *launchable* channel\n // set, not raw channel_configs membership. The Claude launch flags\n // (`--dangerously-load-development-channels`, `--allowedTools`) only\n // include channels whose entry has `status` in {active, pending} AND a\n // non-null config — the same gate writeChannelCredentials runs at line\n // 2571. A status-only flip like `slack: active → revoked` leaves the\n // channel id in `Object.keys(channel_configs)` and would have been\n // missed by a raw-key diff, leaving the running session with stale\n // launch flags. Inversely, a row that never becomes launchable would\n // have triggered an unnecessary restart. Compute the same launchable\n // filter here so add/remove + restart decisions agree with what the\n // session actually loads.\n //\n // Detect channel changes (launchable filter via the shared helper so\n // unit tests cover the same gate this code uses at runtime).\n const currentChannelIds = launchableChannelIds(refreshData.channel_configs);\n const previousChannelIds = knownChannels.get(agent.agent_id);\n const channelsChanged = !previousChannelIds ||\n currentChannelIds.size !== previousChannelIds.size ||\n [...currentChannelIds].some((ch) => !previousChannelIds.has(ch)) ||\n [...previousChannelIds].some((ch) => !currentChannelIds.has(ch));\n\n // CodeRabbit on PR #773 (round 2): track whether the on-disk channel\n // credential state actually converges to `currentChannelIds` before we\n // (a) advance the diff cache (`knownChannels.set`) and (b) trigger an\n // ENG-4807 hot-reload restart. If a remove or write fails, leave the\n // diff live so the next refresh tick sees the change again and retries\n // the credential sync — and don't restart the session on broken\n // credential state.\n let channelConfigConverged = true;\n\n // Remove credentials for channels that were unbound\n if (previousChannelIds && channelsChanged && frameworkAdapter.removeChannelCredentials) {\n for (const ch of previousChannelIds) {\n if (!currentChannelIds.has(ch)) {\n try {\n frameworkAdapter.removeChannelCredentials(agent.code_name, ch);\n log(`Removed ${ch} credentials for '${agent.code_name}'`);\n } catch (err) {\n channelConfigConverged = false;\n log(`Failed to remove ${ch} credentials for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n }\n }\n\n // 3. Content-based re-provisioning: always generate artifacts, compare against\n // what's on disk, and only write files that actually changed. This catches\n // template code changes, DB data changes (role, description), and version bumps.\n try {\n const artifacts = generateArtifacts(agent, refreshData, frameworkAdapter);\n // Some artifacts (`.mcp.json`) need their on-disk merge computed *before*\n // the write loop, so track an optional override content alongside the\n // artifact. When set, it replaces artifact.content at write time.\n const changedFiles: { relativePath: string; content: string }[] = [];\n\n mkdirSync(agentDir, { recursive: true });\n for (const artifact of artifacts) {\n const filePath = join(agentDir, artifact.relativePath);\n\n // For CLAUDE.md: strip the skills index section before comparing,\n // since it's managed separately by refreshSkillsIndexInClaudeMd.\n // Without this, the provisioner re-writes CLAUDE.md every cycle\n // (it generates without the index, but on-disk has it).\n //\n // Both sides must be trimmed the same way — the stripped project copy\n // loses its trailing newline via trimEnd(), and the generated artifact\n // content preserves its trailing newline. Without trimming both,\n // hashes never match and the provisioner churns every poll.\n let existingHash: string | null;\n let newHash: string;\n let writeContent = artifact.content;\n if (artifact.relativePath === 'CLAUDE.md') {\n // Strip dynamically-patched sections from BOTH sides before hashing.\n // Two sections get added to project/CLAUDE.md by side-effect code paths\n // that the generator doesn't know about:\n // 1. `## Available Skills` + `## Updating Plugins` (index) — added by\n // refreshSkillsIndexInClaudeMd, bracketed by AGT:SKILLS_INDEX markers.\n // 2. `## Integrations` — added by frameworkAdapter.writeIntegrations,\n // inserted immediately before `## Rules`.\n // Without stripping both, the hashes never match and every poll churns.\n // Long-term fix (ENG-4418 follow-up): wire integrations through\n // refreshData so generateArtifacts produces the same content.\n const stripDynamicSections = (s: string): string => {\n let out = s.replace(new RegExp(`${SKILLS_INDEX_START}[\\\\s\\\\S]*?${SKILLS_INDEX_END}`), '');\n out = out.replace(/## Integrations[\\s\\S]*?(?=## Rules)/, '');\n return out.trimEnd();\n };\n newHash = sha256(stripDynamicSections(artifact.content));\n try {\n const projectClaudeMd = join(config.configDir, agent.code_name, 'project', 'CLAUDE.md');\n const existing = readFileSync(projectClaudeMd, 'utf-8');\n existingHash = sha256(stripDynamicSections(existing));\n } catch {\n existingHash = null;\n }\n } else if (artifact.relativePath === '.mcp.json') {\n // The `.mcp.json` generator only owns a subset of mcpServers (augmented,\n // qmd, ...); channel servers (slack, direct-chat, …) and plugin-added\n // servers are merged in by other code paths. A naive overwrite would\n // wipe them every poll, which then makes step 5b re-add direct-chat,\n // step 5 re-add slack, etc. — the loop that produces:\n // Updating 'agent': .mcp.json\n // Channel credentials written for 'agent/direct-chat'\n // every cycle. Compare and write only the generator-owned keys,\n // preserving everything else.\n const parseMcp = (raw: string): Record<string, unknown> => {\n try {\n const parsed = JSON.parse(raw) as { mcpServers?: Record<string, unknown> };\n return parsed.mcpServers ?? {};\n } catch {\n return {};\n }\n };\n const generatorServers = parseMcp(artifact.content);\n const generatorKeys = Object.keys(generatorServers);\n let existingRaw = '';\n try { existingRaw = readFileSync(filePath, 'utf-8'); } catch { /* new file */ }\n const existingServers = parseMcp(existingRaw);\n const merged = { mcpServers: { ...existingServers, ...generatorServers } };\n writeContent = JSON.stringify(merged, null, 2);\n // Hash only the generator-owned slice on both sides. If the generator\n // produces the same shape as what's already there, skip the write.\n const sliceGeneratorKeys = (src: Record<string, unknown>): Record<string, unknown> => {\n const out: Record<string, unknown> = {};\n for (const k of generatorKeys) if (k in src) out[k] = src[k];\n return out;\n };\n newHash = sha256(JSON.stringify(generatorServers));\n existingHash = existingRaw\n ? sha256(JSON.stringify(sliceGeneratorKeys(existingServers)))\n : null;\n } else {\n newHash = sha256(artifact.content);\n existingHash = hashFile(filePath);\n }\n\n if (newHash !== existingHash) {\n changedFiles.push({ relativePath: artifact.relativePath, content: writeContent });\n }\n }\n\n if (changedFiles.length > 0) {\n const isFirst = !existsSync(join(agentDir, 'CHARTER.md'));\n const verb = isFirst ? 'Provisioning' : 'Updating';\n const fileNames = changedFiles.map((f) => f.relativePath).join(', ');\n log(`${verb} '${agent.code_name}': ${fileNames}`);\n\n for (const file of changedFiles) {\n const filePath = join(agentDir, file.relativePath);\n mkdirSync(dirname(filePath), { recursive: true });\n writeFileSync(filePath, file.content);\n }\n\n // Prune stale legacy knowledge-* skill dirs from provision dir\n // (replaced by single core-knowledge skill)\n try {\n const provSkillsDir = join(agentDir, '.claude', 'skills');\n if (existsSync(provSkillsDir)) {\n for (const folder of readdirSync(provSkillsDir)) {\n if (folder.startsWith('knowledge-')) {\n try { rmSync(join(provSkillsDir, folder), { recursive: true }); } catch { /* ignore */ }\n }\n }\n }\n } catch { /* non-fatal */ }\n\n lastProvisionAt = new Date().toISOString();\n\n knownVersions.set(agent.agent_id, { charterVersion, toolsVersion });\n\n // Update written hashes for drift detection\n const trackedFiles = frameworkAdapter.driftTrackedFiles();\n const hashes = new Map<string, string>();\n for (const file of trackedFiles) {\n const h = hashFile(join(agentDir, file));\n if (h) hashes.set(file, h);\n }\n writtenHashes.set(agent.agent_id, hashes);\n\n // Register in framework runtime if not already present\n const resolvedModelsForRegistration = resolveModelChain(refreshData);\n const primaryModel = resolvedModelsForRegistration.primary ?? (refreshData.agent.primary_model as string | null | undefined);\n const registeredAgents = await getOrCacheRegisteredAgents(frameworkAdapter, agent.code_name);\n if (!registeredAgents.has(agent.code_name)) {\n const registered = await frameworkAdapter.registerAgent(agent.code_name, agentDir, primaryModel);\n if (registered) {\n registeredAgents.add(agent.code_name);\n log(`Registered '${agent.code_name}' in ${frameworkAdapter.label}`);\n }\n }\n\n send({ type: 'provisioned', agentId: agent.agent_id, codeName: agent.code_name });\n }\n\n // Always deploy artifacts to project dir — the provision dir is the source\n // of truth, and channel MCP servers are merged from a separate path.\n // This must run outside changedFiles so the merge always produces a\n // complete .mcp.json (base servers + channel servers).\n if (frameworkAdapter.deployArtifactsToProject) {\n frameworkAdapter.deployArtifactsToProject(agent.code_name, agentDir);\n }\n } catch (err) {\n log(`Provision failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n\n // 3b. Ensure model is set correctly in per-agent profile (on first run and on change)\n const resolvedForModel = resolveModelChain(refreshData);\n const primaryModel = resolvedForModel.primary ?? (refreshData.agent.primary_model as string | null | undefined);\n if (primaryModel && frameworkAdapter.updateAgentModel) {\n const previousModel = knownModels.get(agent.agent_id);\n if (previousModel !== primaryModel) {\n try {\n const updated = await frameworkAdapter.updateAgentModel(agent.code_name, primaryModel);\n if (updated) {\n if (previousModel) {\n log(`Model updated for '${agent.code_name}': ${previousModel} → ${primaryModel}`);\n } else {\n log(`Model set for '${agent.code_name}': ${primaryModel}`);\n }\n }\n } catch (err) {\n log(`Failed to update model for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n knownModels.set(agent.agent_id, primaryModel);\n }\n\n // 4. Drift check — hash local files and compare\n let lastDriftCheckAt = now;\n const written = writtenHashes.get(agent.agent_id);\n\n if (written && existsSync(agentDir)) {\n const driftedFiles: string[] = [];\n\n for (const [file, expectedHash] of written) {\n const localHash = hashFile(join(agentDir, file));\n if (localHash && localHash !== expectedHash) {\n driftedFiles.push(file);\n }\n }\n\n if (driftedFiles.length > 0) {\n log(`Drift detected for '${agent.code_name}': ${driftedFiles.join(', ')}`);\n send({ type: 'drift-detected', agentId: agent.agent_id, codeName: agent.code_name, files: driftedFiles });\n\n // Report drift to API\n try {\n const localHashes: Record<string, string | null> = {};\n for (const file of driftedFiles) {\n localHashes[file] = hashFile(join(agentDir, file));\n }\n await api.post('/host/drift', {\n agent_id: agent.agent_id,\n drifted_files: driftedFiles,\n local_hashes: localHashes,\n });\n } catch (err) {\n log(`Failed to report drift for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n }\n\n // Cache bot tokens from channel_configs for notification delivery\n if (refreshData.channel_configs) {\n const tokens: { slack?: string; telegram?: string; telegramAllowedChats?: string[] } = {};\n const slackCfg = refreshData.channel_configs['slack'];\n if (slackCfg?.config) {\n const bt = (slackCfg.config as Record<string, unknown>).bot_token;\n if (typeof bt === 'string' && bt) tokens.slack = bt;\n }\n const tgCfg = refreshData.channel_configs['telegram'];\n if (tgCfg?.config) {\n const tgConfig = tgCfg.config as Record<string, unknown>;\n const bt = tgConfig.bot_token;\n if (typeof bt === 'string' && bt) tokens.telegram = bt;\n const allowedChats = tgConfig.allowed_chat_ids;\n if (Array.isArray(allowedChats)) tokens.telegramAllowedChats = allowedChats as string[];\n }\n if (tokens.slack || tokens.telegram) {\n agentChannelTokens.set(agent.code_name, tokens);\n } else {\n agentChannelTokens.delete(agent.code_name);\n }\n }\n\n let needsGatewayRestart = false;\n\n // 5. Write channel credentials to per-agent profile config (only if changed)\n const hasChannelConfigs = refreshData.channel_configs && Object.keys(refreshData.channel_configs).length > 0;\n\n if (refreshData.channel_configs && frameworkAdapter.writeChannelCredentials) {\n if (agent.status === 'active') {\n for (const [channelId, entry] of Object.entries(refreshData.channel_configs)) {\n if ((entry.status === 'active' || entry.status === 'pending') && entry.config) {\n // Track that this channel is active for this agent's profile\n if (!activeChannels.has(channelId)) {\n activeChannels.set(channelId, new Set());\n }\n activeChannels.get(channelId)!.add(agent.code_name);\n\n // Hash the config to detect changes — skip credential write if\n // identical to last poll. ENG-4439: also verify the on-disk state\n // actually holds the entry before skipping. The cache is purely\n // \"what I intended to write\"; if an external process modified the\n // agent's runtime config between polls (manual edit, migration,\n // another tool), the cache would otherwise refuse to re-write\n // forever and the channel stays broken until the manager restarts.\n // Adapters without `hasChannelCredentials` are treated as \"trust\n // the cache\" so older framework adapters keep their existing\n // behavior.\n //\n // ENG-4468: hash the CANONICAL (recursively key-sorted) JSON,\n // not plain `JSON.stringify`. Postgres JSONB doesn't preserve\n // key order, so the same semantic config can serialise to\n // different strings across polls — producing different hashes\n // and forcing constant rewrites. The churn cascaded into\n // .mcp.json drift and MCP subprocess instability on augmenteds-mbp\n // (Phil / Pepper). Canonicalising fixes it regardless of which\n // field moved.\n // ENG-4912 / ENG-4940: fold the channel-agnostic peer kill\n // switch into the hash input so a team-admin flip invalidates\n // the cache and forces a credential rewrite (which propagates\n // the PEER_DISABLED env to the spawned MCP child). Without\n // this the per-channel hash on `entry.config` alone would skip\n // the rewrite — entry.config has nothing to do with team\n // settings — and the kill switch wouldn't take effect until\n // the channel's own config drifted.\n //\n // Reads `peer_disabled` (enum) first, falls back to the legacy\n // `telegram_peer_disabled` boolean (true → 'all') so any\n // pre-migration row still gates correctly while the deploy is\n // mid-flight.\n const teamSettings = (refreshData.team as { settings?: Record<string, unknown> } | undefined)?.settings;\n const peerDisabledMode: 'off' | 'cross_team_only' | 'all' = (() => {\n const next = teamSettings?.['peer_disabled'];\n if (next === 'off' || next === 'cross_team_only' || next === 'all') return next;\n return teamSettings?.['telegram_peer_disabled'] === true ? 'all' : 'off';\n })();\n const teamSettingsForHash =\n channelId === 'telegram' || channelId === 'slack'\n ? { peer_disabled: peerDisabledMode }\n : null;\n // ENG-4923: also fold the resolved peer roster into the hash\n // input. entry.config carries peer_agent_mode + peer_group_ids\n // natively (TelegramChannelConfig fields), but the CHARTER-\n // derived peer list does not — so a CHARTER edit that changes\n // declared peers would otherwise leave the hash unchanged and\n // never propagate the new TELEGRAM_PEERS env to the child.\n const crossTeamData = refreshData as {\n organization?: { cross_team_peer_intra_org?: 'unrestricted' | 'consent_required' } | null;\n cross_team_grants?: { inbound?: ReadonlyArray<{ grant_id: string; granted_agent_bot_id: number | null; revoked_at: string | null; expires_at: string | null }> };\n team_peer_bot_ids?: ReadonlyArray<number>;\n };\n // CodeRabbit on PR #868: only build a gateContext when the\n // /host/refresh response actually carries the ENG-4935 fields.\n // Older API versions (and any partial payloads) leave the\n // discriminator field absent — passing a synthesised context\n // with `[]` defaults would silently mask cross-team peers as\n // `intra_org_unrestricted` and never reach the documented\n // backwards-compat path in extractCharterTelegramPeers.\n const refreshHasCrossTeamFields =\n Array.isArray(crossTeamData.team_peer_bot_ids);\n const gateContext: CharterPeerGateContext | undefined =\n refreshHasCrossTeamFields\n ? {\n teamPeerBotIds: crossTeamData.team_peer_bot_ids ?? [],\n crossTeamGrants: crossTeamData.cross_team_grants?.inbound ?? [],\n crossTeamPeerIntraOrg:\n crossTeamData.organization?.cross_team_peer_intra_org ?? 'unrestricted',\n }\n : undefined;\n const peersForHash =\n channelId === 'telegram'\n ? extractCharterTelegramPeers(refreshData.charter?.raw_content ?? '', gateContext)\n : null;\n // Bump `writeVersion` whenever the env block emitted by\n // `writeChannelCredentials` changes shape (new env var added,\n // existing var renamed, etc.). Existing deployments cache the\n // hash on disk, so without this bump a fresh CLI binary will\n // skip the credentials rewrite — the agent never picks up\n // the new env vars until config / team / peers actually\n // change for some unrelated reason.\n //\n // History:\n // v1 — initial channel hash (ENG-4439)\n // v2 — PR #912 forwards AGT_HOST / AGT_API_KEY / AGT_AGENT_ID\n // to the Telegram MCP child. Required for ENG-4986\n // observed-chat, ENG-4937 audit emission, and ENG-4909\n // durable peer rate limiting to function.\n const CHANNEL_WRITE_VERSION = 2;\n const configHash = createHash('sha256')\n .update(\n canonicalJson({\n writeVersion: CHANNEL_WRITE_VERSION,\n config: entry.config,\n team: teamSettingsForHash,\n peers: peersForHash,\n }),\n )\n .digest('hex');\n const cacheKey = `${agent.agent_id}:${channelId}`;\n let onDiskPresent = true;\n try {\n onDiskPresent =\n frameworkAdapter.hasChannelCredentials?.(agent.code_name, channelId) ?? true;\n } catch (err) {\n // A faulty adapter hook must not abort the provisioning loop.\n // Treat the failure as \"on-disk entry missing\" so we force a\n // rewrite rather than silently trusting a stale cache.\n log(`hasChannelCredentials failed for '${agent.code_name}/${channelId}': ${(err as Error).message} — forcing credential rewrite`);\n onDiskPresent = false;\n }\n if (knownChannelConfigHashes.get(cacheKey) === configHash && onDiskPresent) {\n continue;\n }\n // ENG-4468: capture prevHash BEFORE the on-disk-missing branch\n // can delete it, so the `reason` label below is accurate. Reading\n // after the delete would mislabel on-disk-missing as first-write.\n const prevHash = knownChannelConfigHashes.get(cacheKey);\n const reason = !prevHash\n ? 'first-write'\n : !onDiskPresent\n ? 'on-disk-missing'\n : 'content-changed';\n if (!onDiskPresent && prevHash) {\n log(`Cached hash for '${agent.code_name}/${channelId}' but on-disk entry missing — re-writing credentials`);\n knownChannelConfigHashes.delete(cacheKey);\n }\n try {\n const sessionMode = (refreshData.agent as Record<string, unknown>).session_mode as string | undefined;\n // ENG-4573: pass agent_id through so the slack-channel MCP can\n // wire AGT_AGENT_ID for slack.ask_user (block_kit_ask_user_enabled).\n // ENG-4912 / ENG-4940: thread the channel-agnostic peer kill\n // switch enum through. peerDisabled is the canonical signal\n // (PEER_DISABLED env on the child); telegramPeerDisabled is\n // a derived boolean kept for backwards compat — the MCP\n // child accepts both during the rollout.\n const peerDisabled =\n channelId === 'telegram' || channelId === 'slack'\n ? peerDisabledMode\n : undefined;\n const telegramPeerDisabled =\n channelId === 'telegram' ? peerDisabledMode === 'all' : undefined;\n // ENG-4923: assemble the per-agent peer roster from CHARTER\n // multi_agent.telegram_peers. Each entry is the CHARTER's\n // declared { code_name, bot_id }; agent_id is left empty\n // here because the manager doesn't yet have a cheap way to\n // resolve it from the team roster (would need either an\n // extension to /host/refresh or a separate /host/team-agents\n // call). The classifier doesn't gate on agent_id — only on\n // bot_id — so leaving it blank is safe today; ENG-4904's\n // runtime can fetch the agent_id when it needs to surface\n // the peer's identity in the prompt preamble.\n const telegramPeers =\n channelId === 'telegram'\n ? extractCharterTelegramPeers(refreshData.charter?.raw_content ?? '', gateContext)\n : undefined;\n frameworkAdapter.writeChannelCredentials(\n agent.code_name,\n channelId,\n entry.config as Record<string, unknown>,\n { sessionMode, agentId: agent.agent_id, telegramPeerDisabled, peerDisabled, telegramPeers },\n );\n knownChannelConfigHashes.set(cacheKey, configHash);\n saveChannelHashCache();\n log(`Channel credentials written for '${agent.code_name}/${channelId}' (reason=${reason}, hash=${configHash.slice(0, 8)}${prevHash ? `, prev=${prevHash.slice(0, 8)}` : ''})`);\n } catch (err) {\n channelConfigConverged = false;\n log(`Failed to write channel credentials for '${agent.code_name}/${channelId}': ${(err as Error).message}`);\n }\n }\n }\n\n } else if (agent.status === 'paused') {\n // Paused agents: disable channels in their profile\n if (frameworkAdapter.setChannelEnabled) {\n for (const channelId of Object.keys(refreshData.channel_configs)) {\n frameworkAdapter.setChannelEnabled(channelId, false, agent.code_name);\n }\n }\n }\n }\n\n // CodeRabbit on PR #773 (round 2): only advance the per-agent channel\n // diff cache when the on-disk credential state actually converged. If\n // a remove/write failed, leave `previousChannelIds` in place so the\n // next refresh tick re-sees the diff and retries the credential sync.\n // Otherwise we'd swallow the change forever the moment one transient\n // I/O failure occurred — and worse, fire a hot-reload restart on top\n // of broken credential state.\n if (channelConfigConverged) {\n knownChannels.set(agent.agent_id, currentChannelIds);\n } else {\n log(`[channels] Credential sync did not converge for '${agent.code_name}' — leaving diff live for next tick retry`);\n }\n\n // ENG-4807: when the channel set changes for a live persistent claude-code\n // session, restart the session so its launch flags reflect the new\n // channels. The flags baked at spawn time\n // (`--dangerously-load-development-channels` and `--allowedTools`) don't\n // pick up new channels without a respawn — the MCP loads but its\n // `notifications/claude/channel` get dropped, and tool calls to the new\n // channel's MCP (e.g. `telegram.reply`) get auto-denied. Pre-fix this\n // required an operator to manually run `tmux kill-session`. See ENG-4807\n // for the full diagnosis from Vigil's Telegram setup. The MCP-toolkit\n // hot-reload at line ~2926 already follows the same shape; this mirrors\n // it for channels.\n //\n // The decision logic lives in a pure helper so it's unit-testable\n // without standing up the rest of processAgent.\n //\n // CodeRabbit on PR #773 (round 2): suppress restart when credential\n // sync didn't converge — restarting on broken state would just reload\n // the session with stale flags that match the pre-failure state, then\n // the next tick's successful retry sees no diff (because previousChannelIds\n // is still the pre-change set) and never restarts again. Wait for\n // sync to land first.\n const restartDecision = channelConfigConverged\n ? decideChannelRestart({\n previousChannelIds,\n currentChannelIds,\n sessionMode: (refreshData.agent as Record<string, unknown>).session_mode as string | undefined,\n framework: agentFrameworkCache.get(agent.code_name) ?? 'openclaw',\n sessionHealthy: isSessionHealthy(agent.code_name),\n })\n : { restart: false, added: [], removed: [] };\n if (restartDecision.restart) {\n const reasonParts: string[] = [];\n if (restartDecision.added.length > 0) reasonParts.push(`added=${restartDecision.added.join(',')}`);\n if (restartDecision.removed.length > 0) reasonParts.push(`removed=${restartDecision.removed.join(',')}`);\n const reason = reasonParts.join(' ');\n log(`[hot-reload] Channel set changed for '${agent.code_name}' (${reason}) — restarting session`);\n\n const restartNotice = restartDecision.added.length > 0\n ? `New channels have been wired up (${restartDecision.added.join(', ')}). Note: channels require a session restart to attach their MCP servers as channel listeners. Your manager will restart your session shortly.`\n : `Channels were removed (${restartDecision.removed.join(', ')}). Your manager will restart your session shortly so the launch flags drop those channels.`;\n const delivered = await injectMessage(agent.code_name, 'system', restartNotice, { task_name: 'channel-update' }, log).catch(() => false);\n const delay = delivered ? 8_000 : 3_000;\n if (!delivered) {\n log(`[hot-reload] Inject notification unconfirmed for '${agent.code_name}' — proceeding with shorter delay`);\n }\n scheduleSessionRestart(agent.code_name, delay, 'new channel set');\n }\n\n // 5b. Always provision direct-chat channel for persistent sessions.\n // This runs as a standalone MCP channel server and pushes webapp direct\n // chat messages into the live session.\n //\n // Must live in project-scope .mcp.json (not a separate file). Claude Code's\n // channel name matcher (yG5 in 2.1.114) only resolves server:<name> against\n // MCP servers in enterprise/user/project/local scopes — --mcp-config loads\n // as session scope and is invisible to the lookup, which is why the separate\n // .mcp-channels.json approach always failed with \"no MCP server configured\".\n //\n // Write to the provision-dir .mcp.json (source of truth) then copy to the\n // project dir, so subsequent artifact syncs don't wipe the entry.\n const agentSessionMode = (refreshData.agent as Record<string, unknown>).session_mode as string | undefined;\n if (agentSessionMode === 'persistent' && (agentFrameworkCache.get(agent.code_name) ?? 'openclaw') === 'claude-code') {\n try {\n // ENG-4418: provision dir now lives at ~/.augmented/<codeName>/provision/\n // (formerly split between <codeName>/provision/ and <codeName>/claudecode/provision/).\n // Reuse the refresh-resolved agentDir so this always matches the\n // authoritative framework (not the cached one from /host/agents).\n const agentProvisionDir = agentDir;\n const projectDir = join(homedir(), '.augmented', agent.code_name, 'project');\n mkdirSync(agentProvisionDir, { recursive: true });\n mkdirSync(projectDir, { recursive: true });\n const provisionMcpPath = join(agentProvisionDir, '.mcp.json');\n const projectMcpPath = join(projectDir, '.mcp.json');\n\n let mcpConfig: { mcpServers: Record<string, unknown> } = { mcpServers: {} };\n try {\n mcpConfig = JSON.parse(readFileSync(provisionMcpPath, 'utf-8'));\n if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};\n } catch { /* new file */ }\n\n const localDirectChatChannel = join(homedir(), '.augmented', '_mcp', 'direct-chat-channel.js');\n if (existsSync(localDirectChatChannel) && !mcpConfig.mcpServers['direct-chat']) {\n mcpConfig.mcpServers['direct-chat'] = {\n command: 'node',\n args: [localDirectChatChannel],\n env: {\n AGT_HOST: requireHost(),\n AGT_API_KEY: getApiKey() ?? '',\n AGT_AGENT_ID: agent.agent_id,\n },\n };\n const serialized = JSON.stringify(mcpConfig, null, 2);\n writeFileSync(provisionMcpPath, serialized);\n writeFileSync(projectMcpPath, serialized);\n log(`Channel credentials written for '${agent.code_name}/direct-chat'`);\n }\n\n // Remove stale .mcp-channels.json from the pre-fix layout.\n const staleChannelsPath = join(projectDir, '.mcp-channels.json');\n if (existsSync(staleChannelsPath)) {\n try { rmSync(staleChannelsPath, { force: true }); } catch { /* non-fatal */ }\n }\n } catch (err) {\n log(`Failed to provision direct-chat channel for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n\n // 6. Fetch and write auth profiles (secrets)\n let lastSecretsProvisionAt = state.agents.find((a) => a.agentId === agent.agent_id)?.lastSecretsProvisionAt ?? null;\n let secretsHash = knownSecretsHashes.get(agent.agent_id) ?? null;\n\n try {\n const secretsData = await api.post<{\n profiles: Array<{\n provider: string;\n profile_name: string;\n auth_type: string;\n api_key?: string;\n metadata: Record<string, unknown>;\n }>;\n secrets_hash: string | null;\n }>('/host/secrets', { agent_id: agent.agent_id });\n\n const newHash = secretsData.secrets_hash;\n const hashChanged = newHash !== secretsHash;\n\n if (hashChanged && secretsData.profiles.length > 0) {\n frameworkAdapter.writeAuthProfiles(agent.code_name, secretsData.profiles);\n lastSecretsProvisionAt = new Date().toISOString();\n log(`Secrets updated for '${agent.code_name}'`);\n }\n\n if (newHash) {\n knownSecretsHashes.set(agent.agent_id, newHash);\n secretsHash = newHash;\n } else {\n knownSecretsHashes.delete(agent.agent_id);\n secretsHash = null;\n }\n } catch (err) {\n // Non-fatal — secrets are supplementary to provisioning\n log(`Secrets fetch failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n\n // 6b. Integration provisioning — fetch integrations, resolve satisfied capabilities,\n // install skills + CLI tools\n try {\n const integrationsData = await api.post<{\n integrations: Array<{\n id: string;\n definition_id: string;\n credentials: Record<string, unknown>;\n config: Record<string, unknown>;\n capabilities: Array<{ id: string; name: string; description: string; access: string }>;\n auth_type: string;\n display_name: string;\n scope: string;\n }>;\n }>('/host/agent-integrations', { agent_id: agent.agent_id });\n\n const integrations = integrationsData.integrations ?? [];\n\n // 6b-i. Refresh OAuth tokens nearing expiry (< 10 min remaining)\n for (const integration of integrations) {\n if (integration.auth_type !== 'oauth2') continue;\n const expiresAt = integration.credentials?.token_expires_at as string | undefined;\n const refreshToken = integration.credentials?.refresh_token as string | undefined;\n if (!expiresAt || !refreshToken) continue;\n\n const msRemaining = new Date(expiresAt).getTime() - Date.now();\n if (msRemaining > 10 * 60 * 1000) continue; // Still valid for > 10 min\n\n try {\n const integrationId = integration.id;\n if (!integrationId) continue;\n const refreshResult = await api.post<{ ok: boolean; expires_at?: string; access_token?: string }>(\n `/integrations/oauth/${integrationId}/refresh`,\n {},\n );\n if (refreshResult.ok) {\n // Update in-memory credentials so writeIntegrations() uses the new token\n integration.credentials.token_expires_at = refreshResult.expires_at;\n if (refreshResult.access_token) {\n integration.credentials.access_token = refreshResult.access_token;\n }\n log(`OAuth token refreshed for '${agent.code_name}/${integration.definition_id}'`);\n\n // Write live token file as fallback for agents not yet using the\n // token_refresh MCP tool. Agents with the plugin can call token.refresh\n // on demand instead of relying on this file-based handoff.\n if (frameworkAdapter.writeTokenFile) {\n frameworkAdapter.writeTokenFile(agent.code_name, integrations as Parameters<typeof frameworkAdapter.writeTokenFile>[1]);\n }\n }\n } catch (err) {\n log(`OAuth token refresh failed for '${agent.code_name}/${integration.definition_id}': ${(err as Error).message}`);\n }\n }\n\n // 6b-ii. Integration credentials — env vars + CLAUDE.md snippets. Gated on\n // an integration hash that covers `definition_id + credentials + config`,\n // so config-only DB changes (e.g. switching Xero's `xero_tenant_id`\n // without rotating the access token) propagate to .env.integrations and\n // trigger the env-var-aware MCP child reap below. ENG-4911.\n if (integrations.length > 0) {\n const intHash = computeIntegrationsHash(integrations);\n const prevIntHash = knownIntegrationHashes.get(agent.agent_id);\n\n if (intHash !== prevIntHash) {\n // ENG-4832 (CodeRabbit on PR #797 round 3): capture the\n // pre-write .env.integrations BEFORE writeIntegrations runs,\n // so we can diff against the post-write content and pass the\n // PRECISELY-rotated subset to findMcpServersUsingVars.\n // Without the diff, changedVars would be \"every var currently\n // present in the file\" and we'd over-reap MCP children whose\n // env didn't actually change. read returns undefined when the\n // file doesn't exist yet (first-ever integrations write for\n // this agent) — diffEnvIntegrations treats that as \"every\n // post-write var is new\", which is the correct first-write\n // behaviour: there's no prior state to preserve, so any new\n // vars warrant a reap.\n const projectDir = join(homedir(), '.augmented', agent.code_name, 'project');\n const envIntPath = join(projectDir, '.env.integrations');\n let preWriteEnv: string | undefined;\n try {\n preWriteEnv = readFileSync(envIntPath, 'utf-8');\n } catch {\n // No prior file — treat as empty so the diff shows all\n // post-write vars as added. fine.\n preWriteEnv = undefined;\n }\n\n if (frameworkAdapter.writeIntegrations) {\n // ENG-4920: thread agent.agent_id through so the claudecode\n // adapter can wire AGT_AGENT_ID + AGT_INTEGRATION_ID into the\n // Xero MCP server's spawn env. Older adapters (openclaw,\n // nemoclaw) ignore the third argument.\n frameworkAdapter.writeIntegrations(agent.code_name, integrations as Parameters<typeof frameworkAdapter.writeIntegrations>[1], agent.agent_id);\n }\n log(`Integrations provisioned for '${agent.code_name}' (${integrations.length} integration(s))`);\n\n const fw = agentFrameworkCache.get(agent.code_name) ?? 'openclaw';\n\n // Rotated env vars only reach MCP children that get RE-spawned.\n // The pre-existing children hold their spawn-time env, so the\n // access_token they're calling Xero/Gmail with is whatever was\n // in the env block when claude first launched them. Once that\n // token's 30-min TTL ticks past, every upstream call 401s and\n // the agent reports \"refresh failed\" (Stirling, 3-day blackout).\n // Identify which MCP server keys reference any of the\n // just-rotated env vars and SIGTERM their children so Claude\n // Code's MCP transport detects the dead child and respawns it\n // on next request — fresh env, fresh token, conversation\n // context preserved.\n //\n // CodeRabbit on PR #797 round 4: defer the\n // knownIntegrationHashes.set() update until the reap pipeline\n // completes. If the read/parse/reap path throws, we leave the\n // hash unchanged so the NEXT manager tick retries the rotation\n // end-to-end. Without this defer, a single transient failure\n // (file briefly missing, ps temporarily unavailable, JSON\n // parse error mid-write) would mark the rotation handled and\n // leave the stale MCP child to keep returning 401s\n // indefinitely — exactly the silent failure mode this whole\n // PR exists to eliminate.\n let rotationHandled = true;\n\n if (fw === 'claude-code' && isSessionHealthy(agent.code_name)) {\n try {\n const projectMcpPath = join(projectDir, '.mcp.json');\n const postWriteEnv = readFileSync(envIntPath, 'utf-8');\n const mcpContent = readFileSync(projectMcpPath, 'utf-8');\n // Diff pre-write vs post-write to get ONLY the rotated\n // var names. Without this we'd reap every MCP whose env\n // touches any var in the file — interrupting unrelated\n // tool traffic on every refresh tick.\n const changedVars = diffEnvIntegrations(preWriteEnv, postWriteEnv);\n const mcpJsonForReap = JSON.parse(mcpContent) as Parameters<typeof findMcpServersUsingVars>[0];\n const affectedServerKeys = findMcpServersUsingVars(mcpJsonForReap, changedVars);\n\n if (affectedServerKeys.length > 0) {\n reapStaleMcpChildren({\n log,\n codeName: agent.code_name,\n serverKeys: affectedServerKeys,\n mcpJson: mcpJsonForReap,\n });\n }\n\n const names = integrations.map((i) => i.display_name || i.definition_id).join(', ');\n // Accurate now that the reaper above forces the affected\n // MCP children to respawn with fresh env. There's a brief\n // window where the next call to one of those tools lands\n // during the respawn; the MCP client's retry policy\n // handles that.\n const reapNote = affectedServerKeys.length > 0\n ? ` The MCP servers that depend on rotating credentials (${affectedServerKeys.join(', ')}) have been signalled to reconnect.`\n : '';\n injectMessage(agent.code_name, 'system', `Your integrations have been refreshed. You have access to: ${names}.${reapNote}`, {\n task_name: 'integration-update',\n }, log).catch(() => {});\n log(`[hot-reload] Notified '${agent.code_name}' about integration update: ${names} (reaped ${affectedServerKeys.length} stale MCP server(s))`);\n } catch (err) {\n // Don't mark handled — the next tick will reattempt the\n // full read/parse/reap pipeline. Idempotent\n // writeIntegrations means the second call is a no-op\n // write; the only practical cost is one extra\n // \"Integrations provisioned\" log line per retry, which is\n // the right operator signal that something's wrong.\n rotationHandled = false;\n log(`[hot-reload] Failed to compute / reap affected MCP servers for '${agent.code_name}': ${(err as Error).message} — will retry next tick`);\n }\n }\n\n if (rotationHandled) {\n knownIntegrationHashes.set(agent.agent_id, intHash);\n }\n\n // Env vars changed — gateway needs a restart for openclaw hosts.\n needsGatewayRestart = true;\n }\n }\n\n // 6b-iii. Managed-toolkit MCP server convergence. Runs every poll (not\n // gated on integration credential changes) so the `.mcp.json` desired\n // state self-heals when an earlier write was skipped or lost — plugins\n // added to an agent now always propagate, not only on the exact poll\n // where credentials happened to change. ENG-4481.\n //\n // Idempotency: we hash the desired-state set (server_id → url + header\n // keys) and only re-write .mcp.json (and trigger the restart-session\n // notification) when that hash changes. `writeMcpServer` itself is\n // idempotent via canonical-json compare, so a redundant call with the\n // same config is a cheap no-op.\n const fwForMcp = agentFrameworkCache.get(agent.code_name) ?? 'openclaw';\n if (frameworkAdapter.writeMcpServer) {\n try {\n const toolkitData = await api.post<{\n toolkits: Array<{ agent_id: string; toolkit_id: string; toolkit_name: string; proxy_url: string }>;\n }>('/host/managed-toolkits', { agent_ids: [agent.agent_id] });\n\n const agentToolkits = (toolkitData.toolkits ?? []).filter((tk) => tk.agent_id === agent.agent_id);\n const expectedServerIds = new Set<string>();\n const desiredEntries: Array<{ serverId: string; url: string; headers?: Record<string, string>; name: string }> = [];\n for (const tk of agentToolkits) {\n const serverId = tk.toolkit_id.replace(/[^a-z0-9]/gi, '_').toLowerCase();\n expectedServerIds.add(serverId);\n const mcpUrl = (tk as Record<string, unknown>).mcp_url as string | undefined;\n const mcpHeaders = (tk as Record<string, unknown>).mcp_headers as Record<string, string> | undefined;\n const proxyUrl = tk.proxy_url.startsWith('/') ? `${requireHost()}${tk.proxy_url}` : tk.proxy_url;\n const url = mcpUrl ?? proxyUrl;\n desiredEntries.push({ serverId, url, headers: mcpHeaders, name: tk.toolkit_name });\n }\n\n // Desired-state hash — stable ordering (sort by serverId), url + full\n // headers (keys AND values). Header values must participate so that a\n // provider rotating a credential-bearing header triggers a rewrite;\n // hashing only keys would silently leave `.mcp.json` on stale auth\n // until an unrelated toolkit change bumped the hash.\n const hashBasis = desiredEntries\n .slice()\n .sort((a, b) => a.serverId.localeCompare(b.serverId))\n .map((e) => {\n const headersHash = createHash('sha256')\n .update(canonicalJson(e.headers ?? {}))\n .digest('hex')\n .slice(0, 16);\n return `${e.serverId}|${e.url}|${headersHash}`;\n })\n .join('\\n');\n const mcpHash = createHash('sha256').update(hashBasis).digest('hex').slice(0, 16);\n const prevMcpHash = knownManagedMcpHashes.get(agent.agent_id);\n\n if (mcpHash !== prevMcpHash) {\n for (const e of desiredEntries) {\n frameworkAdapter.writeMcpServer(agent.code_name, e.serverId, { url: e.url, headers: e.headers });\n // Hash the URL — `e.url` can be a provider-issued direct MCP URL\n // that carries signed query tokens. manager.log is persisted to\n // disk, so don't let raw creds land there.\n const urlHash = createHash('sha256').update(e.url).digest('hex').slice(0, 12);\n log(`[managed-toolkit] ${agent.code_name}: wrote '${e.name}' (serverId=${e.serverId}, url_hash=${urlHash})`);\n }\n\n // Prune stale managed MCP servers — skip entirely for adapters that\n // don't expose a local MCP file (e.g. cloud-hosted frameworks). The\n // framework-specific path comes from `getMcpPath`; reconstructing\n // `join(getAgentDir, 'provision', '.mcp.json')` here would hardcode\n // the claude-code layout.\n if (frameworkAdapter.removeMcpServer && frameworkAdapter.getMcpPath) {\n const mcpPath = frameworkAdapter.getMcpPath(agent.code_name);\n if (mcpPath) {\n try {\n const { readFileSync } = await import('node:fs');\n const mcpConfig = JSON.parse(readFileSync(mcpPath, 'utf-8')) as { mcpServers?: Record<string, unknown> };\n if (mcpConfig.mcpServers) {\n const managedPrefixes = ['composio_', 'one_', 'pipedream_', 'nango_', 'paragon_',\n 'composio-', 'one-', 'pipedream-', 'nango-', 'paragon-'];\n for (const key of Object.keys(mcpConfig.mcpServers)) {\n if (managedPrefixes.some((p) => key.startsWith(p)) && !expectedServerIds.has(key)) {\n frameworkAdapter.removeMcpServer(agent.code_name, key);\n log(`[managed-toolkit] Removed stale MCP server '${key}' for '${agent.code_name}'`);\n }\n }\n }\n } catch {\n // MCP file doesn't exist yet — nothing to clean up\n }\n }\n }\n\n knownManagedMcpHashes.set(agent.agent_id, mcpHash);\n\n // Only notify about MCP server changes after the first poll — on\n // initial convergence the session is either not yet started or will\n // be started with the fresh .mcp.json, so a restart notification\n // would be a noisy false alarm. Full-removal case is still a valid\n // change (we need to restart so the session drops the tools it had\n // loaded), so don't gate on `agentToolkits.length > 0`.\n if (prevMcpHash !== undefined && fwForMcp === 'claude-code' && isSessionHealthy(agent.code_name)) {\n const mcpNames = agentToolkits.map((tk) => tk.toolkit_name).join(', ') || 'none (all removed)';\n log(`[hot-reload] MCP servers changed for '${agent.code_name}': ${mcpNames} — restarting session`);\n\n const restartNotice = agentToolkits.length > 0\n ? `New MCP tool servers have been configured: ${mcpNames}. Note: MCP servers require a session restart to connect. Your manager will restart your session shortly.`\n : 'Managed MCP tool servers were removed. Your manager will restart your session shortly so the session drops those tools.';\n const delivered = await injectMessage(agent.code_name, 'system',\n restartNotice,\n { task_name: 'mcp-update' }, log).catch(() => false);\n\n const delay = delivered ? 8_000 : 3_000;\n if (!delivered) {\n log(`[hot-reload] Inject notification unconfirmed for '${agent.code_name}' — proceeding with shorter delay`);\n }\n // ENG-4807 / CodeRabbit on PR #773: route through the\n // coalescing scheduler so a same-tick channel-set change\n // doesn't double-restart the agent.\n scheduleSessionRestart(agent.code_name, delay, 'new MCP servers');\n }\n }\n } catch (err) {\n log(`[managed-toolkit] Failed to provision for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n\n // ENG-4337: QMD CLI install + collection setup are now handled by the QMD\n // plugin (slug 'qmd') via its on_install hook. See refreshData.plugin_install_hooks\n // below — the manager runs the hook script `qmd collection add` after deploying\n // plugin skills. The QMD plugin is auto-installed for any agent that has the\n // QMD integration active (see migration 20260408000002_qmd_native_plugin.sql).\n } catch (err) {\n // Non-fatal — integration provisioning is supplementary\n log(`Integration provisioning failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n\n // 7. Gateway lifecycle — ensure running for active agents with channels\n let gatewayPort: number | null = null;\n let gatewayPid: number | null = null;\n let gatewayRunning = false;\n\n if (agent.status === 'active' && hasChannelConfigs) {\n const gwStatus = await ensureGatewayRunning(agent.code_name, frameworkAdapter);\n gatewayPort = gwStatus.port;\n gatewayPid = gwStatus.pid;\n gatewayRunning = gwStatus.running;\n } else if (agent.status === 'paused') {\n // Stop gateway for paused agents\n await stopGatewayIfRunning(agent.code_name, frameworkAdapter);\n }\n\n // Note: gateway restart after integration changes removed — was killing gateways.\n // New env vars take effect on next gateway restart (manual or via manager cycle).\n\n // 7b. Auto-provision org/team default schedules\n let tasks = refreshData.scheduled_tasks ?? [];\n const existingTemplateIds = new Set(tasks.map((t) => t.template_id));\n\n try {\n const defaultsData = await api.post<{\n schedules: Array<{\n template_id: string;\n name: string;\n schedule_expr: string;\n prompt: string;\n session_target?: string;\n enabled?: boolean;\n }>;\n team_id: string;\n timezone: string;\n }>('/host/resolve-default-schedules', { agent_id: agent.agent_id });\n\n const missing = (defaultsData.schedules ?? []).filter(\n (s) => !existingTemplateIds.has(s.template_id),\n );\n\n // Always call ensure-default-schedules to insert missing tasks and\n // fix delivery_mode on existing tasks that don't match the template default.\n const allSchedules = defaultsData.schedules ?? [];\n if (allSchedules.length > 0) {\n await api.post('/host/ensure-default-schedules', {\n agent_id: agent.agent_id,\n team_id: defaultsData.team_id,\n timezone: defaultsData.timezone,\n schedules: allSchedules,\n });\n if (missing.length > 0) {\n log(`Auto-provisioned ${missing.length} default schedule(s) for '${agent.code_name}': ${missing.map((s) => s.template_id).join(', ')}`);\n }\n\n // Re-fetch tasks since schedules may have changed\n const freshData = await api.post<{ scheduled_tasks?: typeof tasks }>('/host/refresh', { agent_id: agent.agent_id });\n tasks = freshData.scheduled_tasks ?? tasks;\n }\n } catch (err) {\n log(`Default schedule provisioning failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n\n // 7c. Auto-provision Augmented plugin and kanban skill for all active agents\n if (agent.status === 'active') {\n // Augmented OpenClaw plugin — provides kanban_list, kanban_add, kanban_move,\n // kanban_update, kanban_done, status_standup, status_update tools natively\n if (frameworkAdapter.installPlugin) {\n try {\n const pluginPath = join(process.cwd(), 'packages', 'openclaw-plugin-augmented', 'src', 'index.ts');\n if (existsSync(pluginPath)) {\n frameworkAdapter.installPlugin(agent.code_name, 'augmented', pluginPath, {\n agtHost: requireHost(),\n agtApiKey: getApiKey() ?? undefined,\n agentId: agent.agent_id,\n });\n }\n } catch (err) {\n log(`Augmented plugin install failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n\n // Kanban skill files\n if (frameworkAdapter.installSkillFiles) {\n try {\n const skillContent = getBuiltInSkillContent('kanban');\n if (skillContent) {\n frameworkAdapter.installSkillFiles(agent.code_name, 'kanban', skillContent);\n }\n } catch (err) {\n log(`Kanban skill install failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n\n // Integration skills — deploy scope-filtered skills and clean up orphans\n if (frameworkAdapter.installSkillFiles) {\n const currentIntegrationSkillIds = new Set<string>();\n const installedIntegrationSkills: string[] = [];\n const { createHash } = await import('node:crypto');\n\n // ENG-4341: Index resolved integration contexts by slug for fast\n // lookup when rendering each skill. This map is small (one entry\n // per installed integration) and built once per refresh.\n // ENG-4629: prefer the new `integration_*` wire keys; fall back to\n // the legacy `plugin_*` shape so the CLI keeps working against\n // older API deploys mid-rollout. Each row also carries both\n // `integration_slug` and `plugin_slug` aliases.\n const refreshAny = refreshData as unknown as {\n integration_contexts?: Array<{ integration_slug?: string; plugin_slug?: string; values?: Record<string, unknown>; overrides?: string }>;\n plugin_contexts?: Array<{ plugin_slug?: string; integration_slug?: string; values?: Record<string, unknown>; overrides?: string }>;\n integration_skills?: unknown[];\n plugin_skills?: unknown[];\n integration_install_hooks?: Array<{ integration_slug?: string; plugin_slug?: string; script: string }>;\n plugin_install_hooks?: Array<{ plugin_slug?: string; integration_slug?: string; script: string }>;\n integration_toolkits?: Array<{ integration_slug?: string; plugin_slug?: string; toolkits: string[] }>;\n plugin_toolkits?: Array<{ plugin_slug?: string; integration_slug?: string; toolkits: string[] }>;\n };\n const contexts = refreshAny.integration_contexts ?? refreshAny.plugin_contexts ?? [];\n const contextBySlug = new Map<string, { values: Record<string, unknown>; overrides: string }>();\n for (const ctx of contexts) {\n const slug = ctx.integration_slug ?? ctx.plugin_slug;\n if (!slug) continue;\n contextBySlug.set(slug, { values: ctx.values ?? {}, overrides: (ctx.overrides ?? '').trim() });\n }\n\n // ENG-4502: group scopes by plugin and write a single plugin folder per\n // plugin. Claude Code's skill discovery does not recurse, so each\n // plugin's scopes go side-by-side under `plugin-<slug>/scopes/*.md`\n // while `plugin-<slug>/SKILL.md` is the umbrella index Claude actually\n // sees. Previous per-scope top-level folders (`plugin-<slug>-<id>/`)\n // are garbage-collected below alongside the orphan sweep.\n // ENG-4629: prefer integration_skills, fall back to plugin_skills.\n const integrationGroups = groupSkillsByIntegration(\n (refreshAny.integration_skills ?? refreshAny.plugin_skills ?? []) as IntegrationSkillInput[],\n );\n for (const [integrationSlug, scopes] of integrationGroups) {\n try {\n // ENG-4554: write under `integration-<slug>/` to align with the\n // post-cutover vocabulary. The orphan-sweep below recognises both\n // prefixes for one release so existing `plugin-<slug>/` folders\n // get cleaned up the first time the new manager runs.\n const integrationSkillId = `integration-${integrationSlug}`.replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');\n currentIntegrationSkillIds.add(integrationSkillId);\n\n // ENG-4341: substitute {{context.<field>}} placeholders and\n // append the freeform overrides as \"## Team Overrides\".\n const ctx = contextBySlug.get(integrationSlug);\n const renderedScopes: IntegrationSkillInput[] = scopes.map((s) => ({\n plugin_slug: s.plugin_slug,\n skill_id: s.skill_id,\n skill_name: s.skill_name,\n content: renderIntegrationSkillContent(\n s.content,\n ctx?.values ?? {},\n ctx?.overrides ?? '',\n (warning) => log(`[plugin-context] ${s.plugin_slug}/${s.skill_id}: ${warning}`),\n ),\n }));\n\n const bundle = buildIntegrationBundle(renderedScopes);\n\n // Hash the whole bundle so any scope addition / removal / content\n // tweak triggers a re-install. Unchanged bundles skip disk I/O.\n const contentHash = createHash('sha256').update(bundleFingerprint(bundle.files)).digest('hex').slice(0, 12);\n const hashKey = `plugin-skill:${agent.agent_id}:${integrationSkillId}`;\n if (knownSkillHashes.get(hashKey) === contentHash) continue;\n\n frameworkAdapter.installSkillFiles(agent.code_name, integrationSkillId, bundle.files);\n knownSkillHashes.set(hashKey, contentHash);\n for (const s of scopes) installedIntegrationSkills.push(s.skill_name);\n log(`Installed integration skill bundle '${integrationSkillId}' for '${agent.code_name}' (${scopes.length} scope(s))`);\n } catch (err) {\n log(`Integration skill install failed for '${agent.code_name}' / '${integrationSlug}': ${(err as Error).message}`);\n }\n }\n\n // Clean up orphaned plugin skills (removed plugins) and sweep the\n // pre-ENG-4502 flat layout (`plugin-<slug>-<skill_id>/`). After the\n // switch to one folder per plugin, those per-scope folders become\n // stale duplicates — leaving them confuses Claude's skill list.\n try {\n const { readdirSync, rmSync } = await import('node:fs');\n const { homedir } = await import('node:os');\n\n // All on-disk directories that installSkillFiles() may have\n // written into across the two frameworks. installSkillFiles for\n // Claude Code writes to the agent dir's `skills/` AND the project\n // dir's `.claude/skills/`; OpenClaw writes to `~/.openclaw-<cn>/\n // skills/`. The provision/.claude/skills path isn't an install\n // target but is swept defensively in case older manager versions\n // stashed copies there.\n const frameworkId = frameworkAdapter.id;\n const candidateSkillDirs: string[] = [\n // Claude Code — framework runtime tree\n join(homedir(), '.augmented', agent.code_name, 'skills'),\n // Claude Code — project tree\n join(homedir(), '.augmented', agent.code_name, 'project', '.claude', 'skills'),\n // OpenClaw — framework runtime tree\n join(homedir(), `.openclaw-${agent.code_name}`, 'skills'),\n // Defensive: legacy provision-side path, not currently an\n // install target but cheap to sweep.\n join(agentDir, '.claude', 'skills'),\n ];\n\n const existingDirs = candidateSkillDirs.filter((d) => existsSync(d));\n\n // Union of platform-managed entries across every existing skill\n // dir — gives a complete view of what might need cleaning even if\n // the agent has since switched framework or only some paths got\n // written during the transition.\n //\n // ENG-4554: accept BOTH the legacy `plugin-` and the current\n // `integration-` prefix. Old `plugin-<slug>/` folders aren't in\n // currentIntegrationSkillIds (which now holds `integration-<slug>`),\n // so they fall through to the orphan branch below and get removed\n // — that's the migration. The two-prefix logic stays for one\n // release; once everyone's on a manager that writes\n // `integration-`, the `plugin-` half can drop.\n const discoveredEntries = new Set<string>();\n for (const dir of existingDirs) {\n try {\n for (const entry of readdirSync(dir)) {\n if (entry.startsWith('plugin-') || entry.startsWith('integration-')) {\n discoveredEntries.add(entry);\n }\n }\n } catch { /* non-fatal per dir */ }\n }\n\n const removeSkillFolder = (entry: string, reason: string): void => {\n for (const dir of existingDirs) {\n const p = join(dir, entry);\n if (existsSync(p)) {\n rmSync(p, { recursive: true, force: true });\n }\n }\n log(`Removed ${reason} '${entry}' for '${agent.code_name}' (framework=${frameworkId})`);\n };\n\n for (const entry of discoveredEntries) {\n // An entry that isn't in currentIntegrationSkillIds is one of:\n // - a removed integration (uninstalled)\n // - a stale pre-ENG-4502 per-scope folder\n // (`plugin-<slug>-<skill_id>`)\n // - an ENG-4554 legacy `plugin-<slug>/` superseded by the new\n // `integration-<slug>/` written this poll\n // All three are right to delete.\n if (!currentIntegrationSkillIds.has(entry)) {\n removeSkillFolder(entry, 'orphaned skill folder');\n }\n }\n } catch (err) {\n log(`Integration skill cleanup failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n\n // Refresh the \"Available Skills\" index in CLAUDE.md by scanning installed skills\n try {\n const agentFwForIndex = agentFrameworkCache.get(agent.code_name) ?? 'openclaw';\n if (agentFwForIndex === 'claude-code') {\n await refreshSkillsIndexInClaudeMd(config.configDir, agent.code_name, log);\n }\n } catch (err) {\n log(`Skills index refresh failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n\n // Run integration on_install hooks (idempotent — runs every refresh).\n // Hooks are deduplicated by content hash so we only re-run on script changes.\n // ENG-4629: prefer integration_install_hooks; fall back to plugin_install_hooks.\n const installHooks = refreshAny.integration_install_hooks ?? refreshAny.plugin_install_hooks ?? [];\n if (frameworkAdapter.executePluginHook && installHooks.length) {\n for (const hook of installHooks) {\n const slug = hook.integration_slug ?? hook.plugin_slug;\n if (!slug) continue;\n try {\n const scriptHash = createHash('sha256').update(hook.script).digest('hex').slice(0, 12);\n const hookKey = `${agent.agent_id}:${frameworkAdapter.id}:plugin-hook:${slug}:on_install`;\n if (knownSkillHashes.get(hookKey) === scriptHash) continue;\n\n const result = await frameworkAdapter.executePluginHook({\n codeName: agent.code_name,\n pluginSlug: slug,\n hookName: 'on_install',\n script: hook.script,\n });\n\n if (result.exitCode === 0) {\n knownSkillHashes.set(hookKey, scriptHash);\n log(`Integration hook on_install '${slug}' succeeded for '${agent.code_name}' (${result.durationMs}ms)`);\n } else if (result.timedOut) {\n log(`Integration hook on_install '${slug}' TIMED OUT for '${agent.code_name}' after ${result.durationMs}ms`);\n } else {\n // Don't log raw stderr — hooks inherit manager env which may include secrets.\n // Log a fingerprint instead so failures are identifiable without leaking content.\n const stderrHash = createHash('sha256').update(result.stderr).digest('hex').slice(0, 12);\n // ENG-4510: on exit 127, bash prints `bash: <cmd>: command not found`\n // before any user code runs. extractCommandNotFound parses + validates\n // that line, but we still log only a hash — the project guideline is\n // hash-only logging by default, and operators can correlate the hash\n // with `pnpm install <pkg>` failures or recompute it locally with a\n // suspected binary name when triaging.\n const missingCmd = result.exitCode === 127 ? extractCommandNotFound(result.stderr) : null;\n const missingCmdHash = missingCmd ? createHash('sha256').update(missingCmd).digest('hex').slice(0, 8) : null;\n log(\n `Integration hook on_install '${slug}' exited ${result.exitCode} for '${agent.code_name}' ` +\n (missingCmdHash ? `[missing_command_hash=${missingCmdHash}] ` : '') +\n `[stderr_hash=${stderrHash} stderr_len=${result.stderr.length}]`,\n );\n }\n } catch (err) {\n log(`Integration hook on_install failed for '${agent.code_name}' / '${slug}': ${(err as Error).message}`);\n }\n }\n }\n\n // ENG-4421: install CLIs for toolkits declared by the agent's plugins.\n // Union across plugins, dedup per process via toolkitCliEnsured. Runs\n // only for claude-code hosts — openclaw toolkit install lives in its\n // own framework path and isn't wired yet.\n const agentFwForToolkits = agentFrameworkCache.get(agent.code_name) ?? 'openclaw';\n // ENG-4629: prefer integration_toolkits; fall back to plugin_toolkits.\n const toolkitsList = refreshAny.integration_toolkits ?? refreshAny.plugin_toolkits ?? [];\n if (agentFwForToolkits === 'claude-code' && toolkitsList.length) {\n const toolkitUnion = new Set<string>();\n for (const { toolkits } of toolkitsList) {\n for (const t of toolkits) toolkitUnion.add(t);\n }\n for (const toolkitSlug of toolkitUnion) {\n await ensureToolkitCli(toolkitSlug);\n }\n }\n\n // Hot-reload: notify running Claude Code session about new integration skills\n const agentFw = agentFrameworkCache.get(agent.code_name) ?? 'openclaw';\n if (agentFw === 'claude-code' && installedIntegrationSkills.length > 0 && isSessionHealthy(agent.code_name)) {\n const names = installedIntegrationSkills.join(', ');\n injectMessage(agent.code_name, 'system',\n `New integration skills installed: ${names}. These are available immediately — Claude Code loads skills on demand from .claude/skills/.`,\n { task_name: 'plugin-skill-update' }, log).catch(() => {});\n log(`[hot-reload] Notified '${agent.code_name}' about new integration skills: ${names}`);\n }\n }\n }\n\n // 7d. Fetch kanban board for prompt injection into board-aware templates\n let boardItems: BoardItem[] = [];\n const hasBoardTemplates = tasks.some((t) => BOARD_INJECT_TEMPLATES.has(t.template_id));\n if (hasBoardTemplates) {\n try {\n const boardData = await api.post<{ items?: BoardItem[] }>('/host/my-kanban', { agent_id: agent.agent_id });\n boardItems = (boardData.items ?? []).map(sanitizeBoardItem);\n kanbanBoardCache.set(agent.code_name, boardItems);\n } catch {\n // Non-fatal — board may not exist yet\n boardItems = kanbanBoardCache.get(agent.code_name) ?? [];\n }\n }\n\n // Resolve framework once for sections 8-11\n const agentFw = agentFrameworkCache.get(agent.code_name) ?? 'openclaw';\n\n // 8. Sync scheduled tasks\n const sessionMode = (refreshData.agent as Record<string, unknown>).session_mode as string ?? 'oneshot';\n\n if (agentFw === 'claude-code' && sessionMode === 'persistent') {\n // Claude Code persistent mode: manage long-running session with channels\n await ensurePersistentSession(agent, tasks, boardItems, refreshData);\n // ENG-4897: detect structural .mcp.json drift since the session was\n // launched and schedule a respawn. Runs only on persistent claude-code\n // agents — the only path that uses --strict-mcp-config and therefore\n // pins the MCP server set at startup. Other framework/mode combos\n // either re-read .mcp.json on every spawn or don't use it.\n try {\n checkMcpConfigDriftAndScheduleRestart(agent.code_name, psGetProjectDir(agent.code_name));\n } catch (err) {\n log(`[hot-reload] .mcp.json drift check failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n } else if (agentFw === 'claude-code' && tasks.length > 0) {\n // Claude Code oneshot mode: fire-and-forget via claude -p\n await syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData);\n } else if (frameworkAdapter.syncScheduledTasks && gatewayRunning && gatewayPort) {\n // Hash ONLY stable task fields (without board-injected prompts) to decide\n // whether a re-sync is needed. Board context changes every poll cycle as\n // kanban items move around — hashing it caused syncScheduledTasks to fire\n // on every cycle, spawning duplicate openclaw-cron processes.\n const stableTasksHash = createHash('sha256')\n .update(JSON.stringify(tasks))\n .digest('hex')\n .slice(0, 16);\n\n // Board context hash — triggers a re-sync when the board meaningfully changes\n // (items added/removed, moved between columns, reprioritised, or renamed)\n const boardHash = boardItems.length > 0\n ? createHash('sha256')\n .update(JSON.stringify(boardItems.map((b) => ({ id: b.id, title: b.title, status: b.status, priority: b.priority, deliverable: b.deliverable }))))\n .digest('hex')\n .slice(0, 16)\n : 'empty';\n\n // Include resolved models in the hash so default changes trigger resync\n const resolvedModels = resolveModelChain(refreshData);\n const modelsHash = createHash('sha256')\n .update(JSON.stringify(resolvedModels))\n .digest('hex')\n .slice(0, 16);\n\n const combinedHash = `${stableTasksHash}:${boardHash}:${modelsHash}`;\n const prevTasksHash = knownTasksHashes.get(agent.agent_id);\n\n if (combinedHash !== prevTasksHash) {\n // Inject board context into prompts for board-aware templates\n const enrichedTasks = tasks.map((t) => {\n if (BOARD_INJECT_TEMPLATES.has(t.template_id) && boardItems.length > 0) {\n const template = PLAN_TEMPLATES.has(t.template_id) ? 'morning-plan' : 'follow-up';\n const boardPrefix = formatBoardForPrompt(boardItems, template);\n return { ...t, prompt: boardPrefix + t.prompt };\n }\n return t;\n });\n\n try {\n const token = readGatewayToken(agent.code_name);\n await frameworkAdapter.syncScheduledTasks(\n agent.code_name,\n enrichedTasks.map((t) => ({\n id: t.id,\n template_id: t.template_id,\n name: t.name,\n schedule_kind: t.schedule_kind as 'cron' | 'every' | 'at',\n schedule_expr: t.schedule_expr,\n schedule_every: t.schedule_every,\n schedule_at: t.schedule_at,\n timezone: t.timezone,\n prompt: t.prompt,\n session_target: t.session_target as 'main' | 'isolated',\n delivery_mode: t.delivery_mode as 'announce' | 'none',\n delivery_channel: t.delivery_channel,\n delivery_to: t.delivery_to,\n enabled: t.enabled,\n model_tier: ((t as Record<string, unknown>).model_tier as 'primary' | 'secondary' | 'tertiary' | undefined) ?? 'primary',\n })),\n gatewayPort,\n token,\n {\n models: resolvedModels,\n },\n );\n knownTasksHashes.set(agent.agent_id, combinedHash);\n log(`Scheduled tasks synced for '${agent.code_name}' (${enrichedTasks.length} task(s))`);\n } catch (err) {\n log(`Failed to sync scheduled tasks for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n }\n\n // Update task display info map for cron health alerts\n for (const t of tasks) {\n const jobName = `aug:${t.template_id}:${t.id ?? t.name.toLowerCase().replace(/\\s+/g, '-')}`;\n taskDisplayInfo.set(`${agent.code_name}:${jobName}`, {\n taskName: t.name,\n schedule: t.schedule_expr ?? t.schedule_every ?? '',\n agentDisplayName: agent.display_name,\n });\n }\n\n // 9. Harvest cron run results for standup/task updates (OpenClaw only — throttled to reduce process spawning)\n if (agentFw === 'openclaw' && gatewayRunning && gatewayPort && tasks.length > 0) {\n const lastHarvest = lastHarvestAt.get(agent.code_name) ?? 0;\n if (Date.now() - lastHarvest >= HARVEST_INTERVAL_MS) {\n lastHarvestAt.set(agent.code_name, Date.now());\n harvestCronResults(agent.code_name, tasks, gatewayPort).catch((err) => {\n log(`Cron result harvest failed for '${agent.code_name}': ${(err as Error).message}`);\n });\n }\n }\n\n // 10. Immediate work trigger — if webapp signalled a new task, fire kanban-work now\n {\n const triggerAt = (refreshData.agent as Record<string, unknown>).work_trigger_at as string | null;\n if (triggerAt) {\n const triggerTs = new Date(triggerAt).getTime();\n const lastTrigger = lastWorkTriggerAt.get(agent.code_name) ?? 0;\n if (triggerTs > lastTrigger) {\n lastWorkTriggerAt.set(agent.code_name, triggerTs);\n\n if (agentFw === 'openclaw' && gatewayRunning && gatewayPort) {\n // OpenClaw: find kanban-work job ID from cron jobs file\n const homeDir = process.env['HOME'] ?? '/tmp';\n const jobsPath = join(homeDir, `.openclaw-${agent.code_name}`, 'cron', 'jobs.json');\n if (existsSync(jobsPath)) {\n try {\n const jobsData = JSON.parse(readFileSync(jobsPath, 'utf-8'));\n const kanbanJob = (jobsData.jobs ?? []).find((j: Record<string, unknown>) =>\n typeof j.name === 'string' && j.name.includes('kanban-work'),\n );\n if (kanbanJob?.id) {\n const cliBin = resolveAgentFramework(agent.code_name).cliBinary ?? 'openclaw';\n log(`Work trigger: firing kanban-work for '${agent.code_name}'`);\n execFilePromise(cliBin, ['--profile', agent.code_name, 'cron', 'run', kanbanJob.id])\n .then(() => log(`Work trigger succeeded for '${agent.code_name}'`))\n .catch((err) => log(`Work trigger failed for '${agent.code_name}': ${(err as Error).message}`));\n }\n } catch { /* jobs.json read failed — non-fatal */ }\n }\n } else if (agentFw === 'claude-code') {\n // Claude Code: for persistent sessions, inject via tmux; for oneshot, fire claude -p\n if (sessionMode === 'persistent') {\n // Persistent mode: inject into tmux session if healthy, skip if still booting\n if (isSessionHealthy(agent.code_name)) {\n const todayItem = boardItems.find((b) => b.status === 'todo');\n const taskHint = todayItem ? ` Top item: \"${todayItem.title}\" (priority ${todayItem.priority}).` : '';\n injectMessage(agent.code_name, 'task', `New work triggered. Check your kanban board with kanban_list and pick up the next item.${taskHint}`, {\n task_name: 'kanban-work-trigger',\n }, log);\n log(`[persistent-session] Work trigger injected for '${agent.code_name}'`);\n } else {\n log(`[persistent-session] Work trigger skipped for '${agent.code_name}' — session not yet healthy`);\n }\n } else {\n fireClaudeWorkTrigger(agent.code_name, agent.agent_id, boardItems);\n }\n }\n }\n }\n }\n\n // 11. Clean up stale cron session files to prevent context overflow (OpenClaw only)\n if (agentFw === 'openclaw') {\n cleanupStaleSessions(agent.code_name);\n }\n\n // 12. Detect newly-done kanban items and send notifications (runs every cycle)\n {\n const agentId = codeNameToAgentId.get(agent.code_name);\n if (agentId) {\n try {\n const boardData = await api.post<{ items?: BoardItem[] }>('/host/my-kanban', { agent_id: agentId });\n const freshBoard = (boardData.items ?? []).map(sanitizeBoardItem);\n const freshDoneIds = new Set(freshBoard.filter((b) => b.status === 'done' || b.status === 'failed').map((b) => b.id));\n\n const previousDoneIds = notifyBoardCache.get(agent.code_name);\n notifyBoardCache.set(agent.code_name, freshDoneIds);\n\n // Skip first cycle — no previous state to compare against\n if (!previousDoneIds) {\n log(`[${agent.code_name}] Board diff: initial cache (${freshDoneIds.size} done items)`);\n } else {\n const newlyDone = freshBoard.filter(\n (b) => (b.status === 'done' || b.status === 'failed') && !previousDoneIds.has(b.id),\n );\n\n log(`[${agent.code_name}] Board diff: ${previousDoneIds.size} prev done → ${freshDoneIds.size} now, ${newlyDone.length} newly done`);\n\n if (newlyDone.length > 0) {\n const displayName = agentDisplayNames.get(agent.code_name) ?? agent.code_name;\n for (const item of newlyDone) {\n log(`Newly done: '${item.title}' notify_channel=${item.notify_channel ?? 'none'} notify_to=${item.notify_to ?? 'none'}`);\n if (item.notify_channel && item.notify_to) {\n const isFailed = item.status === 'failed';\n const resultLine = item.result ? `\\n${isFailed ? 'Reason' : 'Result'}: ${item.result}` : '';\n const emoji = isFailed ? '❌' : '✅';\n const message = `${emoji} ${isFailed ? 'Task Failed' : 'Task Complete'} — ${displayName}\\n${item.title}${resultLine}`;\n sendTaskNotification(agent.code_name, item.notify_channel, item.notify_to, message).catch((err) => {\n log(`sendTaskNotification failed for '${agent.code_name}': ${(err as Error).message}`);\n });\n }\n }\n }\n }\n // Check for stale in_progress items\n const staleItems = freshBoard.filter((b) => {\n if (b.status !== 'in_progress' || !b.updated_at) return false;\n const age = Date.now() - new Date(b.updated_at).getTime();\n return age > STALE_TASK_THRESHOLD_MS && !alertedStaleItems.has(`${agent.code_name}:${b.id}`);\n });\n\n if (staleItems.length > 0) {\n const displayName = agentDisplayNames.get(agent.code_name) ?? agent.code_name;\n for (const item of staleItems) {\n const age = Math.round((Date.now() - new Date(item.updated_at!).getTime()) / 60_000);\n log(`Stale task: '${item.title}' (id=${item.id}) in_progress for ${age}m — auto-failing for '${agent.code_name}'`);\n alertedStaleItems.add(`${agent.code_name}:${item.id}`);\n\n // Auto-fail the task via API (try ID first, fallback to title)\n try {\n const failResult = await api.post<{ ok?: boolean; updated?: number; error?: string }>('/host/kanban', {\n agent_id: agentId,\n update: [{ id: item.id, title: item.title, status: 'failed', result: `Timed out — in progress for ${age} minutes with no update` }],\n });\n log(`Auto-fail result for '${item.title}': updated=${failResult.updated}`);\n // Mark as done only when mutation actually succeeded,\n // so we don't suppress legitimate later notifications.\n if ((failResult.updated ?? 0) > 0 || failResult.ok === true) {\n freshDoneIds.add(item.id);\n }\n } catch (err) {\n log(`Auto-fail API error for '${item.title}': ${(err as Error).message}`);\n }\n\n // Notify\n const message = `⏳ Task Timed Out — ${displayName}\\n${item.title}\\nIn progress for ${age} minutes with no update — auto-failed`;\n if (item.notify_channel && item.notify_to) {\n sendTaskNotification(agent.code_name, item.notify_channel, item.notify_to, message).catch(() => {});\n }\n sendSlackWebhookMessage(`⏳ *Task Timed Out* — *${displayName}*\\n*${item.title}*\\nIn progress for ${age} minutes — auto-failed`).catch(() => {});\n }\n }\n\n // Clear stale alerts for items no longer in_progress (scoped to this agent)\n const prefix = `${agent.code_name}:`;\n for (const key of alertedStaleItems) {\n if (key.startsWith(prefix)) {\n const itemId = key.slice(prefix.length);\n if (!freshBoard.some((b) => b.id === itemId && b.status === 'in_progress')) {\n alertedStaleItems.delete(key);\n }\n }\n }\n } catch (err) {\n log(`Board diff failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n }\n\n // 13. Memory sync — upload local memory files to DB, download DB memories to local\n if (frameworkId === 'claude-code' && agent.status === 'active') {\n try {\n await syncMemories(agent, config.configDir, log);\n } catch (err) {\n log(`Memory sync failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n\n // ENG-4473: Re-capture writtenHashes AFTER all tracked-file writers have\n // finished this poll — channel credentials (step 5), the direct-chat\n // provisioner (step 5b), managed-provider proxies (step 7), etc. The\n // provisioner write loop at step 3 records `writtenHashes` before those\n // later writers run, so the drift check at the TOP of the next poll\n // sees the final file state vs the step-3 hash and mis-reports every\n // poll as drifted (even when the state is perfectly stable). Re-hashing\n // here converges writtenHashes to what's actually on disk after this\n // agent's full processing, so:\n // - genuine external edits between polls are still detected (drift\n // check runs BEFORE our writers in the next poll)\n // - our own multi-writer sequence stops reading as drift\n const trackedFiles = frameworkAdapter.driftTrackedFiles();\n if (trackedFiles.length > 0 && existsSync(agentDir)) {\n // Rebuild from scratch so files no longer tracked (or missing on disk)\n // don't leave stale hashes that would mis-report drift next poll.\n const hashes = new Map<string, string>();\n for (const file of trackedFiles) {\n const h = hashFile(join(agentDir, file));\n if (h) hashes.set(file, h);\n }\n writtenHashes.set(agent.agent_id, hashes);\n } else {\n writtenHashes.delete(agent.agent_id);\n }\n\n agentStates.push({\n agentId: agent.agent_id,\n codeName: agent.code_name,\n status: agent.status,\n charterVersion,\n toolsVersion,\n secretsHash,\n lastRefreshAt: now,\n lastProvisionAt,\n lastDriftCheckAt,\n lastSecretsProvisionAt,\n gatewayPort,\n gatewayPid,\n gatewayRunning,\n acpSessions: [],\n });\n}\n\n// ---------------------------------------------------------------------------\n// Session cleanup — prevent context overflow from accumulated cron sessions\n// ---------------------------------------------------------------------------\n\nconst CRON_SESSION_KEEP_COUNT = 10;\nconst CRON_RUN_RETENTION_DAYS = 7;\nconst lastCleanupAt = new Map<string, number>();\nconst CLEANUP_INTERVAL_MS = 60_000; // Run cleanup every 1 minute per agent\n\nfunction cleanupStaleSessions(codeName: string): void {\n // Run immediately on first poll (lastRun defaults to 0), then throttle\n const lastRun = lastCleanupAt.get(codeName) ?? 0;\n if (lastRun > 0 && Date.now() - lastRun < CLEANUP_INTERVAL_MS) return;\n lastCleanupAt.set(codeName, Date.now());\n\n const homeDir = process.env['HOME'] ?? '/tmp';\n\n // 1. Clean CRON session files only (preserve chat/Slack/Telegram sessions)\n for (const agentDir of ['main', codeName]) {\n const sessionsDir = join(homeDir, `.openclaw-${codeName}`, 'agents', agentDir, 'sessions');\n cleanupCronSessions(sessionsDir, CRON_SESSION_KEEP_COUNT);\n }\n\n // 2. Clean cron run logs older than 7 days\n const cronRunsDir = join(homeDir, `.openclaw-${codeName}`, 'cron', 'runs');\n cleanupOldFiles(cronRunsDir, CRON_RUN_RETENTION_DAYS, '.jsonl');\n\n // 3. Clear stale \"running\" state from cron jobs.json.\n // If a cron run was in progress when the gateway crashed, the state persists\n // as \"running\" and blocks all future runs with \"already-running\". Reset any\n // run that's been \"running\" for more than 5 minutes.\n const cronJobsPath = join(homeDir, `.openclaw-${codeName}`, 'cron', 'jobs.json');\n clearStaleCronRunState(cronJobsPath);\n}\n\n/**\n * Clean up cron session entries from sessions.json and their corresponding\n * .jsonl files. Preserves chat, Slack, Telegram, and other non-cron sessions.\n *\n * Cron sessions have keys matching `agent:*:cron:*:run:*` in sessions.json.\n */\nfunction cleanupCronSessions(sessionsDir: string, keepCount: number): void {\n const indexPath = join(sessionsDir, 'sessions.json');\n if (!existsSync(indexPath)) return;\n\n try {\n const raw = readFileSync(indexPath, 'utf-8');\n const index = JSON.parse(raw) as Record<string, { sessionId?: string; updatedAt?: number }>;\n\n // Find cron run entries (pattern: agent:*:cron:*:run:*)\n const cronRunKeys = Object.keys(index)\n .filter((k) => k.includes(':cron:') && k.includes(':run:'))\n .map((k) => ({\n key: k,\n sessionId: index[k]?.sessionId,\n updatedAt: index[k]?.updatedAt ?? 0,\n }))\n .sort((a, b) => b.updatedAt - a.updatedAt); // newest first\n\n if (cronRunKeys.length <= keepCount) return;\n\n // Keep the most recent N, delete the rest\n const toDelete = cronRunKeys.slice(keepCount);\n let deletedFiles = 0;\n\n for (const entry of toDelete) {\n // Remove from index\n delete index[entry.key];\n\n // Delete the session .jsonl file\n if (entry.sessionId) {\n const sessionFile = join(sessionsDir, `${entry.sessionId}.jsonl`);\n try {\n if (existsSync(sessionFile)) {\n unlinkSync(sessionFile);\n deletedFiles++;\n }\n } catch { /* ignore */ }\n }\n }\n\n // Also clean up parent cron session keys that have no remaining runs\n const cronParentKeys = Object.keys(index).filter(\n (k) => k.includes(':cron:') && !k.includes(':run:') && k !== 'agent:main:main',\n );\n for (const parentKey of cronParentKeys) {\n const hasRuns = Object.keys(index).some(\n (k) => k.startsWith(parentKey + ':run:'),\n );\n if (!hasRuns) {\n const parentSessionId = index[parentKey]?.sessionId;\n delete index[parentKey];\n if (parentSessionId) {\n try {\n const f = join(sessionsDir, `${parentSessionId}.jsonl`);\n if (existsSync(f)) { unlinkSync(f); deletedFiles++; }\n } catch { /* ignore */ }\n }\n }\n }\n\n // Write updated index\n writeFileSync(indexPath, JSON.stringify(index));\n\n if (toDelete.length > 0) {\n log(`Cleaned ${toDelete.length} cron session(s) and ${deletedFiles} file(s) from ${sessionsDir}`);\n }\n } catch { /* non-fatal */ }\n}\n\nconst STALE_RUN_TIMEOUT_MS = 5 * 60_000; // 5 minutes\n\nfunction clearStaleCronRunState(jobsPath: string): void {\n if (!existsSync(jobsPath)) return;\n\n try {\n const raw = readFileSync(jobsPath, 'utf-8');\n const data = JSON.parse(raw) as Record<string, unknown>;\n const jobs = (data.jobs ?? data) as Array<Record<string, unknown>>;\n if (!Array.isArray(jobs)) return;\n\n let changed = false;\n const now = Date.now();\n\n for (const job of jobs) {\n const state = job.state as Record<string, unknown> | undefined;\n if (!state) continue;\n\n // Check if a run is stuck in \"running\" state\n // OpenClaw uses `running: true` + `runningAtMs` (not `runStartedAtMs`)\n const runStartedAt = (state.runningAtMs ?? state.runStartedAtMs) as number | undefined;\n const isRunning = state.running === true || state.status === 'running';\n\n if (isRunning && runStartedAt && (now - runStartedAt) > STALE_RUN_TIMEOUT_MS) {\n state.running = false;\n delete state.status;\n delete state.runStartedAtMs;\n delete state.currentRunId;\n changed = true;\n log(`Cleared stale running state for cron job '${job.name}' (stuck for ${Math.round((now - runStartedAt) / 60_000)}min)`);\n } else if (isRunning && !runStartedAt) {\n // running=true but no timestamp — clear it\n state.running = false;\n changed = true;\n log(`Cleared stale running state for cron job '${job.name}' (no start timestamp)`);\n }\n }\n\n if (changed) {\n writeFileSync(jobsPath, JSON.stringify(data, null, 2));\n }\n } catch { /* non-fatal */ }\n}\n\nfunction cleanupOldFiles(dir: string, maxAgeDays: number, ext: string): void {\n if (!existsSync(dir)) return;\n\n const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;\n let removed = 0;\n\n try {\n for (const f of readdirSync(dir)) {\n if (!f.endsWith(ext)) continue;\n const fullPath = join(dir, f);\n try {\n const st = statSync(fullPath);\n if (st.mtimeMs < cutoff) {\n unlinkSync(fullPath);\n removed++;\n }\n } catch { /* ignore */ }\n }\n\n if (removed > 0) {\n log(`Cleaned ${removed} old cron run log(s) from ${dir}`);\n }\n } catch { /* non-fatal */ }\n}\n\n// ---------------------------------------------------------------------------\n// Claude Code in-process scheduler\n// ---------------------------------------------------------------------------\n\nimport {\n syncTasksToScheduler,\n getReadyTasks,\n markTaskFired,\n loadSchedulerState,\n findTaskByTemplate,\n getProjectDir as ccGetProjectDir,\n type SchedulerTaskInput,\n type SchedulerTaskState,\n} from './claude-scheduler.js';\n\n// Track in-flight Claude scheduled tasks and concurrency per agent\nconst inFlightClaudeTasks = new Set<string>();\nconst claudeTaskConcurrency = new Map<string, number>();\nconst MAX_CLAUDE_CONCURRENCY = 2;\n\n// In-memory scheduler states (loaded from disk on first access)\nconst claudeSchedulerStates = new Map<string, ReturnType<typeof loadSchedulerState>>();\n\nasync function syncAndCheckClaudeScheduler(\n agent: { agent_id: string; code_name: string; display_name: string },\n tasks: Array<Record<string, unknown>>,\n boardItems: Array<{ id: string; title: string; status: string; priority: number; estimated_minutes?: number; deliverable?: string; result?: string; notify_channel?: string; notify_to?: string }>,\n refreshData: Record<string, unknown>,\n): Promise<void> {\n const codeName = agent.code_name;\n\n // Hash-based change detection (same as OpenClaw path)\n const stableTasksHash = createHash('sha256')\n .update(JSON.stringify(tasks))\n .digest('hex')\n .slice(0, 16);\n\n const boardHash = boardItems.length > 0\n ? createHash('sha256')\n .update(JSON.stringify(boardItems.map((b) => ({ id: b.id, title: b.title, status: b.status, priority: b.priority, deliverable: b.deliverable }))))\n .digest('hex')\n .slice(0, 16)\n : 'empty';\n\n const resolvedModels = resolveModelChain(refreshData);\n const modelsHash = createHash('sha256')\n .update(JSON.stringify(resolvedModels))\n .digest('hex')\n .slice(0, 16);\n\n const combinedHash = `${stableTasksHash}:${boardHash}:${modelsHash}`;\n const prevHash = knownTasksHashes.get(agent.agent_id);\n\n if (combinedHash !== prevHash) {\n // Sync tasks to scheduler state\n const taskInputs: SchedulerTaskInput[] = tasks.map((t) => ({\n id: t.id as string,\n template_id: t.template_id as string,\n name: t.name as string,\n schedule_kind: t.schedule_kind as 'cron' | 'every' | 'at',\n schedule_expr: (t.schedule_expr as string) ?? null,\n schedule_every: (t.schedule_every as string) ?? null,\n schedule_at: (t.schedule_at as string) ?? null,\n timezone: (t.timezone as string) ?? 'UTC',\n prompt: (t.prompt as string) ?? '',\n session_target: (t.session_target as string) ?? 'isolated',\n delivery_mode: (t.delivery_mode as string) ?? 'none',\n delivery_channel: (t.delivery_channel as string) ?? null,\n delivery_to: (t.delivery_to as string) ?? null,\n enabled: (t.enabled as boolean) ?? true,\n triggered_at: (t.triggered_at as string) ?? null,\n }));\n\n const state = syncTasksToScheduler(codeName, agent.agent_id, taskInputs);\n claudeSchedulerStates.set(codeName, state);\n knownTasksHashes.set(agent.agent_id, combinedHash);\n log(`[claude-scheduler] Tasks synced for '${codeName}' (${taskInputs.length} task(s))`);\n }\n\n // Load state if not cached\n if (!claudeSchedulerStates.has(codeName)) {\n claudeSchedulerStates.set(codeName, loadSchedulerState(codeName));\n }\n\n const state = claudeSchedulerStates.get(codeName)!;\n const ready = getReadyTasks(state, inFlightClaudeTasks);\n if (ready.length === 0) return;\n\n for (const task of ready) {\n if ((claudeTaskConcurrency.get(codeName) ?? 0) >= MAX_CLAUDE_CONCURRENCY) break;\n\n // ENG-4410: Skip kanban-work when board has no actionable items.\n // Saves an LLM call every 5 minutes when the agent has nothing to do.\n if (KANBAN_WORK_TEMPLATES.has(task.templateId) && !hasActionableItems(boardItems)) {\n log(`[claude-scheduler] Skipping '${task.name}' for '${codeName}' — board is empty`);\n const updated = markTaskFired(codeName, task.taskId, 'ok');\n claudeSchedulerStates.set(codeName, updated);\n continue;\n }\n\n // Enrich prompt with board context\n let prompt = task.prompt;\n if (BOARD_INJECT_TEMPLATES.has(task.templateId) && boardItems.length > 0) {\n const template = PLAN_TEMPLATES.has(task.templateId) ? 'morning-plan' : 'follow-up';\n const boardPrefix = formatBoardForPrompt(boardItems, template);\n prompt = boardPrefix + prompt;\n }\n\n // For kanban-work: move the top \"todo\" item to in_progress before starting\n if (KANBAN_WORK_TEMPLATES.has(task.templateId)) {\n const todayItem = boardItems.find((b) => b.status === 'todo');\n if (todayItem) {\n try {\n await api.post('/host/kanban', {\n agent_id: agent.agent_id,\n update: [{ id: todayItem.id, title: todayItem.title, status: 'in_progress' }],\n });\n log(`[claude-scheduler] Moved '${todayItem.title}' to in_progress for '${codeName}'`);\n } catch (err) {\n log(`[claude-scheduler] Failed to move item to in_progress: ${(err as Error).message}`);\n }\n }\n }\n\n inFlightClaudeTasks.add(task.taskId);\n claudeTaskConcurrency.set(codeName, (claudeTaskConcurrency.get(codeName) ?? 0) + 1);\n\n log(`[claude-scheduler] Firing '${task.name}' for '${codeName}'`);\n\n executeAndProcessClaudeTask(codeName, agent.agent_id, task, prompt)\n .finally(() => {\n inFlightClaudeTasks.delete(task.taskId);\n claudeTaskConcurrency.set(codeName, Math.max(0, (claudeTaskConcurrency.get(codeName) ?? 1) - 1));\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Runs telemetry helpers (ENG-4561)\n// ---------------------------------------------------------------------------\n//\n// Wraps /host/runs/start and /host/runs/finish so each Claude Code spawn site\n// records a runs row. Best-effort — neither call ever throws to the caller;\n// telemetry failures log and return null so the task itself still fires. The\n// Claude Code spawn passes AGT_RUN_ID into the child env so MCP tools can\n// stamp the run_id onto rows they insert (wiring on the API side lands in a\n// follow-up; the env var is harmless until then).\n\ninterface StartRunOptions {\n agent_id: string;\n source_type: 'kanban' | 'scheduled_task' | 'channel' | 'manual' | 'system';\n source_ref?: string;\n channel?: string;\n trace_id?: string;\n metadata?: Record<string, unknown>;\n // ENG-4540 Phase 2: when set + source_type='scheduled_task', the API also\n // creates an in_progress kanban card tied to this run. The card id comes\n // back so finishRun can close it.\n materialize_kanban?: { title: string; description?: string; priority?: number };\n}\n\ninterface StartRunResult {\n run_id: string | null;\n kanban_item_id: string | null;\n}\n\nasync function startRun(opts: StartRunOptions): Promise<StartRunResult> {\n try {\n const res = await api.post<{ run_id?: string; kanban_item_id?: string | null }>(\n '/host/runs/start',\n opts,\n );\n return { run_id: res.run_id ?? null, kanban_item_id: res.kanban_item_id ?? null };\n } catch (err) {\n // Hash the raw error text so durable manager logs don't carry\n // potentially-sensitive payloads (per CLAUDE.md log policy). The\n // 12-char prefix is enough to correlate with server-side logs.\n const errText = err instanceof Error ? err.message : String(err);\n const errId = createHash('sha256').update(errText).digest('hex').slice(0, 12);\n log(`[runs] start failed for agent_id=${opts.agent_id} source_type=${opts.source_type} error_id=${errId}`);\n return { run_id: null, kanban_item_id: null };\n }\n}\n\ninterface FinishRunOptions {\n metadata?: Record<string, unknown>;\n outcomeMessage?: string;\n // ENG-4540 Phase 2: when startRun materialised a kanban card, pass the id\n // back here so the API closes the card to done/failed in lockstep.\n completeKanbanItemId?: string | null;\n result?: string;\n}\n\nasync function finishRun(\n runId: string,\n outcome: 'completed' | 'failed' | 'cancelled' | 'timeout',\n options: FinishRunOptions = {},\n): Promise<void> {\n try {\n await api.post('/host/runs/finish', {\n run_id: runId,\n outcome,\n outcome_message: options.outcomeMessage,\n metadata: options.metadata,\n complete_kanban_item_id: options.completeKanbanItemId ?? undefined,\n result: options.result,\n });\n } catch (err) {\n const errText = err instanceof Error ? err.message : String(err);\n const errId = createHash('sha256').update(errText).digest('hex').slice(0, 12);\n log(`[runs] finish failed for run_id=${runId} outcome=${outcome} error_id=${errId}`);\n }\n}\n\n// Fetch the recent output text of prior ticks of the same scheduled task so\n// the wrapper can inject \"here's what you already reported today\" context.\n// Best-effort — a lookup failure must not block the firing; the agent just\n// runs without prior context (same as the pre-injection behaviour).\nconst MAX_PRIOR_RUNS = 5;\nasync function fetchPriorScheduledRuns(\n agentId: string,\n taskId: string,\n): Promise<PriorRun[]> {\n try {\n const data = await api.post<{\n runs?: Array<{ started_at: string; output_text: string | null }>;\n }>('/host/scheduled-tasks/recent-outputs', {\n agent_id: agentId,\n task_id: taskId,\n since_hours: 24,\n limit: MAX_PRIOR_RUNS,\n });\n // Defensive client-side clamp: even though the request asks for\n // `limit: MAX_PRIOR_RUNS`, an upstream regression that ever over-returns\n // would balloon prompt size and break scheduled-task executions. Slice\n // here so the wrapper can never see more than the contracted maximum.\n const rows = Array.isArray(data?.runs) ? data.runs.slice(0, MAX_PRIOR_RUNS) : [];\n return rows\n .filter((r): r is { started_at: string; output_text: string } => typeof r.output_text === 'string' && r.output_text.length > 0)\n .map((r) => ({ startedAt: r.started_at, output: r.output_text }));\n } catch (err) {\n const errText = err instanceof Error ? err.message : String(err);\n // Log a short hash instead of the raw error text — upstream error\n // messages can include row data, JWT fragments, or other sensitive\n // context. Matches the redaction pattern already used elsewhere in\n // this file (see the `errId` log around line 1691).\n const errId = createHash('sha256').update(errText).digest('hex').slice(0, 12);\n log(`[runs] prior-runs lookup failed for task_id=${taskId} error_id=${errId}`);\n return [];\n }\n}\n\nasync function executeAndProcessClaudeTask(\n codeName: string,\n agentId: string,\n task: SchedulerTaskState,\n prompt: string,\n): Promise<void> {\n const projectDir = ccGetProjectDir(codeName);\n const mcpConfigPath = join(projectDir, '.mcp.json');\n\n // ENG-4561: track the run across the try/catch boundary so the failure\n // path can close it.\n // ENG-4540 Phase 2: also track the materialised kanban card id so the\n // success/failure paths can close it in lockstep with the run. taskResult\n // captures the truncated stdout — populated in both the success path and\n // the ChildProcessError branch so failed runs still surface partial agent\n // output on the closed card.\n let runId: string | null = null;\n let kanbanItemId: string | null = null;\n let taskResult: string | undefined;\n\n sanitizeMcpJson(mcpConfigPath, requireHost());\n\n // Wrap in the scheduled-task execution preamble so the agent knows this is a\n // non-interactive run (no human to answer clarifications) and that only its\n // final text response reaches the recipient. Raw prompts stored in\n // scheduler-state.json are never pre-wrapped; the claude-code framework\n // adapter's mapScheduledTasks wraps for the native /schedule path but this\n // isolated-session path was missing the wrapper. Idempotent — safe to call\n // on already-wrapped prompts.\n //\n // Also fetch the prior outputs of this scheduled task within the last 24h so\n // the wrapper can inject them as context — the agent then surfaces only what\n // is new or changed instead of re-reporting the same items each tick.\n const priorRuns = await fetchPriorScheduledRuns(agentId, task.taskId);\n prompt = wrapScheduledTaskPrompt(prompt, { priorRuns });\n\n try {\n const claudeMdPath = join(projectDir, 'CLAUDE.md');\n // Build --allowedTools from the agent's configured MCP servers\n const serverNames: string[] = [];\n if (existsSync(mcpConfigPath)) {\n try {\n const d = JSON.parse(readFileSync(mcpConfigPath, 'utf-8'));\n if (d.mcpServers) serverNames.push(...Object.keys(d.mcpServers));\n } catch { /* non-fatal */ }\n }\n // ENG-4487: includes Skill + Agent via shared helper so scheduled tasks\n // can actually invoke plugin skills in `.claude/skills/`. Previously this\n // list omitted them and agents silently ran without their plugins.\n //\n // ENG-4671: kept here for compatibility, but in auto mode (below) the\n // classifier — not the allowlist — is the primary gate. buildAllowedTools\n // also has a known mismatch (e.g. kanban tools live under the\n // `augmented` MCP server, not `kanban`), so the allowlist alone would\n // deny calls Claude needs to make. Auto mode side-steps that.\n const allowedTools = buildAllowedTools(serverNames);\n\n // ENG-4671: Claude CLI refuses --dangerously-skip-permissions (and its\n // alias --permission-mode bypassPermissions) under root, which broke every\n // scheduled task on AWS hosts where the manager supervises as root.\n // --permission-mode auto routes each tool call through a server-side\n // classifier that approves routine work and blocks escalations, with no\n // permission prompts in headless -p — the failure mode is \"task fails one\n // cycle, retries next\" rather than the silent root-error hang. Verified\n // live on the Scout AWS host before merging.\n const claudeArgs = [\n '-p', prompt,\n '--output-format', 'text',\n '--mcp-config', mcpConfigPath,\n '--strict-mcp-config',\n '--permission-mode', 'auto',\n '--allowedTools', allowedTools,\n ];\n // Inject agent identity as system prompt so personality is always present\n if (existsSync(claudeMdPath)) {\n claudeArgs.push('--system-prompt-file', claudeMdPath);\n }\n // Source .env.integrations for integration credentials (Xero, etc.)\n const childEnv = { ...process.env };\n const envIntPath = join(projectDir, '.env.integrations');\n if (existsSync(envIntPath)) {\n try {\n for (const line of readFileSync(envIntPath, 'utf-8').split('\\n')) {\n if (!line || line.startsWith('#') || !line.includes('=')) continue;\n const eqIdx = line.indexOf('=');\n childEnv[line.slice(0, eqIdx)] = line.slice(eqIdx + 1);\n }\n } catch { /* non-fatal */ }\n }\n // Honor the host's configured auth mode (ENG-4417). Force-refresh +\n // fail-closed: if we can't confirm the auth config, don't spawn.\n try {\n await applyClaudeAuthToEnv(childEnv, 'claude-scheduler');\n } catch (err) {\n log(`[claude-scheduler] Skipping task '${task.name}' for '${codeName}' — auth resolve failed: ${(err as Error).message}`);\n return;\n }\n\n // ENG-4561: open a runs telemetry record around the spawn. AGT_RUN_ID\n // is exposed in childEnv so MCP tools can stamp it onto inserted rows.\n //\n // ENG-4540 Phase 2: ask the API to materialise an in_progress kanban\n // card tied to this run. The task firing then shows up live on the\n // user's Todo column, and finishRun closes it to done/failed. If the\n // materialisation fails (kanban_item_id stays null) the task still\n // fires — we just lose the visible record of the firing.\n const startResult = await startRun({\n agent_id: agentId,\n source_type: 'scheduled_task',\n source_ref: task.taskId,\n metadata: { template_id: task.templateId, name: task.name },\n materialize_kanban: { title: task.name, priority: 2 },\n });\n runId = startResult.run_id;\n kanbanItemId = startResult.kanban_item_id;\n if (runId) childEnv['AGT_RUN_ID'] = runId;\n\n const { stdout, stderr } = await execFilePromiseLong(resolveClaudeBinary(), claudeArgs, {\n cwd: projectDir, timeout: 300_000, stdin: 'ignore', env: childEnv,\n });\n\n if (stderr) {\n log(`[claude-scheduler] Task '${task.name}' stderr for '${codeName}': ${stderr.slice(0, 500)}`);\n }\n\n const output = stdout.trim();\n taskResult = output.slice(0, 4000) || undefined;\n log(`[claude-scheduler] Task '${task.name}' completed for '${codeName}' (${output.length} chars): ${output.slice(0, 300)}`);\n\n // Route result based on template + deliver via configured delivery target\n await processClaudeTaskResult(codeName, agentId, task.templateId, output, {\n mode: task.deliveryMode,\n channel: task.deliveryChannel,\n to: task.deliveryTo,\n taskId: task.taskId,\n });\n\n // Close the runs telemetry record (success). When a kanban card was\n // materialised at start, /host/runs/finish moves it to 'done' and\n // stamps the agent's output as the result so the user can see what\n // the firing produced without leaving the board.\n if (runId) {\n await finishRun(runId, 'completed', {\n metadata: { output_length: output.length },\n completeKanbanItemId: kanbanItemId,\n result: taskResult,\n });\n }\n\n // Update scheduler state\n const updated = markTaskFired(codeName, task.taskId, 'ok');\n claudeSchedulerStates.set(codeName, updated);\n\n // Disable one-off 'at' tasks in the DB so they don't re-fire on manager restart\n if (task.scheduleKind === 'at') {\n api.post('/host/schedules/disable', { agent_id: agentId, task_id: task.taskId }).catch((err) =>\n log(`[claude-scheduler] Failed to disable one-off task '${task.name}': ${(err as Error).message}`),\n );\n log(`[claude-scheduler] One-off task '${task.name}' fired and disabled for '${codeName}'`);\n }\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log(`[claude-scheduler] Task '${task.name}' failed for '${codeName}': ${errMsg}`);\n // If the error is a ChildProcessError, dump both streams so we can\n // debug cases where Claude CLI writes errors to stdout and leaves\n // stderr empty (common for startup/MCP failures).\n if (err instanceof ChildProcessError) {\n // Surface partial stdout on the failed card so the user can still see\n // what the agent emitted before the failure (e.g. an MCP startup\n // error printed to stdout). Falls back to whatever was captured in\n // the success path if the failure happened post-spawn.\n const errStdout = err.stdout.trim();\n if (errStdout) {\n taskResult = errStdout.slice(0, 4000) || taskResult;\n log(`[claude-scheduler] Task '${task.name}' stdout for '${codeName}': ${errStdout.slice(0, 1000)}`);\n }\n if (err.stderr.trim()) {\n log(`[claude-scheduler] Task '${task.name}' stderr for '${codeName}': ${err.stderr.trim().slice(0, 1000)}`);\n }\n }\n // Close the runs telemetry record (failure). Best-effort — never let\n // a telemetry write block the task error path. The materialised\n // kanban card (if any) flips to 'failed' in the same call and carries\n // partial agent stdout in `result` so the user has something to read.\n if (runId) {\n try {\n await finishRun(runId, 'failed', {\n outcomeMessage: errMsg,\n completeKanbanItemId: kanbanItemId,\n result: taskResult,\n });\n } catch { /* swallow */ }\n }\n const updated = markTaskFired(codeName, task.taskId, 'error');\n claudeSchedulerStates.set(codeName, updated);\n }\n}\n\nasync function processClaudeTaskResult(\n codeName: string,\n agentId: string,\n templateId: string,\n rawOutput: string,\n delivery?: { mode: string; channel: string | null; to: unknown | null; taskId?: string },\n): Promise<void> {\n try {\n // ENG-4463 / ENG-4480: classify the output before ANYTHING else —\n // suppress / strip / deliver. Conditional tasks (\"DO NOT notify me\n // unless X\") return `<no-delivery/>` alone to signal \"nothing worth\n // delivering this run\". Some agents mix the sentinel into otherwise\n // real content (observed: \"Nothing urgent ...\\n\\n<no-delivery/>\\n\\n—\n // scheduled by ...\"); rather than ship the token to the recipient,\n // we strip every sentinel occurrence and deliver the cleaned text.\n // This must short-circuit template handlers too — otherwise a standup\n // scheduled task could still write \"current_tasks: <no-delivery/>\"\n // into /host/agent-status before we decide to skip the outbound.\n const classification = classifyOutput(rawOutput);\n if (classification.action === 'suppress') {\n // Hash + length only — suppressed output can contain recipient text\n // or PII the agent pulled from Linear/Gmail/etc. and `manager.log`\n // is persisted to disk. redactForDiskLog catches token-shaped\n // secrets but not message bodies, so we never log them raw here.\n const trimmed = (rawOutput ?? '').trim();\n const outputHash = trimmed.length === 0\n ? 'empty'\n : createHash('sha256').update(trimmed).digest('hex').slice(0, 12);\n log(`[claude-scheduler] Suppressing delivery for '${codeName}' (template=${templateId}, task=${delivery?.taskId ?? 'n/a'}) — output_len=${trimmed.length} output_hash=${outputHash}`);\n if (classification.suppressedNotes) {\n const notesHash = createHash('sha256').update(classification.suppressedNotes).digest('hex').slice(0, 12);\n log(`[claude-scheduler] Suppressed notes for '${codeName}' (task=${delivery?.taskId ?? 'n/a'}) — notes_len=${classification.suppressedNotes.length} notes_hash=${notesHash}`);\n }\n if (delivery?.mode === 'announce' && delivery.to) {\n await reportDeliveryStatus(agentId, delivery.taskId, {\n status: 'skipped',\n error_code: 'NO_CONTENT',\n });\n }\n return;\n }\n\n // Either 'deliver' (no sentinel present) or 'strip' (sentinel removed,\n // clean deliverable remains). Template handlers and the outbound both\n // use the classified deliverable, never the raw output.\n const output = classification.deliverable;\n if (classification.action === 'strip') {\n log(`[claude-scheduler] Stripped '<no-delivery/>' sentinel from '${codeName}' output (template=${templateId}, task=${delivery?.taskId ?? 'n/a'}) — agent mixed it with real content; delivering the rest.`);\n }\n\n if (STANDUP_TEMPLATES.has(templateId)) {\n const standup = parseStandupSummary(output);\n await api.post('/host/agent-status', {\n agent_code_name: codeName,\n standup,\n current_status: 'idle',\n });\n log(`[claude-scheduler] Standup posted for '${codeName}'`);\n } else if (TASK_UPDATE_TEMPLATES.has(templateId)) {\n await api.post('/host/agent-status', {\n agent_code_name: codeName,\n current_tasks: output.slice(0, 2000),\n });\n log(`[claude-scheduler] Task update posted for '${codeName}'`);\n } else if (PLAN_TEMPLATES.has(templateId)) {\n const planItems = parsePlanItems(output);\n if (planItems.length > 0) {\n await api.post('/host/kanban', {\n agent_id: agentId,\n add: planItems,\n });\n log(`[claude-scheduler] Plan items posted for '${codeName}' (${planItems.length} items)`);\n }\n } else if (KANBAN_WORK_TEMPLATES.has(templateId)) {\n const kanbanUpdates = parseKanbanUpdates(output);\n if (kanbanUpdates.length > 0) {\n await api.post('/host/kanban', {\n agent_id: agentId,\n update: kanbanUpdates,\n });\n log(`[claude-scheduler] Kanban updates posted for '${codeName}' (${kanbanUpdates.length} updates)`);\n }\n }\n\n // ENG-4422 §5–§6: deliver output via the configured target.\n // Target is either a legacy-shape `channel:<id>` / `chat:<id>` string\n // (OpenClaw agents still produce this form) OR a JSONB DeliveryTarget\n // object (Claude Code agents post-migration). Suppressed output was\n // already short-circuited above, so if we reach here the output is\n // deliverable.\n if (delivery?.mode === 'announce' && delivery.to) {\n await deliverScheduledTaskOutput(\n codeName,\n agentId,\n delivery.to,\n output.slice(0, 4000),\n delivery.taskId,\n );\n }\n } catch (err) {\n log(`[claude-scheduler] Failed to post result for '${codeName}': ${(err as Error).message}`);\n }\n}\n\n\nfunction fireClaudeWorkTrigger(\n codeName: string,\n agentId: string,\n boardItems: Array<{ id: string; title: string; status: string; priority: number; estimated_minutes?: number; deliverable?: string; result?: string }>,\n): void {\n const state = claudeSchedulerStates.get(codeName) ?? loadSchedulerState(codeName);\n const kanbanTask = findTaskByTemplate(state, 'kanban-work');\n if (!kanbanTask) {\n log(`[claude-scheduler] Work trigger: no kanban-work task found for '${codeName}'`);\n return;\n }\n if (inFlightClaudeTasks.has(kanbanTask.taskId)) {\n log(`[claude-scheduler] Work trigger: kanban-work already in-flight for '${codeName}'`);\n return;\n }\n\n let prompt = kanbanTask.prompt;\n if (boardItems.length > 0) {\n const boardPrefix = formatBoardForPrompt(boardItems, 'follow-up');\n prompt = boardPrefix + prompt;\n }\n\n inFlightClaudeTasks.add(kanbanTask.taskId);\n claudeTaskConcurrency.set(codeName, (claudeTaskConcurrency.get(codeName) ?? 0) + 1);\n log(`[claude-scheduler] Work trigger: firing kanban-work for '${codeName}'`);\n\n executeAndProcessClaudeTask(codeName, agentId, kanbanTask, prompt)\n .finally(() => {\n inFlightClaudeTasks.delete(kanbanTask.taskId);\n claudeTaskConcurrency.set(codeName, Math.max(0, (claudeTaskConcurrency.get(codeName) ?? 1) - 1));\n });\n}\n\n// ---------------------------------------------------------------------------\n// Persistent session management (session_mode = 'persistent')\n// ---------------------------------------------------------------------------\n\nimport {\n startPersistentSession,\n stopPersistentSession,\n injectMessage,\n isSessionHealthy,\n getSessionState,\n resetRestartCount,\n stopAllSessions,\n stopAllSessionsAndWait,\n getProjectDir as psGetProjectDir,\n resolveClaudeBinary,\n getLastFailureContext,\n prepareForRespawn,\n} from './persistent-session.js';\nimport { isAgentIdle, isStaleForToday, peekCurrentSession } from './daily-session.js';\nimport { processRestartFlags } from './restart-handler.js';\n\n// Track which agents have persistent sessions started\nconst persistentSessionAgents = new Set<string>();\n\n/**\n * Per-agent fingerprint of the Claude Code auth config used to spawn the\n * current session: `\"<mode>:<fingerprint ?? 'none'>\"`. When this changes\n * between polls (operator toggled subscription→api_key, or rotated the key),\n * the session is stopped so the next cycle respawns it with fresh auth.\n */\nconst claudeAuthTupleBySession = new Map<string, string>();\n\nasync function ensurePersistentSession(\n agent: { agent_id: string; code_name: string; display_name: string },\n tasks: Array<Record<string, unknown>>,\n boardItems: Array<{ id: string; title: string; status: string; priority: number; estimated_minutes?: number; deliverable?: string; result?: string }>,\n refreshData: Record<string, unknown>,\n): Promise<void> {\n const codeName = agent.code_name;\n const projectDir = psGetProjectDir(codeName);\n const mcpConfigPath = join(projectDir, '.mcp.json');\n const claudeMdPath = join(projectDir, 'CLAUDE.md');\n\n // Resolve channel plugins from agent's channel configs\n const channelConfigs = refreshData.channel_configs as Record<string, { config: unknown; status: string }> | null;\n const channels: string[] = [];\n\n // Map known channel types to their Claude Code channel plugins\n const devChannels: string[] = []; // custom channels needing --dangerously-load-development-channels\n if (channelConfigs) {\n // Slack and Telegram use our custom per-agent channel MCP servers\n // (written into the provision .mcp.json by writeChannelCredentials). The\n // `server:<name>` form registers them with Claude Code's channel\n // notification pipeline. Telegram used to ride the\n // `plugin:telegram@claude-plugins-official` plugin, which fails with >1\n // agent per host because the plugin reads a single global bot-token .env\n // — see ENG-4437.\n //\n // Gate registration on the channel actually being provisioned — a config\n // row with status other than `active` / `pending` never triggers\n // writeChannelCredentials, so asking Claude Code to load a dev channel\n // with no MCP entry would fail with a confusing \"unknown channel\" error.\n const isChannelEnabled = (id: string): boolean => {\n const entry = channelConfigs[id];\n // Must also have a truthy config — a row with status=pending but\n // null config never triggers writeChannelCredentials upstream, so\n // registering `server:X` for it would point Claude at an absent MCP\n // entry (same failure mode as the unprovisioned-status case).\n return !!entry?.config && (entry.status === 'active' || entry.status === 'pending');\n };\n if (isChannelEnabled('slack')) {\n devChannels.push('server:slack');\n }\n if (isChannelEnabled('telegram')) {\n devChannels.push('server:telegram');\n }\n if (isChannelEnabled('discord')) {\n channels.push('plugin:discord@claude-plugins-official');\n }\n }\n\n // Always add direct-chat channel for persistent sessions.\n // This is provisioned in step 5b of processAgent() into .mcp-channels.json.\n devChannels.push('server:direct-chat');\n\n // Resolve auth mode + key from the exchange BEFORE any OAuth precheck —\n // otherwise api_key hosts get skipped for never having run `claude /login`\n // even though they don't need OAuth at all. Force-refresh bypasses the\n // ~50m JWT cache so mode/key changes on the webapp are picked up on the\n // next poll (ENG-4417 rotation acceptance criterion).\n //\n // Fail-closed on error: silently falling back to 'subscription' is exactly\n // the confused-deputy path ENG-4417 removes. If /host/exchange rejects with\n // anthropic_key_decrypt_failed, the host is misconfigured — refuse to\n // spawn (or stop a running session) so the operator sees the problem\n // instead of a session quietly booting under the wrong auth.\n let claudeAuthMode: 'subscription' | 'api_key';\n let anthropicApiKey: string | null;\n let anthropicApiKeyFingerprint: string | null;\n try {\n const apiKey = getApiKey();\n if (!apiKey) {\n log(`[persistent-session] Skipping '${codeName}' — AGT_API_KEY not set`);\n return;\n }\n const exchange = await exchangeApiKey(apiKey, false, { forceRefresh: true });\n claudeAuthMode = exchange.claudeAuthMode;\n anthropicApiKey = exchange.anthropicApiKey;\n anthropicApiKeyFingerprint = exchange.anthropicApiKeyFingerprint;\n } catch (err) {\n const msg = (err as Error).message;\n log(`[persistent-session] Failed to resolve auth for '${codeName}': ${msg} — refusing to spawn`);\n // Stop any running session: the auth config is unknown, so continuing is\n // a silent-fallback bug waiting to happen. Next successful poll respawns.\n if (isSessionHealthy(codeName)) {\n stopPersistentSessionAndForgetMcpBaseline(codeName);\n persistentSessionAgents.delete(codeName);\n claudeAuthTupleBySession.delete(codeName);\n }\n return;\n }\n\n // OAuth precheck only applies in subscription mode. api_key hosts have\n // their credentials in the exchange response above — no `claude /login`\n // required — so don't block them on a missing OAuth token. Hosts in\n // api_key mode with no key would already have failed the exchange\n // (503 anthropic_key_decrypt_failed or host_metadata_unavailable).\n if (claudeAuthMode === 'subscription') {\n if (!agentRuntimeAuthenticated) {\n // Re-check in case user logged in since last check\n agentRuntimeAuthenticated = await checkClaudeAuth();\n if (!agentRuntimeAuthenticated) {\n log(`[persistent-session] Skipping '${codeName}' — Claude Code not authenticated (subscription mode)`);\n return;\n }\n }\n } else if (!anthropicApiKey) {\n // Belt-and-braces: exchange should have returned a key when mode=api_key.\n // If it didn't, don't spawn — the session would just fail auth in tmux.\n log(`[persistent-session] Skipping '${codeName}' — api_key mode but no key returned from exchange`);\n return;\n }\n\n // Detect auth rotation: if the (mode, fingerprint) tuple differs from what\n // we last launched with, stop the session so it respawns with fresh auth\n // on the next poll. Fingerprint captures key rotation without exposing the\n // raw value. First launch (no recorded tuple) stamps the baseline only.\n const currentAuthTuple = `${claudeAuthMode}:${anthropicApiKeyFingerprint ?? 'none'}`;\n const recordedAuthTuple = claudeAuthTupleBySession.get(codeName);\n if (recordedAuthTuple && recordedAuthTuple !== currentAuthTuple && isSessionHealthy(codeName)) {\n log(`[persistent-session] Auth config changed for '${codeName}' (${recordedAuthTuple} → ${currentAuthTuple}) — restarting session`);\n stopPersistentSessionAndForgetMcpBaseline(codeName);\n persistentSessionAgents.delete(codeName);\n }\n\n // ENG-4642: scheduled day rollover. When the daily-session.json's\n // current.date no longer matches today's local date, the running\n // session is on yesterday's UUID. Kill it so the next pass mints a\n // fresh UUID via getOrCreateDailySession — but only when the agent\n // is between turns (JSONL mtime older than IDLE_THRESHOLD_S). If the\n // agent is mid-task, defer to the next tick rather than interrupt.\n // First-spawn agents (no current entry yet) and unhealthy sessions\n // (no tmux to kill) are no-ops.\n if (isStaleForToday(codeName) && isSessionHealthy(codeName)) {\n const current = peekCurrentSession(codeName);\n if (current) {\n const idle = isAgentIdle(projectDir, current.sessionId);\n if (idle) {\n log(\n `[persistent-session] Day rollover for '${codeName}' (yesterday=${current.date}) — agent idle, restarting to mint fresh session`,\n );\n stopPersistentSessionAndForgetMcpBaseline(codeName);\n persistentSessionAgents.delete(codeName);\n } else {\n log(\n `[persistent-session] Day rollover for '${codeName}' deferred — agent still active on session ${current.sessionId} (will retry next tick)`,\n );\n }\n }\n }\n\n // Start or maintain the persistent session\n if (!isSessionHealthy(codeName)) {\n if (persistentSessionAgents.has(codeName)) {\n // ENG-4659: include the actual claude error from the captured\n // pane log + a restart-cycle counter so silent restart loops are\n // visible at a glance. Run the recovery hook BEFORE the next\n // spawn so e.g. \"Session ID already in use\" rotates the UUID\n // instead of looping forever.\n //\n // CodeRabbit (PR #618): pane output is untrusted — it can echo\n // user prompts, tool output, secrets, etc. Per CLAUDE.md\n // (\"Default logging to hash-only or redacted in prod; never log\n // raw secrets\") we only embed the raw tail when the signature\n // is in a strict allowlist of known-safe Claude error messages.\n // For everything else we log only a sha256 prefix of the tail —\n // operators can correlate with the full file at\n // ~/.augmented/<codeName>/pane.log when they need it.\n const ctx = getLastFailureContext(codeName);\n const recovery = prepareForRespawn(codeName);\n const tailSummary = !ctx.tail\n ? ''\n : KNOWN_SAFE_TAIL_SIGNATURES.has(ctx.signature)\n ? `; last pane output (${PANE_TAIL_PREVIEW_LINES} of ~20 lines):\\n${truncateForLog(ctx.tail)}`\n : `; pane_tail_hash=sha256:${createHash('sha256').update(ctx.tail).digest('hex').slice(0, 12)} (raw at ~/.augmented/${codeName}/pane.log)`;\n const sigSummary = ctx.signature !== 'unknown' ? `; signature=${ctx.signature}` : '';\n const recoverySummary = recovery ? `; recovery=${recovery}` : '';\n log(\n `[persistent-session] Session for '${codeName}' is unhealthy ` +\n `(restart #${ctx.restartCount}${sigSummary}${recoverySummary}), will restart${tailSummary}`,\n );\n }\n\n // Provision Stop hook for result capture (idempotent)\n try {\n provisionStopHook(codeName);\n } catch (err) {\n log(`[persistent-session] Failed to provision Stop hook for '${codeName}': ${(err as Error).message}`);\n }\n\n // Provision isolation hook to prevent cross-agent file access (idempotent)\n try {\n provisionIsolationHook(codeName);\n } catch (err) {\n log(`[persistent-session] Failed to provision isolation hook for '${codeName}': ${(err as Error).message}`);\n }\n\n startPersistentSession({\n codeName,\n agentId: agent.agent_id,\n projectDir,\n mcpConfigPath,\n claudeMdPath,\n channels,\n devChannels,\n apiHost: requireHost(),\n claudeAuthMode,\n anthropicApiKey,\n log,\n });\n persistentSessionAgents.add(codeName);\n // Record the tuple we launched with so future polls can detect rotation.\n claudeAuthTupleBySession.set(codeName, currentAuthTuple);\n return; // Wait for next cycle to inject tasks (session needs time to boot)\n }\n\n // Session is healthy — reset restart counter.\n resetRestartCount(codeName);\n // Stamp the baseline tuple on first healthy observation (covers the\n // manager-restart case where the tmux session survived but our Map didn't).\n if (!claudeAuthTupleBySession.has(codeName)) {\n claudeAuthTupleBySession.set(codeName, currentAuthTuple);\n }\n\n // Sync scheduler state from API before checking for ready tasks.\n // This ensures deleted/disabled tasks don't fire and new tasks are picked up.\n // The scheduleChanged guard in syncTasksToScheduler preserves nextFireAt.\n const stableTasksHash = createHash('sha256')\n .update(JSON.stringify(tasks))\n .digest('hex')\n .slice(0, 16);\n const prevHash = knownTasksHashes.get(agent.agent_id);\n if (stableTasksHash !== prevHash) {\n const taskInputs: SchedulerTaskInput[] = tasks.map((t) => ({\n id: t.id as string,\n template_id: t.template_id as string,\n name: t.name as string,\n schedule_kind: t.schedule_kind as 'cron' | 'every' | 'at',\n schedule_expr: (t.schedule_expr as string) ?? null,\n schedule_every: (t.schedule_every as string) ?? null,\n schedule_at: (t.schedule_at as string) ?? null,\n timezone: (t.timezone as string) ?? 'UTC',\n prompt: (t.prompt as string) ?? '',\n session_target: (t.session_target as string) ?? 'isolated',\n delivery_mode: (t.delivery_mode as string) ?? 'none',\n delivery_channel: (t.delivery_channel as string) ?? null,\n delivery_to: (t.delivery_to as string) ?? null,\n enabled: (t.enabled as boolean) ?? true,\n triggered_at: (t.triggered_at as string) ?? null,\n }));\n const schedulerState = syncTasksToScheduler(codeName, agent.agent_id, taskInputs);\n claudeSchedulerStates.set(codeName, schedulerState);\n knownTasksHashes.set(agent.agent_id, stableTasksHash);\n log(`[persistent-session] Tasks synced for '${codeName}' (${taskInputs.length} task(s))`);\n } else if (!claudeSchedulerStates.has(codeName)) {\n claudeSchedulerStates.set(codeName, loadSchedulerState(codeName));\n }\n\n // Inject scheduled tasks that are due\n const state = claudeSchedulerStates.get(codeName);\n if (state) {\n // ENG-4675: pass inFlightClaudeTasks so getReadyTasks filters out\n // tasks whose subprocess is still running. Without this, every\n // supervisor tick logged \"N ready task(s)\" + \"Firing task ...\" for\n // the same trigger, even though the in-flight guard below correctly\n // skipped the duplicate spawn.\n const ready = getReadyTasks(state, inFlightClaudeTasks);\n if (ready.length > 0) {\n log(`[persistent-session] ${ready.length} ready task(s) for '${codeName}': ${ready.map(t => `${t.name}(next=${t.nextFireAt ? new Date(t.nextFireAt).toISOString() : 'null'})`).join(', ')}`);\n }\n for (const task of ready) {\n\n // ENG-4410: Skip kanban-work when board has no actionable items.\n if (KANBAN_WORK_TEMPLATES.has(task.templateId) && !hasActionableItems(boardItems)) {\n log(`[persistent-session] Skipping '${task.name}' for '${codeName}' — board is empty`);\n const updated = markTaskFired(codeName, task.taskId, 'ok');\n claudeSchedulerStates.set(codeName, updated);\n continue;\n }\n\n // Enrich prompt with board context\n let prompt = task.prompt;\n if (BOARD_INJECT_TEMPLATES.has(task.templateId) && boardItems.length > 0) {\n const template = PLAN_TEMPLATES.has(task.templateId) ? 'morning-plan' : 'follow-up';\n const boardPrefix = formatBoardForPrompt(boardItems, template);\n prompt = boardPrefix + prompt;\n }\n\n // For kanban-work: move top today item to in_progress\n if (KANBAN_WORK_TEMPLATES.has(task.templateId)) {\n const todayItem = boardItems.find((b) => b.status === 'todo');\n if (todayItem) {\n try {\n await api.post('/host/kanban', {\n agent_id: agent.agent_id,\n update: [{ id: todayItem.id, title: todayItem.title, status: 'in_progress' }],\n });\n log(`[persistent-session] Moved '${todayItem.title}' to in_progress for '${codeName}'`);\n } catch { /* non-fatal */ }\n }\n }\n\n // Guard against duplicate launches (same pattern as oneshot path).\n // ENG-4675: also defends against a stale in-flight set if getReadyTasks'\n // filter is ever bypassed.\n if (inFlightClaudeTasks.has(task.taskId)) {\n continue;\n }\n if ((claudeTaskConcurrency.get(codeName) ?? 0) >= MAX_CLAUDE_CONCURRENCY) {\n break;\n }\n inFlightClaudeTasks.add(task.taskId);\n claudeTaskConcurrency.set(codeName, (claudeTaskConcurrency.get(codeName) ?? 0) + 1);\n\n // ENG-4675: log AFTER the in-flight guard so it only fires when a\n // subprocess actually starts. Previously logged before the guard,\n // making manual triggers look like they fired multiple times.\n log(`[persistent-session] Firing task '${task.name}' for '${codeName}' via claude -p`);\n\n // Use the same oneshot claude -p path as non-persistent mode.\n // This captures output so we can deliver to Slack and process results.\n // The tmux session stays for interactive channels (Slack/Telegram).\n executeAndProcessClaudeTask(codeName, agent.agent_id, task, prompt)\n .catch(() => { /* errors logged inside executeAndProcessClaudeTask */ })\n .finally(() => {\n inFlightClaudeTasks.delete(task.taskId);\n claudeTaskConcurrency.set(codeName, Math.max(0, (claudeTaskConcurrency.get(codeName) ?? 1) - 1));\n });\n\n // Only inject one task per cycle in persistent mode\n break;\n }\n }\n\n}\n\n// ---------------------------------------------------------------------------\n// Supabase Realtime for direct chat (replaces 2s polling)\n// ---------------------------------------------------------------------------\n\nimport {\n startRealtimeChat,\n startRealtimeDrift,\n startRealtimeAssignments,\n startRealtimeConfig,\n startRealtimeKanban,\n startRealtimeIntegrationContext,\n stopRealtimeIntegrationContext,\n stopRealtimeChat,\n isRealtimeConnected,\n updateRealtimeToken,\n} from './realtime-chat.js';\n\nlet realtimeStarted = false;\nlet realtimeDriftStarted = false;\nlet realtimeKanbanStarted = false;\n// Snapshot of the agent_ids currently subscribed to the realtime\n// `plugin_context` channel. Empty set ⇒ not yet subscribed. Compared\n// against the current active-agent set each tick so an agent\n// activated after manager startup also gets realtime context updates\n// (ENG-4725 — previously this used a one-shot boolean and silently\n// missed any agents that came online later).\nlet subscribedIntegrationContextAgentIds: Set<string> = new Set();\nlet realtimeAssignStarted = false;\nlet realtimeConfigStarted = false;\nlet realtimeSubscribedAgentIds = new Set<string>();\n\nfunction ensureRealtimeStarted(agentStates: AgentState[]): void {\n // Check if new agents have appeared since last subscription\n const currentActiveIds = new Set(\n agentStates.filter((a) => a.status === 'active').map((a) => a.agentId),\n );\n if (realtimeStarted) {\n // Detect any change: additions or removals\n const sameSize = currentActiveIds.size === realtimeSubscribedAgentIds.size;\n const sameMembers = sameSize && [...currentActiveIds].every((id) => realtimeSubscribedAgentIds.has(id));\n if (sameMembers) return;\n // Agent set changed — tear down and reconnect with fresh set\n log('[realtime] Agent set changed — reconnecting subscriptions');\n stopRealtimeChat();\n realtimeStarted = false;\n realtimeDriftStarted = false;\n realtimeAssignStarted = false;\n realtimeConfigStarted = false;\n realtimeKanbanStarted = false;\n subscribedIntegrationContextAgentIds = new Set();\n }\n\n const activeAgentIds = agentStates\n .filter((a) => a.status === 'active')\n .map((a) => a.agentId);\n\n if (activeAgentIds.length === 0) return;\n\n const apiKey = process.env['AGT_API_KEY'];\n if (!apiKey) return;\n\n // Get Supabase config + token from the cached exchange result\n void exchangeApiKey(apiKey).then((exchange) => {\n if (!exchange.supabaseUrl || !exchange.supabaseAnonKey) {\n log('[realtime-chat] No Supabase URL/key from exchange — staying on polling');\n return;\n }\n\n startRealtimeChat({\n supabaseUrl: exchange.supabaseUrl,\n supabaseAnonKey: exchange.supabaseAnonKey,\n token: exchange.token,\n agentIds: activeAgentIds,\n onMessage: (msg) => {\n const agent = agentStates.find((a) => a.agentId === msg.agent_id);\n if (!agent) return;\n\n if (directChatInFlight.has(msg.id)) return;\n directChatInFlight.add(msg.id);\n\n processDirectChatMessage(agent, {\n id: msg.id,\n session_id: msg.session_id,\n content: msg.content,\n }).finally(() => {\n directChatInFlight.delete(msg.id);\n });\n },\n onError: (err) => {\n log(`[realtime-chat] Error: ${err.message}`);\n },\n onStatusChange: (status) => {\n if (status === 'disconnected' || status === 'error') {\n log('[realtime] Disconnected — falling back to polling, will reconnect next cycle');\n // Reset all subscription flags so they reconnect with a fresh client\n realtimeStarted = false;\n realtimeDriftStarted = false;\n realtimeAssignStarted = false;\n realtimeConfigStarted = false;\n realtimeKanbanStarted = false;\n subscribedIntegrationContextAgentIds = new Set();\n }\n },\n log,\n });\n\n realtimeStarted = true;\n realtimeSubscribedAgentIds = new Set(activeAgentIds);\n log(`[realtime-chat] Started for ${activeAgentIds.length} agent(s)`);\n }).catch((err) => {\n log(`[realtime-chat] Failed to start: ${(err as Error).message}`);\n });\n}\n\nfunction ensureRealtimeDriftStarted(agentStates: AgentState[]): void {\n if (realtimeDriftStarted) return;\n\n const activeAgentIds = agentStates.filter((a) => a.status === 'active').map((a) => a.agentId);\n if (activeAgentIds.length === 0) return;\n\n const apiKey = process.env['AGT_API_KEY'];\n if (!apiKey) return;\n\n void exchangeApiKey(apiKey).then((exchange) => {\n if (!exchange.supabaseUrl || !exchange.supabaseAnonKey) return;\n\n startRealtimeDrift({\n supabaseUrl: exchange.supabaseUrl,\n supabaseAnonKey: exchange.supabaseAnonKey,\n token: exchange.token,\n agentIds: activeAgentIds,\n onDrift: (doc) => {\n // Invalidate the known version so next poll cycle re-provisions\n const agentState = agentStates.find((a) => a.agentId === doc.agent_id);\n if (agentState) {\n knownVersions.delete(doc.agent_id);\n log(`[realtime] Drift invalidated for '${agentState.codeName}' — will re-provision next cycle`);\n }\n },\n log,\n });\n\n realtimeDriftStarted = true;\n log(`[realtime] Drift subscription started for ${activeAgentIds.length} agent(s)`);\n }).catch((err) => {\n log(`[realtime] Drift subscription failed: ${(err as Error).message}`);\n });\n}\n\nfunction ensureRealtimeAssignStarted(agentStates: AgentState[]): void {\n if (realtimeAssignStarted) return;\n\n const apiKey = process.env['AGT_API_KEY'];\n if (!apiKey) return;\n\n void exchangeApiKey(apiKey).then((exchange) => {\n if (!exchange.supabaseUrl || !exchange.supabaseAnonKey || !exchange.hostId) return;\n\n startRealtimeAssignments({\n supabaseUrl: exchange.supabaseUrl,\n supabaseAnonKey: exchange.supabaseAnonKey,\n token: exchange.token,\n hostId: exchange.hostId,\n onAssign: (payload) => {\n log(`[realtime] Agent ${payload.agent_id} assigned — will pick up next cycle`);\n // ENG-4643: stamp the agent for a fresh memory pull on its next\n // sync tick. Without this, a long-running manager that hosted\n // this agent before its current migration could still have\n // memoryFileHashes / lastDownloadHash entries from the prior\n // assignment and skip the catch-up download (the local-list-\n // hash short-circuit in syncMemories sees \"nothing changed\"\n // and returns). Clearing the caches + flagging for a forced\n // download-first pass keeps the new host in sync with the DB.\n //\n // Deliberately not invoking syncMemories from here. The\n // realtime payload only carries agent_id; syncMemories needs\n // {agent_id, code_name} plus the supervisor's configDir, none\n // of which are reachable from this callback's closure. The\n // next supervisor tick (interval default 10s) iterates the\n // freshly-loaded agent list and consumes the pending flag,\n // so the latency cost of waiting one tick is bounded and\n // the alternative (extra API call to resolve code_name from\n // here, plus duplicating the sync entrypoint plumbing) is\n // not worth the saved seconds.\n markAgentForFreshMemorySync(payload.agent_id);\n },\n onUnassign: (payload) => {\n log(`[realtime] Agent ${payload.agent_id} unassigned`);\n clearAgentCaches(payload.agent_id, '');\n },\n log,\n });\n\n realtimeAssignStarted = true;\n log(`[realtime] Assignment subscription started for host ${exchange.hostId}`);\n }).catch((err) => {\n log(`[realtime] Assignment subscription failed: ${(err as Error).message}`);\n });\n}\n\nfunction ensureRealtimeConfigStarted(agentStates: AgentState[]): void {\n if (realtimeConfigStarted) return;\n\n const activeAgentIds = agentStates.filter((a) => a.status === 'active').map((a) => a.agentId);\n if (activeAgentIds.length === 0) return;\n\n const apiKey = process.env['AGT_API_KEY'];\n if (!apiKey) return;\n\n void exchangeApiKey(apiKey).then((exchange) => {\n if (!exchange.supabaseUrl || !exchange.supabaseAnonKey) return;\n\n startRealtimeConfig({\n supabaseUrl: exchange.supabaseUrl,\n supabaseAnonKey: exchange.supabaseAnonKey,\n token: exchange.token,\n agentIds: activeAgentIds,\n onConfigChange: (agent) => {\n // Only invalidate status cache — version/provision caches are handled by drift subscription.\n // Don't invalidate knownVersions here as that triggers re-provision which updates\n // updated_at, creating a feedback loop with this Realtime subscription.\n knownStatuses.delete(agent.agent_id);\n },\n log,\n });\n\n realtimeConfigStarted = true;\n log(`[realtime] Config subscription started for ${activeAgentIds.length} agent(s)`);\n }).catch((err) => {\n log(`[realtime] Config subscription failed: ${(err as Error).message}`);\n });\n}\n\nfunction ensureRealtimeKanbanStarted(agentStates: AgentState[]): void {\n if (realtimeKanbanStarted) return;\n\n const activeAgentIds = agentStates.filter((a) => a.status === 'active').map((a) => a.agentId);\n if (activeAgentIds.length === 0) return;\n\n const apiKey = process.env['AGT_API_KEY'];\n if (!apiKey) return;\n\n void exchangeApiKey(apiKey).then((exchange) => {\n if (!exchange.supabaseUrl || !exchange.supabaseAnonKey) return;\n\n startRealtimeKanban({\n supabaseUrl: exchange.supabaseUrl,\n supabaseAnonKey: exchange.supabaseAnonKey,\n token: exchange.token,\n agentIds: activeAgentIds,\n onTodayItem: (item) => {\n // Trigger kanban-work immediately for this agent\n const agent = agentStates.find((a) => a.agentId === item.agent_id);\n if (!agent) return;\n\n const agentFw = agentFrameworkCache.get(agent.codeName) ?? 'openclaw';\n\n if (agentFw === 'claude-code') {\n // For persistent sessions, inject via tmux; for oneshot, fire claude -p\n const boardItems = kanbanBoardCache.get(agent.codeName) ?? [];\n if (isSessionHealthy(agent.codeName)) {\n injectMessage(agent.codeName, 'task', `New task added to your board: \"${item.title}\" (priority ${item.priority}). Pick it up — move to in_progress and start working.`, {\n task_name: 'kanban-work-trigger',\n }, log);\n log(`[realtime] Injected kanban-work trigger for '${agent.codeName}': \"${item.title}\"`);\n } else {\n fireClaudeWorkTrigger(agent.codeName, agent.agentId, boardItems);\n log(`[realtime] Fired kanban-work for '${agent.codeName}': \"${item.title}\"`);\n }\n }\n },\n // ENG-4507: kanban completion notification — surfaces user-driven\n // closures to the daemon log + telemetry surface. The agent-runtime\n // ingestion path is the kanban_list `closed_by` annotation (no live\n // injection in this slice — the agent reads its board between turns\n // and sees the new state naturally).\n onCompletion: (event) => {\n const agent = agentStates.find((a) => a.agentId === event.agent_id);\n const codeName = agent?.codeName ?? event.agent_id;\n log(\n `[realtime] Kanban completion forwarded for '${codeName}': ` +\n `item=${event.item_id} status=${event.status} actor=${event.last_actor_id ?? 'unknown'}`,\n );\n },\n log,\n });\n\n realtimeKanbanStarted = true;\n log(`[realtime] Kanban subscription started for ${activeAgentIds.length} agent(s)`);\n }).catch((err) => {\n log(`[realtime] Kanban subscription failed: ${(err as Error).message}`);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Integration context realtime (ENG-4342)\n//\n// Subscribes to plugin_context table changes for the manager's agents and\n// triggers an early poll cycle whenever a context row is inserted or\n// updated. The early poll is what actually re-renders SKILL.md — this\n// realtime channel just collapses the latency from \"next poll cycle\"\n// (~60s) to \"next event loop tick after the change\" (~100ms + RTT).\n// ---------------------------------------------------------------------------\n\nfunction activeAgentIdSetsEqual(current: string[], previous: Set<string>): boolean {\n if (current.length !== previous.size) return false;\n for (const id of current) if (!previous.has(id)) return false;\n return true;\n}\n\nfunction ensureRealtimeIntegrationContextStarted(agentStates: AgentState[]): void {\n const activeAgentIds = agentStates.filter((a) => a.status === 'active').map((a) => a.agentId);\n\n // No active agents → tear down any existing subscription so we don't\n // hold a channel filter for agents we no longer manage.\n if (activeAgentIds.length === 0) {\n if (subscribedIntegrationContextAgentIds.size > 0) {\n stopRealtimeIntegrationContext();\n subscribedIntegrationContextAgentIds = new Set();\n log('[realtime] Integration context subscription torn down (no active agents)');\n }\n return;\n }\n\n // Already subscribed to exactly this set → nothing to do.\n if (activeAgentIdSetsEqual(activeAgentIds, subscribedIntegrationContextAgentIds)) return;\n\n const apiKey = process.env['AGT_API_KEY'];\n if (!apiKey) return;\n\n // Active set changed (agent activated, deactivated, reassigned, etc.).\n // Tear down the existing channel so we can re-subscribe with the\n // updated `agent_id=in.(...)` filter — Supabase realtime filters are\n // immutable per channel, so a re-bind is the only path.\n const isResubscribe = subscribedIntegrationContextAgentIds.size > 0;\n if (isResubscribe) {\n stopRealtimeIntegrationContext();\n }\n\n // Snapshot the set we're about to subscribe to before the async work\n // so a concurrent tick sees the in-flight subscription and skips.\n // exchangeApiKey resolution happens off-tick; if it fails, we reset\n // below so the next tick retries.\n const targetSet = new Set(activeAgentIds);\n subscribedIntegrationContextAgentIds = targetSet;\n\n void exchangeApiKey(apiKey).then((exchange) => {\n // A newer tick may have replaced the snapshot (e.g. agent set changed\n // again, or the broader chat client disconnected and reset all flags).\n // Bail without mutating state or starting a now-stale subscription —\n // identity comparison is enough because we only ever assign fresh\n // `Set` instances to the module-level snapshot.\n if (subscribedIntegrationContextAgentIds !== targetSet) return;\n\n if (!exchange.supabaseUrl || !exchange.supabaseAnonKey) {\n subscribedIntegrationContextAgentIds = new Set();\n return;\n }\n\n startRealtimeIntegrationContext({\n supabaseUrl: exchange.supabaseUrl,\n supabaseAnonKey: exchange.supabaseAnonKey,\n token: exchange.token,\n agentIds: [...targetSet],\n onContextChange: (payload) => {\n // Invalidate the affected agent's cached plugin-skill hashes so the\n // next poll definitely re-renders SKILL.md (the hash check would\n // otherwise be a no-op if the LLM-rendered output happens to be\n // identical, which is rare but possible). Then trigger an early poll.\n const agent = agentStates.find((a) => a.agentId === payload.agent_id);\n if (agent) {\n // Match the cache key shape used when hashes are written in the\n // refresh loop: `plugin-skill:${agent.agent_id}:...`. Using\n // codeName here (as the original version did) silently failed to\n // invalidate anything, so plugin-context changes could be skipped\n // until some unrelated bundle change happened or the manager\n // restarted.\n for (const key of knownSkillHashes.keys()) {\n if (key.startsWith(`plugin-skill:${agent.agentId}:`)) {\n knownSkillHashes.delete(key);\n }\n }\n }\n triggerEarlyPoll(`integration context changed for agent ${payload.agent_id}`);\n },\n log,\n });\n\n log(\n `[realtime] Integration context subscription ${isResubscribe ? 're-bound' : 'started'} for ${targetSet.size} agent(s)`,\n );\n }).catch((err) => {\n // Reset so the next tick retries — but only if we're still the\n // owning snapshot. Otherwise a newer tick has already taken over\n // and clearing here would silently undo its in-flight work.\n if (subscribedIntegrationContextAgentIds === targetSet) {\n subscribedIntegrationContextAgentIds = new Set();\n }\n log(`[realtime] Integration context subscription failed: ${(err as Error).message}`);\n });\n}\n\n/**\n * Cancel the scheduled poll and run a new poll cycle on the next event-loop\n * tick. Used by realtime callbacks (plugin context, etc.) to collapse the\n * latency between an external change and the manager observing it.\n *\n * Idempotent — multiple triggers within the same tick coalesce into one\n * poll because pollCycle() is awaited and scheduleNext() only fires after\n * it completes.\n */\nfunction triggerEarlyPoll(reason: string): void {\n if (!running) return;\n if (pollTimer) {\n clearTimeout(pollTimer);\n pollTimer = null;\n }\n log(`[realtime] Triggering early poll: ${reason}`);\n // Schedule on the next tick so we don't run reentrantly inside a Realtime\n // callback. scheduleNext() will fire on completion.\n pollTimer = setTimeout(() => {\n void pollCycle().then(() => {\n // scheduleNext() is the normal post-poll re-arm. Forward declared at\n // the bottom of this file; safe to reference here because the function\n // declaration is hoisted.\n scheduleNext();\n });\n }, 0);\n}\n\n// ---------------------------------------------------------------------------\n// Direct chat — poll for pending messages (fallback when Realtime is down)\n// ---------------------------------------------------------------------------\n\n// Track in-flight direct chat messages to avoid double-processing\nconst directChatInFlight = new Set<string>();\n\nasync function pollDirectChatMessages(agentStates: AgentState[]): Promise<void> {\n for (const agent of agentStates) {\n if (agent.status !== 'active') continue;\n const fw = agentFrameworkCache.get(agent.codeName) ?? 'openclaw';\n // OpenClaw requires a running gateway; Claude Code does not\n if (fw === 'openclaw' && (!agent.gatewayRunning || !agent.gatewayPort)) continue;\n\n try {\n const data = await api.post<{\n messages: Array<{\n id: string;\n session_id: string;\n content: string;\n created_at: string;\n }>;\n }>('/host/direct-chat/poll', { agent_id: agent.agentId });\n\n for (const msg of data.messages) {\n if (directChatInFlight.has(msg.id)) continue;\n directChatInFlight.add(msg.id);\n\n // Process async so we don't block the poll cycle\n processDirectChatMessage(agent, msg).finally(() => {\n directChatInFlight.delete(msg.id);\n });\n }\n } catch (err) {\n log(`Direct chat poll failed for '${agent.codeName}': ${(err as Error).message}`);\n }\n }\n}\n\nasync function processDirectChatMessage(\n agent: AgentState,\n msg: { id: string; session_id: string; content: string },\n): Promise<void> {\n const fw = agentFrameworkCache.get(agent.codeName) ?? 'openclaw';\n log(`[direct-chat] Processing message for '${agent.codeName}' (fw=${fw}): id=${msg.id} len=${msg.content.length}`);\n\n // ENG-4778: when a persistent session is running for this agent, inject\n // the direct-chat message into it instead of spawning a fresh `claude -p`.\n // The fresh process has no conversation history — it can't pick up where\n // the running session left off (e.g. completing the AWS task that\n // triggered a broker resolution notification). Inject as a `<channel>`\n // tag so the agent's existing system-prompt instructions for direct-chat\n // (call `direct_chat.reply` with the session_id) still apply.\n //\n // For agents WITHOUT a running persistent session (oneshot Claude Code,\n // OpenClaw cron-only deployments) the original `claude -p` path below\n // still applies.\n if (isSessionHealthy(agent.codeName)) {\n // CodeRabbit (PR #748): escape session_id + content before splicing\n // into the XML-shaped envelope so a payload containing quotes,\n // angle brackets, or a literal `</channel>` can't break the\n // envelope structure (and possibly steer the agent's downstream\n // routing). Apply to both attribute values and body for safety —\n // escaped < / > in the body still render legibly to the agent.\n const escapeXml = (value: string): string =>\n value\n .replaceAll('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('\"', '"')\n .replaceAll(\"'\", ''');\n const channelEnvelope =\n `<channel source=\"direct-chat\" session_id=\"${escapeXml(msg.session_id)}\" user=\"webapp\">\\n` +\n `${escapeXml(msg.content)}\\n` +\n `</channel>`;\n const delivered = await injectMessage(\n agent.codeName,\n 'chat',\n channelEnvelope,\n { task_name: 'direct-chat' },\n log,\n );\n if (delivered) {\n log(`[direct-chat] Injected into persistent session for '${agent.codeName}' (msg=${msg.id}); agent will reply via direct_chat.reply tool`);\n return;\n }\n // CodeRabbit (PR #748): injectMessage returned false — acpx is\n // unavailable so it fell back to `tmux send-keys`, which doesn't\n // guarantee submission. Rather than strand the message in\n // 'processing' indefinitely, fall through to the original\n // `claude -p` path below so the user still gets a reply. Worst\n // case: a duplicate response if tmux send-keys DID land — the\n // user sees two answers, which is recoverable. Silent loss is\n // not.\n log(`[direct-chat] Inject reported unverified for '${agent.codeName}' (msg=${msg.id}) — falling back to one-shot direct-chat handling`);\n // Fall through to the existing claude -p / openclaw reply path below.\n }\n\n try {\n let reply: string;\n\n if (fw === 'claude-code') {\n // Always use claude -p for webapp direct chat so the reply comes back\n // through the API. The persistent session handles Slack/Telegram channels.\n const { getProjectDir: ccProjectDir } = await import('./claude-scheduler.js');\n const projDir = ccProjectDir(agent.codeName);\n\n // Build --allowedTools from the agent's configured MCP servers\n const mcpConfigPath = join(projDir, '.mcp.json');\n const serverNames: string[] = [];\n if (existsSync(mcpConfigPath)) {\n try {\n const d = JSON.parse(readFileSync(mcpConfigPath, 'utf-8'));\n if (d.mcpServers) serverNames.push(...Object.keys(d.mcpServers));\n } catch { /* non-fatal */ }\n }\n // ENG-4487: includes Skill + Agent via shared helper.\n const allowedTools = buildAllowedTools(serverNames);\n\n // ENG-4671: matches the scheduled-task fire path above. The webapp\n // direct-chat path didn't have the root-block (it doesn't use\n // --dangerously-skip-permissions), but it does share buildAllowedTools'\n // server-name mismatches (kanban-under-augmented), which empirically\n // caused tool calls to be denied in headless mode. --permission-mode\n // auto fixes both paths consistently.\n const chatArgs = [\n '-p', msg.content,\n '--output-format', 'text',\n '--mcp-config', mcpConfigPath,\n '--strict-mcp-config',\n '--permission-mode', 'auto',\n '--allowedTools', allowedTools,\n ];\n const chatClaudeMd = join(projDir, 'CLAUDE.md');\n if (existsSync(chatClaudeMd)) {\n chatArgs.push('--system-prompt-file', chatClaudeMd);\n }\n\n // Source .env.integrations for the claude process\n const envIntPath = join(projDir, '.env.integrations');\n const childEnv = { ...process.env };\n if (existsSync(envIntPath)) {\n try {\n for (const line of readFileSync(envIntPath, 'utf-8').split('\\n')) {\n if (!line || line.startsWith('#') || !line.includes('=')) continue;\n const eqIdx = line.indexOf('=');\n childEnv[line.slice(0, eqIdx)] = line.slice(eqIdx + 1);\n }\n } catch { /* non-fatal */ }\n }\n // Honor the host's configured auth mode (ENG-4417). Force-refresh +\n // fail-closed: if we can't confirm the auth config, don't spawn.\n try {\n await applyClaudeAuthToEnv(childEnv, 'direct-chat');\n } catch (err) {\n throw new Error(`Auth resolve failed for '${agent.codeName}': ${(err as Error).message}`);\n }\n\n const { stdout } = await execFilePromiseLong(resolveClaudeBinary(), chatArgs, { cwd: projDir, stdin: 'ignore', env: childEnv });\n reply = stdout.trim() || '[No response from agent]';\n } else {\n // OpenClaw: use profile-based agent invocation\n const { stdout } = await execFilePromiseLong('openclaw', [\n '--profile', agent.codeName,\n 'agent',\n '--local',\n '--agent', agent.codeName,\n '--message', msg.content,\n '--session-id', msg.session_id,\n '--json',\n ]);\n\n // OpenClaw agent --json returns either:\n // { payloads: [{ text }], meta: {...} } (success)\n // { result: { payloads: [{ text }] }, meta: {...} } (some versions)\n try {\n const parsed = JSON.parse(stdout);\n const payloads = (parsed?.payloads ?? parsed?.result?.payloads) as Array<{ text?: string }> | undefined;\n reply = payloads?.[0]?.text ?? parsed?.reply ?? parsed?.text ?? parsed?.message ?? stdout;\n } catch {\n reply = stdout.trim() || '[No response from agent]';\n }\n }\n\n // Post the reply back\n await api.post('/host/direct-chat/reply', {\n agent_id: agent.agentId,\n session_id: msg.session_id,\n content: reply,\n });\n\n log(`[direct-chat] Reply sent for '${agent.codeName}'`);\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n const errorId = createHash('sha256').update(errMsg).digest('hex').slice(0, 12);\n log(`[direct-chat] Failed to process message for '${agent.codeName}': error_id=${errorId} error=${errMsg.slice(0, 500)}`);\n\n // Post an error reply so the webapp doesn't poll forever\n try {\n await api.post('/host/direct-chat/reply', {\n agent_id: agent.agentId,\n session_id: msg.session_id,\n content: `[Error] Failed to process message (ref: ${errorId}). Please retry.`,\n });\n } catch {\n // Last resort — nothing more we can do\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Cron result harvesting — polls cron run history and updates agent status\n// ---------------------------------------------------------------------------\n\n// Template IDs that map to agent field updates\nconst STANDUP_TEMPLATES = new Set(['daily-standup', 'end-of-day-summary']);\nconst TASK_UPDATE_TEMPLATES = new Set(['hourly-status', 'task-update']);\nconst PLAN_TEMPLATES = new Set(['morning-plan']);\nconst KANBAN_WORK_TEMPLATES = new Set(['kanban-work']);\nconst BOARD_INJECT_TEMPLATES = new Set(['morning-plan', 'task-update', 'hourly-status', 'end-of-day-summary', 'kanban-work']);\n\n// ENG-4410: Check if board has actionable items (backlog, today, or in_progress).\n// Used to skip kanban-work LLM calls when the board is completely empty.\nconst ACTIONABLE_STATUSES = new Set(['backlog', 'todo', 'in_progress']);\nfunction hasActionableItems(items: BoardItem[]): boolean {\n return items.some((item) => ACTIONABLE_STATUSES.has(item.status));\n}\n\n// Throttle harvest — no need to check cron results every 30s cycle\nconst lastHarvestAt = new Map<string, number>();\nconst HARVEST_INTERVAL_MS = 3 * 60 * 1000; // 3 minutes\n\n// Cache kanban board per agent per poll cycle\ntype BoardItem = { id: string; title: string; status: string; priority: number; estimated_minutes?: number; deliverable?: string; result?: string; notify_channel?: string; notify_to?: string; updated_at?: string };\nconst kanbanBoardCache = new Map<string, BoardItem[]>();\n// Separate cache for notification board diff — not pre-populated by step 7d\nconst notifyBoardCache = new Map<string, Set<string>>();\n\ninterface CronRunEntry {\n ts: number;\n jobId: string;\n action: string;\n status: string;\n summary?: string;\n}\n\nasync function harvestCronResults(\n codeName: string,\n tasks: Array<{ id: string; template_id: string; name: string }>,\n gatewayPort: number,\n): Promise<void> {\n const token = readGatewayToken(codeName);\n const gwArgs = ['--url', `ws://127.0.0.1:${gatewayPort}`, ...(token ? ['--token', token] : [])];\n\n // List current jobs to map job IDs to template IDs\n let gatewayJobs: Array<{ id: string; name: string }> = [];\n try {\n const cliBin = resolveAgentFramework(codeName).cliBinary ?? 'openclaw';\n const { stdout } = await execFilePromise(cliBin, ['--profile', codeName, 'cron', 'list', '--json', ...gwArgs]);\n const parsed = JSON.parse(stdout);\n gatewayJobs = (parsed.jobs ?? []) as Array<{ id: string; name: string }>;\n } catch {\n return; // Gateway not ready\n }\n\n // Build a map from gateway job ID → DB task template_id\n const jobTemplateMap = new Map<string, string>();\n for (const job of gatewayJobs) {\n if (!job.name.startsWith('aug:')) continue;\n // Name format: aug:{template_id}:{task_uuid}\n const parts = job.name.split(':');\n if (parts.length >= 3) {\n jobTemplateMap.set(job.id, parts[1]!);\n }\n }\n\n // Check each relevant job for new completed runs\n for (const [jobId, templateId] of jobTemplateMap) {\n if (!STANDUP_TEMPLATES.has(templateId) && !TASK_UPDATE_TEMPLATES.has(templateId) && !PLAN_TEMPLATES.has(templateId) && !KANBAN_WORK_TEMPLATES.has(templateId)) continue;\n\n let runs: CronRunEntry[] = [];\n try {\n const cliBin2 = resolveAgentFramework(codeName).cliBinary ?? 'openclaw';\n const { stdout } = await execFilePromise(cliBin2, ['--profile', codeName, 'cron', 'runs', '--id', jobId, ...gwArgs]);\n const parsed = JSON.parse(stdout);\n runs = (parsed.entries ?? []) as CronRunEntry[];\n } catch {\n continue;\n }\n\n // Detect API key errors from failed runs\n const latestRun = runs.filter((r) => r.action === 'finished').sort((a, b) => b.ts - a.ts)[0];\n if (latestRun) {\n const agentId = codeNameToAgentId.get(codeName);\n const summary = latestRun.summary ?? '';\n const isKeyError = summary.includes('Key limit exceeded') || summary.includes('key limit') ||\n summary.includes('rate limit') || summary.includes('insufficient_quota') ||\n summary.includes('billing') || summary.includes('402');\n if (agentId) {\n if (latestRun.status === 'error' && isKeyError) {\n const errorMsg = summary.slice(0, 200);\n if (!apiKeyStatusCache.get(codeName)) {\n apiKeyStatusCache.set(codeName, true);\n api.post('/host/agent-api-key-status', { agent_id: agentId, status: 'rate_limited', error: errorMsg }).catch(() => {});\n log(`API key error detected for '${codeName}': ${errorMsg}`);\n }\n } else if (latestRun.status === 'ok' && apiKeyStatusCache.get(codeName)) {\n apiKeyStatusCache.delete(codeName);\n api.post('/host/agent-api-key-status', { agent_id: agentId, status: null, error: null }).catch(() => {});\n log(`API key status cleared for '${codeName}' — run succeeded`);\n }\n }\n }\n\n // Find the latest successful run\n const completed = runs\n .filter((r) => r.action === 'finished' && r.status === 'ok' && r.summary)\n .sort((a, b) => b.ts - a.ts);\n\n if (completed.length === 0) continue;\n\n const latest = completed[0]!;\n const lastSeen = lastCronRunTs.get(jobId) ?? 0;\n if (latest.ts <= lastSeen) continue; // Already processed\n\n lastCronRunTs.set(jobId, latest.ts);\n\n // POST result to API\n const statusUpdate: Record<string, unknown> = { agent_code_name: codeName };\n\n if (STANDUP_TEMPLATES.has(templateId)) {\n // Parse standup summary into structured fields\n const summary = latest.summary ?? '';\n statusUpdate.standup = parseStandupSummary(summary);\n }\n\n if (TASK_UPDATE_TEMPLATES.has(templateId)) {\n statusUpdate.current_tasks = latest.summary ?? '';\n }\n\n // Only POST agent-status if we have something to update (standup or current_tasks)\n if (statusUpdate.standup || statusUpdate.current_tasks !== undefined) {\n try {\n await api.post('/host/agent-status', statusUpdate);\n log(`Updated ${templateId} for '${codeName}' from cron result`);\n } catch (err) {\n log(`Failed to update ${templateId} for '${codeName}': ${(err as Error).message}`);\n }\n }\n\n // Kanban: Parse plan items from morning-plan template\n if (PLAN_TEMPLATES.has(templateId)) {\n const summary = latest.summary ?? '';\n const planItems = parsePlanItems(summary);\n if (planItems.length > 0) {\n // Look up agent_id from code_name\n try {\n const agentId = codeNameToAgentId.get(codeName);\n if (agentId) {\n await api.post('/host/kanban', {\n agent_id: agentId,\n add: planItems,\n archive_days: 7,\n });\n log(`Added ${planItems.length} kanban items for '${codeName}' from morning-plan`);\n }\n } catch (err) {\n log(`Failed to update kanban for '${codeName}': ${(err as Error).message}`);\n }\n }\n }\n\n // Kanban: Parse status updates from task-update/hourly/kanban-work templates\n if (TASK_UPDATE_TEMPLATES.has(templateId) || KANBAN_WORK_TEMPLATES.has(templateId)) {\n const summary = latest.summary ?? '';\n const kanbanUpdates = parseKanbanUpdates(summary);\n if (kanbanUpdates.length > 0) {\n try {\n const agentId = codeNameToAgentId.get(codeName);\n if (agentId) {\n await api.post('/host/kanban', {\n agent_id: agentId,\n update: kanbanUpdates,\n });\n log(`Updated ${kanbanUpdates.length} kanban items for '${codeName}'`);\n }\n } catch (err) {\n log(`Failed to update kanban for '${codeName}': ${(err as Error).message}`);\n }\n }\n\n // Board diff notification detection moved to step 12 in processAgent\n // (runs every poll cycle, not just on cron harvest)\n }\n }\n}\n\nfunction parseStandupSummary(summary: string): { yesterday: string; today: string; blockers: string } {\n // Try to extract sections from the standup text\n const lines = summary.split('\\n');\n let yesterday = '';\n let today = '';\n let blockers = '';\n let currentSection: 'yesterday' | 'todo' | 'blockers' | null = null;\n\n for (const line of lines) {\n const lower = line.toLowerCase();\n if (lower.includes('yesterday') || lower.includes('accomplished')) {\n currentSection = 'yesterday';\n continue;\n } else if (lower.includes('todo') || lower.includes('working on')) {\n currentSection = 'todo';\n continue;\n } else if (lower.includes('blocker')) {\n currentSection = 'blockers';\n continue;\n }\n\n const trimmed = line.replace(/^[-*•]\\s*/, '').trim();\n if (!trimmed) continue;\n\n switch (currentSection) {\n case 'yesterday': yesterday += (yesterday ? '\\n' : '') + trimmed; break;\n case 'todo': today += (today ? '\\n' : '') + trimmed; break;\n case 'blockers': blockers += (blockers ? '\\n' : '') + trimmed; break;\n }\n }\n\n // Fallback: if parsing didn't find sections, use full summary\n if (!yesterday && !today && !blockers) {\n today = summary;\n }\n\n return { yesterday, today, blockers };\n}\n\nfunction parsePlanItems(summary: string): Array<{\n title: string;\n description?: string;\n priority: number;\n estimated_minutes?: number;\n status: string;\n}> {\n const items: Array<{\n title: string;\n description?: string;\n priority: number;\n estimated_minutes?: number;\n status: string;\n }> = [];\n\n const lines = summary.split('\\n');\n let currentItem: (typeof items)[0] | null = null;\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // Match numbered/bulleted items: \"1. [HIGH] Task title (~30min)\" or \"- [MEDIUM] Task (~2hr)\"\n const itemMatch = trimmed.match(/^(?:\\d+[\\.\\)]\\s*|[-*•]\\s*)\\[?(HIGH|MEDIUM|LOW|MED)\\]?\\s*(.+)/i);\n\n if (itemMatch) {\n // Push previous item\n if (currentItem) items.push(currentItem);\n\n const priorityStr = itemMatch[1]!.toUpperCase();\n const rest = itemMatch[2]!;\n\n // Extract time estimate\n let estimatedMinutes: number | undefined;\n const timeMatch = rest.match(/\\(~?(\\d+)\\s*(min(?:utes?)?|hr?(?:ours?)?|h)\\)/i);\n if (timeMatch) {\n const val = parseInt(timeMatch[1]!, 10);\n const unit = timeMatch[2]!.toLowerCase();\n estimatedMinutes = unit.startsWith('h') ? val * 60 : val;\n }\n\n // Title is everything except the time estimate — sanitize LLM output\n const title = sanitizeKanbanString(\n rest.replace(/\\(~?\\d+\\s*(?:min(?:utes?)?|hr?(?:ours?)?|h)\\)/i, ''),\n MAX_KANBAN_TITLE_LENGTH,\n );\n if (!title) continue;\n\n const priorityMap: Record<string, number> = { HIGH: 1, MEDIUM: 2, MED: 2, LOW: 3 };\n const priority = priorityMap[priorityStr] ?? 2;\n if (![1, 2, 3].includes(priority)) continue;\n\n currentItem = {\n title,\n priority,\n estimated_minutes: estimatedMinutes,\n status: 'todo',\n };\n } else if (currentItem && trimmed && !trimmed.match(/^(?:PLAN|---)/i)) {\n // Non-numbered line after a plan item = description (length-limited)\n const descLine = sanitizeKanbanString(trimmed, MAX_KANBAN_NOTES_LENGTH);\n currentItem.description = currentItem.description\n ? sanitizeKanbanString(currentItem.description + '\\n' + descLine, MAX_KANBAN_NOTES_LENGTH)\n : descLine;\n }\n }\n\n if (currentItem) items.push(currentItem);\n return items;\n}\n\nconst VALID_KANBAN_STATUSES = new Set(['backlog', 'todo', 'in_progress', 'done']);\nconst MAX_KANBAN_TITLE_LENGTH = 500;\nconst MAX_KANBAN_NOTES_LENGTH = 2000;\n\n/** Sanitize a string from LLM output: trim + length limit */\nfunction sanitizeKanbanString(value: string, maxLen: number): string {\n if (!value) return '';\n return value\n .replace(/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]/g, '') // strip control chars (keep \\n, \\r, \\t)\n .replace(/\\{\\{.*?\\}\\}/g, '') // strip template injection markers\n .replace(/^(System|Assistant|Human):\\s*/gmi, '') // strip prompt-role prefixes\n .replace(/#{2,}/g, '') // strip markdown heading markers\n .replace(/\\s+/g, ' ') // collapse whitespace\n .trim()\n .slice(0, maxLen);\n}\n\n/** Sanitize all external fields on a board item before caching / prompt injection */\nfunction sanitizeBoardItem<T extends { title: string; status: string; deliverable?: string }>(item: T): T {\n return {\n ...item,\n title: sanitizeKanbanString(item.title, 200),\n status: sanitizeKanbanString(item.status, 50),\n ...(item.deliverable ? { deliverable: sanitizeKanbanString(item.deliverable, 500) } : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Built-in skill files — bundled for auto-deployment to all agents\n// ---------------------------------------------------------------------------\n\nconst builtInSkillCache = new Map<string, Array<{ relativePath: string; content: string }> | null>();\n\nfunction getBuiltInSkillContent(skillId: string): Array<{ relativePath: string; content: string }> | null {\n if (builtInSkillCache.has(skillId)) return builtInSkillCache.get(skillId)!;\n\n try {\n // Resolve skill directory relative to the CLI package root\n // In dev: skills/<skillId>/SKILL.md is in the monorepo root\n // When bundled: skills are embedded at build time or read from cwd\n const candidates = [\n join(process.cwd(), 'skills', skillId, 'SKILL.md'),\n join(new URL('.', import.meta.url).pathname, '..', '..', '..', '..', 'skills', skillId, 'SKILL.md'),\n ];\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n const content = readFileSync(candidate, 'utf-8');\n const files = [{ relativePath: 'SKILL.md', content }];\n builtInSkillCache.set(skillId, files);\n return files;\n }\n }\n\n builtInSkillCache.set(skillId, null);\n return null;\n } catch {\n builtInSkillCache.set(skillId, null);\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n\nfunction parseKanbanUpdates(summary: string): Array<{\n title: string;\n status: string;\n notes?: string;\n result?: string;\n}> {\n const updates: Array<{ title: string; status: string; notes?: string; result?: string }> = [];\n\n // Look for \"KANBAN UPDATE:\" section\n const kanbanIdx = summary.indexOf('KANBAN UPDATE:');\n if (kanbanIdx === -1) return updates;\n\n const kanbanSection = summary.slice(kanbanIdx + 'KANBAN UPDATE:'.length);\n const lines = kanbanSection.split('\\n');\n\n for (const line of lines) {\n const trimmed = line.trim();\n // Match: - \"item title\": status (optional notes/result)\n // Accept legacy 'today' as a synonym for 'todo' so prompts that haven't\n // been re-rendered post-rename still parse cleanly. Map to 'todo' before\n // validating against VALID_KANBAN_STATUSES (which only contains 'todo').\n const match = trimmed.match(/^[-*•]\\s*\"([^\"]+)\":\\s*(backlog|todo|today|in_progress|done)(?:\\s*\\((.+)\\))?/i);\n if (match) {\n const rawStatus = match[2]!.toLowerCase();\n const status = rawStatus === 'today' ? 'todo' : rawStatus;\n // Validate status is in the allowed set\n if (!VALID_KANBAN_STATUSES.has(status)) continue;\n\n const title = sanitizeKanbanString(match[1]!, MAX_KANBAN_TITLE_LENGTH);\n if (!title) continue;\n\n const parenthetical = match[3] ?? undefined;\n\n // Extract result from parenthetical when status is done\n // Pattern: \"result: <value>\" or just the whole parenthetical as notes\n let notes: string | undefined;\n let result: string | undefined;\n\n if (parenthetical && status === 'done') {\n const resultMatch = parenthetical.match(/^result:\\s*(.+)/i);\n if (resultMatch) {\n result = sanitizeKanbanString(resultMatch[1]!, MAX_KANBAN_NOTES_LENGTH);\n } else {\n notes = sanitizeKanbanString(parenthetical, MAX_KANBAN_NOTES_LENGTH);\n }\n } else if (parenthetical) {\n notes = sanitizeKanbanString(parenthetical, MAX_KANBAN_NOTES_LENGTH);\n }\n\n updates.push({\n title,\n status,\n notes,\n result,\n });\n }\n }\n\n return updates;\n}\n\nfunction formatBoardForPrompt(\n items: Array<{ title: string; status: string; priority: number; estimated_minutes?: number; deliverable?: string }>,\n template: 'morning-plan' | 'follow-up',\n): string {\n if (items.length === 0) return '';\n\n const priorityLabel = (p: number) => p === 1 ? 'HIGH' : p === 3 ? 'LOW' : 'MED';\n const timeLabel = (m?: number) => m ? ` (~${m >= 60 ? `${Math.round(m / 60)}hr` : `${m}min`})` : '';\n const deliverableLine = (d?: string) => d ? `\\n Deliverable: ${d}` : '';\n\n const grouped: Record<string, typeof items> = {};\n for (const item of items) {\n const key = item.status;\n if (!grouped[key]) grouped[key] = [];\n grouped[key]!.push(item);\n }\n\n const lines: string[] = [];\n\n if (template === 'morning-plan') {\n lines.push('=== CURRENT BOARD ===');\n\n for (const [status, label] of [['backlog', 'BACKLOG (carry-over)'], ['todo', 'TO DO'], ['in_progress', 'IN PROGRESS']] as const) {\n const statusItems = grouped[status];\n if (statusItems && statusItems.length > 0) {\n lines.push(`${label}:`);\n statusItems.forEach((item, i) => {\n lines.push(` ${i + 1}. [${priorityLabel(item.priority)}] ${item.title}${timeLabel(item.estimated_minutes)}${deliverableLine(item.deliverable)}`);\n });\n }\n }\n\n lines.push('=====================');\n lines.push('');\n lines.push('Create today\\'s plan. You may:');\n lines.push('- Move backlog items to \"todo\"');\n lines.push('- Add new items you\\'ve identified');\n lines.push('- Reprioritise existing items');\n lines.push('');\n } else {\n lines.push('=== YOUR KANBAN BOARD ===');\n\n for (const [status, label] of [['todo', 'TO DO'], ['in_progress', 'IN PROGRESS'], ['backlog', 'BACKLOG']] as const) {\n const statusItems = grouped[status];\n if (statusItems && statusItems.length > 0) {\n lines.push(`${label}:`);\n statusItems.forEach((item, i) => {\n lines.push(` ${i + 1}. [${priorityLabel(item.priority)}] ${item.title}${timeLabel(item.estimated_minutes)}${deliverableLine(item.deliverable)}`);\n });\n }\n }\n\n const doneItems = grouped['done'];\n if (doneItems && doneItems.length > 0) {\n lines.push('DONE TODAY:');\n doneItems.forEach((item, i) => {\n lines.push(` ${i + 1}. ${item.title}`);\n });\n }\n\n lines.push('=========================');\n lines.push('');\n lines.push('IMPORTANT: Use kanban MCP tools to update the board IN REAL TIME:');\n lines.push('1. FIRST call kanban.move to move your chosen item to in_progress BEFORE starting work');\n lines.push('2. Do the work');\n lines.push('3. Call kanban.done with a result summary when finished');\n lines.push('4. If blocked, call kanban.update with notes, then pick the next item');\n lines.push('');\n lines.push('SELF-MANAGEMENT: When you receive a request from a channel (Slack, Telegram)');\n lines.push('that takes more than a quick response, create a task first with kanban.add,');\n lines.push('move to in_progress, do the work, then kanban.done with a result summary.');\n lines.push('');\n lines.push('If MCP tools are unavailable, include a KANBAN UPDATE section in your output:');\n lines.push('KANBAN UPDATE:');\n lines.push('- \"item title\": new_status (optional notes)');\n lines.push('- \"item title\": done (result: <what you produced>)');\n lines.push('Statuses: backlog, today, in_progress, done');\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\nasync function execFilePromise(cmd: string, args: string[]): Promise<{ stdout: string; stderr: string }> {\n const { execFile: ef } = await import('node:child_process');\n return new Promise((resolve, reject) => {\n ef(cmd, args, { timeout: 15_000 }, (err, stdout, stderr) => {\n if (err) reject(err);\n else resolve({ stdout, stderr });\n });\n });\n}\n\n/**\n * Rejection shape for non-zero exits. Includes stdout AND stderr so\n * callers can log both — some tools (Claude CLI in particular) write\n * startup errors to stdout and leave stderr empty, which makes\n * stderr-only logging useless for debugging.\n */\nexport class ChildProcessError extends Error {\n public readonly code: number | null;\n public readonly stdout: string;\n public readonly stderr: string;\n constructor(code: number | null, stdout: string, stderr: string) {\n const stderrSnippet = stderr.trim().slice(0, 500);\n const stdoutSnippet = stdout.trim().slice(0, 500);\n // Prefer stderr in the message; fall back to stdout when stderr is empty.\n const detail = stderrSnippet || stdoutSnippet || '(no output)';\n super(`Exit code ${code}: ${detail}`);\n this.name = 'ChildProcessError';\n this.code = code;\n this.stdout = stdout;\n this.stderr = stderr;\n }\n}\n\nasync function execFilePromiseLong(\n cmd: string,\n args: string[],\n opts?: { cwd?: string; timeout?: number; stdin?: 'ignore'; env?: NodeJS.ProcessEnv },\n): Promise<{ stdout: string; stderr: string }> {\n const { spawn: sp } = await import('node:child_process');\n return new Promise((resolve, reject) => {\n const child = sp(cmd, args, {\n cwd: opts?.cwd,\n stdio: [opts?.stdin === 'ignore' ? 'ignore' : 'pipe', 'pipe', 'pipe'],\n ...(opts?.env ? { env: opts.env } : {}),\n });\n let stdout = '';\n let stderr = '';\n child.stdout?.on('data', (d: Buffer) => { stdout += d.toString(); });\n child.stderr?.on('data', (d: Buffer) => { stderr += d.toString(); });\n const timer = setTimeout(() => { child.kill(); reject(new Error(`Timed out after ${opts?.timeout ?? 120_000}ms`)); }, opts?.timeout ?? 120_000);\n child.on('close', (code) => {\n clearTimeout(timer);\n if (code !== 0) reject(new ChildProcessError(code, stdout, stderr));\n else resolve({ stdout, stderr });\n });\n child.on('error', (err) => { clearTimeout(timer); reject(err); });\n });\n}\n\n// ---------------------------------------------------------------------------\n// Cron health monitoring — detects late/failed jobs and sends alerts\n// ---------------------------------------------------------------------------\n\nconst LATE_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes grace period\n\ninterface CronAlert {\n type: 'late_standup' | 'cron_failure';\n agentCodeName: string;\n agentDisplayName: string;\n jobName: string;\n taskName: string;\n schedule: string;\n jobId: string;\n detail: string;\n}\n\nasync function monitorCronHealth(agentStates: AgentState[]): Promise<void> {\n const alerts: CronAlert[] = [];\n const now = Date.now();\n\n for (const agent of agentStates) {\n if (!agent.gatewayRunning || !agent.gatewayPort) continue;\n\n const token = readGatewayToken(agent.codeName);\n const gwArgs = ['--url', `ws://127.0.0.1:${agent.gatewayPort}`, ...(token ? ['--token', token] : [])];\n\n let jobs: Array<{\n id: string;\n name: string;\n enabled: boolean;\n state?: {\n nextRunAtMs?: number;\n lastRunAtMs?: number;\n lastRunStatus?: string;\n lastDelivered?: boolean;\n consecutiveErrors?: number;\n };\n }> = [];\n\n try {\n const cliBin = resolveAgentFramework(agent.codeName).cliBinary ?? 'openclaw';\n const { stdout } = await execFilePromise(cliBin, ['--profile', agent.codeName, 'cron', 'list', '--json', ...gwArgs]);\n const parsed = JSON.parse(stdout);\n jobs = parsed.jobs ?? [];\n } catch {\n continue;\n }\n\n for (const job of jobs) {\n if (!job.enabled || !job.name.startsWith('aug:')) continue;\n const alertKey = `${agent.codeName}:${job.id}`;\n\n // Look up human-readable task info\n const displayInfo = taskDisplayInfo.get(`${agent.codeName}:${job.name}`);\n const taskName = displayInfo?.taskName ?? job.name;\n const schedule = displayInfo?.schedule ?? '';\n const agentDisplayName = displayInfo?.agentDisplayName ?? agent.codeName;\n\n // Check for late jobs — nextRunAtMs is in the past by more than the threshold\n if (job.state?.nextRunAtMs && job.state.nextRunAtMs + LATE_THRESHOLD_MS < now) {\n if (!alertedJobs.has(`late:${alertKey}`)) {\n const minsLate = Math.round((now - job.state.nextRunAtMs) / 60_000);\n alerts.push({\n type: 'late_standup',\n agentCodeName: agent.codeName,\n agentDisplayName,\n jobName: job.name,\n taskName,\n schedule,\n jobId: job.id,\n detail: `Job is ${minsLate}m late (expected at ${new Date(job.state.nextRunAtMs).toISOString()})`,\n });\n alertedJobs.add(`late:${alertKey}`);\n }\n } else {\n // Clear the late alert if the job is no longer late\n alertedJobs.delete(`late:${alertKey}`);\n }\n\n // Check for failed runs\n if (job.state?.lastRunStatus === 'error' || (job.state?.consecutiveErrors && job.state.consecutiveErrors > 0)) {\n if (!alertedJobs.has(`fail:${alertKey}`)) {\n alerts.push({\n type: 'cron_failure',\n agentCodeName: agent.codeName,\n agentDisplayName,\n jobName: job.name,\n taskName,\n schedule,\n jobId: job.id,\n detail: `Last run failed (${job.state.consecutiveErrors ?? 1} consecutive error(s))`,\n });\n alertedJobs.add(`fail:${alertKey}`);\n }\n } else {\n alertedJobs.delete(`fail:${alertKey}`);\n }\n }\n }\n\n if (alerts.length === 0) return;\n\n // Log all alerts\n for (const alert of alerts) {\n log(`ALERT [${alert.type}] ${alert.agentCodeName}/${alert.jobName}: ${alert.detail}`);\n }\n\n // Send to Slack webhook if configured\n if (alertSlackWebhook) {\n await sendSlackAlert(alerts);\n }\n\n // Post to API for dashboard visibility\n try {\n await api.post('/host/cron-alerts', { alerts });\n } catch {\n // Non-fatal — API may not have this endpoint yet\n }\n}\n\n/**\n * Call Telegram Bot API using Node's https module (not fetch).\n * Node's native fetch (undici) can't reach api.telegram.org on some networks.\n */\nfunction telegramApiCall(botToken: string, method: string, body: unknown): Promise<{ ok: boolean; description?: string }> {\n return new Promise((resolve, reject) => {\n const postData = JSON.stringify(body);\n const req = https.request({\n hostname: 'api.telegram.org',\n port: 443,\n path: `/bot${botToken}/${method}`,\n method: 'POST',\n family: 4,\n timeout: 10_000,\n headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) },\n }, (res) => {\n let data = '';\n res.on('data', (d) => { data += d; });\n res.on('end', () => {\n try { resolve(JSON.parse(data)); } catch { reject(new Error('Invalid JSON from Telegram API')); }\n });\n });\n req.on('error', reject);\n req.on('timeout', () => { req.destroy(); reject(new Error('Telegram API timeout')); });\n req.write(postData);\n req.end();\n });\n}\n\nasync function sendSlackWebhookMessage(text: string): Promise<void> {\n if (!alertSlackWebhook) {\n log('sendSlackWebhookMessage: no alertSlackWebhook configured — message dropped');\n return;\n }\n try {\n const response = await fetch(alertSlackWebhook, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ text }),\n });\n if (!response.ok) {\n log(`Slack webhook failed: ${response.status} ${response.statusText}`);\n }\n } catch (err) {\n log(`Slack webhook error: ${(err as Error).message}`);\n }\n}\n\n/**\n * Post a message to a specific Slack channel using the agent's bot token\n * cached from channel_configs during refresh. Returns true if sent successfully.\n */\nasync function sendSlackChannelMessage(agentCodeName: string, channelId: string, text: string): Promise<boolean> {\n const result = await postSlackChannelMessage(agentCodeName, channelId, text);\n return result.ok;\n}\n\n/**\n * Like sendSlackChannelMessage, but returns the posted message's ts so the\n * caller can thread a follow-up reply under it. Accepts an optional\n * `threadTs` to post *as* a thread reply. ENG-4457 needs this to thread\n * the periodic discoverability hint under the primary scheduled delivery.\n */\nasync function postSlackChannelMessage(\n agentCodeName: string,\n channelId: string,\n text: string,\n threadTs?: string,\n): Promise<{ ok: boolean; ts?: string }> {\n const botToken = agentChannelTokens.get(agentCodeName)?.slack;\n if (!botToken) {\n log(`No Slack bot token cached for '${agentCodeName}' — cannot post to ${channelId}`);\n return { ok: false };\n }\n\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 5_000);\n try {\n const body: Record<string, unknown> = { channel: channelId, text };\n if (threadTs) body.thread_ts = threadTs;\n const response = await fetch('https://slack.com/api/chat.postMessage', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${botToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n clearTimeout(timeout);\n const data = await response.json() as { ok: boolean; error?: string; ts?: string };\n if (!data.ok) {\n log(`Slack chat.postMessage failed for '${agentCodeName}' to ${channelId}: ${data.error}`);\n return { ok: false };\n }\n return { ok: true, ts: data.ts };\n } finally {\n clearTimeout(timeout);\n }\n } catch (err) {\n log(`Slack channel message error for '${agentCodeName}': ${(err as Error).message}`);\n return { ok: false };\n }\n}\n\n// ENG-4457 — periodic discoverability hint. Rolled on each successful\n// scheduled delivery; on a hit, post one follow-up message (Slack as a\n// thread reply, Telegram as a second sendMessage) pointing the user at\n// \"you can change this schedule conversationally\". The core gating lives\n// in delivery-hint.ts so the decision is unit-testable.\n\nasync function maybePostSlackThreadHint(\n agentCodeName: string,\n channelId: string,\n primaryTs: string | undefined,\n): Promise<void> {\n if (!shouldIncludeHint(hintProbability(agentCodeName))) return;\n if (!primaryTs) {\n // No ts means we can't thread. Skip rather than post a loose second\n // message in the channel — that would look like noise.\n return;\n }\n const hint = pickHintVariant();\n const result = await postSlackChannelMessage(agentCodeName, channelId, hint, primaryTs);\n if (result.ok) {\n log(`[delivery-hint] Slack thread hint posted for '${agentCodeName}'`);\n }\n}\n\nasync function maybeSendTelegramFollowUpHint(\n agentCodeName: string,\n botToken: string,\n chatId: string | number,\n): Promise<void> {\n if (!shouldIncludeHint(hintProbability(agentCodeName))) return;\n const hint = pickHintVariant();\n try {\n const result = await telegramApiCall(botToken, 'sendMessage', { chat_id: chatId, text: hint });\n if (!result.ok) {\n // Bot API resolves `{ ok: false, description }` for app-level failures\n // (chat not found, bot blocked, rate limit). Without this branch the\n // hint drops silently despite the PR promising logged failures.\n log(`[delivery-hint] Telegram follow-up failed for '${agentCodeName}': ${result.description ?? 'unknown'}`);\n return;\n }\n log(`[delivery-hint] Telegram follow-up hint posted for '${agentCodeName}'`);\n } catch (err) {\n // Hint is a best-effort discoverability nudge — logging is enough.\n log(`[delivery-hint] Telegram follow-up failed for '${agentCodeName}': ${(err as Error).message}`);\n }\n}\n\nasync function sendSlackAlert(alerts: CronAlert[]): Promise<void> {\n const blocks = alerts.map((a) => {\n const emoji = a.type === 'late_standup' ? ':warning:' : ':x:';\n const label = a.type === 'late_standup' ? 'Late' : 'Failed';\n const scheduleInfo = a.schedule ? ` (${a.schedule})` : '';\n return `${emoji} *${label}* — *${a.agentDisplayName}* / ${a.taskName}${scheduleInfo}\\n${a.detail}`;\n });\n\n await sendSlackWebhookMessage(`:rotating_light: *Cron Health Alert*\\n\\n${blocks.join('\\n\\n')}`);\n}\n\n/**\n * Send a task notification to a specific channel (Slack or Telegram).\n */\n/** ENG-4422 §5–§6: deliver a scheduled task's output via a structured\n * DeliveryTarget (post-JSONB-migration) or a legacy `channel:<id>` /\n * `chat:<id>` opaque string (OpenClaw paths that still pre-date the\n * migration consumer-side).\n *\n * - Channel targets route via the existing Slack/Telegram send helpers.\n * - DM targets call @augmented/core/delivery.resolveDmTarget using the\n * agent's refresh payload (reports_to_slack_user_id / reports_to_person\n * ids, populated by host-runtime — see §8), then dispatch.\n * - Slack DMs open the IM via conversations.open and post with chat:write.\n * - Every DM body gets the attribution footer (§6).\n * - Failure status is posted to /host/schedules/delivery-status so the\n * server can update the observability columns (§10) + emit a\n * high-severity log line on 3+ consecutive failures.\n */\nasync function deliverScheduledTaskOutput(\n agentCodeName: string,\n agentId: string,\n rawTarget: unknown,\n body: string,\n taskId?: string,\n): Promise<void> {\n // ENG-4462: helper — wrap body with the schedule-edit link footer for the\n // given medium, honouring per-agent/host env kill switches. No-op when\n // taskId is absent or footer is disabled.\n const withLink = (b: string, medium: 'slack' | 'telegram'): string =>\n withScheduleLinkFooter({ body: b, medium, codeName: agentCodeName, agentId, taskId, log });\n\n // Legacy string form (OpenClaw -> manager-worker path, and any still-on-wire\n // rows we haven't flushed). Parse and route via sendTaskNotification.\n if (typeof rawTarget === 'string') {\n if (rawTarget.startsWith('channel:')) {\n const result = await sendTaskNotification(agentCodeName, 'slack', rawTarget, withLink(body, 'slack'));\n await reportDeliveryStatus(agentId, taskId, {\n status: result.ok ? 'ok' : 'failed',\n medium: 'slack',\n error_code: result.ok ? null : (result.error_code ?? 'SLACK_SEND_FAILED'),\n });\n return;\n }\n if (rawTarget.startsWith('chat:')) {\n const result = await sendTaskNotification(agentCodeName, 'telegram', rawTarget, withLink(body, 'telegram'));\n await reportDeliveryStatus(agentId, taskId, {\n status: result.ok ? 'ok' : 'failed',\n medium: 'telegram',\n error_code: result.ok ? null : (result.error_code ?? 'TELEGRAM_SEND_FAILED'),\n });\n return;\n }\n // Unknown string prefix — drop + log.\n log(`[delivery] Unrecognised legacy delivery_to string for '${agentCodeName}': ${rawTarget.slice(0, 60)}`);\n await reportDeliveryStatus(agentId, taskId, { status: 'failed', error_code: 'LEGACY_DELIVERY_TO_UNRECOGNISED' });\n return;\n }\n\n const parsed = parseDeliveryTarget(rawTarget);\n if (isParseError(parsed)) {\n log(`[delivery] Malformed delivery_to for '${agentCodeName}': ${parsed.code} — ${parsed.detail}`);\n await reportDeliveryStatus(agentId, taskId, { status: 'failed', error_code: parsed.code });\n return;\n }\n\n if (parsed.kind === 'channel') {\n if (parsed.provider === 'slack') {\n const channelId = parsed.channel_id ?? '';\n const sent = await postSlackChannelMessage(agentCodeName, channelId, withLink(body, 'slack'));\n await reportDeliveryStatus(agentId, taskId, {\n status: sent.ok ? 'ok' : 'failed',\n medium: 'slack',\n error_code: sent.ok ? null : 'SLACK_SEND_FAILED',\n });\n if (sent.ok) {\n await maybePostSlackThreadHint(agentCodeName, channelId, sent.ts);\n // ENG-4576 (PR #538): kick the API to post a threaded rating\n // prompt under the scheduled-task delivery. Server-side decides\n // whether to fire (block_kit_enabled, run outcome, idempotency).\n // Fire-and-forget — never block the delivery path on this.\n if (sent.ts && taskId) {\n await maybePostScheduledTaskRatingPrompt(agentId, taskId, channelId, sent.ts);\n }\n }\n return;\n }\n // Telegram channel-target\n const chatId = parsed.chat_id ?? '';\n const toStr = `chat:${chatId}`;\n const result = await sendTaskNotification(agentCodeName, 'telegram', toStr, withLink(body, 'telegram'));\n await reportDeliveryStatus(agentId, taskId, {\n status: result.ok ? 'ok' : 'failed',\n medium: 'telegram',\n error_code: result.ok ? null : (result.error_code ?? 'TELEGRAM_SEND_FAILED'),\n });\n if (result.ok) {\n const botToken = agentChannelTokens.get(agentCodeName)?.telegram;\n if (botToken) await maybeSendTelegramFollowUpHint(agentCodeName, botToken, chatId);\n }\n return;\n }\n\n // DM target — resolve person + medium, then dispatch with attribution footer.\n const agentRow = agentInfoForDelivery.get(agentCodeName);\n if (!agentRow) {\n log(`[delivery] No agent metadata cached for '${agentCodeName}' — dropping DM`);\n await reportDeliveryStatus(agentId, taskId, { status: 'failed', error_code: 'AGENT_METADATA_UNAVAILABLE' });\n return;\n }\n\n const resolved = resolveDmTarget(parsed, agentRow.resolverAgent, agentRow.peopleByPersonId);\n if (isResolveError(resolved)) {\n log(`[delivery] Cannot resolve DM target for '${agentCodeName}': ${resolved.code} — ${resolved.detail}`);\n await reportDeliveryStatus(agentId, taskId, { status: 'failed', error_code: resolved.code });\n return;\n }\n\n // Attribution footer first (§6), then the ENG-4462 schedule-edit link\n // underneath — order matters: the attribution identifies who sent the\n // message, the link lets the recipient act on it.\n const attributionBody = appendDmFooter(\n body,\n agentRow.ownerTeamName,\n agentRow.agentDisplayName,\n );\n const footeredBody =\n resolved.kind === 'dm'\n ? withLink(attributionBody, resolved.medium === 'slack' ? 'slack' : 'telegram')\n : attributionBody;\n\n if (resolved.kind === 'dm' && resolved.medium === 'slack') {\n const tokens = agentChannelTokens.get(agentCodeName);\n const botToken = tokens?.slack;\n if (!botToken) {\n await reportDeliveryStatus(agentId, taskId, { status: 'failed', error_code: 'SLACK_MISSING_SCOPE', medium: 'slack' });\n log(`[delivery] No Slack bot token for '${agentCodeName}' — DM dropped`);\n return;\n }\n // conversations.open → chat.postMessage. Slack accepts the user id for\n // chat.postMessage directly and will open/reuse the IM channel as long\n // as the bot has im:write.\n // CR #3108398187: 5-second abort on conversations.open matches the\n // timeout used by sendSlackChannelMessage; without it a stalled Slack\n // API call would tie up a scheduler slot indefinitely.\n try {\n const controller = new AbortController();\n const timeoutHandle = setTimeout(() => controller.abort(), 5_000);\n let openJson: { ok: boolean; error?: string; channel?: { id: string } };\n try {\n const openResp = await fetch('https://slack.com/api/conversations.open', {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${botToken}`,\n 'Content-Type': 'application/json; charset=utf-8',\n },\n body: JSON.stringify({ users: resolved.slack_user_id }),\n signal: controller.signal,\n });\n openJson = (await openResp.json()) as { ok: boolean; error?: string; channel?: { id: string } };\n } finally {\n clearTimeout(timeoutHandle);\n }\n if (!openJson.ok || !openJson.channel?.id) {\n const errCode = openJson.error === 'missing_scope' ? 'SLACK_MISSING_SCOPE' : `SLACK_OPEN_FAILED:${openJson.error ?? 'unknown'}`;\n log(`[delivery] conversations.open failed for '${agentCodeName}': ${openJson.error}`);\n await reportDeliveryStatus(agentId, taskId, { status: 'failed', error_code: errCode, medium: 'slack' });\n return;\n }\n const sent = await postSlackChannelMessage(agentCodeName, openJson.channel.id, footeredBody);\n await reportDeliveryStatus(agentId, taskId, {\n status: sent.ok ? 'ok' : 'failed',\n medium: 'slack',\n error_code: sent.ok ? null : 'SLACK_SEND_FAILED',\n });\n if (sent.ok) await maybePostSlackThreadHint(agentCodeName, openJson.channel.id, sent.ts);\n } catch (err) {\n const isAbort = (err as Error).name === 'AbortError';\n const errCode = isAbort ? 'SLACK_OPEN_TIMEOUT' : 'SLACK_EXCEPTION';\n log(`[delivery] Slack DM ${isAbort ? 'timeout' : 'failure'} for '${agentCodeName}': ${(err as Error).message}`);\n await reportDeliveryStatus(agentId, taskId, { status: 'failed', error_code: errCode, medium: 'slack' });\n }\n return;\n }\n\n if (resolved.kind === 'dm' && resolved.medium === 'telegram') {\n const tokens = agentChannelTokens.get(agentCodeName);\n const botToken = tokens?.telegram;\n if (!botToken) {\n log(`[delivery] No Telegram bot token for '${agentCodeName}' — DM dropped`);\n await reportDeliveryStatus(agentId, taskId, { status: 'failed', error_code: 'TELEGRAM_NO_TOKEN', medium: 'telegram' });\n return;\n }\n try {\n const result = await telegramApiCall(botToken, 'sendMessage', {\n chat_id: resolved.telegram_chat_id,\n text: footeredBody,\n });\n if (!result.ok) {\n log(`[delivery] Telegram DM failed for '${agentCodeName}': ${result.description}`);\n await reportDeliveryStatus(agentId, taskId, { status: 'failed', error_code: `TELEGRAM_SEND_FAILED:${result.description ?? 'unknown'}`, medium: 'telegram' });\n return;\n }\n await reportDeliveryStatus(agentId, taskId, { status: 'ok', medium: 'telegram' });\n await maybeSendTelegramFollowUpHint(agentCodeName, botToken, resolved.telegram_chat_id);\n } catch (err) {\n log(`[delivery] Telegram DM exception for '${agentCodeName}': ${(err as Error).message}`);\n await reportDeliveryStatus(agentId, taskId, { status: 'failed', error_code: 'TELEGRAM_EXCEPTION', medium: 'telegram' });\n }\n }\n}\n\n/** Per-agent metadata the delivery resolver needs. Populated during the\n * refresh cycle from the host-runtime payload (see §8). */\ninterface AgentDeliveryMetadata {\n agentDisplayName: string;\n ownerTeamName: string | null;\n resolverAgent: ResolverAgent;\n peopleByPersonId: Map<string, ResolverPerson>;\n}\nconst agentInfoForDelivery = new Map<string, AgentDeliveryMetadata>();\n\n/** Build `AgentDeliveryMetadata` from a refresh payload. Called whenever the\n * manager-worker syncs an agent so the resolver sees the latest reports_to /\n * owner_team / people-map state. */\nfunction cacheAgentDeliveryMetadata(codeName: string, refreshData: Record<string, unknown>): void {\n const agentRow = (refreshData['agent'] ?? {}) as Record<string, unknown>;\n const displayName = (agentRow['display_name'] as string | undefined) ?? codeName;\n const ownerTeamName = (agentRow['owner_team_name'] as string | null | undefined) ?? null;\n const framework = (agentRow['framework'] as string | undefined) ?? 'openclaw';\n const reportsToPersonId = (agentRow['reports_to'] as string | null | undefined) ?? null;\n const reportsToType = (agentRow['reports_to_type'] as 'person' | 'agent' | null | undefined) ?? null;\n\n // dm_capable_mediums: mirror the *same* \"active + credentialed\" predicate\n // the token cache uses (CR #3108333177). A channel config without a\n // bot_token isn't really DM-capable — with medium: 'auto', a pending or\n // broken Slack install would otherwise beat a healthy Telegram one.\n const channelConfigs = (refreshData['channel_configs'] ?? {}) as Record<string, { config?: Record<string, unknown> } | undefined>;\n const dmCapable: Array<'slack' | 'telegram'> = [];\n const slackBotToken = channelConfigs['slack']?.config?.['bot_token'];\n if (typeof slackBotToken === 'string' && slackBotToken.length > 0) {\n dmCapable.push('slack');\n }\n const telegramBotToken = channelConfigs['telegram']?.config?.['bot_token'];\n if (typeof telegramBotToken === 'string' && telegramBotToken.length > 0) {\n dmCapable.push('telegram');\n }\n\n const resolverAgent: ResolverAgent = {\n agent_id: (agentRow['agent_id'] as string) ?? '',\n framework,\n dm_capable_mediums: dmCapable,\n reports_to_person_id: reportsToType === 'person' ? reportsToPersonId : null,\n reports_to_type: reportsToType,\n };\n\n const peopleByPersonId = new Map<string, ResolverPerson>();\n // The reports_to person is carried directly on the agent row with its DM ids.\n if (reportsToPersonId && reportsToType === 'person') {\n peopleByPersonId.set(reportsToPersonId, {\n person_id: reportsToPersonId,\n display_name: (agentRow['reports_to_name'] as string | undefined) ?? 'Reports-To',\n slack_user_id: (agentRow['reports_to_slack_user_id'] as string | null | undefined) ?? null,\n telegram_chat_id: (agentRow['reports_to_telegram_chat_id'] as string | null | undefined) ?? null,\n });\n }\n // Other org people (for pinned dm:person:<id> targets) come via\n // refreshData.people. Keys may not be by person_id — guard and skip rows\n // without a person_id field.\n const people = (refreshData['people'] ?? []) as Array<Record<string, unknown>>;\n for (const p of people) {\n const personId = p['person_id'] as string | undefined;\n if (!personId) continue;\n const contactPrefs = (p['contact_preferences'] ?? {}) as Record<string, unknown>;\n peopleByPersonId.set(personId, {\n person_id: personId,\n display_name: (p['display_name'] as string | undefined) ?? 'person',\n slack_user_id: (contactPrefs['slack_user_id'] as string | null | undefined) ?? null,\n telegram_chat_id: (contactPrefs['telegram_chat_id'] as string | null | undefined) ?? null,\n });\n }\n\n agentInfoForDelivery.set(codeName, {\n agentDisplayName: displayName,\n ownerTeamName,\n resolverAgent,\n peopleByPersonId,\n });\n}\n\n/** POST dispatch status back to the API so the observability columns\n * (last_delivery_status / last_delivery_medium / last_delivery_at /\n * consecutive_failure_count) get updated. Best-effort — a transient API\n * failure here shouldn't take down the scheduler. */\nasync function reportDeliveryStatus(\n agentId: string,\n taskId: string | undefined,\n payload: { status: 'ok' | 'failed' | 'skipped'; medium?: 'slack' | 'telegram'; error_code?: string | null },\n): Promise<void> {\n if (!taskId) return;\n try {\n await api.post('/host/schedules/delivery-status', {\n agent_id: agentId,\n task_id: taskId,\n status: payload.status,\n medium: payload.medium ?? null,\n error_code: payload.error_code ?? null,\n });\n } catch (err) {\n log(`[delivery] Failed to report delivery status for ${agentId}/${taskId}: ${(err as Error).message}`);\n }\n}\n\n/** ENG-4581: dispatch any pending Claude Code OAuth pairing sessions\n * across this host's agents. Runs once per refresh cycle.\n *\n * Two states drive work:\n * - 'initiating' → run startClaudePair, report URL or error\n * - 'code_submitted' → run submitClaudePairCode, report success/failure\n *\n * Best-effort: a single bad session shouldn't block the others, and a\n * transient API failure just means we retry on the next refresh.\n */\ninterface PairableAgent { agentId: string; codeName: string }\n\n// Pair_ids the manager has spawned a tmux session for. Used to ask the\n// server \"of these, which are now terminal?\" so we can kill orphan\n// `agt-pair-<id>` tmux sessions left over from operator cancels or\n// connection drops.\nconst spawnedPairIds = new Set<string>();\n\nasync function processClaudePairSessions(agents: PairableAgent[]): Promise<void> {\n // Keep polling whenever we have orphan pair-tmux sessions to clean\n // up — even if the host temporarily has zero assigned agents.\n // Otherwise stale agt-pair-* sessions could linger until the manager\n // restarts.\n if (agents.length === 0 && spawnedPairIds.size === 0) return;\n const agentIds = agents.map((a) => a.agentId);\n const codeNameByAgentId = new Map(agents.map((a) => [a.agentId, a.codeName]));\n\n const pendingResp = await api.post<{\n pending: Array<{\n pair_id: string;\n agent_id: string;\n status: 'initiating' | 'code_submitted';\n code: string | null;\n created_at: string;\n updated_at: string;\n }>;\n cancelled_pair_ids?: string[];\n }>('/host/claude-pair/pending', {\n agent_ids: agentIds,\n spawned_pair_ids: Array.from(spawnedPairIds),\n });\n\n const {\n startClaudePair,\n submitClaudePairCode,\n spawnPairSession,\n killPairSession,\n pairTmuxSession,\n finalizeClaudePairOnboarding,\n } = await import('./claude-pair-runtime.js');\n\n // Sweep orphans the server flagged as terminal but still in our\n // spawned set (operator cancel, status reported via a different code\n // path, etc.). Kill the tmux session and forget about the id.\n for (const pairId of pendingResp.cancelled_pair_ids ?? []) {\n log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);\n const killed = await killPairSession(pairTmuxSession(pairId));\n if (killed) {\n spawnedPairIds.delete(pairId);\n } else {\n // Kill failed for a non-missing-session reason — leave the id in\n // spawnedPairIds so the next poll retries.\n log(`[claude-pair] kill-session failed for pair ${pairId.slice(0, 8)} — will retry on next poll`);\n }\n }\n\n if (!pendingResp.pending || pendingResp.pending.length === 0) return;\n\n // Terminal states cleared from the manager's perspective — once we\n // report any of these, the pair-scoped tmux session is no longer needed\n // and should be torn down.\n const TERMINAL: ReadonlySet<string> = new Set([\n 'success',\n 'failure',\n 'session_missing',\n 'timeout',\n ]);\n\n async function reportAndCleanup(\n pairId: string,\n body: Record<string, unknown>,\n ): Promise<void> {\n await api.post('/host/claude-pair/result', { pair_id: pairId, ...body });\n if (typeof body.status === 'string' && TERMINAL.has(body.status)) {\n // On success, give claude a beat to persist credentials to\n // ~/.claude/.credentials.json before we kill the tmux session.\n // Killing during the write window can leave the host with an\n // empty ~/.claude (claude shows the theme picker on next launch\n // as if no auth had ever happened).\n if (body.status === 'success') {\n await new Promise<void>((r) => setTimeout(r, 3_000));\n }\n const killed = await killPairSession(pairTmuxSession(pairId));\n if (killed) {\n spawnedPairIds.delete(pairId);\n }\n // If kill failed, the next poll's cancelled_pair_ids sweep will\n // retry — the row is now terminal so it'll be returned again.\n }\n }\n\n for (const session of pendingResp.pending) {\n // codeName is informational only — handy for log lines. The pair\n // flow drives a throwaway tmux session, NOT the agent's persistent\n // session, so we no longer require an entry in codeNameByAgentId.\n const codeName = codeNameByAgentId.get(session.agent_id) ?? '<unassigned>';\n const pairSession = pairTmuxSession(session.pair_id);\n\n try {\n if (session.status === 'initiating') {\n log(`[claude-pair] spawning pair session ${pairSession} for '${codeName}'`);\n const spawn = await spawnPairSession(pairSession);\n if (!spawn.ok) {\n await reportAndCleanup(session.pair_id, {\n status: 'failure',\n error_code: spawn.error.kind,\n error_message:\n spawn.error.kind === 'unknown' ? spawn.error.message : undefined,\n });\n continue;\n }\n spawnedPairIds.add(session.pair_id);\n\n log(`[claude-pair] dispatching /login (pair ${session.pair_id.slice(0, 8)})`);\n const result = await startClaudePair({ session: pairSession });\n if (result.kind === 'url') {\n await api.post('/host/claude-pair/result', {\n pair_id: session.pair_id,\n status: 'awaiting_code',\n url: result.url,\n });\n } else if (result.kind === 'timeout') {\n await reportAndCleanup(session.pair_id, {\n status: 'timeout',\n error_code: 'url_prompt_not_seen',\n error_message: 'Claude Code did not print the URL prompt within the deadline',\n });\n } else {\n const errKind = result.error.kind;\n // Forward the message for any kind that carries one. `oauth-retry-stuck`\n // ships actionable remediation text (\"clear ~/.claude/.credentials.json...\")\n // that operators need; the previous unknown-only filter dropped that\n // guidance and reduced the failure to a bare error_code.\n const errMessage =\n 'message' in result.error ? result.error.message : undefined;\n await reportAndCleanup(session.pair_id, {\n status: errKind === 'no-session' ? 'session_missing' : 'failure',\n error_code: errKind,\n error_message: errMessage,\n });\n }\n } else if (session.status === 'code_submitted' && session.code) {\n log(`[claude-pair] submitting code (pair ${session.pair_id.slice(0, 8)})`);\n const result = await submitClaudePairCode({\n session: pairSession,\n code: session.code,\n });\n if (result.kind === 'success') {\n // ENG-4633: drive Claude Code through its post-OAuth dialogs\n // so it persists ~/.claude.json before we kill the pair tmux\n // session. Without this step claude only wrote\n // ~/.claude/.credentials.json — the next agent launch hit\n // the login picker again. The result of this finalize is\n // logged but never gates the success report; even an\n // un-finalized pair still has working OAuth tokens, the\n // worst-case fallout is a one-time login picker on the\n // next agent launch (which ENG-4634 now surfaces).\n const finalize = await finalizeClaudePairOnboarding(pairSession, log);\n if (!finalize.finalized) {\n log(`[claude-pair] WARN: ~/.claude.json was not updated during onboarding (pair ${session.pair_id.slice(0, 8)}) — first agent launch may show the login picker`);\n }\n await reportAndCleanup(session.pair_id, { status: 'success' });\n } else if (result.kind === 'failure') {\n await reportAndCleanup(session.pair_id, {\n status: 'failure',\n error_code: 'invalid_code',\n error_message: result.rawMatch,\n });\n } else if (result.kind === 'timeout') {\n await reportAndCleanup(session.pair_id, {\n status: 'timeout',\n error_code: 'outcome_not_seen',\n error_message: 'Claude Code did not show a success/failure marker after submitting the code',\n });\n } else {\n const errKind = result.error.kind;\n // Forward the message for any kind that carries one. `oauth-retry-stuck`\n // ships actionable remediation text (\"clear ~/.claude/.credentials.json...\")\n // that operators need; the previous unknown-only filter dropped that\n // guidance and reduced the failure to a bare error_code.\n const errMessage =\n 'message' in result.error ? result.error.message : undefined;\n await reportAndCleanup(session.pair_id, {\n status: errKind === 'no-session' ? 'session_missing' : 'failure',\n error_code: errKind,\n error_message: errMessage,\n });\n }\n }\n } catch (err) {\n // Don't poison the loop. Log and let the next refresh retry —\n // pending rows stay in 'initiating' / 'code_submitted' until we\n // succeed or the operator gives up.\n log(`[claude-pair] dispatch failed for pair ${session.pair_id.slice(0, 8)}: ${(err as Error).message}`);\n }\n }\n}\n\n/** ENG-4576 (PR #538): kick the API to post a 1-5 rating prompt as a\n * threaded reply under a Slack scheduled-task delivery. Server-side\n * decides whether to fire (gated on block_kit_enabled, idempotent\n * per-run). Best-effort — never throws. */\nasync function maybePostScheduledTaskRatingPrompt(\n agentId: string,\n taskId: string,\n channelId: string,\n messageTs: string,\n): Promise<void> {\n try {\n await api.post('/host/scheduled-task/rating-prompt', {\n agent_id: agentId,\n task_id: taskId,\n channel: channelId,\n message_ts: messageTs,\n });\n } catch (err) {\n log(`[rating-prompt] Failed to post rating prompt for ${agentId}/${taskId}: ${(err as Error).message}`);\n }\n}\n\n/** Dispatch a message via the given channel.\n *\n * Returns `{ ok, error_code? }` so callers can faithfully report delivery\n * status to the observability columns rather than assuming a void call\n * succeeded (§10 / CR #3108333175). */\nasync function sendTaskNotification(\n agentCodeName: string,\n channel: string,\n to: string,\n text: string,\n): Promise<{ ok: boolean; error_code?: string }> {\n const tokens = agentChannelTokens.get(agentCodeName);\n\n if (channel === 'slack') {\n const botToken = tokens?.slack;\n const channelId = to.replace(/^channel:/, '');\n if (!botToken) {\n log(`No Slack bot token for '${agentCodeName}' — targeted notification dropped`);\n return { ok: false, error_code: 'SLACK_NO_TOKEN' };\n }\n const sent = await sendSlackChannelMessage(agentCodeName, channelId, text);\n return sent ? { ok: true } : { ok: false, error_code: 'SLACK_SEND_FAILED' };\n }\n\n if (channel === 'telegram') {\n const botToken = tokens?.telegram;\n const chatId = to.replace(/^chat:/, '');\n if (!botToken) {\n log(`No Telegram bot token for '${agentCodeName}' — notification dropped`);\n return { ok: false, error_code: 'TELEGRAM_NO_TOKEN' };\n }\n const allowedChats = tokens?.telegramAllowedChats;\n if (allowedChats && allowedChats.length > 0 && !allowedChats.includes(chatId)) {\n log(`Telegram chat ${chatId} not in allowed_chat_ids for '${agentCodeName}'`);\n return { ok: false, error_code: 'TELEGRAM_CHAT_NOT_ALLOWED' };\n }\n try {\n const result = await telegramApiCall(botToken, 'sendMessage', { chat_id: chatId, text });\n if (!result.ok) {\n log(`Telegram sendMessage failed for '${agentCodeName}': ${result.description}`);\n return { ok: false, error_code: `TELEGRAM_SEND_FAILED:${result.description ?? 'unknown'}` };\n }\n log(`Telegram notification sent for '${agentCodeName}' to chat ${chatId}`);\n return { ok: true };\n } catch (err) {\n log(`Telegram API error for '${agentCodeName}': ${(err as Error).message}`);\n return { ok: false, error_code: 'TELEGRAM_EXCEPTION' };\n }\n }\n\n log(`Unknown notify_channel '${channel}' for '${agentCodeName}'`);\n return { ok: false, error_code: `UNKNOWN_CHANNEL:${channel}` };\n}\n\n/**\n * Generate provision artifacts without writing to disk.\n * Returns the list of artifacts (relativePath + content) for comparison.\n */\nfunction generateArtifacts(\n agent: { agent_id: string; code_name: string; display_name: string; status: string; environment: string },\n refreshData: {\n agent: Record<string, unknown>;\n charter: { raw_content: string; version: string } | null;\n tools: { raw_content: string; version: string } | null;\n channel_configs: Record<string, { config: unknown; status: string }> | null;\n team_channel_policy: {\n team_id: string;\n allowed_channels: string[];\n denied_channels: string[];\n require_elevated_for_pii: boolean;\n } | null;\n team: { name: string; description: string | null; settings?: Record<string, unknown> } | null;\n scheduled_tasks?: unknown;\n knowledge?: Array<{ title: string; slug: string; content: string | null; scope: 'org' | 'team' }>;\n team_members?: Array<{ display_name: string; email?: string; role: string; title?: string; contact_channel?: string }>;\n people?: Array<{ display_name: string; email?: string; title?: string; department?: string; relationship?: string; contact_channel?: string }>;\n },\n adapter: FrameworkAdapter,\n): { relativePath: string; content: string }[] {\n if (!refreshData.charter || !refreshData.tools) {\n throw new Error('No charter/tools available');\n }\n\n const charterContent = refreshData.charter.raw_content;\n const toolsContent = refreshData.tools.raw_content;\n\n const charterParsed = extractFrontmatter(charterContent);\n if (!charterParsed.frontmatter) {\n throw new Error(`Failed to parse CHARTER.md frontmatter: ${charterParsed.error ?? 'unknown'}`);\n }\n\n const toolsParsed = extractFrontmatter(toolsContent);\n if (!toolsParsed.frontmatter) {\n throw new Error(`Failed to parse TOOLS.md frontmatter: ${toolsParsed.error ?? 'unknown'}`);\n }\n\n const charterFrontmatter = charterParsed.frontmatter as unknown as CharterFrontmatter;\n const toolsFrontmatter = toolsParsed.frontmatter as unknown as ToolsFrontmatter;\n\n const configuredChannels = Object.keys(refreshData.channel_configs ?? {}) as ChannelId[];\n\n const agentChannelPolicy: ChannelPolicy = {\n policy: 'allowlist',\n allowed: configuredChannels,\n denied: [],\n require_approval_to_change: true,\n };\n\n let orgChannelPolicy: OrgChannelPolicy | undefined;\n const policyData = refreshData.team_channel_policy;\n\n if (policyData) {\n orgChannelPolicy = {\n organization_id: policyData.team_id,\n allowed_channels: (policyData.allowed_channels ?? []) as ChannelId[],\n denied_channels: (policyData.denied_channels ?? []) as ChannelId[],\n require_elevated_for_pii: policyData.require_elevated_for_pii ?? false,\n };\n }\n\n const resolvedChannels = resolveChannels(agentChannelPolicy, orgChannelPolicy);\n const effectiveChannels = agent.status === 'paused' ? [] : resolvedChannels;\n\n // Resolve timezone deterministically — use unanimous task timezone, then team, then UTC\n const tasks = (refreshData as Record<string, unknown>).scheduled_tasks as Array<{ timezone?: string | null }> | null | undefined;\n const taskTimezones = [...new Set(\n (tasks ?? [])\n .map((t) => (typeof t.timezone === 'string' ? t.timezone.trim() : ''))\n .filter((tz): tz is string => tz.length > 0),\n )];\n const teamTimezoneRaw = (refreshData.team as { timezone?: unknown } | null | undefined)?.timezone;\n const teamTimezone = typeof teamTimezoneRaw === 'string' && teamTimezoneRaw.trim().length > 0 ? teamTimezoneRaw.trim() : undefined;\n const agentTimezone = (taskTimezones.length === 1 ? taskTimezones[0] : undefined) ?? teamTimezone ?? 'UTC';\n\n // Resolve personality seed: agent-level overrides org-level\n const agentPersonalitySeed = (refreshData.agent as Record<string, unknown>).personality_seed as string | undefined;\n const orgDefaults = (refreshData as Record<string, unknown>).model_defaults as { org?: { settings?: Record<string, unknown> } } | undefined;\n const personalitySeed = agentPersonalitySeed || orgDefaults?.org?.settings?.personality_seed as string | undefined;\n\n // Resolve reports_to reference (person or agent name)\n let reportsTo: ProvisionInput['reportsTo'];\n const agentData = refreshData.agent as Record<string, unknown>;\n const reportsToId = agentData.reports_to as string | null | undefined;\n const reportsToType = (agentData.reports_to_type as string | null | undefined) ?? 'agent';\n\n if (reportsToId) {\n const reportsToName = agentData.reports_to_name as string | undefined;\n const reportsToTitle = agentData.reports_to_title as string | undefined;\n const reportsToDesc = agentData.reports_to_description as string | undefined;\n if (reportsToName) {\n reportsTo = {\n name: reportsToName,\n type: reportsToType as 'agent' | 'person',\n title: reportsToTitle,\n description: reportsToDesc,\n };\n }\n }\n\n const provisionInput: ProvisionInput = {\n agent: refreshData.agent as any,\n charterFrontmatter,\n charterContent,\n toolsFrontmatter,\n toolsContent,\n resolvedChannels: effectiveChannels,\n deploymentTarget: 'local_docker' as DeploymentTarget,\n gatewayPort: 9000,\n team: refreshData.team ?? undefined,\n timezone: agentTimezone,\n reportsTo,\n personalitySeed,\n knowledge: (refreshData.knowledge ?? []).filter((k): k is { title: string; slug: string; content: string; scope: 'org' | 'team' } => !!k.content),\n knowledgeDelivery: ((refreshData.agent as Record<string, unknown>).knowledge_delivery as 'search' | 'files' | 'both' | undefined) ?? 'both',\n teamMembers: refreshData.team_members ?? undefined,\n people: refreshData.people ?? undefined,\n };\n\n const provisionOutput = provision(provisionInput, adapter.id);\n return provisionOutput.artifacts;\n}\n\n// ---------------------------------------------------------------------------\n// Memory sync — bidirectional sync between local files and agent_memories DB\n// ---------------------------------------------------------------------------\n\n/** Per-file content hashes to detect individual memory changes */\nconst memoryFileHashes = new Map<string, Map<string, string>>(); // agentId → (fileName → hash)\n/** Hash of DB memories response to skip redundant downloads */\nconst lastDownloadHash = new Map<string, string>(); // agentId → hash\n/** Hash of local file listing to skip redundant download checks */\nconst lastLocalFileHash = new Map<string, string>(); // agentId → hash\n\n/**\n * ENG-4643: agents that have just been assigned to this host (or have\n * otherwise been flagged as needing a clean re-pull from the API).\n * Set populated by the onAssign realtime callback; consumed at the\n * top of the next syncMemories run for that agent. The pending tick\n * skips the local-list-hash short-circuit, downloads first (so DB is\n * authoritative on the migration boundary, never overwritten by stale\n * local files), and clears the per-agent in-memory caches so a long-\n * running manager that previously hosted this agent doesn't carry\n * stale hashes that would suppress fresh writes.\n */\nconst pendingFreshMemorySync = new Set<string>(); // agent_id\n\nexport function markAgentForFreshMemorySync(agentId: string): void {\n pendingFreshMemorySync.add(agentId);\n memoryFileHashes.delete(agentId);\n lastDownloadHash.delete(agentId);\n lastLocalFileHash.delete(agentId);\n}\n\nfunction parseMemoryFile(raw: string, fallbackName: string): { name: string; content: string; type: string } | null {\n const trimmed = raw.trim();\n if (!trimmed) return null;\n\n const fmMatch = trimmed.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n([\\s\\S]*)$/);\n\n if (fmMatch) {\n // Has YAML frontmatter — parse structured fields\n const frontmatter = fmMatch[1] ?? '';\n const body = (fmMatch[2] ?? '').trim();\n if (!body) return null;\n\n const nameMatch = frontmatter.match(/^name:\\s*(.+)$/m);\n const typeMatch = frontmatter.match(/^type:\\s*(.+)$/m);\n const name = nameMatch?.[1]?.trim().replace(/^[\"']|[\"']$/g, '') ?? fallbackName;\n const rawType = typeMatch?.[1]?.trim().replace(/^[\"']|[\"']$/g, '') ?? 'user';\n\n const typeMap: Record<string, string> = { user: 'user', feedback: 'feedback', project: 'project', reference: 'reference', daily: 'daily' };\n const type = typeMap[rawType] ?? 'user';\n\n return { name, content: body, type };\n }\n\n // No frontmatter — treat the entire file as content.\n // Derive type from filename pattern: date-like names (YYYY-MM-DD) → daily, else project.\n const isDaily = /^\\d{4}-\\d{2}-\\d{2}$/.test(fallbackName);\n return {\n name: fallbackName,\n content: trimmed,\n type: isDaily ? 'daily' : 'project',\n };\n}\n\nasync function syncMemories(\n agent: { agent_id: string; code_name: string },\n configDir: string,\n log: (msg: string) => void,\n): Promise<void> {\n const projectDir = join(configDir, agent.code_name, 'project');\n const memoryDir = join(projectDir, 'memory');\n\n // ENG-4643: on a fresh-sync tick (post-migration / post-assignment) we\n // download first and let the DB be authoritative. Any stale local\n // file from a prior provision gets overwritten when its DB twin has\n // different content. Subsequent ticks fall through to the regular\n // upload-then-download bidirectional flow.\n //\n // Skip the upload path entirely when the download fails — without\n // a confirmed catch-up pull, any local .md files we'd upload risk\n // overwriting fresher DB content with stale local copies (the\n // exact scenario we're guarding against). Leave the pending flag\n // set so the next supervisor tick retries the download.\n const isFreshSync = pendingFreshMemorySync.has(agent.agent_id);\n if (isFreshSync) {\n log(`[memory-sync] Fresh-sync requested for '${agent.code_name}' — pulling DB first`);\n const ok = await downloadMemories(agent, memoryDir, log, { force: true });\n if (!ok) {\n log(`[memory-sync] Fresh-sync download failed for '${agent.code_name}' — skipping upload, will retry next tick`);\n return;\n }\n pendingFreshMemorySync.delete(agent.agent_id);\n }\n\n // --- Upload: local → DB (only changed files) ---\n if (existsSync(memoryDir)) {\n const prevHashes = memoryFileHashes.get(agent.agent_id) ?? new Map<string, string>();\n const currentHashes = new Map<string, string>();\n const changedMemories: Array<{ name: string; content: string; type: string; expires_at?: string }> = [];\n\n for (const file of readdirSync(memoryDir)) {\n if (!file.endsWith('.md')) continue;\n try {\n const raw = readFileSync(join(memoryDir, file), 'utf-8');\n const fileHash = createHash('sha256').update(raw).digest('hex').slice(0, 16);\n currentHashes.set(file, fileHash);\n\n // Only parse + upload if file content changed since last cycle\n if (prevHashes.get(file) === fileHash) continue;\n\n const parsed = parseMemoryFile(raw, file.replace(/\\.md$/, ''));\n if (parsed) {\n // Daily memories require expires_at (DB constraint)\n if (parsed.type === 'daily') {\n (parsed as { expires_at?: string }).expires_at = new Date(Date.now() + 48 * 60 * 60 * 1000).toISOString();\n }\n changedMemories.push(parsed);\n }\n } catch { /* skip unreadable */ }\n }\n\n memoryFileHashes.set(agent.agent_id, currentHashes);\n\n if (changedMemories.length > 0) {\n try {\n await api.post('/host/sync-memories', {\n agent_id: agent.agent_id,\n memories: changedMemories,\n });\n log(`Synced ${changedMemories.length} changed memories to DB for '${agent.code_name}'`);\n } catch (err) {\n // Reset hashes for changed files so they retry next cycle\n for (const mem of changedMemories) {\n for (const [file] of currentHashes) {\n const parsed = parseMemoryFile(readFileSync(join(memoryDir, file), 'utf-8'), file.replace(/\\.md$/, ''));\n if (parsed?.name === mem.name) currentHashes.delete(file);\n }\n }\n log(`Memory upload failed for '${agent.code_name}': ${(err as Error).message}`);\n }\n }\n }\n\n // --- Download: DB → local (skip if local files unchanged) ---\n // ENG-4643: skipped when isFreshSync ran above (we already pulled).\n if (!isFreshSync) {\n await downloadMemories(agent, memoryDir, log, { force: false });\n }\n}\n\n/**\n * Pull DB memories and write them to the local memory dir, reconciling\n * against existing files by CONTENT hash instead of slug presence.\n *\n * ENG-4643:\n * - Old behaviour: skip the DB version whenever a same-slug local\n * file existed, regardless of whether the local content matched.\n * Stale provision leftovers permanently shadowed DB updates.\n * - New behaviour: derive slug + render the canonical file body for\n * each DB memory; if the existing file's bytes match exactly,\n * skip; otherwise overwrite. Files with no DB twin are left alone\n * (the upload path handles those on the next pass).\n * - When `force` is true, the local-listing-hash short-circuit is\n * bypassed so the migration / fresh-assignment first sync always\n * reaches the network call.\n */\nasync function downloadMemories(\n agent: { agent_id: string; code_name: string },\n memoryDir: string,\n log: (msg: string) => void,\n { force }: { force: boolean },\n): Promise<boolean> {\n const localFiles = existsSync(memoryDir)\n ? readdirSync(memoryDir).filter((f) => f.endsWith('.md')).sort()\n : [];\n const localListHash = createHash('sha256').update(localFiles.join(',')).digest('hex').slice(0, 16);\n const prevLocalHash = lastLocalFileHash.get(agent.agent_id);\n const prevDownload = lastDownloadHash.get(agent.agent_id);\n\n try {\n const dbMemories = await api.post<{ memories: Array<{ name: string; type: string; content: string }> }>('/host/memories', {\n agent_id: agent.agent_id,\n });\n\n // Hash the response to detect changes server-side\n const responseHash = createHash('sha256')\n .update(JSON.stringify(dbMemories.memories ?? []))\n .digest('hex').slice(0, 16);\n\n // Skip processing if nothing changed (local files same + DB response same).\n // `force=true` callers (post-migration first sync) ignore this guard.\n if (\n !force &&\n prevDownload &&\n prevLocalHash === localListHash &&\n lastDownloadHash.get(agent.agent_id) === responseHash\n ) {\n return true;\n }\n\n lastDownloadHash.set(agent.agent_id, responseHash);\n lastLocalFileHash.set(agent.agent_id, localListHash);\n\n if (dbMemories.memories?.length) {\n mkdirSync(memoryDir, { recursive: true });\n\n let written = 0;\n let overwritten = 0;\n for (let i = 0; i < dbMemories.memories.length; i++) {\n const mem = dbMemories.memories[i]!;\n const rawSlug = mem.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '').slice(0, 60);\n const slug = rawSlug || `memory-${i}`;\n const filePath = join(memoryDir, `${slug}.md`);\n const desired = `---\\nname: ${JSON.stringify(mem.name)}\\ntype: ${mem.type}\\ndescription: ${JSON.stringify(mem.content.slice(0, 200))}\\n---\\n\\n${mem.content}\\n`;\n\n if (existsSync(filePath)) {\n // Content compare — DB wins on mismatch (the local file is\n // either a stale provision leftover or out-of-band edited\n // copy that the upload path didn't catch).\n let existing = '';\n try { existing = readFileSync(filePath, 'utf-8'); } catch { /* unreadable → overwrite */ }\n if (existing === desired) continue;\n writeFileSync(filePath, desired);\n overwritten++;\n } else {\n writeFileSync(filePath, desired);\n written++;\n }\n }\n\n if (written > 0 || overwritten > 0) {\n // Update local file hash since we just wrote new/changed files\n const updatedFiles = readdirSync(memoryDir).filter((f) => f.endsWith('.md')).sort();\n lastLocalFileHash.set(agent.agent_id, createHash('sha256').update(updatedFiles.join(',')).digest('hex').slice(0, 16));\n log(`Memory download for '${agent.code_name}': wrote ${written} new, overwrote ${overwritten} stale`);\n }\n }\n return true;\n } catch (err) {\n log(`Memory download failed for '${agent.code_name}': ${(err as Error).message}`);\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Cleanup helper (for revoked agents)\n// ---------------------------------------------------------------------------\n\nasync function cleanupAgentFiles(codeName: string, agentDir: string): Promise<void> {\n // Remove the provision directory\n if (existsSync(agentDir)) {\n try {\n rmSync(agentDir, { recursive: true, force: true });\n log(`Removed provision directory for '${codeName}'`);\n } catch (err) {\n log(`Failed to remove provision dir for '${codeName}': ${(err as Error).message}`);\n }\n }\n\n // Deregister from the framework runtime\n try {\n const adapter = resolveAgentFramework(codeName);\n const registered = await getOrCacheRegisteredAgents(adapter, codeName);\n if (registered.has(codeName)) {\n await adapter.deregisterAgent(codeName);\n registered.delete(codeName);\n log(`Deregistered '${codeName}' from ${adapter.label}`);\n }\n } catch (err) {\n log(`Failed to deregister '${codeName}': ${(err as Error).message}`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// IPC + lifecycle\n// ---------------------------------------------------------------------------\n\nfunction startGatewayPool(): void {\n if (gatewayPool) return;\n\n gatewayPool = new GatewayClientPool();\n\n gatewayPool.on('connected', (codeName: string) => {\n log(`Gateway WebSocket connected for '${codeName}'`);\n });\n\n gatewayPool.on('disconnected', (codeName: string) => {\n log(`Gateway WebSocket disconnected for '${codeName}'`);\n });\n\n gatewayPool.on('error', (err: Error, codeName: string) => {\n log(`Gateway WebSocket error for '${codeName}': ${err.message}`);\n });\n\n gatewayPool.on('event', (evt: PooledGatewayEvent) => {\n // Forward to parent via IPC\n send({ type: 'gateway-event', event: evt.event, payload: evt.payload, agentCodeName: evt.agentCodeName });\n\n // Forward to API (fire and forget)\n getHostId().then((hostId) => {\n if (!hostId) return;\n api.post('/host/events', {\n host_id: hostId,\n agent_code_name: evt.agentCodeName,\n event_type: evt.event,\n payload: evt.payload,\n timestamp: new Date().toISOString(),\n }).catch((err) => {\n log(`Failed to forward gateway event: ${(err as Error).message}`);\n });\n }).catch(() => {\n // Ignore — host ID resolution is best-effort here\n });\n });\n}\n\nfunction stopGatewayPool(): void {\n if (gatewayPool) {\n gatewayPool.disconnectAll();\n gatewayPool = null;\n }\n}\n\n// Caffeinate subprocess — prevents macOS sleep while manager is running\nlet caffeinateProc: import('node:child_process').ChildProcess | null = null;\n\nasync function startCaffeinate(): Promise<void> {\n if (process.platform !== 'darwin') return;\n try {\n const { spawn } = await import('node:child_process');\n caffeinateProc = spawn('caffeinate', ['-dims'], {\n stdio: 'ignore',\n detached: false,\n });\n caffeinateProc.on('error', (err) => {\n log(`caffeinate failed: ${err.message} — Mac may sleep during operation`);\n caffeinateProc = null;\n });\n caffeinateProc.on('exit', () => { caffeinateProc = null; });\n if (caffeinateProc.pid) {\n log(`caffeinate started (PID ${caffeinateProc.pid}) — Mac sleep prevented while manager is running`);\n }\n } catch (err) {\n log(`caffeinate not available: ${(err as Error).message} — Mac may sleep during operation`);\n }\n}\n\nfunction stopCaffeinate(): void {\n if (caffeinateProc) {\n caffeinateProc.kill();\n caffeinateProc = null;\n log('caffeinate stopped');\n }\n}\n\nfunction startPolling(): void {\n if (!config || running) return;\n running = true;\n\n void startCaffeinate();\n // ENG-4712: rehydrate the channel-config hash cache from disk so a\n // fresh manager process recognises credentials it (or a sibling\n // manager) already wrote on a previous run, and skips the noisy\n // `reason=first-write` rewrite for unchanged configs.\n loadChannelHashCache();\n log(`Starting poll loop (interval=${config.intervalMs}ms, configDir=${config.configDir})`);\n\n // Kill any orphaned agt-* tmux sessions from a previous manager run,\n // then start the poll loop. Must await to avoid race with session spawn.\n void killAllAgtTmuxSessions().catch(() => {}).then(() => migrateToProfiles()).then(() => {\n startGatewayPool();\n return pollCycle();\n }).then(() => {\n scheduleNext();\n });\n}\n\nfunction scheduleNext(): void {\n if (!running || !config) return;\n\n // ENG-4488: if the self-update flipped this during the poll we just\n // finished, exit cleanly instead of scheduling another cycle. Also\n // re-check inside the timer callback — `checkAndUpdateCli()` is kicked\n // off without await, so the flag can flip between scheduleNext() and\n // the next fire. Without the second check we'd run one more pollCycle\n // on the old Cellar path, exactly the failure this is meant to avoid.\n // forcedExitCode: 0 so the supervisor treats this as a clean respawn\n // even if graceful cleanup hits its 15s backstop.\n const restartNow = (): void => {\n log(`[self-update] Restarting manager to load upgraded binary (${pendingUpgradeVersion ?? 'unknown version'})`);\n // Exit with the dedicated restart code so the supervisor distinguishes\n // \"please respawn me\" from \"the operator asked me to stop\" — overloading\n // exit code 0 would make `agt manager stop` unable to actually stop\n // a supervised manager.\n void stopPolling({ forcedExitCode: SUPERVISOR_RESTART_EXIT_CODE }).then(() => {\n process.exit(SUPERVISOR_RESTART_EXIT_CODE);\n });\n };\n\n if (restartAfterUpgrade) {\n restartNow();\n return;\n }\n pollTimer = setTimeout(() => {\n if (restartAfterUpgrade) {\n restartNow();\n return;\n }\n void pollCycle().then(scheduleNext);\n }, config.intervalMs);\n}\n\nasync function killAllAgtTmuxSessions(): Promise<void> {\n try {\n const { stdout: output } = await execFilePromiseLong(\n 'tmux',\n ['list-sessions', '-F', '#{session_name}'],\n { timeout: 2_000, stdin: 'ignore' },\n );\n const sessions = output.trim().split('\\n').filter((s) => s.startsWith('agt-'));\n for (const session of sessions) {\n try {\n await execFilePromiseLong('tmux', ['kill-session', '-t', session], {\n timeout: 2_000,\n stdin: 'ignore',\n });\n log(`Killed tmux session '${session}'`);\n } catch { /* session may have already exited */ }\n }\n if (sessions.length > 0) {\n log(`Cleaned up ${sessions.length} agt-* tmux session(s)`);\n }\n } catch {\n // tmux not installed or no server running — nothing to clean up\n }\n}\n\nasync function stopPolling(opts: { forcedExitCode?: number } = {}): Promise<void> {\n running = false;\n if (pollTimer) {\n clearTimeout(pollTimer);\n pollTimer = null;\n }\n\n // Hard backstop: force exit after 15s if graceful cleanup hangs. Default\n // exit code is 1 (crash-signal) so a SIGTERM handler that can't drain\n // surfaces as a failure to any supervisor. The self-update restart path\n // passes forcedExitCode: 0 so a stuck cleanup after `brew upgrade`\n // doesn't poison the respawn — the supervisor still sees clean exit and\n // re-spawns onto the new binary.\n const shutdownTimer = setTimeout(() => {\n log('Shutdown timeout exceeded (15s), forcing exit');\n process.exit(opts.forcedExitCode ?? 1);\n }, 15000);\n shutdownTimer.unref();\n\n stopCaffeinate();\n stopRealtimeChat();\n stopGatewayPool();\n\n // Kill all agt-* tmux sessions FIRST — this is the most critical cleanup\n // Do it before graceful session stop to ensure sessions are killed even if\n // the graceful path hangs or times out.\n log('Killing tmux sessions...');\n await killAllAgtTmuxSessions();\n\n // Then gracefully stop persistent sessions (no-op if already killed)\n log('Stopping persistent sessions...');\n await stopAllSessionsAndWait(log, { timeoutMs: 4_000 });\n\n // Stop all per-agent gateway processes\n log('Stopping gateway processes...');\n await stopAllGateways();\n\n clearTimeout(shutdownTimer);\n}\n\n// ---------------------------------------------------------------------------\n// Public API — called directly by the CLI command (no fork/IPC)\n// ---------------------------------------------------------------------------\n\n/**\n * Start the manager poll loop. Call this directly from the CLI command.\n * The process stays alive running the poll loop until stopManager() is called\n * or a signal is received.\n */\nexport function startManager(opts: { intervalMs: number; configDir: string }): void {\n config = opts;\n // ENG-4947: rehydrate AgentState[] from disk so dashboard-issued restart\n // acks (lastRestartProcessedAt) survive a manager process restart. We\n // only carry forward `agents`; pid/startedAt/pollCount stay fresh.\n // Errors are logged and ignored — first-run with no state file is the\n // normal case and a corrupt file shouldn't block startup.\n try {\n const stateFile = getStateFile();\n if (existsSync(stateFile)) {\n const raw = readFileSync(stateFile, 'utf-8');\n const parsed = JSON.parse(raw) as Partial<ManagerState>;\n if (Array.isArray(parsed.agents)) {\n state.agents = parsed.agents as AgentState[];\n log(`[startup] rehydrated ${state.agents.length} agent state(s) from ${stateFile}`);\n }\n }\n } catch (err) {\n log(`[startup] state rehydration failed (continuing with empty state): ${(err as Error).message}`);\n }\n // ENG-4658: First log line on every fresh worker. Two purposes:\n // 1. Proves the on-disk log destination is writable from this worker\n // (the bug was a silent write to a stdio fd that pointed nowhere).\n // 2. Gives operators a \"manager process boundary\" marker so a long\n // manager.log can be split into per-worker segments by grepping\n // for `[startup] worker pid=`.\n // If this line doesn't appear in manager.log shortly after a fresh\n // spawn, the worker can't write to the file and the operator should\n // check ~/.augmented permissions.\n log(\n `[startup] worker pid=${process.pid} ppid=${process.ppid} ` +\n `node=${process.version} log=${join(homedir(), '.augmented', 'manager.log')}`,\n );\n deployMcpAssets();\n // ENG-4808: reap any channel-MCP processes that were left orphaned by\n // a prior manager crash or unclean exit. The per-restart reap inside\n // stopPersistentSession handles the in-process case; this catches the\n // pre-existing-orphan case where the manager process itself died with\n // children still attached. Idempotent: a clean restart with no orphans\n // is a no-op.\n reapOrphanChannelMcps({ log });\n // Fetch the host's own framework from /host/exchange and ensure the\n // framework's binaries are installed. Runs async so startup isn't blocked\n // on a brew install — the binary is required only when agents are launched,\n // not for the polling loop itself. Failures are logged, never fatal.\n void ensureHostFrameworkBinaries();\n startPolling();\n}\n\nasync function ensureHostFrameworkBinaries(): Promise<void> {\n const apiKey = getApiKey();\n if (!apiKey) {\n log('[framework-install] AGT_API_KEY not set — skipping host framework binary check');\n return;\n }\n try {\n const exchange = await exchangeApiKey(apiKey);\n if (!exchange.framework) {\n log('[framework-install] host has no framework set — skipping');\n return;\n }\n log(`[framework-install] host framework = ${exchange.framework}`);\n await ensureFrameworkBinary(exchange.framework);\n } catch (err) {\n log(`[framework-install] failed: ${(err as Error).message}`);\n }\n}\n\n/**\n * Deploy bundled MCP server files to ~/.augmented/_mcp/ if they don't exist\n * or are outdated. This ensures prod hosts have the Slack channel server\n * and augmented MCP server without manual file copying.\n */\n/**\n * SIGTERM every running instance of the given channel MCP basenames so they\n * respawn with the newly-deployed bundle on next message.\n *\n * Channel MCPs are stdio children of each agent's Claude Code tmux session —\n * when they exit, Claude Code auto-respawns them via the entry in .mcp.json.\n * That respawn reads the freshly-written bundle from ~/.augmented/_mcp/, so\n * all we need is a death signal.\n *\n * Matches by command line containing the basename — `ps` output on macOS +\n * Linux both include the full path, so `/path/to/slack-channel.js` grep-hits\n * reliably. Not SIGKILL: the channel servers already handle SIGTERM cleanly\n * (ENG-4452) to drain in-flight Socket Mode events.\n */\nfunction restartRunningChannelMcps(basenames: string[]): void {\n try {\n const psOutput = syncExecFile(\n 'ps',\n ['-eo', 'pid=,command='],\n { encoding: 'utf-8', timeout: 5_000 },\n );\n const lines = psOutput.split('\\n').filter((l) => l.trim());\n const selfPid = process.pid;\n let signalled = 0;\n for (const line of lines) {\n const match = line.match(/^\\s*(\\d+)\\s+(.*)$/);\n if (!match) continue;\n const pid = Number(match[1]);\n const command = match[2]!;\n if (pid === selfPid) continue;\n // Only match JS basenames in the command path; env-var substrings like\n // SOMEPATH=/fake/slack-channel.js don't count because the basename\n // token is followed by whitespace or end-of-line when it's the actual\n // script being executed.\n const hit = basenames.find((b) => new RegExp(`/${b}\\\\.js(\\\\s|$)`).test(command));\n if (!hit) continue;\n try {\n process.kill(pid, 'SIGTERM');\n signalled++;\n log(`[manager] sent SIGTERM to pid=${pid} (${hit}.js) — will respawn on next message`);\n } catch (err) {\n // ESRCH = already dead; others are \"not our process\" (EPERM). Ignore.\n const code = (err as NodeJS.ErrnoException).code;\n if (code && code !== 'ESRCH') {\n log(`[manager] failed to signal pid=${pid}: ${code}`);\n }\n }\n }\n if (signalled === 0) {\n log(`[manager] no running instances to restart (deploy still applies to future spawns)`);\n }\n } catch (err) {\n log(`[manager] restartRunningChannelMcps failed: ${(err as Error).message}`);\n }\n}\n\nfunction deployMcpAssets(): void {\n const targetDir = join(homedir(), '.augmented', '_mcp');\n mkdirSync(targetDir, { recursive: true });\n\n // Resolve MCP files from the CLI package's mcp/ directory\n const moduleDir = dirname(fileURLToPath(import.meta.url));\n // Dev: src/lib → ../../mcp, Built: dist/lib → ../../mcp, npm global: dist/lib → ../../mcp\n let mcpSourceDir = '';\n let dir = moduleDir;\n for (let i = 0; i < 6; i++) {\n const candidate = join(dir, 'mcp');\n if (existsSync(join(candidate, 'index.js'))) {\n mcpSourceDir = candidate;\n break;\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n\n if (!mcpSourceDir) {\n log('[manager] MCP assets not found in CLI package — skipping deployment');\n return;\n }\n\n // Track which channel basenames got a new bundle so we can signal running\n // instances to restart. Without this, already-spawned channel MCP children\n // (parented to each agent's Claude Code tmux session) keep running the\n // previous bundle from memory — `deployMcpAssets` only updates the file\n // on disk. That's exactly the scenario where a merged branch like ENG-4503\n // (Slack image auto-download) or ENG-4504 (Telegram image auto-download)\n // silently \"doesn't work\" until the operator manually restarts every agent.\n const changedBasenames: string[] = [];\n const fileHash = (p: string): string | null => {\n try {\n if (!existsSync(p)) return null;\n return createHash('sha256').update(readFileSync(p)).digest('hex');\n } catch {\n return null;\n }\n };\n\n // Only these basenames are eligible for deploy-triggered SIGTERM. `index.js`\n // is deployed alongside the channels but must NEVER trigger the restart path\n // because countless unrelated Node processes on the host have an `index.js`\n // entrypoint — matching on `/index.js` would SIGTERM any of them. The list\n // below is the closed set of channel bundles whose process commands reliably\n // end in `/<basename>.js` with no collisions.\n const RESTARTABLE_CHANNEL_FILES = new Set([\n 'slack-channel.js',\n 'direct-chat-channel.js',\n 'telegram-channel.js',\n ]);\n\n for (const file of ['index.js', 'slack-channel.js', 'direct-chat-channel.js', 'telegram-channel.js']) {\n const src = join(mcpSourceDir, file);\n const dst = join(targetDir, file);\n if (!existsSync(src)) continue;\n const before = fileHash(dst);\n try {\n // Always overwrite to ensure latest version\n copyFileSync(src, dst);\n const after = fileHash(dst);\n if (before !== after && RESTARTABLE_CHANNEL_FILES.has(file)) {\n // file is 'slack-channel.js' → basename 'slack-channel' (matches the\n // ChannelType enum in channel-sweep.ts)\n changedBasenames.push(file.replace(/\\.js$/, ''));\n }\n } catch (err) {\n log(`[manager] Failed to deploy ${file}: ${(err as Error).message}`);\n }\n }\n log(`[manager] MCP assets deployed to ${targetDir}`);\n\n if (changedBasenames.length > 0) {\n log(`[manager] Bundle(s) updated: ${changedBasenames.join(', ')} — signalling running instances to restart`);\n restartRunningChannelMcps(changedBasenames);\n }\n\n // Patch any agent .mcp.json files that still reference the npx fallback\n // (which was never published). Replace with the local node path.\n const localMcpPath = join(targetDir, 'index.js');\n try {\n const agentsDir = join(homedir(), '.augmented', 'agents');\n if (existsSync(agentsDir)) {\n for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue;\n // Check both provision and project dirs\n for (const subdir of ['provision', 'project']) {\n const mcpJsonPath = join(agentsDir, entry.name, subdir, '.mcp.json');\n try {\n const raw = readFileSync(mcpJsonPath, 'utf-8');\n if (!raw.includes('@integrity-labs/augmented-mcp')) continue;\n const mcpConfig = JSON.parse(raw) as { mcpServers?: Record<string, Record<string, unknown>> };\n const augServer = mcpConfig.mcpServers?.['augmented'];\n if (!augServer) continue;\n augServer.command = 'node';\n augServer.args = [localMcpPath];\n writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2));\n log(`[manager] Patched ${entry.name}/${subdir}/.mcp.json: npx → node`);\n } catch {\n // File doesn't exist or isn't valid JSON — skip\n }\n }\n }\n }\n } catch (err) {\n log(`[manager] Failed to patch agent .mcp.json files: ${(err as Error).message}`);\n }\n}\n\n/**\n * Stop the manager poll loop gracefully.\n */\nexport async function stopManager(): Promise<void> {\n await stopPolling();\n}\n\n// Graceful shutdown on signals\nlet shuttingDown = false;\nfor (const sig of ['SIGTERM', 'SIGINT'] as const) {\n process.on(sig, () => {\n if (shuttingDown) {\n log(`Received ${sig} again during shutdown — ignoring (cleanup in progress)`);\n return;\n }\n shuttingDown = true;\n log(`Received ${sig}, shutting down`);\n void stopPolling().then(() => {\n process.exit(0);\n });\n });\n}\n\n// If parent disconnects, exit\nprocess.on('disconnect', () => {\n log('Parent disconnected, exiting');\n void stopPolling().then(() => {\n process.exit(0);\n });\n});\n","/**\n * ENG-4897 — pure decision helper for `.mcp.json` drift detection.\n *\n * Extracted from manager-worker.ts so unit tests can exercise the\n * decision tree without importing the entire worker (which has heavy\n * side effects at module load).\n *\n * The manager polls every 10s. After it deploys the latest `.mcp.json`\n * to a persistent claude session's project directory, it asks: has the\n * file changed since the running claude was launched? Three outcomes\n * matter:\n * - No file on disk yet → nothing to compare.\n * - First observation (no recorded baseline) → adopt current as baseline.\n * - Mismatch → schedule a tmux respawn so claude reads the fresh config.\n *\n * The optimistic baseline (we adopt whatever's on disk on first poll\n * after manager start) is the trade-off documented in the issue. Worst\n * case is one missed drift event after a manager restart; the next\n * change is caught.\n */\n\nexport type McpDriftAction =\n | { kind: 'no-config' }\n | { kind: 'baseline'; hash: string }\n | { kind: 'no-drift' }\n | { kind: 'drift'; previous: string; current: string };\n\nexport function decideMcpDriftAction(\n currentHash: string | null,\n knownHash: string | undefined,\n): McpDriftAction {\n if (!currentHash) return { kind: 'no-config' };\n if (!knownHash) return { kind: 'baseline', hash: currentHash };\n if (knownHash === currentHash) return { kind: 'no-drift' };\n return { kind: 'drift', previous: knownHash, current: currentHash };\n}\n","/**\n * ENG-4911 — pure helper for the integration-change hash used by the\n * `/host/refresh` reconciliation loop in manager-worker.\n *\n * Originally the hash covered only `definition_id + credentials`, which\n * meant a config-only change in the database (e.g. switching\n * `xero_tenant_id` from one tenant to another without rotating the\n * access token) silently failed to propagate: the hash matched the\n * stored value, the manager short-circuited before `writeIntegrations`,\n * `.env.integrations` was never rewritten, and the running MCP child\n * kept calling Xero with the old tenant id. This was a real\n * cross-tenant data exposure path on Sterling 2026-05-10.\n *\n * The fix is to also fold `config` into the hash payload. We use a\n * stable, ordered representation (sorted keys) so that two semantically\n * equivalent config blobs hash identically — without that, JSON's\n * insertion-order serialization could spuriously bump the hash on every\n * poll.\n */\n\nimport { createHash } from 'node:crypto';\n\ninterface HashableIntegration {\n definition_id: string;\n credentials?: Record<string, unknown> | null;\n config?: Record<string, unknown> | null;\n}\n\nfunction canonicalize(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(canonicalize);\n if (value && typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(obj).sort()) {\n out[key] = canonicalize(obj[key]);\n }\n return out;\n }\n return value;\n}\n\nexport function computeIntegrationsHash(integrations: HashableIntegration[]): string {\n const payload = integrations.map((i) =>\n `${i.definition_id}:${JSON.stringify(canonicalize(i.credentials ?? {}))}:${JSON.stringify(canonicalize(i.config ?? {}))}`,\n );\n return createHash('sha256')\n .update(JSON.stringify(payload))\n .digest('hex')\n .slice(0, 16);\n}\n","// ENG-4832: reap MCP child processes whose env references a credential\n// that's just been rotated, so the next call respawns them with the\n// fresh value.\n//\n// Why this exists: the manager polls /host/refresh, gets new OAuth\n// tokens, writes them into the agent's `.env.integrations`. But the\n// MCP children (xero-mcp-server, gmail-mcp, etc.) were spawned by\n// Claude Code at session start and hold their **spawn-time** env. They\n// never re-read .env.integrations, so once the access_token in their\n// env block expires (typically 30 min for Xero), every upstream call\n// 401s — and the agent reports \"refresh failed\" because its view of\n// the token is stale, regardless of what the DB row says. See\n// ENG-4832 for the full triage (Stirling, Day 3).\n//\n// The fix: when integration credentials rotate, identify which MCP\n// children reference the rotated env var(s) AND belong to this agent,\n// SIGTERM them. Claude Code's MCP transport detects the dead child\n// and respawns it on next request — fresh env, fresh token, agent\n// keeps working. Conversation context is preserved.\n//\n// Companion to orphan-channel-mcp-reaper.ts (ENG-4808). Same shape\n// (parse ps, walk ppid chain, kill with grace) but a different\n// targeting rule: that one finds children whose parent claude is\n// dead; this one finds children whose env is stale.\n//\n// Pure helpers exposed for tests:\n// • parseEnvIntegrationsVars() — extract var names from a\n// .env.integrations file\n// • findMcpServersUsingVars() — given the parsed .mcp.json and\n// a set of changed var names, which server keys are affected?\n// • findMcpChildrenForAgent() — walk ps rows, return PIDs\n// belonging to <codeName>'s claude AND matching one of the\n// server-key argv signatures\n//\n// Side-effecting:\n// • reapStaleMcpChildren() — orchestrates the above + SIGTERM\n// / grace / SIGKILL. Idempotent — calling repeatedly is safe.\n\nimport { execFileSync } from 'node:child_process';\nimport { parsePsRows, type PsRow } from './orphan-channel-mcp-reaper.js';\n\n/**\n * Parse a `.env.integrations` file body into a Map of var name → raw\n * value (the value is captured verbatim, including any shell-quote\n * wrapping the writer added). Pure — operate on file contents, not\n * paths. Robust to:\n * - leading/trailing whitespace\n * - shell-quote forms (`X='value'`, `X=\"value\"`, `X=raw`)\n * - comments (lines starting with `#`)\n * - blank lines\n * - duplicates (later wins, matching shell-source semantics)\n *\n * Values are NOT dequoted on purpose — for the diff use case we just\n * need byte-for-byte equality between two writes of the same value,\n * and writeIntegrations is deterministic (always emits the same\n * shellQuote form for the same input). Skipping dequoting also keeps\n * this immune to quote-handling bugs.\n */\nexport function parseEnvIntegrationsEntries(content: string): Map<string, string> {\n const entries = new Map<string, string>();\n for (const raw of content.split(/\\r?\\n/)) {\n const line = raw.trim();\n if (!line || line.startsWith('#')) continue;\n const eq = line.indexOf('=');\n if (eq <= 0) continue;\n const name = line.slice(0, eq).trim();\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) continue;\n const value = line.slice(eq + 1);\n entries.set(name, value);\n }\n return entries;\n}\n\n/**\n * Just the SET of var names declared in a `.env.integrations` body.\n * Convenience wrapper over parseEnvIntegrationsEntries — preserves\n * the original ENG-4832 API for callers that only need names.\n */\nexport function parseEnvIntegrationsVars(content: string): string[] {\n return [...parseEnvIntegrationsEntries(content).keys()];\n}\n\n/**\n * Diff two `.env.integrations` parses. Returns the names whose value\n * changed (rotated), was added, or was removed between the two\n * snapshots.\n *\n * Used by the manager-worker integration-rotation hook to pass the\n * **precisely-rotated** subset to findMcpServersUsingVars rather than\n * \"every var currently present in the file\", which would over-reap\n * unrelated MCP children on every refresh tick (CodeRabbit on PR #797).\n */\nexport function diffEnvIntegrations(\n oldContent: string | undefined,\n newContent: string,\n): string[] {\n const oldEntries = oldContent === undefined ? new Map<string, string>() : parseEnvIntegrationsEntries(oldContent);\n const newEntries = parseEnvIntegrationsEntries(newContent);\n const changed = new Set<string>();\n for (const [name, value] of newEntries) {\n if (oldEntries.get(name) !== value) changed.add(name);\n }\n // Removed vars also count — a server whose env was depending on a\n // now-absent var has effectively been rotated to \"no value\", which\n // is just as breaking as a value change.\n for (const name of oldEntries.keys()) {\n if (!newEntries.has(name)) changed.add(name);\n }\n return [...changed];\n}\n\n/**\n * Pure: given a parsed `.mcp.json` and a set of env-var names that\n * have just rotated, return the MCP server keys whose `env` block\n * references any of those vars via `${VAR}` substitution.\n *\n * Only the `${VAR}` form is checked because that's the only form\n * Claude Code substitutes at MCP launch time from the parent claude's\n * env. A literal value-equals-name (e.g. `\"TOKEN\": \"XERO_ACCESS_TOKEN\"`\n * with no `${}`) is just a constant string the MCP child sees as-is —\n * the rotated value never reaches it, so reaping the child wouldn't\n * fix anything (the child would just respawn with the same broken\n * literal). CodeRabbit on PR #797 caught a doc-vs-impl drift that\n * previously claimed both forms were checked; this comment is now\n * aligned with the implementation, and the test\n * `does NOT match servers whose env contains the var name as a\n * literal value` pins the correct behaviour.\n */\nexport interface McpServerEntry {\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n}\n\nexport interface McpConfig {\n mcpServers?: Record<string, McpServerEntry>;\n}\n\nexport function findMcpServersUsingVars(\n mcp: McpConfig | null | undefined,\n changedVars: Iterable<string>,\n): string[] {\n const changedSet = new Set(changedVars);\n if (!mcp?.mcpServers || changedSet.size === 0) return [];\n\n const result: string[] = [];\n for (const [serverKey, entry] of Object.entries(mcp.mcpServers)) {\n if (!entry || typeof entry !== 'object') continue;\n const env = entry.env;\n if (!env || typeof env !== 'object') continue;\n let matches = false;\n for (const value of Object.values(env)) {\n if (typeof value !== 'string') continue;\n // Match ${VAR} substitution form first — the common case.\n const placeholderMatches = value.match(/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g);\n if (placeholderMatches) {\n for (const ph of placeholderMatches) {\n const name = ph.slice(2, -1);\n if (changedSet.has(name)) {\n matches = true;\n break;\n }\n }\n }\n if (matches) break;\n }\n if (matches) result.push(serverKey);\n }\n return result;\n}\n\n/**\n * Compile MCP-server-key signatures into argv match patterns. Each MCP\n * server is spawned as some variant of `npx -y <package>` or\n * `node <path>` — the running child's argv contains the package\n * binary name (e.g. `xero-mcp-server`) which is stable enough to\n * match against. Falling back to the raw server-key as a literal\n * (e.g. `xero`) catches the cases where the package name and the\n * mcpServers key match.\n */\n/**\n * Build precise argv-matchers from each server's actual `.mcp.json`\n * `command + args`, NOT from the bare server key.\n *\n * Pre-fix this function used `\\b<serverKey>\\b` against argv, which on\n * a server key like `xero` would also match `xero-other-mcp-server` —\n * any sibling package that happens to share a prefix would get bounced\n * along with the intended target (CodeRabbit on PR #797). The fix:\n * extract a precise per-server signature from the entry's command +\n * args. For an `npx -y @xeroapi/xero-mcp-server@latest` entry we end\n * up matching against:\n * 1. the full package spec (`@xeroapi/xero-mcp-server`) — matches\n * the npm-exec wrapper process\n * 2. the bin basename (`xero-mcp-server`) — matches the actual node\n * child whose argv is `node /path/to/.bin/xero-mcp-server`\n *\n * Both forms are needed because the parent npm-exec process and the\n * child node process have different argv shapes but share the same\n * package identity.\n *\n * Falls back to a `(?<![A-Za-z0-9_])${key}(?![A-Za-z0-9_])` matcher\n * (alnum/underscore boundary, but `-` and `/` allowed) when the entry\n * has no resolvable package signature — the bare-key form is still\n * tighter than the original `\\b...\\b` because `_` is now part of the\n * \"name continuation\" set, so a server key `xero` doesn't match\n * `xero_thing` either.\n */\nfunction buildArgvMatchersForEntry(key: string, entry: McpServerEntry | undefined): RegExp[] {\n const escapeRe = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const patterns: RegExp[] = [];\n\n // Try to extract a precise package signature from the args. The\n // common shapes we provision through buildMcpJson:\n // npx -y @scope/package@version\n // npx -y package@version\n // node /absolute/path/to/.bin/package-name\n // Walk args from last to first, looking for the first token that\n // looks like a package spec or an absolute path with a bin basename.\n const args = entry?.args ?? [];\n for (let i = args.length - 1; i >= 0; i--) {\n const arg = args[i];\n if (typeof arg !== 'string' || !arg) continue;\n // Skip flags\n if (arg.startsWith('-')) continue;\n\n // Strip @version suffix from npm package specs.\n const stripped = arg.replace(/@[^/@]*$/, (m) =>\n // @latest, @1.2.3, etc. — drop. But @scope at the START of a\n // package spec must NOT be stripped, so we only strip a tail\n // `@...` if it doesn't itself start with a slash inside.\n // The regex above only matches a single trailing `@...`\n // segment after the last `/`, so this is safe for `@scope/pkg`.\n m.includes('/') ? m : '',\n );\n\n // Tail-boundary that disallows the package-name continuation\n // characters: alnum, `_`, `-`. Without this, matching\n // `@integrity-labs/cloud-broker` would also match\n // `@integrity-labs/cloud-broker-experimental` — exactly the\n // overmatch class CodeRabbit's review on PR #797 flagged.\n const tail = '(?![A-Za-z0-9_-])';\n\n // Looks like an npm package spec? (`@scope/pkg` or `pkg-name`)\n if (/^@?[a-z0-9]([a-z0-9._-]*\\/)?[a-z0-9._-]+$/i.test(stripped)) {\n patterns.push(new RegExp(`${escapeRe(stripped)}${tail}`));\n // Also push the basename if the spec is scoped — matches the\n // node child whose argv only has the bin name.\n const basename = stripped.split('/').pop();\n if (basename && basename !== stripped) {\n patterns.push(new RegExp(`${escapeRe(basename)}${tail}`));\n }\n break;\n }\n\n // Looks like a path? Match the basename (the bin name) — the\n // running child's argv typically has the full path.\n if (stripped.includes('/')) {\n const basename = stripped.split('/').pop();\n if (basename) {\n patterns.push(new RegExp(`${escapeRe(basename)}${tail}`));\n break;\n }\n }\n }\n\n if (patterns.length === 0) {\n // Fallback: bare server key with tighter boundaries than `\\b`.\n // Disallow alphanumerics + `_` either side; `-` and `/` are\n // permitted as separators (so e.g. `cloud-broker` matches\n // `@integrity-labs/cloud-broker` correctly).\n const safe = escapeRe(key);\n patterns.push(new RegExp(`(?<![A-Za-z0-9_])${safe}(?![A-Za-z0-9_])`));\n if (safe.includes('_')) {\n const dashed = safe.replace(/_/g, '-');\n patterns.push(new RegExp(`(?<![A-Za-z0-9_])${dashed}(?![A-Za-z0-9_])`));\n }\n }\n return patterns;\n}\n\n/** Match the parent claude bound to <codeName> via `--name agt-<codeName>`. */\nexport function buildClaudeAgentMatcher(codeName: string): RegExp {\n // Restrict to alnum/dash so a malicious codeName can't widen the\n // match (codeName is validated upstream but defence in depth).\n const safe = codeName.replace(/[^A-Za-z0-9_-]/g, '');\n // Use a tail boundary that disallows hyphen-as-word-character —\n // `\\b` between `g` and `-` matches in regex (since `-` is not a\n // word char), so `\\\\b` would let `agt-stirling-other` match the\n // matcher for `stirling`. Require an actual argument boundary\n // (whitespace) or end-of-string instead. CodeRabbit-detected\n // regression class.\n return new RegExp(`\\\\bclaude\\\\b.*--name\\\\s+agt-${safe}(?=\\\\s|$)`);\n}\n\n/**\n * Pure: given parsed ps rows, find MCP child PIDs that belong to\n * <codeName>'s session AND match one of the rotated-env server keys.\n *\n * \"Belong to <codeName>'s session\" = ppid chain (up to maxDepth)\n * reaches a `claude --name agt-<codeName>` process. Same walk shape\n * as the orphan reaper, but the success direction is inverted: that\n * one collects MCPs whose ancestry does NOT reach a live claude\n * (orphans); this one collects MCPs whose ancestry DOES reach a\n * specific live claude (this agent's claude).\n *\n * Empty `serverKeys` returns []. Empty `psRows` returns [].\n * maxDepth defaults to 8 — same as the orphan reaper.\n */\nexport function findMcpChildrenForAgent(args: {\n rows: PsRow[];\n codeName: string;\n serverKeys: string[];\n /**\n * Parsed `.mcp.json` so the matcher can derive precise per-server\n * argv signatures from each entry's `command + args`. When omitted,\n * matching falls back to the bare-key form which is tighter than\n * the original `\\b...\\b` but can still overmatch for short keys.\n * Production callers pass this; some tests pass only `serverKeys`\n * to exercise the fallback path.\n */\n mcpJson?: McpConfig | null;\n maxDepth?: number;\n}): number[] {\n const { rows, codeName, serverKeys, mcpJson } = args;\n const maxDepth = args.maxDepth ?? 8;\n if (serverKeys.length === 0 || rows.length === 0) return [];\n\n const claudeMatcher = buildClaudeAgentMatcher(codeName);\n // Build precise per-server matchers from each entry's command + args\n // when mcpJson is provided. Falls back to the bare-key form when\n // a key has no resolvable entry (e.g. it was just removed from\n // mcp.json mid-tick or the test passed only serverKeys).\n const argvMatchers: RegExp[] = [];\n for (const key of serverKeys) {\n const entry = mcpJson?.mcpServers?.[key];\n argvMatchers.push(...buildArgvMatchersForEntry(key, entry));\n }\n const byPid = new Map<number, PsRow>(rows.map((r) => [r.pid, r]));\n\n const matched: number[] = [];\n for (const row of rows) {\n // Must match at least one of the server-key argv patterns.\n if (!argvMatchers.some((re) => re.test(row.args))) continue;\n // Must NOT itself be a claude (we want children, not the agent's\n // claude process).\n if (/\\bclaude\\b/.test(row.args) && row.args.includes(`--name agt-${codeName}`)) continue;\n\n // Walk parents looking for the agent's claude.\n let cur: PsRow | undefined = byPid.get(row.ppid);\n let belongs = false;\n for (let depth = 0; depth < maxDepth && cur; depth++) {\n if (claudeMatcher.test(cur.args)) {\n belongs = true;\n break;\n }\n if (cur.pid === 1) break;\n cur = byPid.get(cur.ppid);\n }\n if (belongs) matched.push(row.pid);\n }\n return matched;\n}\n\n/**\n * Run `ps`, find the stale MCP children for <codeName> belonging to\n * the listed serverKeys, and SIGTERM them. After `graceMs`, SIGKILL\n * any that didn't exit. Returns the list of pids that were sent\n * SIGTERM (informational — tests use this without mocking\n * process.kill).\n *\n * Idempotent. Cheap enough to call on every integration-rotation\n * tick — when nothing matches, the work is one ps invocation + a\n * regex pass.\n */\nexport function reapStaleMcpChildren(args: {\n log: (msg: string) => void;\n codeName: string;\n serverKeys: string[];\n /**\n * Parsed `.mcp.json` so the matcher can derive precise per-server\n * argv signatures from each entry's command + args. Production\n * callers always pass this; tests can omit to exercise the\n * fallback (bare-key with tightened boundaries).\n */\n mcpJson?: McpConfig | null;\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, codeName, serverKeys, mcpJson, graceMs = 5_000 } = args;\n if (serverKeys.length === 0) return [];\n\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 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(`[stale-mcp-reaper] ps invocation failed for '${codeName}': ${(err as Error).message} — skipping reap`);\n return [];\n }\n\n const rows = parsePsRows(psOutput);\n const targets = findMcpChildrenForAgent({ rows, codeName, serverKeys, mcpJson });\n if (targets.length === 0) return [];\n\n const byPid = new Map<number, PsRow>(rows.map((r) => [r.pid, r]));\n const describe = (pid: number): string => {\n const argv = byPid.get(pid)?.args ?? '';\n // Pull the first identifiable npm package or script name from argv.\n const pkgMatch = argv.match(/(@[a-z0-9_-]+\\/[a-z0-9_-]+|[a-z0-9_-]+-mcp-server|[a-z0-9_-]+-mcp\\b)/i);\n return pkgMatch ? `${pkgMatch[1]} (pid ${pid})` : `pid ${pid}`;\n };\n\n log(\n `[stale-mcp-reaper] '${codeName}': rotating ${targets.length} stale MCP child(ren) for [${serverKeys.join(', ')}]: ${targets.map(describe).join(', ')}`,\n );\n for (const pid of targets) {\n killProcess(pid, 'SIGTERM');\n }\n\n setTimeout(() => {\n try {\n // CodeRabbit on PR #797: re-resolve ownership before SIGKILL.\n // 5s grace is long enough for the kernel to recycle a freed\n // PID into an entirely unrelated process — `targets.filter(isAlive)`\n // alone would happily kill that bystander. Re-running the\n // original ps + match pipeline against fresh process state\n // confirms the PID is still one of *our* MCP children for this\n // agent. Anything that doesn't survive that re-check is either\n // already dead (good) or has been recycled (do NOT kill).\n let freshPsOutput: string;\n try {\n freshPsOutput = runPs();\n } catch (err) {\n log(`[stale-mcp-reaper] '${codeName}': fresh ps for SIGKILL re-verify failed: ${(err as Error).message} — skipping SIGKILL pass`);\n return;\n }\n const stillOwned = new Set(\n findMcpChildrenForAgent({\n rows: parsePsRows(freshPsOutput),\n codeName,\n serverKeys,\n mcpJson,\n }),\n );\n const stragglers = targets.filter((pid) => isAlive(pid) && stillOwned.has(pid));\n if (stragglers.length === 0) return;\n log(\n `[stale-mcp-reaper] '${codeName}': ${stragglers.length} child(ren) survived SIGTERM; sending SIGKILL: ${stragglers.map(describe).join(', ')}`,\n );\n for (const pid of stragglers) {\n killProcess(pid, 'SIGKILL');\n }\n } catch (err) {\n log(`[stale-mcp-reaper] '${codeName}': error in SIGKILL pass: ${(err as Error).message}`);\n }\n }, graceMs).unref();\n\n return targets;\n}\n","// ENG-4807: pure decision function — should the manager restart an agent's\n// persistent session because its channel set changed?\n//\n// Why this exists: when a channel is added or removed for a live persistent\n// claude-code session, the parent claude was launched with stale flags\n// (`--dangerously-load-development-channels` and `--allowedTools`). Those\n// flags are baked at spawn time and don't pick up the new channel. The\n// only fix is to kill the tmux session so the manager respawns it.\n// See ENG-4807 for the full diagnosis.\n//\n// Pulled out as a pure function so the gating logic is unit-testable\n// without mocking processAgent's dozens of dependencies.\n\nexport interface ChannelRestartDecisionInput {\n /**\n * The channel ids the manager saw on the *previous* refresh tick for\n * this agent, or `undefined` if this is the first time we've seen the\n * agent (first poll, or fresh manager process). On first poll the\n * persistent session is either not started yet or about to be started\n * with the fresh launch flags, so a restart would be a noisy no-op.\n */\n previousChannelIds: Set<string> | undefined;\n /** The channel ids in this refresh tick's response. */\n currentChannelIds: Set<string>;\n /**\n * Agent's `session_mode` from the refresh response. Restart only\n * applies to `persistent` sessions — oneshot agents don't have a\n * tmux session whose launch flags need refreshing.\n */\n sessionMode: string | undefined;\n /**\n * Agent's framework. Channel listener flags are claude-code-specific;\n * other frameworks (openclaw, nemoclaw) don't use the\n * `--dangerously-load-development-channels` mechanism so the restart\n * doesn't apply.\n */\n framework: string;\n /**\n * Whether the agent's tmux session is currently alive. If it isn't,\n * there's nothing to kill — the manager will spawn a fresh one\n * downstream with the right flags.\n */\n sessionHealthy: boolean;\n}\n\nexport interface ChannelRestartDecision {\n /** True iff the manager should kill the tmux session. */\n restart: boolean;\n /** Channels that appeared in this tick (informational, for log line). */\n added: string[];\n /** Channels that disappeared in this tick (informational, for log line). */\n removed: string[];\n}\n\n/**\n * ENG-4807 (CodeRabbit on PR #773 round 4): the channel set we care about\n * for restart purposes is NOT every key in `channel_configs` — it's only\n * the channels that the running session would actually load as channel\n * listeners. Those are the ones the manager calls\n * `writeChannelCredentials` for, gated on `status ∈ {active, pending}`\n * AND a non-null `config`. A status-only flip (e.g. active → revoked)\n * removes the channel from the launchable set even though its row stays\n * in `channel_configs`, and therefore should trigger a restart so the\n * launch flags drop the now-unauthorised channel. Inverse: a row that\n * never becomes launchable (e.g. always errored) should NOT trigger a\n * restart.\n *\n * Pure helper so the launchable filter has the same unit-test coverage\n * as the rest of the decision logic.\n */\nexport interface ChannelConfigEntry {\n status?: string;\n config?: unknown;\n}\n\nexport function launchableChannelIds(\n channelConfigs: Record<string, ChannelConfigEntry | undefined> | null | undefined,\n): Set<string> {\n if (!channelConfigs) return new Set();\n const result = new Set<string>();\n for (const [channelId, entry] of Object.entries(channelConfigs)) {\n if (!entry) continue;\n if (entry.config == null) continue;\n if (entry.status !== 'active' && entry.status !== 'pending') continue;\n result.add(channelId);\n }\n return result;\n}\n\nexport function decideChannelRestart(input: ChannelRestartDecisionInput): ChannelRestartDecision {\n const { previousChannelIds, currentChannelIds, sessionMode, framework, sessionHealthy } = input;\n\n // First poll for this agent — nothing to restart, the session (if any)\n // either doesn't exist yet or was just spawned with the right flags.\n if (previousChannelIds === undefined) {\n return { restart: false, added: [], removed: [] };\n }\n\n // Compute the diff regardless — it's used in the log line even when we\n // ultimately don't restart, and it's cheap.\n const added = [...currentChannelIds].filter((c) => !previousChannelIds.has(c));\n const removed = [...previousChannelIds].filter((c) => !currentChannelIds.has(c));\n\n // No actual set change — nothing to do (debounces same-set writes that\n // happen on every refresh tick once the cache is warm).\n if (added.length === 0 && removed.length === 0) {\n return { restart: false, added, removed };\n }\n\n // Restart only applies to live persistent claude-code sessions.\n if (sessionMode !== 'persistent') return { restart: false, added, removed };\n if (framework !== 'claude-code') return { restart: false, added, removed };\n if (!sessionHealthy) return { restart: false, added, removed };\n\n return { restart: true, added, removed };\n}\n","/**\n * ENG-4341: Integration context rendering.\n *\n * Substitutes `{{context.foo}}` placeholders in a SKILL.md body with values\n * from the resolved plugin context, and appends a \"## Team Overrides\"\n * section if the freeform overrides text is non-empty.\n *\n * Substitution rules (matching the RFC §4 + §4b contracts):\n * - String / boolean / number values are interpolated as-is.\n * - Arrays and maps are JSON-stringified (no per-type formatter in the\n * initial slice; skill authors can use JSON syntax in their prose).\n * - A placeholder that references a missing key is replaced with the\n * empty string and a warning is emitted via the warn callback. Never\n * leave a literal {{context.foo}} in the rendered output.\n *\n * Lives in its own module so tests can import it without pulling in the\n * full manager-worker entry-point side effects.\n */\n\nconst PLUGIN_CONTEXT_PLACEHOLDER_RE = /\\{\\{\\s*context\\.([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\}\\}/g;\n\nconst TEAM_OVERRIDES_HEADER =\n '## Team Overrides\\n\\n> **The following overrides anything you\\'ve read above.** ' +\n 'If any rule here conflicts with earlier instructions in this skill, follow what is ' +\n 'written here. These are user-supplied directives that take precedence over the ' +\n 'plugin\\'s default guidance.\\n';\n\nfunction formatContextValue(value: unknown): string {\n if (value === null || value === undefined) return '';\n if (typeof value === 'string') return value;\n if (typeof value === 'boolean' || typeof value === 'number') return String(value);\n // Arrays and objects: JSON-stringify so the agent sees a stable shape.\n // Skill authors who want a different format should describe the field\n // in their skill prose, not transform it here.\n try {\n return JSON.stringify(value);\n } catch {\n return '';\n }\n}\n\nexport function renderIntegrationSkillContent(\n raw: string,\n values: Record<string, unknown>,\n overrides: string,\n warn: (message: string) => void = () => {},\n): string {\n // 1. Substitute {{context.<field>}} placeholders.\n const substituted = raw.replace(PLUGIN_CONTEXT_PLACEHOLDER_RE, (_match, fieldName: string) => {\n if (!(fieldName in values)) {\n warn(`unresolved placeholder {{context.${fieldName}}} — substituting empty string`);\n return '';\n }\n return formatContextValue(values[fieldName]);\n });\n\n // 2. Append the freeform overrides section if non-empty.\n const trimmedOverrides = overrides.trim();\n if (!trimmedOverrides) return substituted;\n\n const separator = substituted.endsWith('\\n') ? '\\n' : '\\n\\n';\n return `${substituted}${separator}---\\n\\n${TEAM_OVERRIDES_HEADER}\\n${trimmedOverrides}\\n`;\n}\n","/**\n * Integration skill folder-layout builder (ENG-4502).\n *\n * Historically every integration scope landed as its own top-level skill folder:\n *\n * .claude/skills/integration-google-sheets-skill-googlesheets-read/SKILL.md\n * .claude/skills/integration-google-sheets-skill-googlesheets-write/SKILL.md\n * .claude/skills/integration-google-sheets-skill-googlesheets-delete/SKILL.md\n * .claude/skills/integration-google-sheets-skill-googlesheets-other/SKILL.md\n *\n * Confirmed via the claude-code-guide skill: Claude Code's skill discovery\n * does NOT recurse, so `integration-google-sheets/read/SKILL.md` style\n * nesting would be invisible. This module implements the compromise that\n * does work:\n *\n * .claude/skills/integration-google-sheets/\n * SKILL.md ← single umbrella Claude sees; references scopes\n * scopes/read.md ← scope-specific reference, loaded on demand\n * scopes/write.md\n * scopes/delete.md\n * scopes/other.md\n *\n * The umbrella SKILL.md is synthesized from the per-scope `skill_name`s and\n * an extracted description from each scope's frontmatter. Original scope\n * content (frontmatter and body) lands at `scopes/<slug>.md`.\n *\n * Pure functions here so the layout logic can be unit-tested without\n * involving the filesystem.\n */\n\nimport type { CapabilitySkillFile } from '@augmented/core';\n\nexport interface IntegrationSkillInput {\n /**\n * Wire-contract field — matches the `plugin_slug` key in the\n * /host/refresh payload. Renaming the field would break the host adapter\n * contract; the type alias here keeps the rename surface bounded.\n */\n plugin_slug: string;\n skill_id: string;\n skill_name: string;\n /** Rendered scope content including YAML frontmatter. */\n content: string;\n}\n\nexport interface IntegrationBundle {\n /** Slug used as the top-level folder name (`integration-<slug>`). */\n integrationSlug: string;\n /** Files relative to the integration folder root. */\n files: CapabilitySkillFile[];\n /**\n * Stable set of scope-file relative paths (e.g. `scopes/read.md`) included\n * in this bundle. Exposed for per-file hashing / migration cleanup.\n */\n scopePaths: string[];\n}\n\n/** Extract the `description:` line from a skill frontmatter block. */\nexport function extractDescription(content: string): string | null {\n const match = content.match(/^---\\s*([\\s\\S]*?)---/);\n if (!match) return null;\n const frontmatter = match[1] ?? '';\n // Match either a double-quoted string with escaped quotes / backslashes, or an\n // unquoted scalar up to end-of-line. The quoted branch captures the raw inner\n // content so escapes can be normalised below.\n const descMatch = frontmatter.match(/^\\s*description:\\s*(?:\"((?:\\\\.|[^\"\\\\])*)\"|([^\\n]+))\\s*$/m);\n if (!descMatch) return null;\n const quoted = descMatch[1];\n if (quoted !== undefined) {\n return quoted.replace(/\\\\([\"\\\\])/g, '$1').trim();\n }\n return descMatch[2]?.trim() ?? null;\n}\n\n/** Sanitize a scope slug so it's safe as a filename. */\nfunction sanitizeScopeSlug(skillId: string, integrationSlug: string): string {\n // Strip the integration slug prefix if present — `googlesheets-read` under\n // `integration-google-sheets` reads better as just `read`.\n const normalized = skillId.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');\n const prefix = integrationSlug.replace(/-/g, '');\n const stripped =\n normalized.startsWith(`${prefix}-`) ? normalized.slice(prefix.length + 1) : normalized;\n return stripped.replace(/^-|-$/g, '') || normalized || 'scope';\n}\n\n/**\n * Build the umbrella integration bundle from one or more scope skills.\n *\n * Contract:\n * - The umbrella SKILL.md always exists, even for a single-scope integration,\n * so the on-disk shape is predictable.\n * - Its frontmatter uses an integration-wide `name` derived from the slug\n * and a `description` that concatenates scope descriptions with \" \", so\n * Claude can match any scope keyword against a single skill entry.\n * - The body lists every scope with a link to its scope doc, giving Claude\n * a deterministic way to load the right reference on demand.\n * - Scope content is written verbatim under `scopes/<slug>.md` — including\n * its own frontmatter — so an agent following a link sees the full\n * original doc.\n */\nexport function buildIntegrationBundle(skills: IntegrationSkillInput[]): IntegrationBundle {\n if (skills.length === 0) {\n throw new Error('buildIntegrationBundle: empty skills list');\n }\n const integrationSlug = skills[0]!.plugin_slug;\n // Deterministic scope order so the bundle content (and its hash) doesn't\n // flap when the DB returns rows in a different order between polls.\n const ordered = [...skills].sort((a, b) => a.skill_id.localeCompare(b.skill_id));\n\n const entries = ordered.map((s) => {\n const scopeSlug = sanitizeScopeSlug(s.skill_id, integrationSlug);\n const description = extractDescription(s.content);\n return {\n skillId: s.skill_id,\n skillName: s.skill_name,\n description,\n scopeSlug,\n scopePath: `scopes/${scopeSlug}.md`,\n content: s.content,\n };\n });\n\n // Two different skill_ids can normalise to the same scopeSlug (e.g. `Read`\n // and `read!`). Without a guard the later file silently overwrites the\n // earlier one at install time while the umbrella links both bullets to the\n // same doc — fail fast so the collision is visible.\n const scopePathGroups = new Map<string, typeof entries>();\n for (const entry of entries) {\n const bucket = scopePathGroups.get(entry.scopePath);\n if (bucket) bucket.push(entry);\n else scopePathGroups.set(entry.scopePath, [entry]);\n }\n for (const [scopePath, group] of scopePathGroups) {\n if (group.length > 1) {\n const conflicts = group.map((e) => `${e.skillId} (${e.skillName})`).join(', ');\n throw new Error(\n `buildIntegrationBundle: duplicate scope path '${scopePath}' for integration '${integrationSlug}' — conflicting skills: ${conflicts}`,\n );\n }\n }\n\n const integrationName = slugToTitle(integrationSlug);\n // Description: join scope descriptions so Claude has broad keyword coverage\n // across the integration. Fall back to joined scope names when a scope's\n // frontmatter is missing a description line (defensive — content is\n // expected to always have frontmatter, but this keeps the umbrella\n // renderable even on weird input).\n const umbrellaDescription = entries\n .map((e) => e.description ?? `Use for ${e.skillName}.`)\n .join(' ');\n\n const scopeList = entries\n .map((e) => {\n const label = e.skillName || slugToTitle(e.scopeSlug);\n const desc = e.description ? ` — ${e.description}` : '';\n return `- **${label}** ([${e.scopePath}](${e.scopePath}))${desc}`;\n })\n .join('\\n');\n\n const umbrella = [\n '---',\n `name: \"${integrationName}\"`,\n `description: \"${escapeYamlDouble(umbrellaDescription)}\"`,\n '---',\n '',\n `# ${integrationName}`,\n '',\n `This skill bundles ${entries.length === 1 ? 'one scope' : `${entries.length} scopes`} for the ${integrationSlug} integration. Each scope has a dedicated reference under \\`scopes/\\` that you should load on demand when the user's intent maps to it.`,\n '',\n '## Scopes',\n '',\n scopeList,\n '',\n '## How to use',\n '',\n `Identify which scope matches the user's request, then read the matching file under \\`scopes/\\` for the exact tool names and usage details. Do not guess tool names from this umbrella file alone.`,\n '',\n ].join('\\n');\n\n const files: CapabilitySkillFile[] = [\n { relativePath: 'SKILL.md', content: umbrella },\n ...entries.map((e) => ({\n relativePath: e.scopePath,\n content: e.content,\n })),\n ];\n\n return {\n integrationSlug,\n files,\n scopePaths: entries.map((e) => e.scopePath),\n };\n}\n\n/** `google-sheets` → `Google Sheets` */\nfunction slugToTitle(slug: string): string {\n return slug\n .split(/[-_]/)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(' ');\n}\n\n/** Escape characters that would break a double-quoted YAML scalar. */\nfunction escapeYamlDouble(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n\n/** Group integration skills by `plugin_slug` (wire-contract field), preserving insertion order of slugs. */\nexport function groupSkillsByIntegration(skills: IntegrationSkillInput[]): Map<string, IntegrationSkillInput[]> {\n const map = new Map<string, IntegrationSkillInput[]>();\n for (const s of skills) {\n const bucket = map.get(s.plugin_slug);\n if (bucket) bucket.push(s);\n else map.set(s.plugin_slug, [s]);\n }\n return map;\n}\n\n/**\n * Hash the umbrella-bundle file set into a single value so the manager only\n * re-writes when something actually changed. Hashing the concatenated\n * (relativePath, content) pairs catches new scope additions, scope\n * reorderings, and content tweaks without false positives.\n */\nexport function bundleFingerprint(files: CapabilitySkillFile[]): string {\n // Sort so the fingerprint is order-independent — buildIntegrationBundle already\n // sorts scopes, but a defensive re-sort here protects callers that build\n // bundles differently (tests, future shortcuts).\n const sorted = [...files].sort((a, b) => a.relativePath.localeCompare(b.relativePath));\n return sorted.map((f) => `${f.relativePath}\\x00${f.content}`).join('\\x01');\n}\n","/**\n * Gateway WebSocket Client — connects to the OpenClaw gateway event stream\n * and emits typed events for consumption by the manager worker.\n */\n\nimport { EventEmitter } from 'node:events';\nimport WebSocket from 'ws';\n\nconst DEFAULT_PORT = 18789;\nconst RECONNECT_INITIAL_MS = 1_000;\nconst RECONNECT_BASE_MS = 5_000;\nconst RECONNECT_MAX_MS = 30_000;\nconst HEARTBEAT_INTERVAL_MS = 30_000;\n\nexport interface GatewayEvent {\n type: 'event';\n event: string;\n payload: unknown;\n seq?: number;\n stateVersion?: number;\n}\n\nexport interface GatewayClientOptions {\n port?: number;\n token?: string;\n}\n\nexport interface PooledGatewayEvent extends GatewayEvent {\n agentCodeName: string;\n}\n\nexport class GatewayClient extends EventEmitter {\n private readonly port: number;\n private readonly token: string | undefined;\n private ws: WebSocket | null = null;\n private _connected = false;\n private _everConnected = false;\n private reconnectAttempts = 0;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private pongReceived = true;\n private intentionalClose = false;\n private pendingRpc = new Map<string, { resolve: (v: unknown) => void; reject: (e: Error) => void; timer: ReturnType<typeof setTimeout> }>();\n private rpcSeq = 0;\n\n constructor(options: GatewayClientOptions = {}) {\n super();\n this.port = options.port ?? Number(process.env['OPENCLAW_GATEWAY_PORT']) ?? DEFAULT_PORT;\n this.token = options.token ?? process.env['OPENCLAW_GATEWAY_TOKEN'];\n }\n\n get connected(): boolean {\n return this._connected;\n }\n\n connect(): void {\n this.intentionalClose = false;\n this.doConnect();\n }\n\n disconnect(): void {\n this.intentionalClose = true;\n this.clearTimers();\n\n if (this.ws) {\n this.ws.removeAllListeners();\n this.ws.close(1000);\n this.ws = null;\n }\n\n if (this._connected) {\n this._connected = false;\n this.emit('disconnected');\n }\n }\n\n // ── Private ──────────────────────────────────────────────────────────────\n\n private doConnect(): void {\n const url = `ws://127.0.0.1:${this.port}`;\n\n try {\n const headers: Record<string, string> = {};\n if (this.token) {\n headers['Authorization'] = `Bearer ${this.token}`;\n }\n\n this.ws = new WebSocket(url, { headers });\n } catch (err) {\n this.emit('error', err);\n this.scheduleReconnect();\n return;\n }\n\n this.ws.on('open', () => {\n // Wait for challenge message from gateway — connection established, auth pending\n });\n\n this.ws.on('message', (data: WebSocket.Data) => {\n try {\n const msg = JSON.parse(data.toString());\n this.handleMessage(msg);\n } catch {\n // Ignore non-JSON messages\n }\n });\n\n this.ws.on('close', (code: number, reason: Buffer) => {\n this.clearHeartbeat();\n if (this._connected) {\n this._connected = false;\n this.emit('disconnected');\n } else if (!this._everConnected) {\n // Never connected — log the close reason for debugging\n this.emit('error', new Error(`WebSocket closed before auth (code=${code}, reason=${reason?.toString() || 'none'})`));\n }\n if (!this.intentionalClose) {\n this.scheduleReconnect();\n }\n });\n\n this.ws.on('error', (err: Error) => {\n // Suppress ECONNREFUSED before first successful connection — the gateway\n // may still be booting. The reconnect loop will retry with backoff.\n if (!this._everConnected && (err as NodeJS.ErrnoException).code === 'ECONNREFUSED') {\n return;\n }\n this.emit('error', err);\n });\n\n this.ws.on('pong', () => {\n this.pongReceived = true;\n });\n }\n\n private handleMessage(msg: Record<string, unknown>): void {\n switch (msg.type) {\n case 'challenge':\n // Respond to challenge handshake\n this.sendJson({\n type: 'connect',\n nonce: msg.nonce,\n role: 'operator',\n scopes: ['events:read'],\n });\n break;\n\n case 'hello-ok':\n this._connected = true;\n this._everConnected = true;\n this.reconnectAttempts = 0;\n this.startHeartbeat();\n this.emit('connected');\n break;\n\n case 'event':\n this.emit('event', {\n type: 'event',\n event: msg.event,\n payload: msg.payload,\n seq: msg.seq,\n stateVersion: msg.stateVersion,\n } as GatewayEvent);\n break;\n\n case 'heartbeat':\n case 'tick':\n // Gateway-initiated heartbeat — no action needed\n break;\n\n case 'rpc-result':\n case 'rpc-error': {\n const rpcId = msg.id as string;\n const pending = this.pendingRpc.get(rpcId);\n if (pending) {\n this.pendingRpc.delete(rpcId);\n clearTimeout(pending.timer);\n if (msg.type === 'rpc-error') {\n pending.reject(new Error((msg.error as string) ?? 'RPC error'));\n } else {\n pending.resolve(msg.result);\n }\n }\n break;\n }\n\n default:\n // Unknown message type — check if it's an RPC response with a matching id\n if (msg.id && typeof msg.id === 'string') {\n const pending = this.pendingRpc.get(msg.id);\n if (pending) {\n this.pendingRpc.delete(msg.id);\n clearTimeout(pending.timer);\n if (msg.error) {\n pending.reject(new Error(String(msg.error)));\n } else {\n pending.resolve(msg);\n }\n }\n }\n break;\n }\n }\n\n /**\n * Send an RPC call to the gateway and wait for a response.\n * Returns the response payload or throws on timeout/error.\n */\n sendRpc(method: string, params: Record<string, unknown> = {}, timeoutMs = 10_000): Promise<unknown> {\n return new Promise((resolve, reject) => {\n if (!this._connected || this.ws?.readyState !== WebSocket.OPEN) {\n reject(new Error('Gateway not connected'));\n return;\n }\n const id = `rpc-${++this.rpcSeq}-${Date.now()}`;\n const timer = setTimeout(() => {\n this.pendingRpc.delete(id);\n reject(new Error(`RPC ${method} timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n this.pendingRpc.set(id, { resolve, reject, timer });\n this.sendJson({ type: 'rpc', id, method, params });\n });\n }\n\n private sendJson(obj: unknown): void {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify(obj));\n }\n }\n\n private startHeartbeat(): void {\n this.clearHeartbeat();\n this.pongReceived = true;\n\n this.heartbeatTimer = setInterval(() => {\n if (!this.pongReceived) {\n // Stale connection — force reconnect\n this.ws?.terminate();\n return;\n }\n this.pongReceived = false;\n this.ws?.ping();\n }, HEARTBEAT_INTERVAL_MS);\n }\n\n private clearHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private scheduleReconnect(): void {\n if (this.intentionalClose) return;\n\n // First retry uses a short delay (gateway may still be booting), then exponential backoff\n const delay = this.reconnectAttempts === 0\n ? RECONNECT_INITIAL_MS\n : Math.min(RECONNECT_BASE_MS * Math.pow(2, this.reconnectAttempts - 1), RECONNECT_MAX_MS);\n this.reconnectAttempts++;\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.doConnect();\n }, delay);\n }\n\n private clearTimers(): void {\n this.clearHeartbeat();\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// GatewayClientPool — manages multiple per-agent gateway connections\n// ---------------------------------------------------------------------------\n\nexport class GatewayClientPool extends EventEmitter {\n private clients = new Map<string, GatewayClient>();\n\n addAgent(codeName: string, port: number, token?: string): void {\n if (this.clients.has(codeName)) {\n this.removeAgent(codeName);\n }\n\n const client = new GatewayClient({ port, token });\n\n client.on('connected', () => {\n this.emit('connected', codeName);\n });\n\n client.on('disconnected', () => {\n this.emit('disconnected', codeName);\n });\n\n client.on('error', (err: Error) => {\n this.emit('error', err, codeName);\n });\n\n client.on('event', (evt: GatewayEvent) => {\n const pooledEvent: PooledGatewayEvent = {\n ...evt,\n agentCodeName: codeName,\n };\n this.emit('event', pooledEvent);\n });\n\n this.clients.set(codeName, client);\n client.connect();\n }\n\n removeAgent(codeName: string): void {\n const client = this.clients.get(codeName);\n if (client) {\n client.disconnect();\n this.clients.delete(codeName);\n }\n }\n\n hasAgent(codeName: string): boolean {\n return this.clients.has(codeName);\n }\n\n isConnected(codeName: string): boolean {\n return this.clients.get(codeName)?.connected ?? false;\n }\n\n /**\n * Send an RPC call to a specific agent's gateway.\n * Returns the response or throws on timeout/error/not connected.\n */\n async sendRpc(codeName: string, method: string, params: Record<string, unknown> = {}, timeoutMs = 10_000): Promise<unknown> {\n const client = this.clients.get(codeName);\n if (!client) throw new Error(`No gateway client for agent '${codeName}'`);\n return client.sendRpc(method, params, timeoutMs);\n }\n\n disconnectAll(): void {\n for (const [, client] of this.clients) {\n client.disconnect();\n }\n this.clients.clear();\n }\n\n get size(): number {\n return this.clients.size;\n }\n}\n","import { readFile, readdir } from 'node:fs/promises';\nimport { homedir, platform } from 'node:os';\nimport { join } from 'node:path';\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { ClaudeAuthReport, ClaudeAuthStatus } from '@augmented/core';\n\nconst execFileAsync = promisify(execFile);\n\n// Anything less than this is reported as \"expiring_soon\" — gives the operator\n// enough runway to re-pair before the host goes offline.\nconst EXPIRING_SOON_MS = 48 * 60 * 60 * 1000; // 48 hours\n\n/**\n * Best-effort detection of the host's Claude Code authentication state.\n *\n * Precedence:\n * 1. ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN env var → mode=api_key, valid forever\n * 2. macOS: Keychain entry \"Claude Code-credentials\"\n * 3. Linux: ~/.claude/.credentials.json (or credentials.json)\n *\n * Reported in the host heartbeat so the console can surface a \"re-pair\"\n * prompt before Claude Code goes silently broken.\n *\n * Returns null if no authentication state can be detected at all (e.g. the\n * host has never logged in and has no API key set).\n */\nexport async function detectClaudeAuth(): Promise<ClaudeAuthReport | null> {\n if (process.env['ANTHROPIC_API_KEY'] || process.env['ANTHROPIC_AUTH_TOKEN']) {\n return { mode: 'api_key', status: 'valid', expires_at: null };\n }\n\n const creds = await readOauthCredentials();\n if (!creds) return null;\n\n return computeSubscriptionStatus(creds);\n}\n\ntype OauthCreds = {\n expiresAt?: number | string;\n accessToken?: string;\n refreshToken?: string;\n};\n\n/**\n * List every path where Claude Code OAuth credentials might live on this\n * host, ordered by preference (calling user's home first, then /home/* on\n * Linux-root). Useful for other CLI code that wants to e.g. copy the creds\n * into the manager's HOME before spawning claude.\n */\nexport async function findClaudeCredentialsPaths(): Promise<string[]> {\n const candidates: string[] = [\n join(homedir(), '.claude', '.credentials.json'),\n join(homedir(), '.claude', 'credentials.json'),\n ];\n\n const isLinuxRoot =\n platform() === 'linux'\n && typeof process.getuid === 'function'\n && process.getuid() === 0;\n\n if (isLinuxRoot) {\n try {\n const entries = await readdir('/home', { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n candidates.push(join('/home', entry.name, '.claude', '.credentials.json'));\n candidates.push(join('/home', entry.name, '.claude', 'credentials.json'));\n }\n } catch {\n // /home doesn't exist or isn't readable\n }\n }\n\n return candidates;\n}\n\nasync function readOauthCredentials(): Promise<OauthCreds | null> {\n if (platform() === 'darwin') {\n const fromKeychain = await readMacKeychain();\n if (fromKeychain) return fromKeychain;\n }\n\n const candidates = await findClaudeCredentialsPaths();\n\n for (const path of candidates) {\n const parsed = await readJsonSilently(path);\n if (parsed) {\n // Claude Code has historically used several keys at the top level.\n return (parsed.claudeAiOauth ?? parsed.oauth ?? parsed) as OauthCreds;\n }\n }\n\n return null;\n}\n\nasync function readMacKeychain(): Promise<OauthCreds | null> {\n try {\n const { stdout } = await execFileAsync(\n 'security',\n ['find-generic-password', '-s', 'Claude Code-credentials', '-w'],\n { timeout: 3000 }\n );\n const parsed = JSON.parse(stdout.trim()) as Record<string, unknown>;\n return (parsed.claudeAiOauth ?? parsed) as OauthCreds;\n } catch {\n return null;\n }\n}\n\nasync function readJsonSilently(path: string): Promise<Record<string, unknown> | null> {\n try {\n const raw = await readFile(path, 'utf-8');\n return JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nfunction computeSubscriptionStatus(creds: OauthCreds): ClaudeAuthReport {\n const expiresAtMs = parseExpiresAt(creds.expiresAt);\n\n // `expiresAt` on Claude Code subscription creds is the short-lived access\n // token (~8 hours). When a refresh token is present, Claude Code auto-\n // refreshes it silently in the background — the access-token expiry is\n // not a meaningful signal for \"needs human action\". Only warn on access-\n // token expiry when there's no refresh token to fall back on.\n //\n // Caveat: we don't track refresh-token expiry because Claude Code's\n // credentials format doesn't expose one. Refresh tokens are typically\n // long-lived (months). If one does expire, Claude Code surfaces the\n // failure at the next auto-refresh attempt, which the operator will\n // see in their manager.log. Trading off a rare silent-fail scenario\n // for a much worse \"warn on every login\" experience.\n const hasRefreshToken = typeof creds.refreshToken === 'string' && creds.refreshToken.trim().length > 0;\n\n if (expiresAtMs === null) {\n return {\n mode: 'subscription',\n status: 'unknown',\n expires_at: null,\n };\n }\n\n const msUntilExpiry = expiresAtMs - Date.now();\n let status: ClaudeAuthStatus;\n if (hasRefreshToken) {\n // Even past the access-token expiry we expect auto-refresh to succeed.\n // Only report 'expired' if we're past expiry AND don't have a refresh\n // token — we can't reach that branch here, so always 'valid' when\n // refresh token is present.\n status = 'valid';\n } else {\n status =\n msUntilExpiry <= 0 ? 'expired'\n : msUntilExpiry <= EXPIRING_SOON_MS ? 'expiring_soon'\n : 'valid';\n }\n\n return {\n mode: 'subscription',\n status,\n expires_at: new Date(expiresAtMs).toISOString(),\n };\n}\n\nfunction parseExpiresAt(raw: unknown): number | null {\n if (typeof raw === 'number' && Number.isFinite(raw)) {\n // Ms-since-epoch or seconds-since-epoch — disambiguate: anything below\n // year 2100 in seconds is <= 4e9, while the same instant in ms is 4e12.\n return raw < 1e12 ? raw * 1000 : raw;\n }\n if (typeof raw === 'string') {\n const parsed = Date.parse(raw);\n return Number.isNaN(parsed) ? null : parsed;\n }\n return null;\n}\n","/**\n * Deterministic JSON serialisation for use as a hash key (ENG-4468).\n *\n * The manager caches channel-config hashes to decide whether to rewrite\n * `.mcp.json` each poll. The previous implementation hashed\n * `JSON.stringify(entry.config)` directly, but Postgres JSONB doesn't\n * preserve key order — each `SELECT config FROM ...` can return the same\n * semantic object with different key ordering. `JSON.stringify` then\n * produces a different string, the hash mismatches, and the cache is\n * missed every poll. For agents with nested config objects (e.g. Slack's\n * `manifest`) the churn was constant: credentials rewritten every\n * ~1–2 min → drift detection fires → MCP subprocess gets re-read and\n * sometimes fails to respawn cleanly.\n *\n * `canonicalJson` walks the value and emits keys in sorted order at every\n * object level so two semantically-identical configs produce identical\n * output regardless of the input key order.\n */\n\nexport function canonicalJson(value: unknown): string {\n return JSON.stringify(normalize(value));\n}\n\n/**\n * Recursively rebuild the value with every object's keys in alphabetic\n * order. Arrays preserve their order (semantic). Primitives pass\n * through. Circular references would throw here the same way they\n * would in JSON.stringify, which is fine — this codepath is fed by\n * plain DB JSONB so cycles are impossible.\n */\nfunction normalize(value: unknown): unknown {\n if (value === null || typeof value !== 'object') return value;\n if (Array.isArray(value)) return value.map(normalize);\n const sorted: Record<string, unknown> = {};\n const keys = Object.keys(value as Record<string, unknown>).sort();\n for (const k of keys) {\n sorted[k] = normalize((value as Record<string, unknown>)[k]);\n }\n return sorted;\n}\n","// ENG-4712: persist the manager's channel-config hash cache across\n// process restarts.\n//\n// The manager keeps an in-memory Map of `agentId:channelId → sha256` so\n// that consecutive polls don't re-write `.mcp.json` channel entries\n// that haven't changed. That Map lives at module scope, so every fresh\n// manager process (post self-update on Linux EC2, post launchd respawn\n// on macOS, post SIGTERM, etc.) starts empty. With an empty cache the\n// very next poll re-emits a `Channel credentials written … reason=first-write`\n// log line for every active agent × channel pair, even though the\n// on-disk content already matches the desired config — a noisy and\n// misleading signal that operators read as \"credentials keep churning\".\n//\n// Round-tripping the Map through a JSON sidecar at the configDir root\n// turns those churn lines into silent no-ops on restart while leaving\n// the existing in-memory cache contract unchanged.\n\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nconst CACHE_FILENAME = 'channel-hash-cache.json';\n\nexport function getChannelHashCacheFile(configDir: string): string {\n return join(configDir, CACHE_FILENAME);\n}\n\n/**\n * Hydrate `target` with persisted entries. Missing / unreadable /\n * malformed files are treated as \"empty cache\" — the next poll will\n * rewrite credentials and repopulate, which costs at most one extra\n * round of `first-write` log lines but never breaks correctness.\n */\nexport function loadChannelHashCache(\n target: Map<string, string>,\n configDir: string,\n): void {\n const path = getChannelHashCacheFile(configDir);\n if (!existsSync(path)) return;\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(path, 'utf-8'));\n } catch {\n return;\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return;\n for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {\n if (typeof value === 'string' && value.length > 0) target.set(key, value);\n }\n}\n\n/**\n * Persist `source` to disk. Failures are non-fatal — the cache stays\n * in memory and the next save attempt may succeed. We accept one extra\n * `first-write` round on the next manager restart over crashing the\n * poll loop.\n */\nexport function saveChannelHashCache(\n source: Map<string, string>,\n configDir: string,\n): void {\n const path = getChannelHashCacheFile(configDir);\n const obj: Record<string, string> = {};\n for (const [key, value] of source) obj[key] = value;\n try {\n writeFileSync(path, JSON.stringify(obj, null, 2));\n } catch {\n // intentional no-op; see doc comment\n }\n}\n","/**\n * Channel MCP process sweep (ENG-4453 / ENG-4448).\n *\n * Defense-in-depth: kill surplus per-agent channel MCP processes that escaped\n * the stdin-close + SIGTERM/SIGINT handlers added in ENG-4434. Slack and\n * Telegram load-balance inbound events across every connected long-poll /\n * Socket Mode waiter, so orphan channel children silently swallow ~85% of\n * traffic when they accumulate.\n *\n * The sweep runs on a throttle (default every 5 min) from the manager poll\n * loop. For each configured agent, it looks for `slack-channel.js`,\n * `direct-chat-channel.js`, `telegram-channel.js` processes whose env includes\n * `AGT_AGENT_CODE_NAME=<code_name>` (all three servers set this, starting\n * ENG-4436). Expected count per agent per channel type is 1.\n *\n * Live-process determination — three signals, in priority order:\n * 1. **PPID chain anchored on the agent's tmux pane** (authoritative). The\n * caller passes `livePidsByAgent`, populated by `tmux list-panes`. Any\n * MCP whose ancestor chain reaches one of those pids is live; everything\n * else for that agent is zombie. This deterministically resolves the\n * \"two non-orphans\" case that older sweep versions punted on.\n * 2. **PPID === 1** (definite orphan). Used as a fallback when the tmux\n * anchor is unavailable (no live session, lookup failed).\n * 3. **Singleton in group**. If exactly one process exists for an agent's\n * channel and it's not visibly orphaned, leave it — might be mid-spawn.\n *\n * Kill selection is pure — see `pickKillTargets` — so it can be unit-tested\n * without spawning processes. The ps-query, tmux query, and actual SIGTERM\n * live behind `sweepChannelProcesses`.\n */\n\nimport { execFileSync } from 'node:child_process';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type ChannelType = 'slack-channel' | 'direct-chat-channel' | 'telegram-channel';\n\n/** Basenames the MCP channel servers publish in their bin field — keep in\n * sync with packages/mcp/package.json. */\nconst CHANNEL_BASENAMES: ChannelType[] = [\n 'slack-channel',\n 'direct-chat-channel',\n 'telegram-channel',\n];\n\nexport interface ChannelProcessInfo {\n pid: number;\n ppid: number;\n channelType: ChannelType;\n codeName: string;\n /** Monotonically increasing elapsed-time seconds. Higher = older. */\n etimeSeconds: number;\n /** Original ps command field, truncated. Kept for log messages. */\n command: string;\n}\n\nexport interface SweepKill {\n pid: number;\n codeName: string;\n channelType: ChannelType;\n etimeSeconds: number;\n /**\n * - `orphan`: PPID === 1 (parent died, reparented to init/launchd).\n * - `not-in-live-chain`: PPID chain doesn't reach any of the agent's\n * known tmux pane pids — i.e. it belongs to a previous Claude Code\n * session that didn't clean up its children.\n */\n reason: 'orphan' | 'not-in-live-chain';\n}\n\nexport interface SweepResult {\n /** Kill targets that were actually signalled (or would be in dry-run). */\n kills: SweepKill[];\n /** How many channel-matching processes the sweep inspected. */\n inspected: number;\n /** Agents the sweep considered (for log correlation). */\n scannedAgents: string[];\n dryRun: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// etime parsing — `[[dd-]hh:]mm:ss`\n// ---------------------------------------------------------------------------\n\n/** Convert a ps `etime` field (macOS + Linux format) to seconds. */\nexport function parseEtime(s: string): number {\n const trimmed = s.trim();\n if (!trimmed) return 0;\n\n let days = 0;\n let rest = trimmed;\n const dashIdx = rest.indexOf('-');\n if (dashIdx >= 0) {\n days = parseInt(rest.slice(0, dashIdx), 10) || 0;\n rest = rest.slice(dashIdx + 1);\n }\n const parts = rest.split(':').map((p) => parseInt(p, 10) || 0);\n // Normalise to [h, m, s] right-aligned (ps omits leading zero fields).\n let h = 0;\n let m = 0;\n let sec = 0;\n if (parts.length === 3) {\n [h, m, sec] = parts as [number, number, number];\n } else if (parts.length === 2) {\n [m, sec] = parts as [number, number];\n } else if (parts.length === 1) {\n [sec] = parts as [number];\n }\n return days * 86_400 + h * 3_600 + m * 60 + sec;\n}\n\n// ---------------------------------------------------------------------------\n// ps output parsing\n// ---------------------------------------------------------------------------\n\n/**\n * Parse `ps eww -o pid=,ppid=,etime=,command=` output. `eww` appends the\n * process's env to the command field (`KEY=val KEY=val ...`), which is how\n * we scope kills to a specific agent via AGT_AGENT_CODE_NAME.\n *\n * Exported for unit tests — the live path calls this from sweepChannelProcesses.\n */\nexport function parsePsOutput(psOutput: string): ChannelProcessInfo[] {\n const results: ChannelProcessInfo[] = [];\n const lines = psOutput.split('\\n');\n\n for (const rawLine of lines) {\n const line = rawLine.trimStart();\n if (!line) continue;\n\n // Columns: PID PPID ETIME COMMAND...\n // etime has no spaces, so splitting on whitespace for the first 3 fields\n // is safe. Everything after field 3 is the command + env.\n const firstSpace = line.search(/\\s+/);\n if (firstSpace < 0) continue;\n const pid = parseInt(line.slice(0, firstSpace), 10);\n if (!Number.isFinite(pid)) continue;\n\n const afterPid = line.slice(firstSpace).trimStart();\n const secondSpace = afterPid.search(/\\s+/);\n if (secondSpace < 0) continue;\n const ppid = parseInt(afterPid.slice(0, secondSpace), 10);\n if (!Number.isFinite(ppid)) continue;\n\n const afterPpid = afterPid.slice(secondSpace).trimStart();\n const thirdSpace = afterPpid.search(/\\s+/);\n if (thirdSpace < 0) continue;\n const etime = afterPpid.slice(0, thirdSpace);\n const command = afterPpid.slice(thirdSpace).trimStart();\n\n // Match the script token at a word boundary — `ps eww` appends the whole\n // process env to the command field, so a plain `.includes()` can\n // false-positive when an env value happens to contain `slack-channel.js`\n // or similar. Path prefix excludes `=` to rule out `KEY=/path/…-channel.js`\n // env-value tokens.\n const channelMatch = command.match(\n new RegExp(`(?:^|\\\\s)(?:[^\\\\s=]*/)?(${CHANNEL_BASENAMES.join('|')})\\\\.js(?:\\\\s|$)`),\n );\n const channelType = channelMatch?.[1] as ChannelType | undefined;\n if (!channelType) continue;\n\n // Extract AGT_AGENT_CODE_NAME from the env-suffix on the command line.\n // Matches on a word boundary so `AGT_AGENT_CODE_NAME_FOO` can't spoof it.\n const match = command.match(/(?:^|\\s)AGT_AGENT_CODE_NAME=([^\\s]+)/);\n if (!match) continue;\n const codeName = match[1]!;\n\n results.push({\n pid,\n ppid,\n channelType,\n codeName,\n etimeSeconds: parseEtime(etime),\n command: command.slice(0, 500),\n });\n }\n\n return results;\n}\n\n/**\n * Parse the same `ps eww` output to build a PID → PPID map covering EVERY\n * process on the host (not just channel children). Used to walk the parent\n * chain of an MCP up to the agent's tmux pane pid.\n */\nexport function parseAllPids(psOutput: string): Map<number, number> {\n const map = new Map<number, number>();\n for (const rawLine of psOutput.split('\\n')) {\n const line = rawLine.trimStart();\n if (!line) continue;\n const firstSpace = line.search(/\\s+/);\n if (firstSpace < 0) continue;\n const pid = parseInt(line.slice(0, firstSpace), 10);\n if (!Number.isFinite(pid)) continue;\n const afterPid = line.slice(firstSpace).trimStart();\n const secondSpace = afterPid.search(/\\s+/);\n if (secondSpace < 0) continue;\n const ppid = parseInt(afterPid.slice(0, secondSpace), 10);\n if (!Number.isFinite(ppid)) continue;\n map.set(pid, ppid);\n }\n return map;\n}\n\n/**\n * Walk the PPID chain from `startPid` upward until we hit pid 0/1 or visit a\n * pid we've already seen (cycle guard). Returns the set of ancestors visited\n * INCLUDING `startPid` itself.\n *\n * Bounded to 64 hops — process trees on real hosts never approach that.\n */\nexport function walkPpidChain(startPid: number, parentMap: Map<number, number>): Set<number> {\n const visited = new Set<number>();\n let cur = startPid;\n for (let i = 0; i < 64; i++) {\n if (cur <= 1 || visited.has(cur)) break;\n visited.add(cur);\n const parent = parentMap.get(cur);\n if (parent === undefined) break;\n cur = parent;\n }\n return visited;\n}\n\n// ---------------------------------------------------------------------------\n// Kill target selection — pure\n// ---------------------------------------------------------------------------\n\nexport interface PickKillTargetsArgs {\n processes: ChannelProcessInfo[];\n agentCodeNames: Set<string>;\n /** PID → PPID map for the entire host, used for chain walks. */\n parentMap: Map<number, number>;\n /** codeName → set of pids identified as live anchors (typically tmux pane\n * pids for the agent's `agt-<codeName>` session). When a key is missing or\n * the set is empty, the sweep falls back to PPID===1 orphan detection only\n * for that agent. */\n livePidsByAgent: Map<string, Set<number>>;\n}\n\nexport interface PickKillTargetsResult {\n kills: SweepKill[];\n /** Reserved for future use. The chain-walk eliminates the previous\n * \"ambiguous\" case entirely — when livePidsByAgent has an entry for the\n * agent, every process is decisively classified. Kept on the result type\n * for backward compatibility with logging code. */\n ambiguousGroups: string[];\n}\n\n/**\n * Given every channel process on the host, group by (codeName, channelType)\n * and identify zombies via PPID-chain walk against the agent's known live\n * anchor pids.\n *\n * Kill rules:\n * 1. **PPID chain reaches the agent's live pid set** → live (keep).\n * 2. **PPID === 1** → orphan (kill).\n * 3. **Live anchor known but chain doesn't reach it** → not-in-live-chain\n * (kill). This catches the previously-ambiguous case where two non-orphan\n * processes both claim to be the agent's MCP — the one parented under the\n * dead-but-not-yet-reaped Claude Code session gets killed.\n * 4. **Live anchor unknown** (no tmux session, lookup failed) → fall back\n * to PPID===1 orphan detection only; never kill non-orphans without\n * positive evidence.\n * 5. **Singleton non-orphan with no live-anchor info** → leave alone (might\n * be mid-spawn).\n *\n * Only considers agents in `agentCodeNames` — a host shared with another\n * manager instance must not cross-kill into that instance's agents.\n */\nexport function pickKillTargets(args: PickKillTargetsArgs): PickKillTargetsResult {\n const { processes, agentCodeNames, parentMap, livePidsByAgent } = args;\n const kills: SweepKill[] = [];\n\n // Seed the parent map with each channel process's known ppid so a sparse\n // input map (or a unit-test fixture that omits these) still walks correctly.\n const fullParentMap = new Map(parentMap);\n for (const proc of processes) {\n if (!fullParentMap.has(proc.pid)) fullParentMap.set(proc.pid, proc.ppid);\n }\n\n const groups = new Map<string, ChannelProcessInfo[]>();\n for (const proc of processes) {\n if (!agentCodeNames.has(proc.codeName)) continue;\n const key = `${proc.codeName}:${proc.channelType}`;\n const bucket = groups.get(key);\n if (bucket) bucket.push(proc);\n else groups.set(key, [proc]);\n }\n\n for (const [, bucket] of groups) {\n if (bucket.length === 0) continue;\n const codeName = bucket[0]!.codeName;\n const liveAnchors = livePidsByAgent.get(codeName);\n const haveAnchors = !!liveAnchors && liveAnchors.size > 0;\n\n for (const proc of bucket) {\n // Definite orphan — always a kill target regardless of anchor info.\n if (proc.ppid === 1) {\n // …unless it's the only process for this group and we have no\n // anchor info: keep it as a last-resort listener (mid-respawn case).\n if (!haveAnchors && bucket.length === 1) continue;\n kills.push({\n pid: proc.pid,\n codeName: proc.codeName,\n channelType: proc.channelType,\n etimeSeconds: proc.etimeSeconds,\n reason: 'orphan',\n });\n continue;\n }\n\n // Non-orphan: classify via PPID-chain walk if we have anchor info.\n if (!haveAnchors) continue;\n\n const chain = walkPpidChain(proc.pid, fullParentMap);\n let isLive = false;\n for (const anchor of liveAnchors!) {\n if (chain.has(anchor)) { isLive = true; break; }\n }\n if (!isLive) {\n kills.push({\n pid: proc.pid,\n codeName: proc.codeName,\n channelType: proc.channelType,\n etimeSeconds: proc.etimeSeconds,\n reason: 'not-in-live-chain',\n });\n }\n }\n }\n\n return { kills, ambiguousGroups: [] };\n}\n\n// ---------------------------------------------------------------------------\n// Live sweep — wraps ps + kill, throttled by the caller\n// ---------------------------------------------------------------------------\n\nexport interface SweepOptions {\n agentCodeNames: Set<string>;\n /** When true, log kill targets but do not send SIGTERM. */\n dryRun: boolean;\n /** Injected for testability; defaults to process-level SIGTERM. */\n killFn?: (pid: number) => void;\n log: (msg: string) => void;\n}\n\nfunction defaultKill(pid: number): void {\n try {\n process.kill(pid, 'SIGTERM');\n } catch {\n // Process may have exited between ps and kill — race is harmless.\n }\n}\n\n/**\n * Resolve the live tmux pane pid for each agent — the authoritative anchor for\n * \"this MCP belongs to the currently-running Claude Code session\". Returns an\n * empty set for agents whose tmux session isn't found (no live anchor → sweep\n * falls back to orphan-only detection for that agent).\n *\n * Each agent's session name is `agt-<codeName>`; the pane pid is the program\n * running directly in that pane (Claude Code), and every MCP child has it\n * somewhere on its parent chain.\n */\nexport function resolveLiveAnchorPids(agentCodeNames: Iterable<string>): Map<string, Set<number>> {\n const result = new Map<string, Set<number>>();\n for (const codeName of agentCodeNames) {\n const pids = new Set<number>();\n try {\n const out = execFileSync('tmux', ['list-panes', '-t', `agt-${codeName}`, '-F', '#{pane_pid}'], {\n encoding: 'utf-8',\n timeout: 2_000,\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n for (const line of out.split('\\n')) {\n const pid = parseInt(line.trim(), 10);\n if (Number.isFinite(pid) && pid > 1) pids.add(pid);\n }\n } catch {\n // tmux session doesn't exist (paused/never started/crashed) — leave\n // pids empty so the sweep falls back to orphan-only detection.\n }\n result.set(codeName, pids);\n }\n return result;\n}\n\n/**\n * Run `ps eww`, classify surplus per-agent channel processes, and (unless\n * dry-run) SIGTERM them.\n *\n * The poll loop calls this; it's responsible for the throttling interval.\n */\nexport async function sweepChannelProcesses(opts: SweepOptions): Promise<SweepResult> {\n const { agentCodeNames, dryRun, log } = opts;\n const kill = opts.killFn ?? defaultKill;\n\n let psOutput = '';\n try {\n // -E on macOS, `e` on Linux — both append the process environment to the\n // command field. macOS accepts `-E`, Linux's procps-ng does not, but both\n // accept `e` as a BSD-style option when passed without the leading dash.\n psOutput = execFileSync('ps', ['eww', '-o', 'pid=,ppid=,etime=,command='], {\n encoding: 'utf-8',\n timeout: 5_000,\n maxBuffer: 10 * 1024 * 1024,\n });\n } catch (err) {\n log(`[channel-sweep] ps failed: ${(err as Error).message}`);\n return { kills: [], inspected: 0, scannedAgents: [...agentCodeNames], dryRun };\n }\n\n const processes = parsePsOutput(psOutput);\n const parentMap = parseAllPids(psOutput);\n const livePidsByAgent = resolveLiveAnchorPids(agentCodeNames);\n const { kills } = pickKillTargets({ processes, agentCodeNames, parentMap, livePidsByAgent });\n\n for (const target of kills) {\n const ageMin = Math.round(target.etimeSeconds / 60);\n log(\n `[channel-sweep]${dryRun ? '[dry-run]' : ''} surplus ${target.channelType} for ${target.codeName}: ` +\n `killing pid=${target.pid} (${target.reason}, age=${ageMin}m)`,\n );\n if (!dryRun) kill(target.pid);\n }\n\n return {\n kills,\n inspected: processes.length,\n scannedAgents: [...agentCodeNames],\n dryRun,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Targeted teardown — kill every channel MCP for one (de-provisioned) agent\n// ---------------------------------------------------------------------------\n\n/**\n * Pure: from a list of channel processes, return every pid whose\n * AGT_AGENT_CODE_NAME matches `codeName`. Exported for unit tests.\n *\n * Used by the de-provision path: when an agent is unassigned/deleted from a\n * host, the periodic `sweepChannelProcesses` will *not* clean up its slack /\n * telegram / direct-chat MCPs because that sweep is scoped to currently-known\n * agent code names. Without targeted teardown, the slack-channel and\n * telegram-channel processes survive (often reparented to init when the tmux\n * session dies), and Telegram's getUpdates load-balances inbound traffic to\n * whichever poller is alive — including the orphan on the old host. That's\n * exactly the conflict that took Scout offline after migrating from one host\n * to another.\n */\nexport function pickTeardownTargets(\n processes: ChannelProcessInfo[],\n codeName: string,\n): ChannelProcessInfo[] {\n return processes.filter((p) => p.codeName === codeName);\n}\n\nexport interface KillAgentChannelProcessesOptions {\n /** Injected for testability; defaults to process.kill SIGTERM/SIGKILL. */\n killFn?: (pid: number, signal: NodeJS.Signals) => void;\n /** Injected for testability; defaults to ps eww. */\n psFn?: () => string;\n log: (msg: string) => void;\n /** When true, log targets but do not signal them. */\n dryRun?: boolean;\n}\n\nfunction defaultKillSignal(pid: number, signal: NodeJS.Signals): void {\n try {\n process.kill(pid, signal);\n } catch {\n // Already exited — race is harmless.\n }\n}\n\nfunction defaultPs(): string {\n return execFileSync('ps', ['eww', '-o', 'pid=,ppid=,etime=,command='], {\n encoding: 'utf-8',\n timeout: 5_000,\n maxBuffer: 10 * 1024 * 1024,\n });\n}\n\n/**\n * SIGTERM every slack/telegram/direct-chat MCP for `codeName`. Best-effort —\n * failures are logged and swallowed so the de-provision path can continue.\n *\n * Returns the pids it signalled (useful for tests + log correlation).\n */\nexport function killAgentChannelProcesses(\n codeName: string,\n opts: KillAgentChannelProcessesOptions,\n): number[] {\n const { log, dryRun = false } = opts;\n const kill = opts.killFn ?? defaultKillSignal;\n const ps = opts.psFn ?? defaultPs;\n\n let psOutput = '';\n try {\n psOutput = ps();\n } catch (err) {\n log(`[channel-teardown] ps failed for '${codeName}': ${(err as Error).message}`);\n return [];\n }\n\n const targets = pickTeardownTargets(parsePsOutput(psOutput), codeName);\n if (targets.length === 0) return [];\n\n const pids = targets.map((t) => t.pid);\n log(\n `[channel-teardown]${dryRun ? '[dry-run]' : ''} de-provision '${codeName}': ` +\n `killing ${targets.length} channel MCP(s) — ` +\n targets.map((t) => `${t.channelType}#${t.pid}`).join(', '),\n );\n\n if (!dryRun) {\n for (const pid of pids) kill(pid, 'SIGTERM');\n }\n return pids;\n}\n","/**\n * ENG-4705: Channel input watchdog.\n *\n * Symptom: an inbound Slack/Telegram/Direct-Chat message lands in the Claude\n * Code TUI input buffer (visible as `❯ <text>` between the input-box rule\n * lines) but the channel server's auto-submit doesn't fire — the text just\n * sits there until something sends Enter manually.\n *\n * The dispatcher pattern landed in ENG-4684 reduces the *frequency* (slow\n * requests get fanned out to a background subagent so the parent's listener\n * turn returns immediately), but doesn't fix the underlying race: when\n * triage decides a request is \"fast\" and the parent handles it inline, the\n * main turn still occupies the TUI for several seconds, and any channel\n * message that arrives during that window stacks in the input buffer\n * un-submitted.\n *\n * Workaround the ticket itself documents: `tmux send-keys -t <session>\n * Enter`. This watchdog automates that — every poll cycle it captures the\n * pane for each managed claude-code agent, looks for un-submitted text in\n * the input box, and fires Enter once the same text has been sitting there\n * unchanged for STUCK_THRESHOLD_MS.\n *\n * Safety:\n * - Skip the agent if a tmux client is attached (a human might be typing).\n * - Require the text to be unchanged for STUCK_THRESHOLD_MS — short windows\n * avoid racing the channel server's own (eventual) submit.\n * - Single-shot per stuck buffer: once we fire Enter for a hash we don't\n * fire again until the input changes.\n * - Skip if Claude is actively rendering a spinner (`✻ <verb>ing…`); a\n * fresh send-keys Enter into a busy TUI is harmless but noisy in logs.\n */\n\nconst STUCK_THRESHOLD_MS = 5_000;\n// ENG-4716: when a tmux client is attached we don't skip the agent\n// outright (the original safety-first carve-out swallowed the steady-\n// state \"monitoring\" case operators care about). Instead we widen the\n// stable-buffer window so a human typing has more than 5s to finish\n// before the watchdog steps in. Active typing changes the buffer hash\n// every keystroke and keeps resetting `firstSeenAt`, so this only\n// matters when the buffer is *truly* stable.\nconst ATTACHED_STUCK_THRESHOLD_MS = 15_000;\nconst INPUT_BOX_DIVIDER = /^[─━]{10,}/;\nconst PROMPT_PREFIX = '❯ ';\n\nexport interface AgentInputState {\n /** Hash of the input-box text observed last poll. */\n lastInputHash: string;\n /** Wall-clock ms when this hash was first seen. */\n firstSeenAt: number;\n /** Have we already sent Enter for this hash? */\n resolved: boolean;\n}\n\nexport interface WatchdogIo {\n /** Snapshot of the agent's tmux pane (multiline). Empty / null if the session doesn't exist. */\n capturePane: (codeName: string) => string | null;\n /** Whether any tmux client is attached to the session right now. */\n isClientAttached: (codeName: string) => boolean;\n /** Send a single Enter keystroke to the agent's tmux session. */\n sendEnter: (codeName: string) => void;\n /** Logger. */\n log: (msg: string) => void;\n /** Current wall-clock ms (injectable for tests). */\n now: () => number;\n}\n\nexport interface WatchdogConfig {\n /** ms a stuck buffer must persist before we fire Enter. Defaults to STUCK_THRESHOLD_MS. */\n stuckThresholdMs?: number;\n /**\n * ms a stuck buffer must persist before we fire Enter when a tmux\n * client is attached to the session. Higher than `stuckThresholdMs`\n * to give a human typer extra headroom. Defaults to\n * ATTACHED_STUCK_THRESHOLD_MS.\n */\n attachedStuckThresholdMs?: number;\n}\n\n/**\n * Single-agent decision step — pure given the pane text + state. Returns\n * the next state and whether to fire Enter.\n *\n * Exported for unit testing.\n */\nexport function decide(\n pane: string,\n prev: AgentInputState | undefined,\n now: number,\n config: WatchdogConfig = {},\n): { fire: boolean; next: AgentInputState | undefined } {\n const threshold = config.stuckThresholdMs ?? STUCK_THRESHOLD_MS;\n\n const inputText = extractInputBoxText(pane);\n if (!inputText) {\n return { fire: false, next: undefined };\n }\n\n if (isActivelyProcessing(pane)) {\n // Don't reset the timer — the input might still be stuck once Claude\n // finishes; we just don't fire while a spinner is live.\n return { fire: false, next: prev };\n }\n\n const hash = simpleHash(inputText);\n if (!prev || prev.lastInputHash !== hash) {\n return {\n fire: false,\n next: { lastInputHash: hash, firstSeenAt: now, resolved: false },\n };\n }\n\n if (prev.resolved) return { fire: false, next: prev };\n if (now - prev.firstSeenAt < threshold) return { fire: false, next: prev };\n\n return {\n fire: true,\n next: { ...prev, resolved: true },\n };\n}\n\n/**\n * Extract the contents of the Claude Code input box from a pane snapshot.\n * Returns null when the input box is empty or absent.\n *\n * The TUI bracketed input area looks like:\n * ────────── agt-bob ──\n * ❯ ping repro 2\n * ──────────────────────\n *\n * We accept any line starting with `❯ ` whose previous non-empty line is a\n * row of `─` characters (the top divider) — that's robust to width changes\n * and to the optional ` agt-<codeName> ` label embedded in the divider.\n *\n * Exported for unit testing.\n */\nexport function extractInputBoxText(pane: string): string | null {\n const lines = pane.split('\\n');\n for (let i = 1; i < lines.length; i++) {\n const line = lines[i] ?? '';\n if (!line.startsWith(PROMPT_PREFIX)) continue;\n // Walk back to the most recent non-empty line; it must be a divider row.\n let j = i - 1;\n while (j >= 0 && (lines[j] ?? '').trim() === '') j--;\n if (j < 0) continue;\n if (!INPUT_BOX_DIVIDER.test((lines[j] ?? '').trim())) continue;\n const text = line.slice(PROMPT_PREFIX.length).trim();\n return text.length > 0 ? text : null;\n }\n return null;\n}\n\n/**\n * True when the pane shows a live spinner (`✻ Cogitating…`,\n * `✻ Crunching…`, etc.). Past tense forms like `✻ Cogitated for 7s` mean\n * the work has finished and we treat the agent as idle.\n *\n * Exported for unit testing.\n */\nexport function isActivelyProcessing(pane: string): boolean {\n // Search bottom-up for the most recent spinner line.\n const lines = pane.split('\\n');\n for (let i = lines.length - 1; i >= 0; i--) {\n const line = (lines[i] ?? '').trim();\n if (!line.startsWith('✻')) continue;\n // Past tense: \"✻ Cogitated for 7s\", \"✻ Crunched for 51s\" — work done.\n if (/\\bfor\\s+\\d+s\\s*$/.test(line)) return false;\n // Present participle: \"✻ Cogitating…\", \"✻ Crunching…\" — still working.\n if (/\\b\\w+ing[…\\.]{0,3}\\s*$/i.test(line)) return true;\n // Ambiguous spinner line — don't block on it.\n return false;\n }\n return false;\n}\n\nfunction simpleHash(s: string): string {\n let h = 0;\n for (let i = 0; i < s.length; i++) {\n h = ((h << 5) - h + s.charCodeAt(i)) | 0;\n }\n return h.toString(16);\n}\n\n/**\n * Run one watchdog pass over the given agents. Stateful: keeps an internal\n * map of per-agent input state across calls.\n */\nexport function checkChannelInputs(\n codeNames: readonly string[],\n io: WatchdogIo,\n config: WatchdogConfig = {},\n states: Map<string, AgentInputState> = sharedStates,\n): void {\n const live = new Set(codeNames);\n for (const codeName of codeNames) {\n try {\n checkOne(codeName, io, config, states);\n } catch (err) {\n io.log(`[channel-input-watchdog] '${codeName}': ${(err as Error).message}`);\n }\n }\n // Drop state for agents that are no longer in scope.\n for (const key of [...states.keys()]) {\n if (!live.has(key)) states.delete(key);\n }\n}\n\nfunction checkOne(\n codeName: string,\n io: WatchdogIo,\n config: WatchdogConfig,\n states: Map<string, AgentInputState>,\n): void {\n const pane = io.capturePane(codeName);\n if (!pane) {\n states.delete(codeName);\n return;\n }\n\n // ENG-4716: don't skip outright when a client is attached — operators\n // routinely keep a tmux client open just to monitor agents, and the\n // original blanket skip ate the steady-state case. Widen the stable-\n // buffer threshold instead. Active typing changes the buffer hash on\n // every keystroke, so the unchanged-hash gate already covers the\n // \"don't fight the human\" concern.\n const attached = io.isClientAttached(codeName);\n const effectiveConfig: WatchdogConfig = attached\n ? {\n ...config,\n stuckThresholdMs:\n config.attachedStuckThresholdMs ?? ATTACHED_STUCK_THRESHOLD_MS,\n }\n : config;\n\n const prev = states.get(codeName);\n const { fire, next } = decide(pane, prev, io.now(), effectiveConfig);\n\n if (next === undefined) {\n states.delete(codeName);\n } else {\n states.set(codeName, next);\n }\n\n if (fire) {\n // Log hash + length only — channel input may contain PII / secrets, so\n // prod logging stays hash-only per the project's logging policy.\n const text = extractInputBoxText(pane) ?? '';\n const hash = next?.lastInputHash ?? simpleHash(text);\n io.log(\n `[channel-input-watchdog] '${codeName}': stuck channel input — firing Enter (input_hash=${hash}, len=${text.length})`,\n );\n io.sendEnter(codeName);\n }\n}\n\nconst sharedStates = new Map<string, AgentInputState>();\n\n/** Test seam — clear the singleton map between tests. */\nexport function _resetSharedStatesForTests(): void {\n sharedStates.clear();\n}\n","/**\n * Periodic discoverability hint for scheduled task deliveries (ENG-4457).\n *\n * Roughly every 10th scheduled delivery gets a follow-up message that\n * reminds the user their schedule is editable conversationally — Slack as\n * a thread reply, DM platforms as a second message. Uses a randomized\n * gate rather than a persisted counter because:\n * - state survives nothing (no DB writes / per-task schedule counters)\n * - sums to the right rate over time\n * - no race between concurrent deliveries\n *\n * Behaviour is purely a function of `(random, probability, env)` so the\n * decision logic is unit-testable without spawning a delivery.\n */\n\nconst DEFAULT_PROBABILITY = 0.1;\n\n/** Normalise an agent code_name to the env-var suffix form. Code names are\n * kebab-case; env vars use UPPER_SNAKE. Non-ident chars become `_`. */\nfunction envSuffixFor(codeName: string): string {\n return codeName.replace(/[^A-Za-z0-9]+/g, '_').toUpperCase();\n}\n\n/**\n * Configurable via env so operators can tune cadence or kill it entirely.\n *\n * Per-agent override takes precedence over host-global. For an agent with\n * code_name `phil-shout`:\n * AGT_DELIVERY_HINT_DISABLED__PHIL_SHOUT=1 → 0 (disabled for this agent)\n * AGT_DELIVERY_HINT_PROBABILITY__PHIL_SHOUT=0 → 0\n * AGT_DELIVERY_HINT_PROBABILITY__PHIL_SHOUT=0.5 → 0.5\n *\n * Fallback chain: per-agent disable → per-agent probability → host disable\n * → host probability → default (0.1). This lets an operator dial down a\n * noisy agent without affecting the rest of the host.\n */\nexport function hintProbability(\n codeName?: string,\n env: NodeJS.ProcessEnv = process.env,\n): number {\n const suffix = codeName ? `__${envSuffixFor(codeName)}` : '';\n\n // Per-agent disable wins outright — if an operator flagged a specific\n // agent off, a global probability override shouldn't re-enable it.\n if (suffix && env[`AGT_DELIVERY_HINT_DISABLED${suffix}`] === '1') return 0;\n\n const perAgent = suffix ? env[`AGT_DELIVERY_HINT_PROBABILITY${suffix}`] : undefined;\n if (perAgent != null) return clampProbability(perAgent);\n\n if (env['AGT_DELIVERY_HINT_DISABLED'] === '1') return 0;\n\n const host = env['AGT_DELIVERY_HINT_PROBABILITY'];\n if (host != null) return clampProbability(host);\n\n return DEFAULT_PROBABILITY;\n}\n\nfunction clampProbability(raw: string): number {\n const parsed = parseFloat(raw);\n if (!Number.isFinite(parsed)) return DEFAULT_PROBABILITY;\n if (parsed < 0) return 0;\n if (parsed > 1) return 1;\n return parsed;\n}\n\n/**\n * Pure decision gate. `rng` defaults to Math.random but is injectable so\n * tests can pin the outcome.\n */\nexport function shouldIncludeHint(\n probability: number,\n rng: () => number = Math.random,\n): boolean {\n if (probability <= 0) return false;\n if (probability >= 1) return true;\n return rng() < probability;\n}\n\n/**\n * The hint text. 10 variants to avoid \"I've seen this one before\" fatigue\n * when a heavy user catches a couple in the same week. Each variant must\n * surface at least two concrete example prompts so the reader has\n * something specific to try.\n */\nexport const HINT_VARIANTS: ReadonlyArray<string> = Object.freeze([\n \"Quick note: you can change this scheduled task just by asking me — e.g. \\\"Change the schedule for this task\\\" or \\\"Make this report less verbose in future\\\".\",\n \"By the way, this is on a schedule — if you'd like to tweak it, just say something like \\\"run this weekly instead of daily\\\" or \\\"make this report less verbose in future\\\" and I'll handle it.\",\n \"Heads up: I deliver this on a schedule you can edit conversationally. Try \\\"Change the schedule for this task\\\" or \\\"Make this report shorter\\\" whenever you want to adjust it.\",\n \"PS — no UI needed to tune this. Just tell me \\\"Send this at 9am instead\\\" or \\\"Skip weekends for this report\\\" and I'll update the schedule.\",\n \"FYI this is a scheduled delivery. You can reshape it in plain English — e.g. \\\"Make this fortnightly\\\" or \\\"Only include items from the last 24 hours\\\".\",\n \"You're the boss of this schedule — ask me things like \\\"Pause this for two weeks\\\" or \\\"Include a summary at the top next time\\\" and I'll apply it.\",\n \"Side note: you can say \\\"Change this to Mondays only\\\" or \\\"Drop the preamble in future reports\\\" and I'll update the task. No config screen required.\",\n \"Reminder: I run this on a schedule you can edit by talking. Good openers: \\\"Change when this fires\\\" or \\\"Make future reports more concise\\\".\",\n \"Small tip — this delivery is editable on the fly. Try \\\"Move this to afternoons\\\" or \\\"Cut the detail down in future runs\\\" and I'll retune it.\",\n \"If this cadence or format isn't quite right, just ask — \\\"Only run this on weekdays\\\" or \\\"Shorter summaries from now on\\\" both work, no form to fill out.\",\n]);\n\nexport function pickHintVariant(rng: () => number = Math.random): string {\n const idx = Math.floor(rng() * HINT_VARIANTS.length) % HINT_VARIANTS.length;\n return HINT_VARIANTS[idx]!;\n}\n","/**\n * Schedule-edit deep-link footer for scheduled-task deliveries (ENG-4462).\n *\n * ENG-4444 added the `/agents/:agentId/schedules/:taskId` route and a\n * `build_schedule_edit_link` MCP tool; ENG-4456 added a prompt-preamble line\n * telling the agent to embed kanban deep links. Neither guarantees the user\n * sees a clickable edit link on *every* scheduled delivery — the agent has\n * to opt in. This module closes that gap delivery-side by appending a\n * schedule-edit link to the body before it hits Slack/Telegram.\n *\n * All functions are pure so behaviour is unit-testable without spawning a\n * delivery. The manager-worker wrapper calls into these from\n * deliverScheduledTaskOutput.\n */\n\n/** Media whose footer format differs. Fallback is a plain URL. */\nexport type FooterMedium = 'slack' | 'telegram' | 'plain';\n\n/** Normalise an agent code_name to the env-var suffix form. Kebab → snake,\n * uppercased. Mirrors the pattern in delivery-hint.ts for consistency. */\nfunction envSuffixFor(codeName: string): string {\n return codeName.replace(/[^A-Za-z0-9]+/g, '_').toUpperCase();\n}\n\n/**\n * Is the schedule-link footer enabled for this delivery?\n *\n * Precedence (matches delivery-hint.ts):\n * per-agent disable → host disable → default (enabled).\n *\n * Per-agent env: `AGT_SCHEDULE_LINK_FOOTER_DISABLED__<CODE_NAME>=1`\n * Host-global env: `AGT_SCHEDULE_LINK_FOOTER_DISABLED=1`\n */\nexport function scheduleLinkFooterEnabled(\n codeName?: string,\n env: NodeJS.ProcessEnv = process.env,\n): boolean {\n if (codeName) {\n const perAgent = env[`AGT_SCHEDULE_LINK_FOOTER_DISABLED__${envSuffixFor(codeName)}`];\n if (perAgent === '1') return false;\n }\n if (env['AGT_SCHEDULE_LINK_FOOTER_DISABLED'] === '1') return false;\n return true;\n}\n\n/**\n * Resolve the webapp console URL from env. Manager-worker runs client-side,\n * so we can't derive from a request Origin — we rely on the operator\n * configuring `AGT_CONSOLE_URL` (the canonical name per the SST setup) or\n * `NEXT_PUBLIC_APP_URL` (local-dev parity with the rest of the stack).\n * Returns null if neither is set; callers should skip footer emission.\n */\nexport function getConsoleUrl(env: NodeJS.ProcessEnv = process.env): string | null {\n // Evaluate each candidate trimmed and fall back past whitespace-only\n // values. Plain `AGT_CONSOLE_URL || NEXT_PUBLIC_APP_URL` would treat a\n // whitespace-only AGT_CONSOLE_URL as truthy and suppress the fallback,\n // returning null despite a valid NEXT_PUBLIC_APP_URL being set.\n const canonical = env['AGT_CONSOLE_URL']?.trim();\n if (canonical) return canonical.replace(/\\/+$/, '');\n\n const fallback = env['NEXT_PUBLIC_APP_URL']?.trim();\n if (fallback) return fallback.replace(/\\/+$/, '');\n\n return null;\n}\n\n/**\n * Build the canonical schedule-edit URL. Mirrors\n * packages/openclaw-plugin-augmented/src/tools/schedule-link-url.ts so the\n * CLI isn't pulling an openclaw-plugin dep it doesn't otherwise use.\n * agentId / taskId are encodeURIComponent'd defensively.\n */\nexport function buildScheduleEditLink(\n consoleUrl: string,\n agentId: string,\n taskId: string,\n): string {\n const base = consoleUrl.replace(/\\/+$/, '');\n return `${base}/agents/${encodeURIComponent(agentId)}/schedules/${encodeURIComponent(taskId)}`;\n}\n\n/**\n * Format the footer line for a given medium.\n * - Slack: `<url|Edit schedule>` — Slack's native link syntax, keeps the\n * URL out of the visible text.\n * - Telegram: `Edit schedule: <url>` — Telegram's text-mode sendMessage\n * auto-linkifies bare URLs, so a plain label + URL reads cleanly.\n * - plain: just the URL, for callers that aren't sure which medium it is.\n */\nexport function formatScheduleLinkFooter(url: string, medium: FooterMedium): string {\n if (medium === 'slack') return `<${url}|Edit schedule>`;\n if (medium === 'telegram') return `Edit schedule: ${url}`;\n return url;\n}\n\n/**\n * Append the schedule-edit footer to a body. Separated from the body by a\n * blank line, and idempotent against the identical footer — retries /\n * re-deliveries won't stack multiple copies.\n */\nexport function appendScheduleLinkFooter(\n body: string,\n url: string,\n medium: FooterMedium,\n): string {\n const footer = formatScheduleLinkFooter(url, medium);\n const trimmed = body.replace(/\\s+$/, '');\n if (trimmed.endsWith(footer)) return trimmed;\n return `${trimmed}\\n\\n${footer}`;\n}\n\n/**\n * ENG-4495: one-time warning guard. We used to silently drop the footer\n * when AGT_CONSOLE_URL was unset, which is how Scout's prod host went\n * months without deep links before anyone noticed. Emit exactly one log\n * line per process so misconfigured hosts leave a breadcrumb in\n * ~/.augmented/manager.log without spamming it on every delivery tick.\n */\nlet warnedNullConsoleUrl = false;\n\n/** Test helper — reset the once-per-process warning latch between tests. */\nexport function resetConsoleUrlWarning(): void {\n warnedNullConsoleUrl = false;\n}\n\n/**\n * Convenience wrapper — the thing the manager-worker will call on each\n * successful delivery. Returns the body unchanged if any precondition fails\n * (disabled, missing console URL, missing taskId, etc.) so callers don't\n * need a pile of guards at every integration site.\n *\n * `log` is optional so tests / tooling can call this without a logger;\n * manager-worker passes its persistent `log()` so the missing-console-URL\n * warning lands in manager.log alongside other delivery diagnostics.\n */\nexport function withScheduleLinkFooter(opts: {\n body: string;\n medium: FooterMedium;\n codeName?: string;\n agentId: string;\n taskId?: string;\n env?: NodeJS.ProcessEnv;\n log?: (msg: string) => void;\n}): string {\n if (!opts.taskId) return opts.body;\n if (!scheduleLinkFooterEnabled(opts.codeName, opts.env)) return opts.body;\n const consoleUrl = getConsoleUrl(opts.env);\n if (!consoleUrl) {\n if (!warnedNullConsoleUrl && opts.log) {\n warnedNullConsoleUrl = true;\n try {\n opts.log(\n '[schedule-link] AGT_CONSOLE_URL unset and NEXT_PUBLIC_APP_URL unset — schedule-edit deep-link footer disabled. Run `agt setup` again or export AGT_CONSOLE_URL (e.g. https://app.augmented.team) to restore it.',\n );\n } catch {\n // Diagnostic log must never break delivery fallback — swallow.\n }\n }\n return opts.body;\n }\n const url = buildScheduleEditLink(consoleUrl, opts.agentId, opts.taskId);\n return appendScheduleLinkFooter(opts.body, url, opts.medium);\n}\n","/**\n * Restart-flag IPC between channel MCP subprocesses (e.g. telegram-channel)\n * and the manager. A channel handler that observes a `/restart` command for\n * its agent writes a flag file here; the manager poll loop scans this dir\n * each cycle, kills the agent's tmux session (which lets the existing\n * \"ensure session\" path respawn it), and posts an ack back to the\n * originating channel.\n *\n * Filename: `<codeName>.flag` so concurrent /restart commands collapse to\n * one (idempotent — repeated taps don't queue multiple restarts).\n *\n * Body: JSON record describing where the request came from so the manager\n * can ack to the right place.\n */\n\nimport { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { randomUUID } from 'node:crypto';\n\nexport interface RestartFlag {\n /** Agent code_name. Matches the filename stem. */\n codeName: string;\n /** Channel source for the ack reply, e.g. \"telegram\". */\n source: string;\n /** Unix epoch ms when the flag was written. */\n ts: number;\n /** Channel-specific routing for the ack reply (chat id, thread id, etc). */\n reply?: Record<string, string | number>;\n}\n\nexport function restartFlagsDir(): string {\n return join(homedir(), '.augmented', 'restart-flags');\n}\n\nfunction flagPath(codeName: string): string {\n return join(restartFlagsDir(), `${codeName}.flag`);\n}\n\nexport function writeRestartFlag(flag: RestartFlag): string {\n const dir = restartFlagsDir();\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const path = flagPath(flag.codeName);\n // Atomic write: write to a sibling temp path then rename. Without this,\n // a manager poll that fires during the writeFileSync window would read a\n // partial flag and fall into the corrupt-file fallback — losing source +\n // reply routing, which means the restart fires but the operator never gets\n // the final ack.\n // Include a per-call randomUUID so concurrent writes (overlapping channel\n // handlers, retries) can't collide on the same tmp path and have one\n // renameSync remove the file the other is about to rename.\n const tmpPath = `${path}.${process.pid}.${randomUUID()}.tmp`;\n writeFileSync(tmpPath, JSON.stringify(flag) + '\\n', 'utf8');\n renameSync(tmpPath, path);\n return path;\n}\n\nexport function readRestartFlags(): RestartFlag[] {\n const dir = restartFlagsDir();\n if (!existsSync(dir)) return [];\n const out: RestartFlag[] = [];\n for (const entry of readdirSync(dir)) {\n if (!entry.endsWith('.flag')) continue;\n try {\n const raw = readFileSync(join(dir, entry), 'utf8');\n const parsed = JSON.parse(raw) as RestartFlag;\n // Defensive: derive codeName from filename if the body is malformed,\n // so a stray flag file always identifies an agent.\n if (typeof parsed.codeName !== 'string' || parsed.codeName.length === 0) {\n parsed.codeName = entry.replace(/\\.flag$/, '');\n }\n out.push(parsed);\n } catch {\n // Corrupt flag — treat as a bare restart request for the agent in\n // the filename. The manager will still kick the session.\n out.push({\n codeName: entry.replace(/\\.flag$/, ''),\n source: 'unknown',\n ts: Date.now(),\n });\n }\n }\n return out;\n}\n\nexport function deleteRestartFlag(codeName: string): void {\n const path = flagPath(codeName);\n if (existsSync(path)) {\n rmSync(path, { force: true });\n }\n}\n","/**\n * ENG-4568: Process restart flags written by channel MCP subprocesses\n * (telegram-channel etc) when an authorised user posts `/restart`. Each\n * cycle, the manager poll loop calls processRestartFlags() to:\n *\n * 1. Read flag files in ~/.augmented/restart-flags/\n * 2. For each flag, verify the agent is one we manage and is on the\n * Claude Code framework (out of scope for v1: openclaw, nemoclaw)\n * 3. Stop the agent's tmux session via stopPersistentSession() — the\n * ensure-session path on the same poll respawns it\n * 4. Post an ack back to the originating channel\n * 5. Delete the flag so we don't re-process it\n *\n * The handler is intentionally framework-aware but channel-agnostic in its\n * core: each `source` gets a small dispatcher for the post-restart ack.\n * Today only Telegram is wired up.\n */\n\nimport { deleteRestartFlag, readRestartFlags, type RestartFlag } from './restart-flags.js';\n\nexport interface ProcessRestartFlagsOpts {\n /** Logger function from manager-worker */\n log: (msg: string) => void;\n /**\n * Returns the framework code for an agent's code_name, or null if the\n * agent is unknown to this manager. Used to gate /restart to claude-code\n * only per the ENG-4568 v1 scope.\n */\n resolveFramework: (codeName: string) => string | null;\n /**\n * Stop helper from persistent-session. Kills the tmux session and clears\n * internal state so the next ensure-session pass respawns. Only invoked\n * for claude-code agents.\n */\n stopSession: (codeName: string) => void;\n /**\n * Returns the Telegram bot token + chat allowlist for an agent, or null\n * if the agent has no telegram channel binding. The handler uses this to\n * post the post-restart confirmation back to the originating chat.\n */\n getTelegramTokens: (codeName: string) => { token: string; allowedChats?: string[] } | null;\n /**\n * Send a Telegram message via the manager's existing `telegramApiCall`\n * helper. Kept as an injected callback so this module doesn't have to\n * reach into manager-worker's https client.\n */\n sendTelegram: (\n botToken: string,\n method: 'sendMessage',\n body: Record<string, unknown>,\n ) => Promise<{ ok: boolean; description?: string }>;\n /**\n * Returns the Slack bot token for an agent, or null if no slack binding\n * exists.\n */\n getSlackToken: (codeName: string) => string | null;\n /**\n * Post a Slack message (chat.postMessage). Returns ok flag and an\n * optional error string mirroring the Slack Web API shape.\n */\n sendSlack: (\n botToken: string,\n body: Record<string, unknown>,\n ) => Promise<{ ok: boolean; error?: string }>;\n}\n\nexport async function processRestartFlags(opts: ProcessRestartFlagsOpts): Promise<void> {\n const flags = readRestartFlags();\n if (flags.length === 0) return;\n\n for (const flag of flags) {\n try {\n await processOne(flag, opts);\n } catch (err) {\n opts.log(`[restart-handler] Failed to process flag for '${flag.codeName}': ${(err as Error).message}`);\n // Surface the failure to the operator instead of leaving them with\n // just the \"⏳ queued\" ack and silence (CodeRabbit feedback): the\n // resolveFramework / stopSession call paths can throw, and silent\n // log-only handling hides the real failure mode.\n try {\n await sendError(flag, opts, `❌ Restart failed for \\`${flag.codeName}\\`: internal error. Check manager logs.`);\n } catch (ackErr) {\n opts.log(`[restart-handler] Error-ack send failed for '${flag.codeName}': ${(ackErr as Error).message}`);\n }\n } finally {\n // Always delete — a stuck flag would otherwise loop the manager into\n // restarting the same agent every cycle. If processing legitimately\n // failed, the user will see the error reply (or absence of an ack)\n // and can re-issue /restart.\n try {\n deleteRestartFlag(flag.codeName);\n } catch (err) {\n opts.log(`[restart-handler] Failed to delete flag for '${flag.codeName}': ${(err as Error).message}`);\n }\n }\n }\n}\n\nasync function processOne(flag: RestartFlag, opts: ProcessRestartFlagsOpts): Promise<void> {\n const framework = opts.resolveFramework(flag.codeName);\n if (framework === null) {\n opts.log(`[restart-handler] Ignoring /restart for unknown agent '${flag.codeName}'`);\n await sendError(flag, opts, `Agent \\`${flag.codeName}\\` is not managed by this host.`);\n return;\n }\n\n if (framework !== 'claude-code') {\n opts.log(`[restart-handler] Ignoring /restart for '${flag.codeName}' — framework is '${framework}', not claude-code`);\n await sendError(flag, opts, `\\`/restart\\` is only supported for Claude Code agents (this agent runs on \\`${framework}\\`).`);\n return;\n }\n\n opts.log(`[restart-handler] Restarting tmux session for '${flag.codeName}' (source: ${flag.source})`);\n opts.stopSession(flag.codeName);\n\n // Downgraded from \"✅ Restarted\" — at this point we've only killed the\n // session. The respawn happens on the same poll's ensure-session pass and\n // persistent-session.ts doesn't expose a \"session healthy\" signal we can\n // wait on without restructuring that module. Saying \"Restarted\" before\n // the new session is up is a false positive (CodeRabbit feedback).\n await sendAck(flag, opts, `🔄 Restart initiated for \\`${flag.codeName}\\` — the replacement session is being created.`);\n}\n\nasync function sendAck(flag: RestartFlag, opts: ProcessRestartFlagsOpts, text: string): Promise<void> {\n if (flag.source === 'telegram') {\n const tokens = opts.getTelegramTokens(flag.codeName);\n if (!tokens) {\n opts.log(`[restart-handler] No telegram tokens cached for '${flag.codeName}' — skipping ack`);\n return;\n }\n const chatId = flag.reply?.['chat_id'];\n if (chatId == null) return;\n const messageId = flag.reply?.['message_id'];\n const body: Record<string, unknown> = { chat_id: chatId, text, parse_mode: 'Markdown' };\n if (messageId != null) body.reply_to_message_id = Number(messageId);\n try {\n const resp = await opts.sendTelegram(tokens.token, 'sendMessage', body);\n if (!resp.ok) {\n opts.log(`[restart-handler] Telegram ack failed for '${flag.codeName}': ${resp.description ?? 'unknown'}`);\n }\n } catch (err) {\n opts.log(`[restart-handler] Telegram ack threw for '${flag.codeName}': ${(err as Error).message}`);\n }\n return;\n }\n\n if (flag.source === 'slack') {\n const token = opts.getSlackToken(flag.codeName);\n if (!token) {\n opts.log(`[restart-handler] No slack token cached for '${flag.codeName}' — skipping ack`);\n return;\n }\n const channel = flag.reply?.['channel'];\n if (channel == null) return;\n const threadTs = flag.reply?.['thread_ts'];\n const body: Record<string, unknown> = { channel, text };\n // Reply in-thread when we have one so the ack stays scoped to the\n // restart command's conversation rather than spamming the channel.\n if (threadTs != null) body.thread_ts = String(threadTs);\n try {\n const resp = await opts.sendSlack(token, body);\n if (!resp.ok) {\n opts.log(`[restart-handler] Slack ack failed for '${flag.codeName}': ${resp.error ?? 'unknown'}`);\n }\n } catch (err) {\n opts.log(`[restart-handler] Slack ack threw for '${flag.codeName}': ${(err as Error).message}`);\n }\n return;\n }\n\n opts.log(`[restart-handler] Unknown ack source '${flag.source}' for '${flag.codeName}' — skipping`);\n}\n\nasync function sendError(flag: RestartFlag, opts: ProcessRestartFlagsOpts, text: string): Promise<void> {\n // Same shape as sendAck — separate function for clarity at the call site.\n await sendAck(flag, opts, text);\n}\n","/**\n * Supabase Realtime subscriptions for the manager.\n *\n * Replaces polling with WebSocket subscriptions:\n * - direct_chat_messages INSERT → instant chat delivery (~100ms vs 2s)\n * - agent_doc_versions INSERT → instant drift detection (~100ms vs 5min)\n * - host_agents INSERT/DELETE → instant agent assignment changes\n *\n * Falls back to polling if the Realtime connection drops.\n */\n\nimport { createClient, type SupabaseClient, type RealtimeChannel } from '@supabase/supabase-js';\nimport { formatActorId, isSelfCompletion, type KanbanCompletionEvent } from '@augmented/core';\n\n// ---------------------------------------------------------------------------\n// Shared client\n// ---------------------------------------------------------------------------\n\nlet client: SupabaseClient | null = null;\nlet chatChannel: RealtimeChannel | null = null;\nlet driftChannel: RealtimeChannel | null = null;\nlet assignChannel: RealtimeChannel | null = null;\nlet configChannel: RealtimeChannel | null = null;\nlet kanbanChannel: RealtimeChannel | null = null;\nlet integrationContextChannel: RealtimeChannel | null = null;\nlet connected = false;\nlet tearingDown = false; // Guard against re-entrant teardown\n\nexport interface RealtimeConfig {\n supabaseUrl: string;\n supabaseAnonKey: string;\n token: string;\n log: (msg: string) => void;\n}\n\nfunction ensureClient(config: RealtimeConfig): SupabaseClient {\n if (client) return client;\n\n client = createClient(config.supabaseUrl, config.supabaseAnonKey, {\n global: {\n headers: { Authorization: `Bearer ${config.token}` },\n },\n realtime: {\n params: { apikey: config.supabaseAnonKey },\n },\n });\n\n client.realtime.setAuth(config.token);\n return client;\n}\n\n// ---------------------------------------------------------------------------\n// Direct chat subscription\n// ---------------------------------------------------------------------------\n\nexport interface ChatMessagePayload {\n id: string;\n agent_id: string;\n session_id: string;\n content: string;\n status: string;\n created_at: string;\n}\n\nexport function startRealtimeChat(\n config: RealtimeConfig & {\n agentIds: string[];\n onMessage: (msg: ChatMessagePayload) => void;\n onError: (err: Error) => void;\n onStatusChange: (status: 'connected' | 'disconnected' | 'error') => void;\n },\n): void {\n const { agentIds, onMessage, onStatusChange, log } = config;\n if (agentIds.length === 0) return;\n\n const sb = ensureClient(config);\n const filterStr = agentIds.length === 1\n ? `agent_id=eq.${agentIds[0]}`\n : `agent_id=in.(${agentIds.join(',')})`;\n\n chatChannel = sb\n .channel('direct-chat-realtime')\n .on('postgres_changes', {\n event: 'INSERT',\n schema: 'public',\n table: 'direct_chat_messages',\n filter: filterStr,\n }, (payload) => {\n const msg = payload.new as ChatMessagePayload;\n if (msg.status !== 'pending') return;\n log(`[realtime] Chat message for agent ${msg.agent_id}: id=${msg.id}`);\n onMessage(msg);\n })\n .subscribe((status) => {\n if (status === 'SUBSCRIBED') {\n connected = true;\n log('[realtime] Chat channel connected');\n onStatusChange('connected');\n } else if (status === 'CLOSED' || status === 'CHANNEL_ERROR' || status === 'TIMED_OUT') {\n if (tearingDown) return; // Prevent re-entrant teardown from recursive close events\n connected = false;\n log(`[realtime] Chat channel: ${status} — will reconnect next cycle`);\n // Don't call stopRealtimeChat here — it triggers more CLOSED events.\n // Just null out references and let the manager reset flags for fresh reconnect.\n chatChannel = null;\n driftChannel = null;\n assignChannel = null;\n configChannel = null;\n kanbanChannel = null;\n integrationContextChannel = null;\n if (client) { try { client.removeAllChannels(); } catch { /* ignore */ } client = null; }\n onStatusChange(status === 'TIMED_OUT' ? 'error' : 'disconnected');\n }\n });\n\n log(`[realtime] Subscribing to direct_chat_messages for ${agentIds.length} agent(s)`);\n}\n\n// ---------------------------------------------------------------------------\n// Drift detection subscription\n// ---------------------------------------------------------------------------\n\nexport interface DriftPayload {\n version_id: string;\n agent_id: string;\n doc_type: string;\n version: string;\n created_at: string;\n}\n\nexport function startRealtimeDrift(\n config: RealtimeConfig & {\n agentIds: string[];\n onDrift: (payload: DriftPayload) => void;\n },\n): void {\n const { agentIds, onDrift, log } = config;\n if (agentIds.length === 0) return;\n\n const sb = ensureClient(config);\n const filterStr = agentIds.length === 1\n ? `agent_id=eq.${agentIds[0]}`\n : `agent_id=in.(${agentIds.join(',')})`;\n\n driftChannel = sb\n .channel('drift-realtime')\n .on('postgres_changes', {\n event: 'INSERT',\n schema: 'public',\n table: 'agent_doc_versions',\n filter: filterStr,\n }, (payload) => {\n const doc = payload.new as DriftPayload;\n log(`[realtime] Doc version change for agent ${doc.agent_id}: ${doc.doc_type} v${doc.version}`);\n onDrift(doc);\n })\n .subscribe((status) => {\n if (status === 'SUBSCRIBED') {\n log('[realtime] Drift channel connected');\n } else if (status === 'CLOSED' || status === 'CHANNEL_ERROR') {\n log(`[realtime] Drift channel: ${status}`);\n }\n });\n\n log(`[realtime] Subscribing to agent_doc_versions for ${agentIds.length} agent(s)`);\n}\n\n// ---------------------------------------------------------------------------\n// Agent assignment subscription\n// ---------------------------------------------------------------------------\n\nexport interface AssignmentPayload {\n host_id: string;\n agent_id: string;\n assigned_by: string;\n}\n\nexport function startRealtimeAssignments(\n config: RealtimeConfig & {\n hostId: string;\n onAssign: (payload: AssignmentPayload) => void;\n onUnassign: (payload: { agent_id: string }) => void;\n },\n): void {\n const { hostId, onAssign, onUnassign, log } = config;\n\n const sb = ensureClient(config);\n\n assignChannel = sb\n .channel('assignment-realtime')\n .on('postgres_changes', {\n event: 'INSERT',\n schema: 'public',\n table: 'host_agents',\n filter: `host_id=eq.${hostId}`,\n }, (payload) => {\n const row = payload.new as AssignmentPayload;\n log(`[realtime] Agent assigned: ${row.agent_id} to host ${row.host_id}`);\n onAssign(row);\n })\n .on('postgres_changes', {\n event: 'DELETE',\n schema: 'public',\n table: 'host_agents',\n }, (payload) => {\n // DELETE events can't be filtered by Supabase Realtime\n // We receive all DELETEs and filter client-side\n const old = payload.old as { host_id?: string; agent_id?: string };\n if (old.host_id === hostId && old.agent_id) {\n log(`[realtime] Agent unassigned: ${old.agent_id} from host ${hostId}`);\n onUnassign({ agent_id: old.agent_id });\n }\n })\n .subscribe((status) => {\n if (status === 'SUBSCRIBED') {\n log('[realtime] Assignment channel connected');\n } else if (status === 'CLOSED' || status === 'CHANNEL_ERROR') {\n log(`[realtime] Assignment channel: ${status}`);\n }\n });\n\n log(`[realtime] Subscribing to host_agents for host ${hostId}`);\n}\n\n// ---------------------------------------------------------------------------\n// Agent config change subscription\n// ---------------------------------------------------------------------------\n\nexport interface AgentConfigPayload {\n agent_id: string;\n code_name: string;\n status: string;\n framework: string;\n session_mode: string;\n primary_model: string | null;\n updated_at: string;\n}\n\nexport function startRealtimeConfig(\n config: RealtimeConfig & {\n agentIds: string[];\n onConfigChange: (payload: AgentConfigPayload) => void;\n },\n): void {\n const { agentIds, onConfigChange, log } = config;\n if (agentIds.length === 0) return;\n\n const sb = ensureClient(config);\n const filterStr = agentIds.length === 1\n ? `agent_id=eq.${agentIds[0]}`\n : `agent_id=in.(${agentIds.join(',')})`;\n\n // Track last known values to filter out timestamp-only updates\n const lastKnown = new Map<string, string>();\n\n configChannel = sb\n .channel('config-realtime')\n .on('postgres_changes', {\n event: 'UPDATE',\n schema: 'public',\n table: 'agents',\n filter: filterStr,\n }, (payload) => {\n const agent = payload.new as AgentConfigPayload;\n\n // Build a fingerprint of meaningful fields (ignore timestamps like updated_at, last_heartbeat_at)\n const fingerprint = `${agent.status}|${agent.framework}|${agent.session_mode}|${agent.primary_model}`;\n const prev = lastKnown.get(agent.agent_id);\n\n if (prev === fingerprint) return; // timestamp-only change, ignore\n lastKnown.set(agent.agent_id, fingerprint);\n\n log(`[realtime] Agent config changed: ${agent.code_name} (status=${agent.status})`);\n onConfigChange(agent);\n })\n .subscribe((status) => {\n if (status === 'SUBSCRIBED') {\n log('[realtime] Config channel connected');\n } else if (status === 'CLOSED' || status === 'CHANNEL_ERROR') {\n log(`[realtime] Config channel: ${status}`);\n }\n });\n\n log(`[realtime] Subscribing to agents table for ${agentIds.length} agent(s)`);\n}\n\n// ---------------------------------------------------------------------------\n// Kanban item subscription — trigger work when items hit 'todo'\n// ---------------------------------------------------------------------------\n\nexport interface KanbanItemPayload {\n id: string;\n agent_id: string;\n title: string;\n status: string;\n priority: number;\n last_actor_id?: string | null;\n completed_at?: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Kanban delivery telemetry (ENG-4514)\n//\n// Tracks DB→manager latency for completion events and how many we suppress\n// (self-actor) or drop (subscriber error). Surfaced via getKanbanMetrics()\n// so an operator-facing status command can render p50/p95 + counters\n// without having to scrape logs.\n// ---------------------------------------------------------------------------\n\ninterface KanbanMetrics {\n delivered: number;\n suppressedSelfActor: number;\n dropped: number;\n /** Last 200 latencies (ms) — bounded ring; old samples roll off. */\n latencies: number[];\n}\n\nconst METRICS_WINDOW = 200;\nconst kanbanMetrics: KanbanMetrics = {\n delivered: 0,\n suppressedSelfActor: 0,\n dropped: 0,\n latencies: [],\n};\n\nfunction recordLatency(ms: number): void {\n kanbanMetrics.latencies.push(ms);\n if (kanbanMetrics.latencies.length > METRICS_WINDOW) {\n kanbanMetrics.latencies.shift();\n }\n}\n\n/**\n * Visible for testing — nearest-rank percentile on a pre-sorted array.\n * Returns 0 when the array is empty so callers don't need a guard.\n */\nexport function percentile(sorted: number[], p: number): number {\n if (sorted.length === 0) return 0;\n const idx = Math.min(sorted.length - 1, Math.floor((sorted.length - 1) * p));\n return sorted[idx] ?? 0;\n}\n\nexport interface KanbanMetricsSnapshot {\n delivered: number;\n suppressedSelfActor: number;\n dropped: number;\n sampleSize: number;\n p50LatencyMs: number;\n p95LatencyMs: number;\n}\n\nexport function getKanbanMetrics(): KanbanMetricsSnapshot {\n const sorted = [...kanbanMetrics.latencies].sort((a, b) => a - b);\n return {\n delivered: kanbanMetrics.delivered,\n suppressedSelfActor: kanbanMetrics.suppressedSelfActor,\n dropped: kanbanMetrics.dropped,\n sampleSize: sorted.length,\n p50LatencyMs: percentile(sorted, 0.5),\n p95LatencyMs: percentile(sorted, 0.95),\n };\n}\n\n/**\n * Visible for testing — reset the in-memory counters between runs.\n */\nexport function resetKanbanMetrics(): void {\n kanbanMetrics.delivered = 0;\n kanbanMetrics.suppressedSelfActor = 0;\n kanbanMetrics.dropped = 0;\n kanbanMetrics.latencies.length = 0;\n}\n\nexport function startRealtimeKanban(\n config: RealtimeConfig & {\n agentIds: string[];\n onTodayItem: (payload: KanbanItemPayload) => void;\n /**\n * ENG-4514: invoked when a kanban item transitions to `done` or\n * `failed` and the actor was NOT the agent itself. Forwarding into\n * the agent runtime (Claude Code MCP surface) lands in ENG-4515.\n */\n onCompletion?: (event: KanbanCompletionEvent) => void;\n },\n): void {\n const { agentIds, onTodayItem, onCompletion, log } = config;\n if (agentIds.length === 0) return;\n\n const sb = ensureClient(config);\n const filterStr = agentIds.length === 1\n ? `agent_id=eq.${agentIds[0]}`\n : `agent_id=in.(${agentIds.join(',')})`;\n\n kanbanChannel = sb\n .channel('kanban-realtime')\n .on('postgres_changes', {\n event: 'INSERT',\n schema: 'public',\n table: 'agent_kanban_items',\n filter: filterStr,\n }, (payload) => {\n const item = payload.new as KanbanItemPayload;\n if (item.status === 'todo') {\n // Don't log raw titles — task content can be sensitive. Identifiers only.\n log(`[realtime] New kanban item in 'todo': item_id=${item.id} agent=${item.agent_id}`);\n onTodayItem(item);\n }\n })\n .on('postgres_changes', {\n event: 'UPDATE',\n schema: 'public',\n table: 'agent_kanban_items',\n filter: filterStr,\n }, (payload) => {\n const item = payload.new as KanbanItemPayload;\n const old = payload.old as KanbanItemPayload;\n // Only trigger when status changes TO 'todo' (not from todo to something else)\n if (item.status === 'todo' && old.status !== 'todo') {\n log(`[realtime] Kanban item moved to 'todo': item_id=${item.id} agent=${item.agent_id}`);\n onTodayItem(item);\n }\n // ENG-4514: completion events. Fire only on the *transition* into\n // done/failed so we don't double-emit when a downstream UPDATE (e.g. an\n // embedding write) touches an already-completed row.\n if (\n onCompletion &&\n (item.status === 'done' || item.status === 'failed') &&\n old.status !== item.status\n ) {\n const event: KanbanCompletionEvent = {\n agent_id: item.agent_id,\n item_id: item.id,\n status: item.status,\n last_actor_id: item.last_actor_id ?? null,\n completed_at: item.completed_at ?? null,\n title: item.title,\n };\n if (isSelfCompletion(event)) {\n kanbanMetrics.suppressedSelfActor++;\n // Don't log per-event for the suppressed path — the agent's own\n // kanban_done log already covered it. Counter is enough.\n return;\n }\n // Latency is best-effort: postgres_changes payloads carry\n // `commit_timestamp`; subtract from now() for an approximate\n // DB→manager delivery time. Falls back to 0 if missing.\n const commitTs = (payload as unknown as { commit_timestamp?: string }).commit_timestamp;\n const latencyMs = commitTs ? Math.max(0, Date.now() - new Date(commitTs).getTime()) : 0;\n try {\n // Only count as delivered when the handler returns successfully —\n // otherwise the same event would land in both `delivered` and\n // `dropped`, overstating success.\n onCompletion(event);\n recordLatency(latencyMs);\n kanbanMetrics.delivered++;\n log(\n `[realtime] Kanban completion: agent=${item.agent_id} item=${item.id} ` +\n `status=${item.status} actor=${item.last_actor_id ?? 'unknown'} ` +\n `latency_ms=${latencyMs}`,\n );\n } catch (err) {\n // The handler threw — count as a drop and keep the subscription\n // healthy. Without this guard a runtime adapter bug would knock\n // the manager off the channel for every agent.\n kanbanMetrics.dropped++;\n log(`[realtime] Kanban completion handler error: ${(err as Error).message}`);\n }\n }\n })\n .subscribe((status) => {\n if (status === 'SUBSCRIBED') {\n log('[realtime] Kanban channel connected');\n } else if (status === 'CLOSED' || status === 'CHANNEL_ERROR') {\n log(`[realtime] Kanban channel: ${status}`);\n }\n });\n\n log(`[realtime] Subscribing to agent_kanban_items for ${agentIds.length} agent(s)`);\n // Suppress unused-import warnings — formatActorId is exported from core\n // for symmetry with isSelfCompletion and used by tests.\n void formatActorId;\n}\n\n// ---------------------------------------------------------------------------\n// Integration context subscription (ENG-4342)\n//\n// Subscribes to INSERT/UPDATE on `plugin_context` filtered by the manager's\n// agent IDs. Fires `onContextChange(agentId)` whenever a row touches an\n// agent we manage. The handler is responsible for triggering an early poll\n// or re-rendering — this module just delivers the event.\n// ---------------------------------------------------------------------------\n\nexport interface IntegrationContextChangePayload {\n agent_id: string;\n plugin_id: string;\n}\n\nexport function startRealtimeIntegrationContext(\n config: RealtimeConfig & {\n agentIds: string[];\n onContextChange: (payload: IntegrationContextChangePayload) => void;\n },\n): void {\n const { agentIds, onContextChange, log } = config;\n if (agentIds.length === 0) return;\n\n const sb = ensureClient(config);\n const filterStr = agentIds.length === 1\n ? `agent_id=eq.${agentIds[0]}`\n : `agent_id=in.(${agentIds.join(',')})`;\n\n integrationContextChannel = sb\n .channel('plugin-context-realtime')\n .on('postgres_changes', {\n event: 'INSERT',\n schema: 'public',\n table: 'plugin_context',\n filter: filterStr,\n }, (payload) => {\n const row = payload.new as IntegrationContextChangePayload;\n log(`[realtime] plugin_context INSERT for agent ${row.agent_id} (plugin ${row.plugin_id})`);\n onContextChange(row);\n })\n .on('postgres_changes', {\n event: 'UPDATE',\n schema: 'public',\n table: 'plugin_context',\n filter: filterStr,\n }, (payload) => {\n const row = payload.new as IntegrationContextChangePayload;\n log(`[realtime] plugin_context UPDATE for agent ${row.agent_id} (plugin ${row.plugin_id})`);\n onContextChange(row);\n })\n .subscribe((status) => {\n if (status === 'SUBSCRIBED') {\n log('[realtime] Integration context channel connected');\n } else if (status === 'CLOSED' || status === 'CHANNEL_ERROR') {\n log(`[realtime] Integration context channel: ${status}`);\n }\n });\n\n log(`[realtime] Subscribing to plugin_context for ${agentIds.length} agent(s)`);\n}\n\n/**\n * Tear down only the integration-context channel — used when the active\n * agent set changes and we need to re-subscribe with an updated filter\n * (ENG-4725). Leaves the rest of the realtime client + other channels\n * untouched.\n */\nexport function stopRealtimeIntegrationContext(): void {\n if (!integrationContextChannel) return;\n try { integrationContextChannel.unsubscribe(); } catch {}\n integrationContextChannel = null;\n}\n\n// ---------------------------------------------------------------------------\n// Shared utilities\n// ---------------------------------------------------------------------------\n\nexport function updateRealtimeToken(token: string): void {\n if (client) {\n client.realtime.setAuth(token);\n }\n}\n\nexport function isRealtimeConnected(): boolean {\n return connected;\n}\n\nexport function stopRealtimeChat(): void {\n if (tearingDown) return;\n tearingDown = true;\n try {\n if (chatChannel) { try { chatChannel.unsubscribe(); } catch {} chatChannel = null; }\n if (driftChannel) { try { driftChannel.unsubscribe(); } catch {} driftChannel = null; }\n if (assignChannel) { try { assignChannel.unsubscribe(); } catch {} assignChannel = null; }\n if (configChannel) { try { configChannel.unsubscribe(); } catch {} configChannel = null; }\n if (kanbanChannel) { try { kanbanChannel.unsubscribe(); } catch {} kanbanChannel = null; }\n if (integrationContextChannel) { try { integrationContextChannel.unsubscribe(); } catch {} integrationContextChannel = null; }\n if (client) {\n try { client.removeAllChannels(); } catch {}\n client = null;\n }\n connected = false;\n } finally {\n tearingDown = false;\n }\n}\n"],"mappings":"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","names":["createHash","readFileSync","writeFileSync","mkdirSync","existsSync","rmSync","readdirSync","join","homedir","log","client","join","execFileSync","log","config","envSuffixFor","existsSync","readFileSync","writeFileSync","homedir","join","existsSync","readFileSync","join","config","log","config","log","join","createHash","readFileSync","saveChannelHashCache","execFileSync","existsSync","join","homedir","rmSync","readFileSync","mkdirSync","writeFileSync","loadChannelHashCache","saveChannelHashCache","createHash","log","readdirSync","platform","state","trackedFiles","primaryModel","sessionMode","frameworkId","agentFw","getProjectDir"]}
|