@integrity-labs/agt-cli 0.25.1 → 0.25.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +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/mcp-presence-reaper.ts","../../src/lib/channel-restart-decision.ts","../../src/lib/restart-breaker.ts","../../src/lib/model-change-respawn.ts","../../src/lib/usage-banner-monitor.ts","../../src/lib/token-usage-monitor.ts","../../src/lib/activity-cache-monitor.ts","../../src/lib/poll-backoff.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/persistent-session-stuck-tracker.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 KANBAN_CHECK_COMMAND,\n formatRunMarker,\n classifyActor,\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, decideMcpRestartOnDrift, managedMcpStructureHash } from './mcp-config-drift.js';\nimport { computeIntegrationsHash } from './integration-hash.js';\nimport {\n diffEnvIntegrations,\n findMcpServersUsingVars,\n reapStaleMcpChildren,\n} from './stale-mcp-reaper.js';\n// ENG-5053: presence check for declared MCP children — restarts the\n// session when a declared mcpServers entry has no live child under the\n// agent's claude (e.g. xero-mcp-server crashed and Claude Code's\n// transport didn't respawn it). See lib/mcp-presence-reaper.ts.\nimport {\n reapMissingMcpSessions,\n clearPresenceReaperState,\n clearPresenceReaperStateForKeys,\n} from './mcp-presence-reaper.js';\nimport { decideChannelRestart, launchableChannelIds } from './channel-restart-decision.js';\nimport { RestartBreaker, reaperRestartBreakerReason, type RestartReason, type TripState } from './restart-breaker.js';\nimport { shouldRespawnForModelChange } from './model-change-respawn.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, provisionOrientHook, estimateActiveTasksTokens } from '@augmented/core/provisioning/frameworks/claudecode/index.js';\nimport '@augmented/core/provisioning/frameworks/managed-agents/index.js';\n\nimport { api, getHostId, exchangeApiKey, ApiError } from './api-client.js';\nimport { maybeReportUsageBanner } from './usage-banner-monitor.js';\nimport { maybeReportTokenUsage } from './token-usage-monitor.js';\nimport { maybeReportActivityCache } from './activity-cache-monitor.js';\nimport { classifyPollError, currentPollBackoffMs } from './poll-backoff.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\nexport interface 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 // ENG-5441: serialised circuit-breaker trips keyed by code_name. Only\n // tripped breakers are persisted — the in-window event log is per-\n // worker and rebuilds from scratch on manager restart. A previously-\n // tripped agent stays tripped across a manager restart so an operator\n // can rely on the trip surviving a `systemctl restart agt-manager`.\n circuitBreakerTrips?: Record<string, TripState>;\n}\n\n/**\n * ENG-5008: stamp `lastRestartProcessedAt` on the fresh AgentState rows\n * built this cycle, carrying forward any prior ack from `state.agents`\n * when no new restart signal arrived this poll.\n *\n * Pre-ENG-5008 only `restartAcks` (fresh signals) populated the field, so\n * every cycle where a previously-acked agent had no new signal dropped\n * the ack — and the very next cycle saw `prev?.lastRestartProcessedAt =\n * null`, the timestamp comparison failed, and the manager re-fired the\n * kill. That's the loop that pinned vigil/vera/stirling for hours on\n * 2026-05-12.\n *\n * Exported for unit testing — caller wires it into the poll loop after\n * `processAgent` has filled `agentStates`.\n */\nexport function applyRestartAcks(args: {\n agentStates: AgentState[];\n priorAgents: AgentState[];\n restartAcks: Map<string, string>;\n}): boolean {\n const { agentStates, priorAgents, restartAcks } = args;\n if (agentStates.length === 0) return false;\n\n const priorAcks = new Map<string, string>();\n for (const prev of priorAgents) {\n if (prev.lastRestartProcessedAt) {\n priorAcks.set(prev.agentId, prev.lastRestartProcessedAt);\n }\n }\n // CodeRabbit (PR #953): return whether anything actually changed in\n // this cycle's agentStates rows so the caller can force a crash-safe\n // immediate write on EITHER a fresh ack OR a carry-forward repair.\n // Pre-fix, the immediate write was keyed off `restartAcks.size > 0`,\n // which missed the case where a prior write had failed silently and\n // this cycle is restoring the ack only via carry-forward.\n let changed = false;\n for (const ack of agentStates) {\n const freshAck = restartAcks.get(ack.agentId);\n const carryForward = priorAcks.get(ack.agentId);\n // Fresh ack from this cycle wins (caller only adds to restartAcks\n // when the requested timestamp is strictly newer than any prior).\n // When no fresh ack arrived, preserve the prior ack so the\n // already-acked restart doesn't re-fire next poll.\n if (freshAck) {\n if (ack.lastRestartProcessedAt !== freshAck) changed = true;\n ack.lastRestartProcessedAt = freshAck;\n } else if (carryForward) {\n if (ack.lastRestartProcessedAt !== carryForward) changed = true;\n ack.lastRestartProcessedAt = carryForward;\n }\n }\n return changed;\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\n/**\n * ENG-4970 / ENG-4974: Slack mirror of CharterPeerWithGate. The runtime\n * Slack classifier (ENG-4936) expects entries keyed on `bot_user_id`\n * (Slack `U…` identity), so the manager extractor produces this shape.\n */\nexport interface CharterSlackPeerWithGate {\n code_name: string;\n bot_user_id: string;\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 * ENG-4970 / ENG-4973: Slack `U…` user_ids of same-team agents (from\n * /host/refresh team_peer_slack_user_ids). Parallel to teamPeerBotIds.\n */\n teamPeerSlackUserIds?: ReadonlyArray<string>;\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. ENG-4973 adds the\n * parallel `granted_agent_slack_user_id` so the Slack extractor can\n * match the same way.\n */\n crossTeamGrants?: ReadonlyArray<{\n grant_id: string;\n granted_agent_bot_id: number | null;\n /** ENG-4970 / ENG-4973: Slack `U…` user_id of granted_agent_id. */\n granted_agent_slack_user_id?: string | 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\n/**\n * ENG-4970 / ENG-4974: Slack parallel of `extractCharterTelegramPeers`.\n * Reads `multi_agent.slack_peers[]` from a CHARTER's frontmatter and\n * resolves a gate_path per peer using the same four-state model\n * (same_team / intra_org_unrestricted / grant:<id> / null).\n *\n * Slack-specific differences from the Telegram extractor:\n * - Keyed on `bot_user_id` (string `U…`) instead of `bot_id` (integer)\n * - Same-team detection uses `teamPeerSlackUserIds` from /host/refresh\n * - Grant match compares `granted_agent_slack_user_id` (ENG-4973)\n *\n * Backwards-compat: when `gateContext` is omitted, every peer is admitted\n * as `same_team` — matches the Telegram extractor's pre-ENG-4935\n * behaviour for managers that haven't picked up the new refresh fields.\n */\nexport function extractCharterSlackPeers(\n rawContent: string,\n gateContext?: CharterPeerGateContext,\n): CharterSlackPeerWithGate[] {\n if (!rawContent || rawContent.length === 0) return [];\n try {\n const parsed = extractFrontmatter(rawContent);\n const frontmatter = parsed.frontmatter as\n | {\n multi_agent?: {\n slack_peers?: Array<{\n code_name?: unknown;\n bot_user_id?: unknown;\n cross_team_grant_id?: unknown;\n }>;\n };\n }\n | null;\n const peers = frontmatter?.multi_agent?.slack_peers;\n if (!peers || !Array.isArray(peers)) return [];\n const teamUserIds = new Set(gateContext?.teamPeerSlackUserIds ?? []);\n const grants = gateContext?.crossTeamGrants ?? [];\n const intraOrg = gateContext?.crossTeamPeerIntraOrg ?? 'unrestricted';\n const now = (gateContext?.now ?? (() => new Date()))();\n const out: CharterSlackPeerWithGate[] = [];\n for (const p of peers) {\n if (\n !p ||\n typeof p !== 'object' ||\n typeof p.code_name !== 'string' ||\n typeof p.bot_user_id !== 'string' ||\n p.bot_user_id.length === 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 gate = 'same_team';\n } else if (teamUserIds.has(p.bot_user_id)) {\n // Same-team wins over a stale CHARTER grant_id — same precedence\n // rule as the Telegram extractor (CodeRabbit on PR #868).\n gate = 'same_team';\n } else if (grantId) {\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_slack_user_id ?? null) === p.bot_user_id\n ) {\n gate = `grant:${grantId}` as const;\n } else {\n gate = null;\n }\n } else if (intraOrg === 'unrestricted') {\n gate = 'intra_org_unrestricted';\n } else {\n gate = null;\n }\n out.push({\n code_name: p.code_name,\n bot_user_id: p.bot_user_id,\n agent_id: '',\n gate_path: gate,\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/**\n * ENG-5090: gate the revoked-agent cleanup block in processAgent so it\n * only runs on the first tick after the status flips to `revoked`.\n * Extracted as an exported pure function so it has a regression-pin\n * test without dragging in the full processAgent surface (matches the\n * extract-and-test pattern in extractCharterTelegramPeers etc.).\n *\n * @param previousKnownStatus the value of `knownStatuses.get(agent.agent_id)`\n * captured at the top of the current processAgent call — `undefined`\n * means the manager has never seen this agent before this tick.\n * @returns true when the caller should skip the cleanup side effects\n * (gateway stop, tmux kill, fs cleanup, cache purge) because they\n * already ran on a prior tick.\n */\nexport function shouldSkipRevokedCleanup(previousKnownStatus: string | undefined): boolean {\n return previousKnownStatus === 'revoked';\n}\n\n/**\n * ENG-5090: residual check that runs after `shouldSkipRevokedCleanup`\n * says \"yes, we already cleaned this up on a prior tick\". A couple of\n * the cleanup ops in processAgent's revoked branch — `stopGatewayIfRunning`\n * (manager-worker.ts:2055) and `cleanupAgentFiles` (manager-worker.ts:8272)\n * — wrap their inner work in try/catch and log on failure rather than\n * rethrowing. That means a failure on the first revoked pass can still\n * reach `knownStatuses.set('revoked')` and trip the short-circuit on\n * subsequent ticks while real residuals (running gateway, allocated\n * port, surviving provision dir) remain.\n *\n * Treat the obvious residuals as a signal to fall back into the full\n * cleanup path. The check is cheap: an `isGatewayRunning` adapter call\n * (no-op `false` for adapters without a gateway, e.g. ClaudeCode), an\n * in-memory JSON read for the port file, and a single `existsSync` for\n * the provision dir.\n *\n * `gatewayRunning` is essential because the revoked branch calls\n * `freePort` and `cleanupAgentFiles` *after* `stopGatewayIfRunning`,\n * and those steps succeed even when the stop failed silently. Without\n * the live-gateway check the port and dir residuals are both wiped to\n * false on the next tick and the short-circuit would leave a zombie\n * gateway running forever — the exact bug CodeRabbit on PR #1054 caught.\n */\nexport function hasRevokedResiduals(state: {\n gatewayRunning: boolean;\n portAllocated: boolean;\n provisionDirExists: boolean;\n}): boolean {\n return state.gatewayRunning || state.portAllocated || state.provisionDirExists;\n}\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\n// ENG-5441: per-manager circuit breaker for runaway session restart loops\n// (today's Scout incident: day-rollover repeated every ~55s for hours).\n// Trip state is mirrored into manager-state.json — see saveBreakerState /\n// hydrateBreakerFromState below — so a manager restart can't accidentally\n// clear a tripped breaker and revive the loop the operator stopped.\nconst restartBreaker = new RestartBreaker();\n// Tracks which trips we've already told the API about, so the same trip\n// doesn't POST /host/circuit-breaker/trip on every poll.\nconst reportedTrips = new Map<string, number>();\n\nfunction recordRestartForBreaker(codeName: string, reason: RestartReason): void {\n const result = restartBreaker.record(codeName, reason);\n if (!result.tripped || !result.trip) return;\n const trip = result.trip;\n log(\n `[persistent-session-decision] agent=${codeName} decision=circuit-tripped ` +\n `detail=${JSON.stringify(trip.statusMessage)} (ENG-5441)`,\n );\n // Fire-and-forget — a missed API call leaves the trip in `reportedTrips`\n // unset, so `maybeReportCurrentTrip` will retry on the next poll.\n // The breaker keeps gating spawns locally regardless.\n maybeReportCurrentTrip(codeName);\n}\n\nasync function reportCircuitTripped(codeName: string, trip: TripState): Promise<void> {\n // Skip the API call if we've already reported this exact trip (same\n // trippedAt timestamp). Without this, every subsequent poll would\n // re-POST until the operator clears, spamming the audit log.\n if (reportedTrips.get(codeName) === trip.trippedAt) return;\n // Look up agent_id from the local cache (populated by processAgent on\n // every poll). Cold-start manager that hydrated a trip but hasn't\n // observed the agent in /host/agents yet will hit this — leave\n // `reportedTrips` untouched so `maybeReportCurrentTrip` retries once\n // the agent_id lands. CodeRabbit on PR #1302.\n const agentId = codeNameToAgentId.get(codeName);\n if (!agentId) throw new Error(`no agent_id known yet for '${codeName}' — will retry next poll`);\n // CodeRabbit on PR #1302: do NOT mark `reportedTrips` until the POST\n // succeeds. Pre-fix, a transient API failure (5xx, JWT expiry race)\n // would stamp the timestamp anyway and silently swallow the trip\n // forever; the breaker would still gate spawns locally but the\n // dashboard / status_message would stay stale.\n await api.post('/host/circuit-breaker/trip', {\n agent_id: agentId,\n status_message: trip.statusMessage,\n tripped_at: new Date(trip.trippedAt).toISOString(),\n events: trip.eventsAtTrip.map((e) => ({ reason: e.reason, at: new Date(e.at).toISOString() })),\n });\n reportedTrips.set(codeName, trip.trippedAt);\n}\n\n// Idempotent helper — call after `codeNameToAgentId` is populated for an\n// agent to retry a pending trip report. Used both on the first trip and\n// on the first poll after a manager restart (where the trip was\n// hydrated from manager-state.json before any /host/agents call).\nfunction maybeReportCurrentTrip(codeName: string): void {\n const trip = restartBreaker.getTrip(codeName);\n if (!trip) return;\n if (reportedTrips.get(codeName) === trip.trippedAt) return;\n reportCircuitTripped(codeName, trip).catch((err) => {\n log(`[circuit-breaker] Failed to report trip for '${codeName}' to API: ${(err as Error).message}`);\n });\n}\n\n\nfunction scheduleSessionRestart(\n codeName: string,\n delayMs: number,\n reason: string,\n // ENG-5441: typed restart reason for the circuit breaker. Defaults to\n // 'hot-reload-mcp' since the only callers today are .mcp.json drift and\n // channel-set change (both hot-reload class), but the parameter is\n // explicit so a future caller can't silently bypass the breaker by\n // omitting it.\n breakerReason: RestartReason = 'hot-reload-mcp',\n): 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 // ENG-5441: record into the breaker AFTER the actual stop fires so\n // coalesced restarts (two triggers in the same tick collapsed into\n // one timer) only count as one restart event — matching what the\n // operator sees in manager.log.\n recordRestartForBreaker(codeName, breakerReason);\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\n// ENG-5285: parallel map of the SET of declared mcpServers keys for the\n// running session. Used by the drift handler to compute a precise diff\n// (added/removed keys) so we only reset presence-reaper give-up state\n// for keys whose membership actually changed — not for keys whose value\n// rotated (e.g. composio refreshing a signed proxy URL every ~20 min,\n// which was the root cause of Stirling flapping 450× over six days\n// post-ENG-5279).\nconst runningMcpServerKeys = new Map<string, Set<string>>();\n\nfunction projectMcpHash(_codeName: string, projectDir: string): string | null {\n try {\n // ENG-5071: hash the canonical (key-sorted) JSON, not the raw bytes.\n // Pre-fix this hashed `readFileSync(...)` directly, so two writers\n // producing logically-equivalent files with different key insertion\n // order would trip drift detection every poll — Scout flapped for\n // hours because writeMcpServer emitted `{ url, headers, command,\n // args }` in one order and sanitizeMcpJson rewrote the same logical\n // entry in another. Canonicalising means only real structural\n // changes (new server, removed server, changed values) move the\n // hash; pure shape/order churn no longer fires a restart. Belt-and-\n // braces alongside the sanitize-headers fix in the same PR — the\n // sanitizer was producing semantically-different content for\n // Granola, but byte-level hashing would still bite the next time\n // any writer pair disagreed on key order.\n const raw = readFileSync(join(projectDir, '.mcp.json'), 'utf-8');\n return createHash('sha256').update(canonicalJson(JSON.parse(raw))).digest('hex');\n } catch {\n return null;\n }\n}\n\n/**\n * ENG-5285: read the project `.mcp.json` and return the set of declared\n * `mcpServers` keys. Returns null when the file is missing or unparseable\n * (caller treats that as \"no information\" — falls back to the wholesale\n * give-up-state clear).\n */\nfunction projectMcpKeys(_codeName: string, projectDir: string): Set<string> | null {\n try {\n const raw = readFileSync(join(projectDir, '.mcp.json'), 'utf-8');\n const parsed = JSON.parse(raw);\n const servers = (parsed as { mcpServers?: Record<string, unknown> }).mcpServers;\n if (!servers || typeof servers !== 'object') return new Set();\n return new Set(Object.keys(servers));\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(\n codeName: string,\n // ENG-5441: every call site that represents a *restart loop trigger*\n // (day-rollover, auth-tuple rotation, reaper-driven restart, hot-reload,\n // channel-set change) passes its reason so the breaker can count it.\n // De-provisioning paths (agent paused / revoked / removed from host /\n // operator-initiated dashboard restart) pass undefined — those are\n // intentional and shouldn't accumulate against the breaker.\n breakerReason?: RestartReason,\n): void {\n cancelPendingSessionRestart(codeName);\n stopPersistentSession(codeName, log);\n runningMcpHashes.delete(codeName);\n runningMcpServerKeys.delete(codeName);\n // ENG-5565: a session teardown (pause / revoke / remove / restart trigger)\n // interrupts any in-flight injected kanban run — close it so it doesn't hang\n // open. 'cancelled' since the work was interrupted, not completed.\n closeInjectedRunIfOpen(codeName, 'cancelled', `session stopped (${breakerReason ?? 'deprovision'})`);\n // ENG-5599: likewise close any in-flight scheduled-task runs routed in-session\n // so an interrupted firing doesn't hang open and mis-attribute later turns.\n closeScheduledRunsForCode(codeName, 'cancelled', `session stopped (${breakerReason ?? 'deprovision'})`);\n if (breakerReason) {\n recordRestartForBreaker(codeName, breakerReason);\n }\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 // ENG-5285: also capture the declared mcpServers key set so the\n // next drift event can diff added/removed keys without re-reading\n // the previous file (which is already overwritten by then).\n //\n // ENG-5285 follow-up (CodeRabbit on PR #1178): preserve the\n // \"unknown\" signal from projectMcpKeys — a transient read/parse\n // race should NOT poison the baseline with an empty set, which\n // would make the next drift see every current key as \"newly\n // added\" and reset the cap on every MCP. Treat null as \"no\n // baseline\" — the drift handler falls back to the wholesale\n // clear, which is the safe choice when we don't know what the\n // previous declared set was.\n const baselineKeys = projectMcpKeys(codeName, projectDir);\n if (baselineKeys) {\n runningMcpServerKeys.set(codeName, baselineKeys);\n } else {\n runningMcpServerKeys.delete(codeName);\n }\n return;\n }\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)}) (ENG-4897)`,\n );\n // ENG-5285: clear presence-reaper give-up state ONLY for keys\n // whose membership in the declared set actually changed — i.e.\n // keys that were added or removed. Keys present in both before\n // and after (typical for value-only drift, e.g. composio rotating\n // a signed proxy URL every ~20 min) keep their cap accumulated.\n // Pre-fix this wholesale-cleared the agent's reaper state on\n // every URL rotation, which was why Stirling flapped 450× over\n // six days despite ENG-5279's restart cap — the cap was reset\n // before it could trip.\n // ENG-5285 follow-up (CodeRabbit on PR #1178): preserve the\n // \"unknown\" signal from projectMcpKeys. Coercing either side to\n // an empty set would either poison the comparison (every current\n // key looks \"newly added\") or hide the drift (both empty looks\n // like \"no membership change\"). When either side is unknown,\n // fall back to the wholesale clear — the safe behaviour we had\n // pre-ENG-5285, used here as the fallback rather than the\n // default.\n const previousKeys = runningMcpServerKeys.get(codeName);\n const currentKeys = projectMcpKeys(codeName, projectDir);\n // ENG-5537: only a *membership* change (server added/removed) warrants\n // restarting the running session. A value-only drift — the same set of\n // servers with a rotated url/header value, e.g. composio re-signing\n // `mcp_url` every poll — leaves the running session's established MCP\n // connections intact, and a future respawn reads the freshly-written\n // file anyway. Pre-fix this scheduled a restart on EVERY drift, so a\n // flapping signed url tripped the ENG-5441 breaker and auto-paused the\n // agent within minutes. When key membership is unknown we keep the\n // pre-ENG-5537 safe default (treat as a restart-worthy change).\n const decision = decideMcpRestartOnDrift(previousKeys, currentKeys);\n if (decision.membershipUnknown) {\n clearPresenceReaperState(codeName);\n log(\n `[hot-reload] .mcp.json key membership unavailable for '${codeName}' — clearing full presence-reaper state and restarting (ENG-5285/ENG-5537)`,\n );\n } else if (decision.restart) {\n clearPresenceReaperStateForKeys(codeName, new Set(decision.addedOrRemoved));\n log(\n `[hot-reload] .mcp.json membership changed for '${codeName}': ` +\n `${decision.addedOrRemoved.length} key(s) added/removed [${decision.addedOrRemoved.join(', ')}] — reaper state cleared for those keys only (ENG-5285)`,\n );\n } else {\n log(\n `[hot-reload] .mcp.json value-only drift for '${codeName}' — preserving presence-reaper state and NOT restarting (ENG-5285/ENG-5537)`,\n );\n }\n\n if (decision.restart) {\n scheduleSessionRestart(codeName, 0, '.mcp.json content change (ENG-4897)');\n // Drop the entries — the next poll after respawn captures the\n // new hash + key set as baseline. Keeping the old values would\n // re-fire every poll until the respawn timer triggers.\n runningMcpHashes.delete(codeName);\n runningMcpServerKeys.delete(codeName);\n } else {\n // ENG-5537: value-only drift — adopt the new content as the baseline\n // so we don't re-log/re-evaluate every poll, but leave the running\n // session untouched. currentKeys is non-null here (membershipUnknown\n // would have set decision.restart=true), so the key set stays in sync.\n runningMcpHashes.set(codeName, action.current);\n if (currentKeys) runningMcpServerKeys.set(codeName, currentKeys);\n }\n return;\n }\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>();\n// ENG-5537: structure-only hash of the same desired set — serverId membership\n// + header KEY names, excluding url and header VALUES. Composio (and peers)\n// rotate a freshly-signed `mcp_url` on most polls, which moves the value hash\n// above (so .mcp.json re-converges to the live credential) but must NOT\n// restart a healthy session. The session restart is gated on THIS hash so a\n// pure credential-URL rotation no longer flaps the agent into the ENG-5441\n// circuit breaker (phil, agt-aws-1, 2026-05-25).\nconst knownManagedMcpStructure = new Map<string, string>();\nconst knownSkillHashes = new Map<string, string>();\n// ENG-5292: reverse mapping from (agent_id, serverId) to toolkit_id.\n// The serverId is the .mcp.json key — the lowercased-non-alnum-to-\n// underscore form of toolkit_id that writeMcpServer uses. The reaper's\n// onGiveUp callback consults this so it can tell the API which toolkit\n// to mark unhealthy.\n//\n// CodeRabbit on PR #1184: scoped per-agent (composite key) — NOT keyed\n// by serverId alone. Two different agents can declare the same\n// toolkit (same serverId), and stale entries from a now-removed agent\n// must not misattribute a give-up on another agent's MCP to the wrong\n// toolkit_id. The composite key also lets us prune per-agent on agent\n// removal without a full scan.\nconst managedToolkitIdByAgentAndServerId = new Map<string, string>();\n\nfunction managedToolkitMapKey(agentId: string, serverId: string): string {\n return `${agentId}\\x00${serverId}`;\n}\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// ENG-5408: separate set for the structured stuck-task watchdog — we want\n// one log line per stuck row per occurrence so CloudWatch metric filters\n// can count them, but we still don't want to spam every poll tick for\n// the same row. Cleared in lockstep with alertedStaleItems below.\nconst stuckKanbanLogged = new Set<string>();\n// Track agents with known API key errors (avoid spamming the API)\nconst apiKeyStatusCache = new Map<string, boolean>();\n// ENG-5408: threshold env var, falls back to 30 min. The existing\n// auto-fail path (around manager-worker.ts:5042) and the new structured\n// stuck-task watchdog log line both honour this — operators can tighten\n// or loosen the budget without a code change.\nconst STALE_TASK_THRESHOLD_MS = (() => {\n const env = process.env['AUGMENTED_STUCK_TASK_THRESHOLD_MINUTES'];\n const minutes = env ? Number.parseInt(env, 10) : NaN;\n if (Number.isFinite(minutes) && minutes > 0) return minutes * 60 * 1000;\n return 30 * 60 * 1000;\n})();\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 knownManagedMcpStructure.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 // CodeRabbit on PR #1318: drop the per-agent dedupe so a re-enrolled\n // agent's first banner observation isn't suppressed by the previous\n // generation's cached count.\n lastMcpFailedBannerCount.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// ENG-5399: Tier 1 responsiveness probe — pane.log freshness reported\n// per agent on a slow cadence. Throttled here because pollCycle runs\n// every 10s but we only need this once every few minutes (the existing\n// SyntheticReplyAgeSeconds cron runs every 15 min as the deeper check).\nlet lastResponsivenessProbeAt = 0;\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 * Detect whether the `claude` binary is installed on this host. Mirrors\n * resolveBrewPath's three-platform-fallback shape: Linuxbrew (EC2 default),\n * Apple-silicon macOS, Intel macOS — then `which claude` as a last resort\n * for unusual install locations.\n *\n * Pre-CodeRabbit fix (PR #1226 review): only the Linuxbrew path was\n * checked before falling back to `which`, which fails on macOS launchd\n * with a sparse PATH. The manager would then treat Claude as missing,\n * stamp the upgrade marker, and skip the upgrade — silently never\n * refreshing Claude Code on dev macs.\n */\nfunction claudeBinaryInstalled(\n execFileSync: typeof import('node:child_process').execFileSync,\n): boolean {\n const canonical = [\n '/home/linuxbrew/.linuxbrew/bin/claude',\n '/opt/homebrew/bin/claude',\n '/usr/local/bin/claude',\n ];\n if (canonical.some((path) => existsSync(path))) return true;\n try {\n execFileSync('which', ['claude'], { timeout: 5_000 });\n return true;\n } catch {\n return false;\n }\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 const claudeExists = claudeBinaryInstalled(execFileSync);\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\n // ENG-5357: fresh install is by definition the latest version — stamp\n // the upgrade marker so the next poll's maybeUpgradeClaudeCode() won't\n // immediately fire a redundant `brew upgrade`. Same disk-persisted\n // throttle described next to maybeUpgradeClaudeCode().\n stampClaudeCodeUpgradeMarker();\n }\n // ENG-5357: upgrade for existing installs is no longer kicked off here.\n // The in-memory `frameworkBinaryChecked` guard above is process-scope,\n // so on alyve-host-1 a restart storm fired N concurrent `brew upgrade\n // --cask claude-code` jobs and pegged the host until it wedged. Upgrade\n // now lives in maybeUpgradeClaudeCode() which is called from pollCycle\n // and throttled via an on-disk 24h marker — restarts can no longer\n // re-trigger it.\n\n // Check if Claude Code is authenticated. The binary that's already on\n // disk works regardless of upgrade state.\n agentRuntimeAuthenticated = await checkClaudeAuth();\n}\n\n// ---------------------------------------------------------------------------\n// Claude Code background upgrade (ENG-5357)\n// ---------------------------------------------------------------------------\n//\n// Called from pollCycle on every tick. Returns immediately when the disk\n// marker is fresh — only the once-per-day actual `brew upgrade` does any\n// real work. This is the same disk-marker pattern as the agt CLI self-\n// update path (checkAndUpdateCli); the window is just longer because\n// Claude Code releases on a much slower cadence than the agt CLI.\n//\n// The marker is stamped BEFORE the fire-and-forget upgrade kicks off so\n// concurrent poll cycles inside the 30-120s upgrade window cannot double-\n// trigger. Without that pre-stamp, every poll tick during a slow upgrade\n// would see \"no marker yet\" and queue another brew job behind the lock —\n// the same restart-loop pathology this issue was created to fix.\n\nconst CLAUDE_CODE_UPGRADE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24h\nlet claudeCodeUpgradeInFlight = false;\n\n// Exported for unit testing — internal callers should use the wrappers below.\nexport function claudeCodeUpgradeMarkerPath(): string {\n return join(homedir(), '.augmented', '.last-claude-code-upgrade-check');\n}\n\nexport function stampClaudeCodeUpgradeMarker(): void {\n try {\n writeFileSync(claudeCodeUpgradeMarkerPath(), String(Date.now()));\n } catch { /* non-fatal — next poll will try again */ }\n}\n\nexport function claudeCodeUpgradeThrottled(): boolean {\n try {\n const lastCheck = parseInt(readFileSync(claudeCodeUpgradeMarkerPath(), 'utf-8').trim(), 10);\n if (!Number.isFinite(lastCheck)) return false;\n return Date.now() - lastCheck < CLAUDE_CODE_UPGRADE_CHECK_INTERVAL_MS;\n } catch {\n // No marker yet → not throttled → first call writes it.\n return false;\n }\n}\n\nexport async function maybeUpgradeClaudeCode(): Promise<void> {\n if (claudeCodeUpgradeInFlight) return;\n if (claudeCodeUpgradeThrottled()) return;\n\n // Stamp BEFORE kicking off the upgrade so concurrent polls inside the\n // upgrade window see \"throttled\" and bail. Without this, every poll\n // tick during the 30-120s brew run would queue another job. The\n // in-flight flag is a second belt for the rare case where two polls\n // race past the disk read in the same event-loop tick.\n claudeCodeUpgradeInFlight = true;\n stampClaudeCodeUpgradeMarker();\n\n const { execFileSync } = await import('node:child_process');\n const brewPath = resolveBrewPath(execFileSync);\n if (!brewPath) {\n claudeCodeUpgradeInFlight = false;\n return;\n }\n\n // Only upgrade if the binary already exists; install lives in\n // ensureFrameworkBinary's hot path (it's a hard prerequisite, not a\n // refresh, and shouldn't be deferred to the poll loop).\n if (!claudeBinaryInstalled(execFileSync)) {\n claudeCodeUpgradeInFlight = false;\n return;\n }\n\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 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 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 .finally(() => { claudeCodeUpgradeInFlight = false; });\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 // Release channel — `latest` (prod default) or a gated channel like `test`\n // (set on the test environment's hosts via AGT_CLI_RELEASE_CHANNEL). The\n // channel is the npm dist-tag we track: prod hosts follow `latest`,\n // test hosts follow `test`, same registry. Unset ⇒ `latest`, so prod\n // behaviour is unchanged.\n const channel = process.env.AGT_CLI_RELEASE_CHANNEL || 'latest';\n\n // Query the npm registry's channel 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/${encodeURIComponent(channel)}`,\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} (channel=${channel})`);\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 // `latest` channel: never move backwards — only upgrade to a strictly\n // newer semver (prod-safe; ignores prerelease tags entirely). A gated\n // channel is a moving pointer to a specific build, so track it exactly:\n // install whenever the tag points somewhere other than what we run,\n // including prereleases that isNewerSemver can't compare.\n const shouldUpdate =\n channel === 'latest' ? isNewerSemver(agtCliVersion, latest) : latest !== agtCliVersion;\n if (!shouldUpdate) {\n if (!selfUpdateUpToDateLogged) {\n log(`[self-update] agt CLI is up to date (npm, channel=${channel}, ${agtCliVersion})`);\n selfUpdateUpToDateLogged = true;\n }\n return;\n }\n\n log(`[self-update] agt CLI update available: ${agtCliVersion} → ${latest} (channel=${channel}). 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\n// ENG-5041: in-process backoff for /host/exchange and poll-cycle failures.\n// Pre-fix the manager would `process.exit(1)` on any \"exchange failed\" or\n// \"401\" error and rely on the systemd/launchd watchdog to restart it,\n// which during a sustained API outage (e.g. the S3 SDK prod-down on\n// 2026-05-12 that returned 500 for ~30 min) caused a restart storm and\n// blackholed the agent for the duration. Now we stay running, log each\n// attempt, and double the delay up to a 60s cap. A successful cycle\n// resets the counter so we go straight back to the normal interval.\n// Math + classifier extracted to ./poll-backoff.ts so they're testable\n// without dragging in this whole module's import graph.\nlet consecutivePollFailures = 0;\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 // ENG-5441: restart-flags are written by channel MCPs (slack/\n // telegram/direct-chat) when they hit an unrecoverable error\n // they can't fix in-place. If a single channel keeps writing\n // restart flags, it counts against the breaker — same protection\n // as a hot-reload loop.\n stopPersistentSessionAndForgetMcpBaseline(codeName, 'channel-set-change');\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 // ENG-5357: same fire-and-forget shape for the Claude Code framework\n // binary. Throttled to once per 24h via an on-disk marker so a restart\n // storm (e.g. transient API 5xx during a deploy) cannot fire N\n // concurrent `brew upgrade --cask claude-code` jobs the way it could\n // pre-fix — that pathology pegged alyve-host-1 until reboot.\n maybeUpgradeClaudeCode().catch((err) => log(`[claude-code-upgrade] 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 // ENG-5614: inbound-loop liveness. The actual phoenix socket state\n // behind the direct-chat Realtime subscription. When this is false\n // but the manager keeps heartbeating, inbound is being served by the\n // polling fallback (healRealtimeSocketDivergence forces it). Surfaced\n // so a sustained socket-down window is observable rather than hidden\n // behind a fresh heartbeat.\n realtime_socket_connected: isRealtimeSocketConnected(),\n });\n } catch (err) {\n log(`Heartbeat failed: ${(err as Error).message}`);\n }\n\n // 1d. ENG-5399 Tier 1 responsiveness probe — pane.log freshness per\n // agent. Reports `PaneActivityAgeSeconds` on a slow cadence (default\n // 5 min via AUGMENTED_RESPONSIVENESS_INTERVAL_MS). Complements the\n // existing 15-min agent-synthetic-probe cron with a faster, zero-\n // token canary that catches silent agents before the 35-min\n // SyntheticReplyAgeSeconds alarm window.\n try {\n const {\n collectResponsivenessProbes,\n getResponsivenessIntervalMs,\n } = await import('./responsiveness-probe.js');\n const probeIntervalMs = getResponsivenessIntervalMs();\n if (now - lastResponsivenessProbeAt > probeIntervalMs) {\n const probeCodeNames = [...persistentSessionAgents];\n if (probeCodeNames.length > 0) {\n const probes = collectResponsivenessProbes(probeCodeNames);\n if (probes.length > 0) {\n // Fire-and-forget — never block pollCycle on CloudWatch\n // latency, and a transient API error here mustn't pause\n // the rest of the cycle.\n void api\n .post('/host/responsiveness-probe', { host_id: hostId, probes })\n .catch((err: unknown) => {\n log(`[responsiveness-probe] post failed: ${(err as Error).message}`);\n });\n }\n }\n lastResponsivenessProbeAt = now;\n }\n } catch (err) {\n log(`[responsiveness-probe] collection failed: ${(err as Error).message}`);\n }\n\n // 1e. ENG-5459 — scrape \"N MCP servers failed · /mcp\" from each\n // active agent's pane.log and POST to the API so the webapp can\n // surface a \"needs attention\" signal alongside the existing\n // needsReconnect indicator. Catches the broken-but-process-alive\n // case mcp-presence-reaper can't see (Composio MCP whose URL\n // 401s on every tool call — process up, API dead).\n //\n // Fire-and-forget per agent. Only POSTs when the count differs\n // from the last observation (cheap dedupe — most ticks the count\n // is stable). Skipped entirely for agents with no pane.log.\n try {\n const { scrapeMcpFailedBannerCount } = await import('./pane-mcp-banner-scraper.js');\n const observations: Array<{ agent_id: string; count: number }> = [];\n // CodeRabbit on PR #1318: commit dedupe-cache only after POST\n // succeeds, otherwise a transient API failure permanently\n // suppresses future POSTs of the same value. Build a parallel\n // (codeName, count) list while we walk agents so the success\n // handler can apply the cache without a reverse lookup.\n const pendingCacheCommits: Array<{ codeName: string; count: number }> = [];\n for (const codeName of persistentSessionAgents) {\n const agentId = codeNameToAgentId.get(codeName);\n if (!agentId) continue;\n // 40 lines is enough to catch the banner — it renders on\n // every frame, so the last few KB always contain it.\n const tail = readPaneLogTail(codeName, 40);\n if (!tail) continue;\n const observed = scrapeMcpFailedBannerCount(tail);\n // Null = no banner detected this tick. Treat as 0 — Claude\n // Code omits the banner when all MCPs are healthy. Persisting\n // 0 also lets the alarm transition back to OK on recovery.\n const count = observed ?? 0;\n const last = lastMcpFailedBannerCount.get(codeName);\n if (last === count) continue; // dedupe — no change since last poll\n observations.push({ agent_id: agentId, count });\n pendingCacheCommits.push({ codeName, count });\n }\n if (observations.length > 0) {\n // CodeRabbit on PR #1318 round 4: only commit the cache for\n // observations the API actually accepted. The endpoint\n // returns `{ results: [{ agent_id, ok, error? }] }` — a row\n // that failed (e.g. host not assigned anymore) should be\n // retried on the next tick, not silently suppressed.\n void api\n .post<{ results?: Array<{ agent_id: string; ok: boolean }> }>(\n '/host/mcp-banner-observation',\n { observations },\n )\n .then((resp) => {\n const accepted = new Set(\n (resp?.results ?? [])\n .filter((r) => r.ok)\n .map((r) => r.agent_id),\n );\n for (const { codeName, count } of pendingCacheCommits) {\n const agentId = codeNameToAgentId.get(codeName);\n if (agentId && accepted.has(agentId)) {\n lastMcpFailedBannerCount.set(codeName, count);\n }\n }\n })\n .catch((err: unknown) => {\n log(`[mcp-banner] post failed: ${(err as Error).message}`);\n });\n }\n } catch (err) {\n log(`[mcp-banner] collection 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-5445: forward Claude Code's host-wide activity counts from\n // `~/.claude/stats-cache.json` to /host/activity-observations. Once\n // per cycle (not per-agent) — the cache is host-scoped, every\n // session on the machine contributes to the same numbers. The\n // monitor handles its own rate-limiting and the API endpoint\n // upserts on (host_id, date), so calling here every cycle is\n // safe. Fire-and-forget — observability writes must not slow the\n // poll cycle.\n void maybeReportActivityCache({ api, log });\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 //\n // ENG-5008: carry forward `lastRestartProcessedAt` from `state.agents`\n // (the prior cycle's state) for every agent in this cycle's\n // agentStates, regardless of whether `restartAcks` has a fresh\n // timestamp this poll. `processAgent` rebuilds AgentState from scratch\n // on every successful path (line 4416+), so without this copy-forward\n // the field gets dropped any cycle where a previously-acked agent\n // doesn't have a brand-new restart signal — and the very next cycle\n // sees `prev?.lastRestartProcessedAt = null`, the timestamp comparison\n // fails, and the manager re-fires the kill. That's the loop that\n // pinned vigil/vera/stirling for hours on 2026-05-12.\n // CodeRabbit (PR #953): key the immediate write off whether any\n // ack state actually changed this cycle, not just fresh signals.\n // A carry-forward repair after a prior write-failure should also\n // persist immediately to close the same crash-replay window.\n const restartAckStateChanged = applyRestartAcks({\n agentStates,\n priorAgents: state.agents,\n restartAcks,\n });\n if (restartAckStateChanged) {\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 // ENG-5614: reconcile the realtime socket vs the channel-subscription\n // flag BEFORE deciding whether to poll. A half-open socket leaves the\n // flag stale-true, which would otherwise skip the poll fallback and\n // silently drop inbound direct-chat. On divergence this forces the\n // flag false (and a reconnect next tick) so the poll fallback runs now.\n healRealtimeSocketDivergence();\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 // ENG-5441: serialise trip state on every poll so manager restarts\n // never silently clear a tripped breaker. Cheap — only tripped\n // entries are stored, and the typical state is zero entries.\n circuitBreakerTrips: restartBreaker.serialize(),\n };\n\n // ENG-5041: cycle completed — clear the backoff counter so the next\n // tick fires at the normal interval. Logging only on the transition\n // (n>0 → 0) so a healthy host doesn't spam recovery messages.\n if (consecutivePollFailures > 0) {\n log(`[poll-backoff] recovered after ${consecutivePollFailures} failure(s), resuming normal interval`);\n consecutivePollFailures = 0;\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 // ENG-5041: stay running and back off in-process instead of exiting\n // for the watchdog. Auth and 5xx failures are usually transient (key\n // rotation, API restart, prod outage) and the watchdog restart loop\n // was both slow and noisy — see the 2026-05-12 prod-down where the\n // S3 SDK regression made every /host/exchange return 500 for ~30\n // min and agt-aws-1 thrashed the whole time.\n consecutivePollFailures += 1;\n // CR (CodeRabbit on PR #982): log the *actual* scheduled delay (which\n // scheduleNext computes as max(intervalMs, backoffMs)) rather than the\n // raw backoff value, so the log line agrees with what really happens.\n // Use `_ms` suffix instead of seconds-with-s so downstream parsers\n // (Loki, awk, etc.) don't have to strip units.\n const scheduledDelayMs = Math.max(\n config?.intervalMs ?? 0,\n currentPollBackoffMs(consecutivePollFailures),\n );\n const errorClass = classifyPollError(message);\n log(`[poll-backoff] attempt=${consecutivePollFailures} class=${errorClass} next_retry_ms=${scheduledDelayMs}`);\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 // ENG-5080: the per-agent banner used to fire here unconditionally —\n // one line per agent per poll-cycle (10s default), so an idle host with\n // three agents wrote ~26k pure-decoration lines per day to CloudWatch.\n // Every nested log() in this function carries `'${agent.code_name}'`,\n // so removing the banner doesn't cost any forensic context. Keep the\n // map writes; they're cheap and other call sites depend on them.\n //\n // The status-branches below (draft / paused / revoked) similarly used\n // to log unconditionally; they now edge-trigger on `knownStatuses`,\n // matching the existing \"status changed\" pattern further down.\n const previousKnownStatus = knownStatuses.get(agent.agent_id);\n agentDisplayNames.set(agent.code_name, agent.display_name);\n codeNameToAgentId.set(agent.code_name, agent.agent_id);\n // ENG-5441 + CodeRabbit on PR #1302: if a trip was hydrated from\n // `manager-state.json` (manager restart with a pre-existing trip),\n // `codeNameToAgentId` wasn't populated when the breaker's record path\n // first tried to report. Retry now that we know the agent_id.\n // Idempotent — `reportedTrips` short-circuits already-reported trips.\n maybeReportCurrentTrip(agent.code_name);\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 // ENG-5389: scrape the Claude Code \"X% of your weekly limit\" banner from\n // pane.log and forward observations to the API. Claude Code only — the\n // banner doesn't exist in other framework runtimes. The monitor handles\n // its own rate-limiting and dedupes against an in-memory last value, so\n // calling it every cycle for every active CC agent is safe. Fire-and-\n // forget — observability writes must not slow the poll cycle.\n if (agent.framework === 'claude-code' && agent.status === 'active') {\n void maybeReportUsageBanner({\n api,\n codeName: agent.code_name,\n agentId: agent.agent_id,\n log,\n });\n\n // ENG-5516: read per-agent token usage from the session transcripts and\n // upsert it via /host/token-usage so usage is comparable across agents.\n // Same fire-and-forget contract as the banner monitor: self-rate-limited,\n // self-deduped, errors swallowed — must not slow the poll cycle.\n void maybeReportTokenUsage({\n api,\n codeName: agent.code_name,\n agentId: agent.agent_id,\n log,\n });\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 if (previousKnownStatus !== agent.status) {\n log(`Agent '${agent.code_name}' is ${agent.status}, skipping provisioning`);\n }\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 // ENG-5080: needed for the edge-triggered log above. The branch\n // returns before the canonical knownStatuses.set near the end of\n // processAgent, so we'd otherwise log \"is draft, skipping\" every\n // 10s for the lifetime of the draft.\n knownStatuses.set(agent.agent_id, agent.status);\n return;\n }\n\n if (agent.status === 'revoked') {\n // ENG-5090: short-circuit on the second-and-later tick. All cleanup\n // below is idempotent — gateway already stopped, tmux session\n // already dead, files already removed, caches already cleared — so\n // re-running it every 10s for the lifetime of a revoked agent is\n // pure waste (child-process spawn for tmux, fs walks, Map churn).\n // ENG-5080 silenced the log line; this short-circuits the work.\n //\n // Re-enrolment safety: if the agent ever flips off `revoked` (e.g.\n // an operator un-revokes — rare but possible), the canonical\n // `status changed: prev → curr` branch a few lines below picks up\n // the transition because we still keep `knownStatuses` accurate.\n if (shouldSkipRevokedCleanup(previousKnownStatus)) {\n // CodeRabbit on PR #1052: stopGatewayIfRunning and cleanupAgentFiles\n // swallow internal errors and log them, so a failed first-pass\n // cleanup can still set knownStatuses['revoked'] and silently\n // skip retries. If obvious residuals remain (gateway process\n // alive, port still in the gateway-ports file, provision dir\n // still on disk), fall through to retry instead of short-circuiting.\n //\n // Gateway liveness is checked first because `freePort` and\n // `cleanupAgentFiles` run *after* `stopGatewayIfRunning` and\n // succeed even when the stop failed silently. Without this check\n // the port + dir residuals would both be false on the next tick\n // while the gateway process kept running forever (CodeRabbit on\n // PR #1054).\n const ports = loadGatewayPorts();\n const gatewayLiveness = adapter.isGatewayRunning\n ? await adapter.isGatewayRunning(agent.code_name).catch(() => ({ running: false }))\n : { running: false };\n const residuals = {\n gatewayRunning: gatewayLiveness.running,\n portAllocated: Object.prototype.hasOwnProperty.call(ports, agent.code_name),\n provisionDirExists: existsSync(agentDir),\n };\n if (!hasRevokedResiduals(residuals)) {\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 // Residuals detected — note it once (this branch only triggers\n // when the first-pass cleanup partially failed, which is rare)\n // and fall through to the full cleanup block below for retry.\n log(\n `[revoked] residuals for '${agent.code_name}': ` +\n `gateway=${residuals.gatewayRunning} ` +\n `dir=${residuals.provisionDirExists} ` +\n `port=${residuals.portAllocated ? ports[agent.code_name] : 'none'} ` +\n `— retrying cleanup`,\n );\n }\n\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 // ENG-5441: paused → active means the operator cleared the trip\n // (either through the webapp resume or `agt agent resume`). Drop the\n // breaker state so the agent can spawn on this tick. We don't clear\n // on every status-change because draft/revoked transitions don't\n // imply operator intent to recover from a trip.\n if (previousStatus === 'paused' && agent.status === 'active' && restartBreaker.isTripped(agent.code_name)) {\n restartBreaker.clear(agent.code_name);\n reportedTrips.delete(agent.code_name);\n log(`[circuit-breaker] Cleared trip for '${agent.code_name}' on operator resume (paused → active)`);\n }\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 * Effective guardrails for this agent, pre-resolved server-side\n * (org → team → agent + overrides) and joined with their\n * definitions. Threaded straight into `provisionInput.guardrails`\n * for the framework adapter to render. Omitted by older API\n * builds; downstream code treats `undefined` and `[]` the same.\n */\n guardrails?: ProvisionInput['guardrails'];\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 // ENG-5635: a model change only takes effect on the next spawn — the\n // launcher derives `--model <alias>` from primary_model at launch\n // (ENG-5631); the settings.json write above is inert for claude-code at\n // runtime. So for a live claude-code session, proactively respawn now\n // (via the coalescing scheduler, which preserves the presence-reaper\n // baseline since channels/MCP are unchanged) instead of leaving the\n // agent on its old model until the next natural restart. The gate skips\n // first-provision (no previousModel — the imminent first spawn already\n // carries the model) and the no-session case (next spawn picks it up).\n if (\n shouldRespawnForModelChange({\n previousModel,\n primaryModel,\n framework: frameworkId,\n sessionHealthy: isSessionHealthy(agent.code_name),\n })\n ) {\n const restartNotice =\n `Your model was changed to ${primaryModel}. Claude Code applies the model at launch, ` +\n `so your manager will restart your session shortly to switch.`;\n const delivered = await injectMessage(\n agent.code_name, 'system', restartNotice, { task_name: 'model-update' }, log,\n ).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 log(`[hot-reload] Model changed for '${agent.code_name}': ${previousModel} → ${primaryModel} — restarting session`);\n scheduleSessionRestart(agent.code_name, delay, 'model change', 'model-change');\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 // ENG-5363: team timezone propagates into the spawned channel\n // MCP child as the `TZ` env var so progress-message timestamps\n // (\"last update: HH:MM:SS TZ\") render in the agent's local\n // time instead of the host system zone (UTC on Lambda/EC2).\n // Folded into the hash so a team timezone change invalidates\n // the cache and triggers a credential rewrite.\n const agentTimezone = (() => {\n const tz = teamSettings?.['timezone'];\n return typeof tz === 'string' && tz.trim() !== '' ? tz.trim() : undefined;\n })();\n const teamSettingsForHash =\n channelId === 'telegram' || channelId === 'slack' || channelId === 'direct-chat'\n ? { peer_disabled: peerDisabledMode, timezone: agentTimezone ?? null }\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?: {\n inbound?: ReadonlyArray<{\n grant_id: string;\n granted_agent_bot_id: number | null;\n /** ENG-4970 / ENG-4973: Slack `U…` user_id of granted_agent_id. */\n granted_agent_slack_user_id?: string | null;\n revoked_at: string | null;\n expires_at: string | null;\n }>;\n };\n team_peer_bot_ids?: ReadonlyArray<number>;\n /** ENG-4970 / ENG-4973: Slack `U…` user_ids of same-team agents. */\n team_peer_slack_user_ids?: ReadonlyArray<string>;\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 //\n // CodeRabbit on PR #934: pick the discriminator per-channel.\n // The previous all-Telegram discriminator would short-circuit\n // the Slack backwards-compat fallback on managers running\n // against a /host/refresh that has the Telegram fields but\n // not yet the Slack ones (mid-rollout). Without this, same-team\n // Slack peers would get downgraded to intra_org_unrestricted /\n // null because gateContext is \"present but with empty Slack\n // arrays\" — not the same as \"absent entirely\".\n const refreshHasCrossTeamFields =\n channelId === 'telegram'\n ? Array.isArray(crossTeamData.team_peer_bot_ids)\n : channelId === 'slack'\n ? Array.isArray(crossTeamData.team_peer_slack_user_ids)\n : false;\n const gateContext: CharterPeerGateContext | undefined =\n refreshHasCrossTeamFields\n ? {\n teamPeerBotIds: crossTeamData.team_peer_bot_ids ?? [],\n teamPeerSlackUserIds: crossTeamData.team_peer_slack_user_ids ?? [],\n crossTeamGrants: crossTeamData.cross_team_grants?.inbound ?? [],\n crossTeamPeerIntraOrg:\n crossTeamData.organization?.cross_team_peer_intra_org ?? 'unrestricted',\n }\n : undefined;\n // Peer roster folded into the hash so a CHARTER edit / grant\n // flip invalidates the cache. Telegram + Slack each carry\n // their own peer arrays in CHARTER; whichever channel we're\n // rendering picks the matching one.\n const peersForHash =\n channelId === 'telegram'\n ? extractCharterTelegramPeers(refreshData.charter?.raw_content ?? '', gateContext)\n : channelId === 'slack'\n ? extractCharterSlackPeers(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 // v3 — ENG-4970 / ENG-4974: Slack MCP child gets the same\n // AGT auth trio + SLACK_PEERS / SLACK_PEERS_GATE /\n // SLACK_PEER_AGENT_MODE / SLACK_PEER_GROUP_IDS env so\n // cross-team peer collab finally works on Slack at\n // runtime (previously the classifier had an empty peer\n // list).\n // v5 — ENG-5363: fold `agentTimezone` (from\n // team.settings.timezone) into the hash + emit `TZ` env\n // var on telegram / slack MCP children so the\n // in-place progress heartbeat's \"last update: HH:MM:SS\"\n // footer renders in the agent's local timezone instead\n // of UTC.\n // v4 — ENG-5155: fold `session_mode` into the hash. The\n // rendered slack/direct-chat blocks branch on\n // `isPersistent` (`claudecode/index.ts:2149`), so a\n // oneshot→persistent flip changes the output of\n // `writeChannelCredentials` — but pre-v4 the hash\n // didn't notice and the rewrite was skipped forever.\n // Sherlock (2026-05-17) hit this: created as oneshot\n // before #1108, then flipped to persistent, but his\n // `.mcp.json` kept the broken `npx -y @augmented/\n // claude-code-channel-slack` fallback until we cleared\n // the cache by hand. The version bump itself forces\n // one rewrite per agent on first poll after upgrade,\n // which auto-repairs any other agents in the same\n // stuck state.\n const sessionModeForHash =\n (refreshData.agent as Record<string, unknown>).session_mode as string | undefined;\n const CHANNEL_WRITE_VERSION = 5;\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 sessionMode: sessionModeForHash ?? null,\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 // ENG-4970 / ENG-4974: Slack mirror. Parallel resolution\n // from CHARTER multi_agent.slack_peers — same gate-path\n // model, keyed on bot_user_id.\n const slackPeers =\n channelId === 'slack'\n ? extractCharterSlackPeers(refreshData.charter?.raw_content ?? '', gateContext)\n : undefined;\n frameworkAdapter.writeChannelCredentials(\n agent.code_name,\n channelId,\n entry.config as Record<string, unknown>,\n {\n sessionMode,\n agentId: agent.agent_id,\n telegramPeerDisabled,\n peerDisabled,\n telegramPeers,\n slackPeers,\n agentTimezone,\n },\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 // ENG-5363: team timezone propagates as `TZ` so the direct-chat\n // progress-heartbeat's \"last update\" footer renders in the agent's\n // local time. Mirror of the Slack/Telegram fix in\n // claudecode/index.ts. The current block only writes on first\n // install (`!mcpConfig.mcpServers['direct-chat']`), so existing\n // agents will pick up TZ the next time their `.mcp.json` is reset\n // — acceptable for a hotfix; a follow-up could re-evaluate the\n // env block on each refresh.\n const directChatTeamSettings = (refreshData.team as { settings?: Record<string, unknown> } | undefined)?.settings;\n const directChatTz = (() => {\n const tz = directChatTeamSettings?.['timezone'];\n return typeof tz === 'string' && tz.trim() !== '' ? tz.trim() : undefined;\n })();\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 ...(directChatTz ? { TZ: directChatTz } : {}),\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 // ENG-5292: record reverse mapping so the reaper's give-up\n // callback (later in this file) can resolve a serverId back\n // to the toolkit_id when POSTing to /host/integration-health.\n managedToolkitIdByAgentAndServerId.set(\n managedToolkitMapKey(agent.agent_id, serverId),\n tk.toolkit_id,\n );\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 // ENG-5537: structure-only hash — serverId membership + header KEY\n // names, deliberately excluding the url and header VALUES. The write\n // below still keys off `mcpHash` (so .mcp.json converges to the live\n // signed url), but the session RESTART is gated on this hash so a\n // value-only rotation (composio re-signing `mcp_url` every poll)\n // refreshes the file without churning the running session. Only a\n // real structural change — toolkit added/removed, or a header key\n // appearing/disappearing — restarts the session.\n const structureHash = managedMcpStructureHash(desiredEntries);\n const prevStructureHash = knownManagedMcpStructure.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 knownManagedMcpStructure.set(agent.agent_id, structureHash);\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 //\n // ENG-5537: additionally gate on a *structural* change\n // (`structureHash`), not just any `mcpHash` change. A value-only\n // rotation (composio re-signing `mcp_url`) writes the fresh file\n // above but leaves `structureHash` identical — the running session's\n // established MCP connections are unaffected, so we skip the restart\n // and the inject-notice. `prevStructureHash === undefined` is the\n // first-convergence case (no running session to disturb).\n if (\n prevStructureHash !== undefined &&\n structureHash !== prevStructureHash &&\n fwForMcp === 'claude-code' &&\n isSessionHealthy(agent.code_name)\n ) {\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 // 7e. ENG-5435 hybrid kanban-work + ENG-5599/5600/5601 scheduled-task\n // reconcile & delivery. Both need a fresh board snapshot for a claude-code\n // persistent agent (oneshot agents have no live REPL; openclaw / nemoclaw use\n // the cron / gateway pipeline), so they share one fetch — but they're gated\n // by SEPARATE flags and must not be coupled:\n // - reconcile + delivery (ENG-5601) runs whenever scheduled-via-kanban is\n // on. It's the PRIMARY delivery trigger; gating it behind the hybrid flag\n // would silently drop every scheduled-task delivery when an operator\n // enables AGT_SCHEDULED_VIA_KANBAN alone. (Harmless no-op when nothing is\n // tracked in scheduledRunsByCode.)\n // - the hybrid kanban-work inject (ENG-5435) runs only when the hybrid flag\n // is on. Uses the tighter HYBRID_ACTIONABLE_STATUSES predicate\n // (todo / in_progress — NOT backlog) per\n // docs/research/eng-5435-hybrid-kanban-inject.md.\n const wantsHybridInject = isKanbanHybridEnabled();\n const wantsScheduledReconcile = isScheduledViaKanbanEnabled();\n if ((wantsHybridInject || wantsScheduledReconcile) && agentFw === 'claude-code' && sessionMode === 'persistent') {\n // CR (CodeRabbit on PR #1293): the kanbanBoardCache is process-wide\n // and persists across poll ticks. The previous `!hybridBoard` short\n // circuit meant that once we populated the cache, we'd read stale\n // data forever for agents without a board-aware scheduled template\n // (step 7d only fetches when `hasBoardTemplates`). Reuse step 7d's\n // freshly-fetched board when it ran this tick; otherwise fetch our\n // own and update the cache. Falls back to whatever cached snapshot\n // exists on transport failure rather than treating the agent as\n // having an empty board.\n let hybridBoard = hasBoardTemplates ? boardItems : undefined;\n if (!hybridBoard) {\n try {\n const boardData = await api.post<{ items?: BoardItem[] }>(\n '/host/my-kanban',\n { agent_id: agent.agent_id },\n );\n hybridBoard = (boardData.items ?? []).map(sanitizeBoardItem);\n kanbanBoardCache.set(agent.code_name, hybridBoard);\n } catch {\n hybridBoard = kanbanBoardCache.get(agent.code_name) ?? [];\n }\n }\n // ENG-5600/5601: reconcile in-flight scheduled-task runs against this fresh\n // board snapshot — deliver the agent's result from the done card and close\n // the per-card run. Done here because we already have a current board and\n // the agent self-completing its card means the realtime onCompletion path\n // is suppressed by isSelfCompletion (so this poll is the primary trigger).\n // Fire-and-forget: it awaits delivery per card internally but the poll loop\n // doesn't block on it.\n void reconcileScheduledRuns(agent.code_name, agent.agent_id, hybridBoard);\n if (wantsHybridInject) {\n if (hasHybridActionableItems(hybridBoard)) {\n const actionableCount = hybridBoard.filter(isHybridActionable).length;\n // Fire-and-forget per design-doc Q2 — the 10s waitForPromptReady\n // cap would otherwise serialise across N agents and starve the\n // poll cycle. Caller doesn't await.\n void maybeInjectKanbanCheck(agent.code_name, agent.agent_id, actionableCount);\n } else {\n // ENG-5565: board drained (no todo/in_progress) — the agent has worked\n // through its injected kanban items, so close any open injected run so\n // it doesn't hang open and absorb the agent's subsequent non-kanban turns.\n closeInjectedRunIfOpen(agent.code_name, 'completed', 'kanban board drained');\n }\n }\n }\n\n if (agentFw === 'claude-code' && sessionMode === 'persistent') {\n // Claude Code persistent mode: manage long-running session with channels.\n //\n // ENG-5116: wrap the per-agent call in try/catch so an exception\n // affecting one agent's evaluation can't silently take that agent\n // down. Pre-fix, a throw inside ensurePersistentSession bypassed\n // every log statement and the operator only learned about it when\n // a user reported the agent unresponsive in Slack (Vigil, 2026-05-16).\n // Snapshot health *before* the call so an exception thrown after the\n // session is already healthy doesn't get misreported as down — that\n // would start a bogus stuck streak for an agent still serving traffic\n // (CodeRabbit PR #1070).\n const wasSessionHealthy = isSessionHealthy(agent.code_name);\n let psResult: PersistentSessionResult;\n try {\n psResult = await ensurePersistentSession(agent, tasks, boardItems, refreshData);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n log(`[persistent-session] EXCEPTION evaluating '${agent.code_name}': ${msg}`);\n // Construct a synthetic result so the decision-summary + stuck\n // tracker still see this tick. Carry forward the pre-call health\n // observation — the throw doesn't kill the tmux session, only our\n // evaluation of it.\n psResult = {\n decision: 'skipped-auth-resolve-failed',\n spawnAttempted: false,\n sessionHealthyAfter: wasSessionHealthy,\n detail: `exception: ${msg}`,\n };\n }\n\n // One structured decision-summary line per agent per tick. This is\n // the line that would have shown Vigil silently stuck — pre-fix only\n // the happy path logged. `detail` is JSON-stringified so embedded\n // quotes/newlines from exception messages can't break single-line\n // parsing downstream (CodeRabbit PR #1070).\n const normalizedDetail = psResult.detail?.replace(/\\s+/g, ' ').trim();\n const detailSuffix = normalizedDetail\n ? ` detail=${JSON.stringify(normalizedDetail)}`\n : '';\n log(\n `[persistent-session-decision] agent=${agent.code_name} decision=${psResult.decision} ` +\n `spawn_attempted=${psResult.spawnAttempted} session_healthy_after=${psResult.sessionHealthyAfter}${detailSuffix}`,\n );\n\n const stuck = persistentSessionStuckTracker.record({\n codeName: agent.code_name,\n sessionHealthy: psResult.sessionHealthyAfter,\n spawnAttempted: psResult.spawnAttempted,\n });\n\n // ENG-5404: retry the in-session /loop arm on every healthy tick. The\n // spawn-time arm is best-effort (it can race the prompt-ready check);\n // this catches the rare \"spawn succeeded but arm injection failed\"\n // case so the agent doesn't sit unarmed until the next manual respawn.\n // ensureKanbanLoopArmed short-circuits when already armed (cheap), so\n // it's safe to call every tick.\n if (psResult.sessionHealthyAfter) {\n ensureKanbanLoopArmed(agent.code_name, log).catch(() => { /* idempotent — next tick retries */ });\n }\n if (stuck.shouldWarn) {\n log(\n `[persistent-session-stuck] WARN: agent=${agent.code_name} unhealthy with no spawn attempt for ${stuck.consecutiveTicks} consecutive ticks ` +\n `(last_decision=${psResult.decision}) — manager-worker may need restart, or upstream guard is mis-firing (see ENG-5116)`,\n );\n } else if (stuck.recovered) {\n log(`[persistent-session-stuck] agent=${agent.code_name} recovered from stuck state`);\n }\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\n // ENG-5053: presence check — flag declared MCP servers that have\n // no live children under this agent's claude (e.g. xero-mcp-server\n // crashed silently) and kill the session so the next ensure pass\n // respawns it. Skipped within the cold-start grace window so the\n // initial 20-30s npm-install of @composio/mcp + xero-mcp-server\n // doesn't trip a false-positive restart.\n try {\n const sess = getSessionState(agent.code_name);\n let mcpJsonParsed: import('./stale-mcp-reaper.js').McpConfig | null = null;\n try {\n const mcpPath = join(psGetProjectDir(agent.code_name), '.mcp.json');\n mcpJsonParsed = JSON.parse(readFileSync(mcpPath, 'utf-8'));\n } catch {\n // Missing/unreadable .mcp.json — nothing to check. The drift\n // check above already logs unparseable configs; we silently\n // skip presence so we don't double-log on a transient read.\n }\n reapMissingMcpSessions({\n log,\n codeName: agent.code_name,\n mcpJson: mcpJsonParsed,\n sessionStartedAt: sess?.startedAt ?? null,\n // ENG-5441 / ENG-5547: a mcp-presence-reaper restart counts against\n // the agent-wide breaker ONLY when several distinct MCP servers are\n // missing at once — a thrashing collection that rotates restarts of\n // the whole session, which the per-MCP cap can't see. A restart\n // driven by a SINGLE persistently-failing server is left to that\n // server's own ENG-5279 give-up cap (→ marked unhealthy via onGiveUp\n // → dropped from the next .mcp.json), so one bad integration is\n // isolated and the agent keeps running degraded instead of being\n // paused wholesale. reaperRestartBreakerReason() encodes that\n // single-vs-multi decision; undefined means \"restart, don't count\".\n stopSession: (codeName, ctx) =>\n stopPersistentSessionAndForgetMcpBaseline(codeName, reaperRestartBreakerReason(ctx.activeKeys)),\n // ENG-5292: when the reaper gives up on a managed MCP (cap from\n // ENG-5279 + state-preservation from ENG-5285 both said \"this\n // MCP keeps failing after 3 restart cycles\"), mark the matching\n // integration unhealthy server-side. The next /host/managed-\n // toolkits poll drops it from the response → next writeMcpServer\n // omits it → broken MCP is cleanly gone instead of declared-but-\n // dead.\n //\n // Async fire-and-forget — the reaper's correctness must not\n // depend on the API call succeeding. Errors are caught inside\n // the .then/.catch chain and logged.\n onGiveUp: (codeName, serverKey) => {\n const toolkitId = managedToolkitIdByAgentAndServerId.get(\n managedToolkitMapKey(agent.agent_id, serverKey),\n );\n if (!toolkitId) {\n log(\n `[mcp-presence-reaper] give-up on '${codeName}:${serverKey}' — not a managed toolkit (no toolkit_id mapping), skipping integration-health API call`,\n );\n return;\n }\n const agentId = agent.agent_id;\n api.post('/host/integration-health', {\n agent_id: agentId,\n toolkit_id: toolkitId,\n action: 'mark_unhealthy',\n reason: `mcp-presence-reaper cap (${3} consecutive failed restart cycles) tripped on '${serverKey}'`,\n }).then((res) => {\n log(\n `[mcp-presence-reaper] marked integration unhealthy for '${codeName}' toolkit '${toolkitId}' (ENG-5292): ${JSON.stringify(res).slice(0, 200)}`,\n );\n }).catch((err) => {\n log(\n `[mcp-presence-reaper] failed to mark integration unhealthy for '${codeName}' toolkit '${toolkitId}' (ENG-5292): ${(err as Error).message} — local give-up state still recorded; will retry on next agent provisioning cycle if .mcp.json changes`,\n );\n });\n },\n // ENG-5286: record the restart event with the API so the\n // agent-heartbeat-monitor cron can aggregate per-agent counts\n // into the McpReaperRestartsHourly CloudWatch metric. Fire-and-\n // forget — the reaper's correctness must not depend on this.\n onRestart: (codeName, { activeKeys, givenUpKeys }) => {\n api.post('/host/mcp-reaper-restart', {\n agent_id: agent.agent_id,\n code_name: codeName,\n server_keys: activeKeys,\n given_up_keys: givenUpKeys,\n }).catch((err) => {\n log(\n `[mcp-presence-reaper] failed to record restart event for '${codeName}' (ENG-5286): ${(err as Error).message} — local restart still proceeded; CloudWatch metric will under-count this event`,\n );\n });\n },\n });\n } catch (err) {\n log(`[mcp-presence-reaper] presence 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 // ENG-5405: include the top todo item's id in the inject so\n // the agent can kanban_move(id, \"in_progress\") without a\n // round-trip via kanban_list — saves ~one MCP call per\n // trigger. Falls back to a board-list prompt when there's\n // no obvious top item (empty board, all-blocked, etc).\n const todayItem = boardItems.find((b) => b.status === 'todo');\n // CodeRabbit PR #1275: titles are user-authored; JSON.stringify\n // so embedded quotes/newlines can't reshape the injected\n // prompt and mis-direct the agent to a different item.\n const triggerMessage = todayItem\n ? `New work triggered. Top item: id=${todayItem.id} title=${JSON.stringify(todayItem.title)} priority=${todayItem.priority}. Call kanban_move(\"${todayItem.id}\", \"in_progress\") and start working — no need to kanban_list first.`\n : 'New work triggered. Check your kanban board with kanban_list and pick up the next item.';\n injectMessage(agent.code_name, 'task', triggerMessage, {\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 // ENG-5408: structured stuck-task watchdog line. Lands BEFORE\n // the existing auto-fail so CloudWatch metric filters can\n // count occurrences even when the auto-fail succeeds. Same\n // shape as `[persistent-session-stuck]` (ENG-5116) so the\n // alerting/monitoring pipeline can pattern-match without a\n // new parser. One line per row per occurrence — `stuckKanbanLogged`\n // dedupes within the session.\n const stuckKey = `${agent.code_name}:${item.id}`;\n if (!stuckKanbanLogged.has(stuckKey)) {\n log(\n `[persistent-session-stuck-kanban] agent=${agent.code_name} ` +\n `item_id=${item.id} title=${JSON.stringify(item.title)} ` +\n `status=in_progress age_minutes=${age} ` +\n `threshold_minutes=${Math.round(STALE_TASK_THRESHOLD_MS / 60_000)}`,\n );\n stuckKanbanLogged.add(stuckKey);\n }\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 // ENG-5408: clear the structured-log dedupe set in lockstep, so an\n // item that gets cancelled / fixed / re-spawned can re-trigger the\n // watchdog if it goes stuck again later in this session.\n for (const key of stuckKanbanLogged) {\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 stuckKanbanLogged.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 a kanban card tied to this run. The card id comes back so\n // finishRun can close it. initial_status (ENG-5599) selects the opening\n // column — default 'in_progress' (legacy oneshot), 'todo' for the in-session\n // route where the persistent session picks the card up itself.\n materialize_kanban?: {\n title: string;\n description?: string;\n priority?: number;\n initial_status?: 'todo' | 'in_progress';\n };\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\n// ---------------------------------------------------------------------------\n// ENG-5599 (Slice 1 of ENG-5576): route scheduled tasks through a todo card +\n// in-session nudge instead of a `claude -p` oneshot.\n//\n// Anthropic's billing change on `claude -p` makes a per-task oneshot\n// cost-prohibitive for persistent-session agents that already have a live\n// tmux session. Instead we materialise the firing as a TODO kanban card and\n// nudge the live session to work it in-session — the board becomes the work\n// queue (generalising the ENG-5404/5435 kanban-work cutover to all scheduled\n// tasks).\n//\n// Cost attribution (the ENG-5557 seam): the API stamps the per-card run_id\n// onto the materialised card (agent_kanban_items.run_id), and the nudge\n// carries that run's marker (formatRunMarker). The per-turn transcript parser\n// (ENG-5566) bounds the card's in-session turns by that marker, so\n// scheduled_task_cost_windows keeps attributing cost per task exactly as it\n// did for the oneshot — one run = one card = one firing — with no claude -p\n// process. A board-level inject run (ENG-5565) would NOT satisfy this: the\n// todo card would carry no run_id and a shared nudge run would over-attribute\n// to every due task. Hence the per-card run grain here.\n// ---------------------------------------------------------------------------\n\nexport function isScheduledViaKanbanEnabled(): boolean {\n const v = process.env['AGT_SCHEDULED_VIA_KANBAN'];\n return v === '1' || v?.toLowerCase() === 'true';\n}\n\n// codeName → (kanbanItemId → run_id) for scheduled-task runs opened on the\n// in-session route. A session teardown closes these so an interrupted run\n// doesn't hang open and mis-attribute the agent's later turns. Board-drain /\n// card-done close (the normal happy-path close) lands in S2 (ENG-5600).\nconst scheduledRunsByCode = new Map<string, Map<string, string>>();\n\n// ENG-5601: cardId delivery dedupe across the poll path (reconcileScheduledRuns)\n// and the realtime onCompletion handler, which can BOTH fire for the same done\n// card. Two states, keyed on the cardId alone — cardIds are globally-unique\n// UUIDs, so no per-agent bucket is needed, and that sidesteps the\n// poll-vs-onCompletion keying mismatch (poll has code_name, onCompletion can\n// fall back to the agent UUID when the agent isn't in the active set):\n// - `claimedScheduledCards`: a card someone is actively delivering (in-flight).\n// - `completedScheduledCards`: a card whose delivery RESOLVED (delivered or\n// terminally not-deliverable). The claim alone can't tell \"in-flight\" from\n// \"done\" — and conflating them lets the poll path close (and drop the retry\n// handle for) a run while the other path's delivery is still in flight and\n// might yet fail. So delivery returns a 4-state result and only a RESOLVED\n// state lets the caller close the run.\n// Deliberately NOT cleared on session teardown: a done card lingers in the\n// board's 24h done window and the run map (the retry handle) is dropped on\n// close, so these must outlive both to stop a re-delivery within the process.\n// (Cross-process restart can't be deduped without durable state — a known,\n// narrow limitation: a human re-completing an already-delivered card after a\n// manager restart could re-send. Bounded growth: one UUID per delivered card\n// over manager uptime.)\nconst claimedScheduledCards = new Set<string>();\nconst completedScheduledCards = new Set<string>();\n\n/** Outcome of a delivery attempt, telling the caller whether the run can close. */\ntype ScheduledDeliveryResult =\n | 'delivered' // we delivered it this call — caller may close the run\n | 'terminal' // not deliverable (wrong status/source/template, no task) OR already\n // delivered by the other path — caller may close the run\n | 'retry' // transient failure (claim released) — caller MUST keep the run open\n | 'in_flight'; // another path holds the claim and hasn't resolved — caller must\n // keep the run open; a later tick sees 'terminal' once it resolves\n\n// Claim a card for delivery. Returns true the first time (caller should\n// deliver), false if it's already claimed (in-flight or resolved).\nfunction claimScheduledCardDelivery(cardId: string): boolean {\n if (claimedScheduledCards.has(cardId)) return false;\n claimedScheduledCards.add(cardId);\n return true;\n}\n\n// Release a claim so a later attempt can retry — used when the delivery\n// fetch/post throws BEFORE processClaudeTaskResult ran (that helper swallows\n// its own errors, so a thrown error here always means delivery didn't happen).\nfunction releaseScheduledCardDelivery(cardId: string): void {\n claimedScheduledCards.delete(cardId);\n}\n\n// Mark a card's delivery as RESOLVED (delivered or terminal). The claim stays\n// held; completion is what lets a concurrent caller's `in_flight` settle to\n// `terminal` on its next look.\nfunction markScheduledCardDeliveryComplete(cardId: string): void {\n completedScheduledCards.add(cardId);\n}\n\n// Test-only: clear the delivery dedupe sets so a cardId constant reused across\n// test cases doesn't leak a claim between them. No production caller.\nexport function __resetScheduledDeliveryDedupeForTest(): void {\n claimedScheduledCards.clear();\n completedScheduledCards.clear();\n}\n\nfunction trackScheduledRun(codeName: string, cardId: string, runId: string): void {\n let m = scheduledRunsByCode.get(codeName);\n if (!m) {\n m = new Map();\n scheduledRunsByCode.set(codeName, m);\n }\n m.set(cardId, runId);\n}\n\n// True if this card was materialised by the in-session route\n// (routeScheduledTaskViaKanban) and still has an open per-card run. cardIds are\n// globally unique, so scanning every code's map is unambiguous — and it avoids\n// depending on a resolved codeName (the realtime path can only fall back to the\n// agent UUID when the agent isn't in the active set). Used to keep the\n// onCompletion delivery off oneshot/fallback scheduled_task cards (those are\n// materialised by executeAndProcessClaudeTask, which already self-delivers and\n// never records the card here).\nexport function isScheduledCardTracked(cardId: string): boolean {\n for (const m of scheduledRunsByCode.values()) {\n if (m.has(cardId)) return true;\n }\n return false;\n}\n\nfunction closeScheduledRunsForCode(\n codeName: string,\n outcome: 'completed' | 'failed' | 'cancelled' | 'timeout',\n reason: string,\n): void {\n const m = scheduledRunsByCode.get(codeName);\n if (!m || m.size === 0) return;\n scheduledRunsByCode.delete(codeName);\n for (const runId of m.values()) {\n void finishRun(runId, outcome, { outcomeMessage: reason });\n }\n}\n\n/** Full, untruncated delivery fields for one card (from POST /host/kanban-item). */\ninterface ScheduledCardForDelivery {\n id: string;\n title: string;\n status: string;\n result: string | null;\n deliverable: string | null;\n source_type: string | null;\n source_ref: string | null;\n last_actor_id: string | null;\n completed_at: string | null;\n}\n\n// ENG-5601: manager-side delivery from a done scheduled-task card. Centralises\n// delivery + suppression manager-side (the locked 2026-05-26 decision): the\n// in-session agent records its result on the card and marks it done WITHOUT\n// sending anything itself; the manager then reads the full result and delivers\n// via the existing processClaudeTaskResult path (deliveryMode/channel/to +\n// `<no-delivery/>` handling).\n//\n// Called from two places against a shared idempotency claim:\n// - reconcileScheduledRuns (poll path) — the PRIMARY trigger, because the\n// agent self-completes the card and realtime onCompletion is therefore\n// suppressed by isSelfCompletion.\n// - the realtime onCompletion handler — only for non-self (human) completions\n// of a scheduled-task card.\n//\n// Returns a ScheduledDeliveryResult so the caller knows whether it may close the\n// run. Only 'delivered'/'terminal' authorise closing it; 'retry' (transient\n// fetch failure, claim released) and 'in_flight' (another path holds an\n// unresolved claim) keep the run open so the retry handle survives.\nexport async function deliverScheduledCardResult(\n codeName: string,\n agentId: string,\n cardId: string,\n): Promise<ScheduledDeliveryResult> {\n if (!claimScheduledCardDelivery(cardId)) {\n // Someone else owns the claim. If their delivery already RESOLVED, this is\n // terminal (close the run); if it's still in flight, the caller must keep\n // the run open — closing it now would drop the retry handle should that\n // in-flight attempt fail and release the claim.\n return completedScheduledCards.has(cardId) ? 'terminal' : 'in_flight';\n }\n let card: ScheduledCardForDelivery | undefined;\n try {\n const resp = await api.post<{ item?: ScheduledCardForDelivery }>('/host/kanban-item', {\n agent_id: agentId,\n item_id: cardId,\n });\n card = resp.item;\n } catch (err) {\n // A 4xx is terminal: a 404 means the card was deleted between the board read\n // and this fetch (or a 400/403 — none retryable), so resolve it and let the\n // caller close the run rather than re-fetching a gone card every poll until\n // the stale-run reaper steps in. Only a 5xx / network/transport error is\n // transient: release the claim and signal retry so the run stays open.\n if (err instanceof ApiError && err.status >= 400 && err.status < 500) {\n log(`[scheduled-kanban] delivery: card ${cardId} on '${codeName}' fetch returned ${err.status} (${err.message}) — treating as terminal, not retrying`);\n markScheduledCardDeliveryComplete(cardId);\n return 'terminal';\n }\n releaseScheduledCardDelivery(cardId);\n log(`[scheduled-kanban] delivery fetch failed for card ${cardId} on '${codeName}': ${(err as Error).message} — will retry`);\n return 'retry';\n }\n\n // Terminal non-deliverable cases (resolve + let the caller close the run): a\n // failed/cancelled card has no deliverable; a non-scheduled_task card isn't\n // ours to deliver.\n if (!card || card.status !== 'done' || card.source_type !== 'scheduled_task' || !card.source_ref) {\n markScheduledCardDeliveryComplete(cardId);\n return 'terminal';\n }\n const state = claudeSchedulerStates.get(codeName) ?? loadSchedulerState(codeName);\n const task = state.tasks[card.source_ref];\n if (!task) {\n log(`[scheduled-kanban] delivery: no scheduler task for source_ref=${card.source_ref} on '${codeName}' — skipping`);\n markScheduledCardDeliveryComplete(cardId);\n return 'terminal';\n }\n // ENG-5601: only the \"plain\" templates route in-session (isPlainScheduledTemplate\n // gates routeScheduledTaskViaKanban). Guard the delivery path too — a human\n // completing a NON-plain scheduled_task card (e.g. a standup card the oneshot\n // path materialised) must not run processClaudeTaskResult's template handler\n // and parse free text as standup/plan JSON into /host/agent-status or the board.\n if (!isPlainScheduledTemplate(task.templateId)) {\n log(`[scheduled-kanban] delivery: card ${cardId} template '${task.templateId}' on '${codeName}' is not a plain scheduled template — skipping manager delivery`);\n markScheduledCardDeliveryComplete(cardId);\n return 'terminal';\n }\n // processClaudeTaskResult swallows its own errors internally, so this won't\n // throw; the card is delivered (or suppressed via <no-delivery/>) exactly once.\n await processClaudeTaskResult(codeName, agentId, task.templateId, card.result ?? '', {\n mode: task.deliveryMode,\n channel: task.deliveryChannel,\n to: task.deliveryTo,\n taskId: task.taskId,\n });\n markScheduledCardDeliveryComplete(cardId);\n log(`[scheduled-kanban] Delivered result from card ${cardId} (task '${task.name}') for '${codeName}'`);\n return 'delivered';\n}\n\n// ENG-5600: happy-path close. Given a fresh board snapshot, close the per-card\n// run for any tracked scheduled-task card the session has finished, and drop\n// the map entry — completing the todo → in_progress → done lifecycle without\n// leaking an open run. A card still todo/in_progress is left open; a card\n// absent from this snapshot is left for the server-side reaper rather than\n// guessed (it may just be filtered out of the board's done window). Idempotent:\n// only acts on cards we're tracking, and deletes the entry on close so a\n// repeated snapshot is a no-op.\nexport async function reconcileScheduledRuns(codeName: string, agentId: string, board: BoardItem[]): Promise<void> {\n const m = scheduledRunsByCode.get(codeName);\n if (!m || m.size === 0) return;\n const byId = new Map(board.map((i) => [i.id, i]));\n for (const [cardId, runId] of [...m.entries()]) {\n const card = byId.get(cardId);\n if (!card) continue; // not in snapshot — leave for the reaper, don't guess\n if (card.status === 'done') {\n // ENG-5601: deliver the agent's result, THEN close the run. This is the\n // PRIMARY delivery path — the agent self-completes the card so realtime\n // onCompletion is suppressed by isSelfCompletion and never fires here.\n // Critically, only drop the run-map entry once delivery has RESOLVED\n // ('delivered'/'terminal'). On a transient failure ('retry') or while\n // another path's claim is still in flight ('in_flight') the entry stays so\n // a later poll retries — the map IS the only retry handle, so deleting it\n // before a resolved delivery could silently drop the result.\n const result = await deliverScheduledCardResult(codeName, agentId, cardId);\n if (result === 'retry' || result === 'in_flight') continue;\n m.delete(cardId);\n void finishRun(runId, 'completed', { outcomeMessage: 'scheduled-task card done' });\n } else if (card.status === 'failed') {\n m.delete(cardId);\n void finishRun(runId, 'failed', { outcomeMessage: 'scheduled-task card failed' });\n }\n }\n if (m.size === 0) scheduledRunsByCode.delete(codeName);\n}\n\n// Materialise a due scheduled task as a TODO card bound to a per-card run, then\n// nudge the live session to work it. Returns true when the card was created\n// AND the nudge was delivered; false otherwise so the caller can fall back to\n// the `claude -p` oneshot (keeps the cutover reversible and never drops a\n// firing). Assumes the caller has already confirmed the session is healthy.\nexport async function routeScheduledTaskViaKanban(\n codeName: string,\n agentId: string,\n task: SchedulerTaskState,\n prompt: string,\n): Promise<boolean> {\n // Remaining limitation (delivery itself landed in S3 / ENG-5601 — the manager\n // now delivers from the done card via deliverScheduledCardResult): the card\n // description is still the raw task prompt. The oneshot path additionally\n // wraps it via wrapScheduledTaskPrompt — prior-runs context\n // (fetchPriorScheduledRuns, the last 24h of outputs so the agent reports only\n // what changed) and the [NOW] time anchor. The in-session agent inherits live\n // session context, but recurring \"report what's new\" tasks will re-report\n // until that wrapper is re-homed onto the card. Tracked as a follow-up.\n //\n // Open the run and materialise the card together. The card carries the task\n // prompt as its description (the in-session work spec) and starts in 'todo'\n // so the session promotes it itself.\n const { run_id, kanban_item_id } = 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, via: 'kanban-inject' },\n materialize_kanban: {\n title: task.name,\n description: prompt,\n priority: 2,\n initial_status: 'todo',\n },\n });\n\n if (!run_id || !kanban_item_id) {\n log(`[scheduled-kanban] materialise incomplete for task '${task.name}' on '${codeName}' (run_id=${run_id ? 'ok' : 'null'}, card=${kanban_item_id ? 'ok' : 'null'}) — falling back to claude -p`);\n // Close a half-opened run + card so the fallback oneshot doesn't leave a\n // phantom open run behind it.\n if (run_id) void finishRun(run_id, 'failed', { outcomeMessage: 'kanban materialise incomplete', completeKanbanItemId: kanban_item_id });\n return false;\n }\n\n // Targeted, per-card nudge. Plain text (not a slash command) so acpx delivers\n // it as a user turn; the run marker is appended so ENG-5566's per-turn parser\n // attributes this card's turns to this run. Title is JSON.stringify'd because\n // it's user-authored and could otherwise break the injected prompt shape\n // (same hardening as the realtime kanban-work trigger).\n // ENG-5601: the agent records its result on the card and marks it done, but\n // must NOT send/post the result itself — the manager delivers from the done\n // card (deliveryMode/channel/to honored centrally). Without this instruction\n // the card description (the raw task prompt) may tell the agent to \"send me\n // X\", which would double up with the manager's delivery.\n const nudge =\n `You have a new scheduled task on your kanban board: id=${kanban_item_id} ` +\n `title=${JSON.stringify(task.name)}. Call kanban_move(\"${kanban_item_id}\", \"in_progress\"), ` +\n `do the work described on the card, then write the finished result onto the card and mark it done. ` +\n `Do NOT send, post, or message the result to anyone yourself — recording it on the card is enough; ` +\n `delivery to the recipient is handled for you.\\n` +\n formatRunMarker(run_id);\n\n // Use the status-returning variant: a bare `false` from injectMessage is\n // ambiguous — tmux send-keys returns it even after the keystroke landed. If\n // we fell back to the oneshot on that, a non-acpx host would BOTH nudge the\n // live session and spawn claude -p, double-executing the task. So only the\n // \"neither delivered nor sent\" case is a real failure that warrants fallback.\n // A rejection here (vs the normal {delivered,fallbackUsed} resolve) would\n // exit after the run + card were materialised, and the caller would fall back\n // to claude -p — orphaning the card/run and double-executing the firing.\n // Catch it: close the card + run and return false (fall back cleanly).\n let injectStatus: import('./persistent-session.js').InjectResult;\n try {\n injectStatus = await injectMessageWithStatus(codeName, 'task', nudge, { task_name: 'scheduled-task' }, log);\n } catch (err) {\n log(`[scheduled-kanban] nudge injection threw for task '${task.name}' on '${codeName}': ${(err as Error).message} — closing card + falling back to claude -p`);\n void finishRun(run_id, 'failed', { outcomeMessage: 'scheduled-task nudge delivery threw', completeKanbanItemId: kanban_item_id });\n return false;\n }\n const { delivered, fallbackUsed } = injectStatus;\n if (!delivered && !fallbackUsed) {\n log(`[scheduled-kanban] nudge delivery failed for task '${task.name}' on '${codeName}' — closing card + falling back to claude -p`);\n // Close the card + run so the fallback oneshot doesn't double up on a card\n // the session never picked up.\n void finishRun(run_id, 'failed', { outcomeMessage: 'scheduled-task nudge delivery failed', completeKanbanItemId: kanban_item_id });\n return false;\n }\n if (!delivered && fallbackUsed) {\n // Sent via tmux send-keys (submission unverified). Treat as committed so we\n // don't also spawn the oneshot; if the keystroke didn't submit, the card\n // sits in 'todo' and recovery is the S2 reaper's job — preferable to the\n // double-billing this epic exists to eliminate.\n log(`[scheduled-kanban] nudge sent via send-keys (unverified) for task '${task.name}' on '${codeName}' — proceeding without oneshot fallback`);\n }\n\n trackScheduledRun(codeName, kanban_item_id, run_id);\n log(`[scheduled-kanban] Routed task '${task.name}' for '${codeName}' via todo card ${kanban_item_id} run_id=${run_id}`);\n return true;\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 injectMessageWithStatus,\n isSessionHealthy,\n getSessionState,\n resetRestartCount,\n stopAllSessions,\n stopAllSessionsAndWait,\n getProjectDir as psGetProjectDir,\n resolveClaudeBinary,\n getLastFailureContext,\n prepareForRespawn,\n takeZombieDetection,\n ensureKanbanLoopArmed,\n isKanbanLoopArmedForCurrentSession,\n sendToAgent,\n isAgentPromptReady,\n isKanbanLoopDisabled,\n readPaneLogTail,\n} from './persistent-session.js';\nimport { isAgentIdle, isStaleForToday, peekCurrentSession } from './daily-session.js';\nimport { processRestartFlags } from './restart-handler.js';\nimport { PersistentSessionStuckTracker } from './persistent-session-stuck-tracker.js';\n\n// Track which agents have persistent sessions started\nconst persistentSessionAgents = new Set<string>();\n\n// ENG-5459: per-agent dedupe for the pane.log MCP-failure-banner\n// observation. Manager only POSTs when the count differs from the last\n// reported value, so a stable healthy agent (banner-not-present →\n// count 0) emits at most one POST per fresh worker.\nconst lastMcpFailedBannerCount = new Map<string, number>();\n\n/**\n * ENG-5116 — discriminator for the *one decision* a persistent-session\n * tick made for a given agent. Every code path inside\n * `ensurePersistentSession` records exactly one of these so the caller\n * can emit a single structured `[persistent-session-decision]` log line\n * even when the path took an early return. Before this, only the happy\n * path logged (\"Starting tmux session ...\") and silent skips were\n * effectively invisible — see the Vigil incident (2026-05-16).\n */\ntype PersistentSessionDecision =\n | 'spawn' // startPersistentSession() invoked this tick\n | 'healthy' // session is up; tasks may have synced\n | 'restart-pending' // stopped this tick (auth tuple changed); next tick respawns\n | 'day-rollover-restart' // stopped this tick for date rollover; next tick respawns\n | 'day-rollover-deferred' // stale but agent still active mid-task; will retry\n | 'circuit-tripped' // ENG-5441: breaker tripped — no spawn until operator resumes\n | 'skipped-no-api-key'\n | 'skipped-auth-resolve-failed'\n | 'skipped-not-authenticated'\n | 'skipped-api-key-mode-no-key';\n\ninterface PersistentSessionResult {\n decision: PersistentSessionDecision;\n /** True when `startPersistentSession` was called this tick. */\n spawnAttempted: boolean;\n /** Session health observed AFTER the tick's work — what the next tick will see. */\n sessionHealthyAfter: boolean;\n /** Optional human-readable suffix for the decision log line. */\n detail?: string;\n}\n\n/**\n * ENG-5116 — once an agent has been unhealthy without a respawn for\n * `threshold` consecutive ticks, emit a loud WARN exactly once until\n * the streak breaks. See `persistent-session-stuck-tracker.ts`.\n */\nconst persistentSessionStuckTracker = new PersistentSessionStuckTracker();\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<PersistentSessionResult> {\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 // ENG-5441: circuit breaker — if this agent's session restart count\n // crossed the threshold inside the sliding window, refuse to spawn\n // until an operator clears the trip (currently: webapp resume / agt\n // agent resume flips the agent's status from paused → active, which\n // the breaker observes on the next poll via clearBreakerOnResume).\n if (restartBreaker.isTripped(codeName)) {\n const trip = restartBreaker.getTrip(codeName)!;\n return {\n decision: 'circuit-tripped',\n spawnAttempted: false,\n sessionHealthyAfter: isSessionHealthy(codeName),\n detail: trip.statusMessage,\n };\n }\n\n // ENG-5371: resolve the agent's IANA timezone from team settings (same\n // source as ENG-5363's channel-MCP TZ env var). Used to scope the\n // daily-session day rollover so it fires at the agent's midnight\n // rather than host UTC. `undefined` keeps the original ENG-4642\n // host-local behaviour, which is what we want for teams that never\n // configured a timezone.\n const teamSettingsForTz = (\n refreshData.team as { settings?: Record<string, unknown> } | undefined\n )?.settings;\n const agentTimezone = (() => {\n const tz = teamSettingsForTz?.['timezone'];\n return typeof tz === 'string' && tz.trim() !== '' ? tz.trim() : undefined;\n })();\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 decision: 'skipped-no-api-key',\n spawnAttempted: false,\n sessionHealthyAfter: isSessionHealthy(codeName),\n };\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 decision: 'skipped-auth-resolve-failed',\n spawnAttempted: false,\n sessionHealthyAfter: isSessionHealthy(codeName),\n detail: msg,\n };\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 decision: 'skipped-not-authenticated',\n spawnAttempted: false,\n sessionHealthyAfter: isSessionHealthy(codeName),\n };\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 decision: 'skipped-api-key-mode-no-key',\n spawnAttempted: false,\n sessionHealthyAfter: isSessionHealthy(codeName),\n };\n }\n\n // ENG-5116: track which reason (if any) triggered a session stop on this\n // tick. Used at the end of the function to distinguish \"we stopped and\n // also respawned\" (decision=spawn) from the silent-bug case \"we stopped\n // but didn't respawn this tick\" (decision=restart-pending /\n // day-rollover-restart, with sessionHealthyAfter=true exposing the\n // false-positive that took Vigil down).\n let restartTrigger: 'auth-tuple' | 'day-rollover' | null = null;\n let dayRolloverDeferred = false;\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, 'auth-tuple-change');\n persistentSessionAgents.delete(codeName);\n restartTrigger = 'auth-tuple';\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, new Date(), agentTimezone) && 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, 'day-rollover');\n persistentSessionAgents.delete(codeName);\n restartTrigger = 'day-rollover';\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 dayRolloverDeferred = true;\n }\n }\n }\n\n // ENG-5441 + CodeRabbit on PR #1302: re-check the breaker after the\n // auth-tuple-change and day-rollover blocks. Either of those can call\n // `recordRestartForBreaker` for the (max+1)th time *inside this tick*,\n // and without a re-gate the spawn block below would still respawn.\n // The initial gate at the top of this function catches *already-tripped*\n // breakers; this second check catches the case where this tick is the\n // one that trips it.\n if (restartBreaker.isTripped(codeName)) {\n const trip = restartBreaker.getTrip(codeName)!;\n return {\n decision: 'circuit-tripped',\n spawnAttempted: false,\n sessionHealthyAfter: isSessionHealthy(codeName),\n detail: trip.statusMessage,\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 // ENG-5391: if this unhealthy detection was the dead-claude-in-live-tmux\n // case, ship an audit event so we can graph occurrences. Fire-and-forget —\n // a failed POST must not block the respawn that follows.\n const zombie = takeZombieDetection(codeName);\n if (zombie) {\n log(\n `[persistent-session] ZOMBIE DETECTED for '${codeName}' — tmux session ` +\n `${zombie.tmuxSession} alive but no claude process inside; killed shell, will respawn`,\n );\n getHostId()\n .then((hostId) => {\n if (!hostId) return;\n // CodeRabbit: pane output is untrusted (echoed user prompts,\n // tool output, secrets). Same redaction policy as the manager\n // log — ship a sha256 prefix the operator can correlate with\n // ~/.augmented/<codeName>/pane.log instead of the raw text.\n const paneTailHash = zombie.paneTail\n ? `sha256:${createHash('sha256').update(zombie.paneTail).digest('hex').slice(0, 12)}`\n : null;\n return api.post('/host/events', {\n host_id: hostId,\n agent_code_name: codeName,\n event_type: 'manager.agent_zombie_detected',\n payload: {\n agent_code_name: codeName,\n agent_id: agent.agent_id,\n tmux_session: zombie.tmuxSession,\n detected_at: new Date(zombie.detectedAt).toISOString(),\n pane_tail_hash: paneTailHash,\n pane_tail_redacted: zombie.paneTail !== null,\n },\n timestamp: new Date(zombie.detectedAt).toISOString(),\n });\n })\n .catch((err) => {\n log(`[persistent-session] Failed to forward zombie audit event for '${codeName}': ${(err as Error).message}`);\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 // ENG-5397: provision SessionStart orient hook (idempotent). Replaces\n // --resume's transcript continuity with a fresh-session + injected\n // orientation context model.\n try {\n provisionOrientHook(codeName);\n } catch (err) {\n log(`[persistent-session] Failed to provision orient hook for '${codeName}': ${(err as Error).message}`);\n }\n\n // ENG-5051: mint a run UUID for the persistent session so Claude\n // Code's `${AGT_RUN_ID}` substitution in .mcp.json resolves to a\n // real value. Without this the cloud-broker MCP boots with the\n // literal `${AGT_RUN_ID}` placeholder, fails its startup guard,\n // and every aws_request_access 400s. Best-effort: if startRun\n // fails, we fall through to the legacy behaviour (placeholder\n // leaks; agent has to mint manually). Tied to the SESSION boot,\n // not per-tool-call — one run UUID covers everything inside this\n // long-running session, which matches the cloud-broker's\n // single-startup-guard semantics.\n const sessionRunResult = await startRun({\n agent_id: agent.agent_id,\n source_type: 'system',\n metadata: { type: 'persistent_session_boot' },\n });\n\n // ENG-5631: resolve the agent's model (agent override → org → platform\n // default) and hand it to the launcher so it can pass `--model <alias>`.\n // Same resolution as provisioning/registration above (resolveModelChain).\n const spawnPrimaryModel =\n resolveModelChain(refreshData).primary ??\n ((refreshData.agent as Record<string, unknown> | undefined)?.['primary_model'] as\n | string\n | null\n | undefined) ??\n null;\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 primaryModel: spawnPrimaryModel,\n runId: sessionRunResult.run_id,\n agentTimezone,\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 {\n decision: 'spawn',\n spawnAttempted: true,\n sessionHealthyAfter: isSessionHealthy(codeName),\n detail: restartTrigger ? `trigger=${restartTrigger}` : undefined,\n };\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-5405: when the in-session `/loop` is doing the board polling\n // (armed by ENG-5404 at session bootstrap), the external `claude -p\n // kanban-work` cron is redundant — and the redundancy is noisy:\n // subprocess spawn cost ~every 5 minutes per agent, plus the\n // parallel control path that ENG-5408's policy is moving off.\n // Flag-on => skip; flag-off (default) => legacy cron continues so\n // the cutover is reversible by toggling the env var.\n //\n // CodeRabbit PR #1275: gate the skip on the loop ACTUALLY being\n // armed for the current session UUID — not just on the env var.\n // The arm path is asynchronous (waitForPromptReady + injectMessage),\n // so on the first healthy tick after spawn or on a failed arm we\n // can land here with the cron suppressed but no loop running. Fall\n // back to firing the legacy task in that window so the board is\n // never left unattended.\n if (\n KANBAN_WORK_TEMPLATES.has(task.templateId) &&\n isKanbanWorkCronDisabled() &&\n isKanbanLoopArmedForCurrentSession(codeName)\n ) {\n // Mark the task as fired so the scheduler doesn't keep treating\n // it as \"ready\" every tick (otherwise the ready-log would spam\n // with the same skipped task every poll).\n const updated = markTaskFired(codeName, task.taskId, 'ok');\n claudeSchedulerStates.set(codeName, updated);\n continue;\n }\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-5599 (S1 of ENG-5576): for a healthy persistent session, route the\n // firing through a todo card + in-session nudge instead of a `claude -p`\n // oneshot (Anthropic's headless billing change). Excluded for S1:\n // - kanban-work (own in-session loop, ENG-5404/5408); and\n // - templates whose claude -p result drives side effects beyond message\n // delivery — standup → /host/agent-status, plan/task-update → board\n // writes in processClaudeTaskResult. Re-homing those side effects\n // in-session is S3 (ENG-5601); until then they keep the oneshot.\n // Falls back to claude -p when the flag is off, the template is excluded,\n // or the route can't materialise/deliver — the cutover stays reversible\n // and a firing is never dropped. We're already inside the isSessionHealthy\n // branch, so no extra health check is needed.\n if (isScheduledViaKanbanEnabled() && isPlainScheduledTemplate(task.templateId)) {\n let routed = false;\n try {\n routed = await routeScheduledTaskViaKanban(codeName, agent.agent_id, task, prompt);\n } catch (err) {\n // The route threw before committing (no card/nudge). Don't leak the\n // in-flight/concurrency guard — fall through to the oneshot, which\n // holds the guard and releases it in its own .finally().\n log(`[scheduled-kanban] route threw for '${task.name}' on '${codeName}': ${(err as Error).message}`);\n routed = false;\n }\n if (routed) {\n // Commit point: the firing has happened in-session (card materialised\n // + nudge delivered), so we must NOT also spawn the oneshot. Advance\n // the schedule, but guard markTaskFired's state write so a disk error\n // can't leak the in-flight/concurrency guard (which would wedge the\n // task until restart). A failed advance just risks a re-fire next\n // cycle — idempotent pickup is S2 (ENG-5600).\n try {\n const updated = markTaskFired(codeName, task.taskId, 'ok');\n claudeSchedulerStates.set(codeName, updated);\n } catch (err) {\n log(`[scheduled-kanban] markTaskFired failed for '${task.name}' on '${codeName}': ${(err as Error).message}`);\n }\n if (task.scheduleKind === 'at') {\n api.post('/host/schedules/disable', { agent_id: agent.agent_id, task_id: task.taskId }).catch((err) =>\n log(`[scheduled-kanban] Failed to disable one-off task '${task.name}': ${(err as Error).message}`),\n );\n }\n // No long-running subprocess on the routed path — release the guard.\n inFlightClaudeTasks.delete(task.taskId);\n claudeTaskConcurrency.set(codeName, Math.max(0, (claudeTaskConcurrency.get(codeName) ?? 1) - 1));\n // One task per cycle in persistent mode (mirrors the oneshot path).\n break;\n }\n // routed === false → fall through to the claude -p oneshot below.\n }\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 // ENG-5116: we reach here only when the spawn branch was NOT entered,\n // i.e. `isSessionHealthy(codeName)` returned true at line ~5719.\n // - restartTrigger set ⇒ we stopped the session earlier on this tick\n // but somehow it still looks healthy. That's the Vigil silent-bug\n // signature: surface it explicitly so the caller can warn after N\n // consecutive ticks instead of waiting hours for a customer to\n // notice. (Pre-ENG-5116 this collapsed into the same \"tasks synced\"\n // log as a normal healthy tick.)\n // - dayRolloverDeferred set ⇒ stale-for-today but agent mid-turn;\n // benign, will retry next tick.\n // - otherwise ⇒ ordinary healthy steady state.\n const finalDecision: PersistentSessionDecision =\n restartTrigger === 'auth-tuple'\n ? 'restart-pending'\n : restartTrigger === 'day-rollover'\n ? 'day-rollover-restart'\n : dayRolloverDeferred\n ? 'day-rollover-deferred'\n : 'healthy';\n return {\n decision: finalDecision,\n spawnAttempted: false,\n sessionHealthyAfter: isSessionHealthy(codeName),\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 isRealtimeSocketConnected,\n shouldForcePollFallback,\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\n/**\n * ENG-5614: reconcile the realtime channel-subscription flag against the\n * actual phoenix socket state. A half-open websocket can leave\n * `isRealtimeConnected()` reporting `true` while the socket is dead and the\n * channel's CLOSED/CHANNEL_ERROR callback never fired — inbound direct-chat\n * delivery silently stops, yet the manager keeps skipping the polling\n * fallback (it only polls `if (!isRealtimeConnected())`). The manager\n * heartbeat stays fresh the whole time (this same poll cycle posts it), so\n * nothing else catches it.\n *\n * On divergence we tear the realtime client down (which clears the flag and\n * nulls the client) and reset the subscription flags so `ensureRealtime\n * Started()` rebuilds a fresh client+subscription next tick. Because the flag\n * is now false, the polling fallback runs THIS cycle — inbound delivery is\n * restored immediately rather than waiting on the ~125-min synthetic-probe\n * backstop. Returns true when a heal was performed (for heartbeat reporting).\n */\nfunction healRealtimeSocketDivergence(): boolean {\n if (!shouldForcePollFallback(isRealtimeConnected(), isRealtimeSocketConnected())) return false;\n log('[realtime] Socket divergence — channel flag connected but socket dead; forcing poll fallback + reconnect');\n stopRealtimeChat();\n realtimeStarted = false;\n realtimeDriftStarted = false;\n realtimeAssignStarted = false;\n realtimeConfigStarted = false;\n realtimeKanbanStarted = false;\n subscribedIntegrationContextAgentIds = new Set();\n return true;\n}\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 // ENG-5599: scheduled-task cards are already nudged by\n // routeScheduledTaskViaKanban with their own per-card run marker. The\n // generic kanban-work trigger here would double-nudge them (and the\n // second nudge carries no marker, so any turns it provokes are\n // unattributed) — skip them.\n if (item.source_type === 'scheduled_task') return;\n\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 // ENG-5405: include the item's id in the inject so the agent\n // can call kanban_move(id, \"in_progress\") directly instead of\n // having to kanban_list first to look up its UUID.\n // CodeRabbit PR #1275: JSON.stringify the title — see the\n // ensurePersistentSession poll-driven trigger above for the\n // same hardening rationale (user-authored content can break\n // the injected prompt shape).\n injectMessage(agent.codeName, 'task', `New task added to your board: id=${item.id} title=${JSON.stringify(item.title)} priority=${item.priority}. Call kanban_move(\"${item.id}\", \"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 // ENG-5601: secondary delivery path, for a HUMAN completing the agent's\n // scheduled-task card from the console. Three gates, all load-bearing:\n // 1. Behind the rollout flags. Manager-side delivery is the new model;\n // when neither flag is on, the legacy claude -p oneshot owns\n // delivery. The poll path is already flag-gated upstream; mirror it.\n // 2. `user:` actor only. Agent self-completion (the in-session happy\n // path) is suppressed upstream by isSelfCompletion and handled by\n // the poll path; the oneshot's own `manager:scheduled-task` closure\n // classifies as 'unknown' and is excluded.\n // 3. Manager-ROUTED cards only (isScheduledCardTracked). The flag +\n // actor gates alone aren't enough: executeAndProcessClaudeTask (the\n // oneshot/fallback path) ALSO materialises plain scheduled_task\n // cards and already self-delivers, but never records them in\n // scheduledRunsByCode. Restricting to tracked cards keeps this off\n // those — matching the poll path, which only ever iterates tracked\n // cards — so a human completing an oneshot card can't double-send.\n // deliverScheduledCardResult shares the idempotency claim with the poll\n // path, so a human closing a card the poll already delivered is a no-op.\n if (\n event.status === 'done' &&\n (isScheduledViaKanbanEnabled() || isKanbanHybridEnabled()) &&\n classifyActor(event.last_actor_id, event.agent_id) === 'user' &&\n isScheduledCardTracked(event.item_id)\n ) {\n void deliverScheduledCardResult(codeName, event.agent_id, event.item_id);\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('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&apos;');\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']);\n\n// ENG-5599 (S1 of ENG-5576): a \"plain\" scheduled task is one whose claude -p\n// result only needs delivering — no template-specific side effects in\n// processClaudeTaskResult (standup posts to /host/agent-status; plan /\n// task-update write board items) and not the kanban-work loop. Only plain\n// tasks are eligible for the in-session kanban route in S1; the rest keep the\n// oneshot until their side effects are re-homed in-session (S3, ENG-5601).\nexport function isPlainScheduledTemplate(templateId: string): boolean {\n return (\n !STANDUP_TEMPLATES.has(templateId) &&\n !TASK_UPDATE_TEMPLATES.has(templateId) &&\n !PLAN_TEMPLATES.has(templateId) &&\n !KANBAN_WORK_TEMPLATES.has(templateId)\n );\n}\n\n// ENG-5405: opt-in cutover from external `claude -p kanban-work` cron to\n// the in-session `/loop kanban-work` armed at bootstrap by ENG-5404. When\n// truthy, persistent-mode Claude Code agents stop firing kanban-work\n// subprocesses — the live REPL handles board polling via the loop.\n// Non-persistent (oneshot) agents are unaffected: they keep firing\n// kanban-work via `claude -p` because they have no live REPL to inject\n// into. The flag is read every poll tick so operators can toggle without\n// a restart. Truthy = literal '1' or 'true' (case-insensitive); anything\n// else preserves the legacy cron behaviour.\nexport function isKanbanWorkCronDisabled(): boolean {\n const v = process.env['AGT_DISABLE_KANBAN_WORK_CRON'];\n return v === '1' || v?.toLowerCase() === 'true';\n}\n\n// ENG-5435: hybrid kanban-work — manager-gated edge-triggered injection.\n// Trigger lives in step 7e of the per-agent poll. Helpers below.\n//\n// Layering with the two predecessor env vars (see operator matrix in\n// docs/research/eng-5435-hybrid-kanban-inject.md):\n// AGT_DISABLE_KANBAN_WORK_CRON=1 — legacy cron off (ENG-5405)\n// AGT_DISABLE_KANBAN_LOOP=1 — in-session /loop off (ENG-5433)\n// AGT_KANBAN_HYBRID=1 — manager-gated inject on ← this\n// Set all three for clean hybrid mode. `warnOnConflictingKanbanModes`\n// fires a startup WARN if HYBRID is on without both predecessors off,\n// because both kanban sources running at once doubles token spend.\n\nexport function isKanbanHybridEnabled(): boolean {\n const v = process.env['AGT_KANBAN_HYBRID'];\n return v === '1' || v?.toLowerCase() === 'true';\n}\n\nexport function isKanbanHybridDryRun(): boolean {\n const v = process.env['AGT_KANBAN_HYBRID_DRY_RUN'];\n return v === '1' || v?.toLowerCase() === 'true';\n}\n\n// Same status set as ACTIONABLE_STATUSES (both exclude `backlog` as of\n// ENG-5605 — backlog is curatorial, not \"pick this up now\"; the board owner\n// promotes backlog → todo when ready). The hybrid path's *additional* narrowing\n// over the plain kanban-work gate is the scheduled_task exclusion in\n// isHybridActionable below, not the status set.\nconst HYBRID_ACTIONABLE_STATUSES = new Set(['todo', 'in_progress']);\n\n// ENG-5600: a board item the generic hybrid nudge should act on. Excludes\n// scheduled_task cards — those are driven by routeScheduledTaskViaKanban's own\n// per-card nudge + run (ENG-5599). Letting the board-level nudge also fire for\n// them opens a competing source_type='kanban' run whose marker would absorb the\n// card's turns, mis-attributing the scheduled task's cost (and double-nudging).\nexport function isHybridActionable(item: BoardItem): boolean {\n return HYBRID_ACTIONABLE_STATUSES.has(item.status) && item.source_type !== 'scheduled_task';\n}\n\nexport function hasHybridActionableItems(items: BoardItem[]): boolean {\n return items.some(isHybridActionable);\n}\n\n// Module-level debounce state. See design-doc Q3 for the\n// \"no manager-state.json persistence\" rationale.\nconst lastKanbanInjectAt = new Map<string, number>();\n\n// ENG-5565: the currently-open injected `runs` row per agent (codeName →\n// run_id). Each manager-injected kanban nudge opens a run; the next inject\n// (the agent is idle again, so the prior work is done) closes it. The run_id\n// is also written into the injected prompt as a marker (see formatRunMarker)\n// so the per-turn transcript parser can attribute that run's token usage.\n// At most one open injected run per agent; the last one stays open until the\n// next inject (or session end) — fine, since attribution is marker-bounded.\nconst openInjectedRunByCode = new Map<string, string>();\n\n// acpx delivers a file-loaded user message downstream of the REPL slash\n// parser, so slash commands must go via tmux send-keys (and carry no marker).\n// Plain-text nudges (e.g. the kanban check) go via acpx + a run marker.\nfunction isSlashCommand(command: string): boolean {\n return command.trimStart().startsWith('/');\n}\n\n// ENG-5565: close an agent's open injected run if one exists. Fire-and-forget\n// (the map is drained synchronously; finishRun POSTs in the background). Called\n// when the board drains (kanban work done), the next inject supersedes it, or\n// the session is torn down — so a run never hangs open past its real\n// prompt-ready boundary, which would mis-attribute later turns. A manager\n// restart that loses this in-memory map can't close in-process; a server-side\n// stale-run reaper is the backstop there (tracked for B3/hardening).\nfunction closeInjectedRunIfOpen(\n codeName: string,\n outcome: 'completed' | 'failed' | 'cancelled' | 'timeout',\n outcomeMessage: string,\n): void {\n const runId = openInjectedRunByCode.get(codeName);\n if (!runId) return;\n openInjectedRunByCode.delete(codeName);\n void finishRun(runId, outcome, { outcomeMessage });\n}\n\n// Floor at 10s — anything tighter risks pummelling the REPL when the\n// agent's first response itself adds new actionable items.\n// CR (CodeRabbit on PR #1293): clamp sub-10 overrides to 10s rather\n// than discarding them and falling back to the 60s default. An operator\n// who sets `=5` is asking for \"as fast as you can safely go\", not \"give\n// me a slow default.\"\nconst KANBAN_HYBRID_DEBOUNCE_MS = (() => {\n const raw = process.env['AGT_KANBAN_HYBRID_DEBOUNCE_SECONDS'];\n const parsed = raw ? parseInt(raw, 10) : NaN;\n if (Number.isFinite(parsed) && parsed > 0) return Math.max(parsed, 10) * 1000;\n return 60_000;\n})();\n\n/**\n * Per-agent inject decision: debounce-then-idle-then-send. Stamps the\n * debounce BEFORE the idle check (design-doc Q3 mitigation) so two\n * back-to-back ticks don't both proceed into a double send. Idle skip\n * is a soft-fail: the agent gets another shot on the next tick.\n *\n * Fire-and-forget — caller MUST NOT await the outer promise, or a\n * busy agent's 10s `waitForPromptReady` cap serialises every later\n * agent in the same poll cycle.\n */\nexport async function maybeInjectKanbanCheck(\n codeName: string,\n agentId: string,\n actionableCount: number,\n): Promise<void> {\n const lastAt = lastKanbanInjectAt.get(codeName) ?? 0;\n if (Date.now() - lastAt < KANBAN_HYBRID_DEBOUNCE_MS) {\n // Quiet log — every 5s poll tick on a chatty board would otherwise\n // emit a debounce line per agent. Only log every ~10th skip via a\n // counter would be nicer; for v1 we just log nothing on debounce-\n // skip and let the next successful inject mark the cadence.\n return;\n }\n // Stamp NOW so a concurrent tick can't race past the debounce. A\n // failed send (idle skip, tmux gone) leaves the stamp in place for\n // one debounce window — acceptable: the next inject just waits one\n // window before retrying.\n lastKanbanInjectAt.set(codeName, Date.now());\n\n const tmuxSession = `agt-${codeName}`;\n const ready = await isAgentPromptReady(tmuxSession);\n if (!ready) {\n log(`[manager-worker] kanban inject skipped — agent busy ('${codeName}')`);\n return;\n }\n\n if (isKanbanHybridDryRun()) {\n log(\n `[manager-worker] kanban inject DRY-RUN — would inject for '${codeName}' (${actionableCount} actionable items)`,\n );\n // Roll back the stamp so the next tick can DRY-RUN-log again\n // immediately. Without this, dry-run output would only appear once\n // per debounce window per agent and be hard to use as a verifier.\n lastKanbanInjectAt.delete(codeName);\n return;\n }\n\n // ENG-5565: the agent is idle (ready), so any previously-injected run is\n // finished — close it before opening the next. Marker-bounded attribution\n // means the prior run's turns are everything up to this new marker.\n closeInjectedRunIfOpen(codeName, 'completed', 'kanban inject superseded by next inject');\n\n // Open a run for this injection so its token usage is attributable. A\n // start failure (run_id null) is non-fatal: we still nudge, just without\n // a marker this cycle (the work counts toward the agent total, unattributed).\n const { run_id } = await startRun({\n agent_id: agentId,\n source_type: 'kanban',\n metadata: { injected: 'kanban-check', actionable_count: actionableCount },\n });\n\n // Deliver the nudge. Plain text → acpx file-inject with the run marker\n // appended as an inert trailing line (acpx isn't paste-limited and lands a\n // user turn the per-turn parser can read). A slash command would break\n // under acpx, so it falls back to tmux send-keys and carries no marker.\n let delivered: boolean;\n if (isSlashCommand(KANBAN_CHECK_COMMAND)) {\n delivered = sendToAgent(tmuxSession, KANBAN_CHECK_COMMAND);\n } else {\n const content = run_id\n ? `${KANBAN_CHECK_COMMAND}\\n${formatRunMarker(run_id)}`\n : KANBAN_CHECK_COMMAND;\n delivered = await injectMessage(codeName, 'system', content, undefined, log);\n }\n\n if (delivered) {\n if (run_id) openInjectedRunByCode.set(codeName, run_id);\n log(\n `[manager-worker] Injected kanban check for '${codeName}' (${actionableCount} actionable items)${run_id ? ` run_id=${run_id}` : ''}`,\n );\n } else {\n // Delivery failed — don't leave a phantom open run for a nudge that\n // never landed. Close it so it isn't billed turns it didn't cause.\n if (run_id) {\n void finishRun(run_id, 'failed', { outcomeMessage: 'kanban inject delivery failed' });\n }\n log(`[manager-worker] kanban inject failed for '${codeName}'`);\n }\n}\n\n/**\n * Single WARN at manager start when AGT_KANBAN_HYBRID is on but a\n * predecessor isn't disabled — both sources would fire simultaneously\n * and double-spend tokens. We warn rather than refuse to start so a\n * bad env push can't strand the host.\n */\nexport function warnOnConflictingKanbanModes(): void {\n if (!isKanbanHybridEnabled()) return;\n const cronOff = isKanbanWorkCronDisabled();\n const loopOff = isKanbanLoopDisabled();\n if (!cronOff || !loopOff) {\n const stillOn = !cronOff && !loopOff\n ? 'AGT_DISABLE_KANBAN_WORK_CRON and AGT_DISABLE_KANBAN_LOOP are'\n : !cronOff\n ? 'AGT_DISABLE_KANBAN_WORK_CRON is'\n : 'AGT_DISABLE_KANBAN_LOOP is';\n log(\n `[manager-worker] WARN AGT_KANBAN_HYBRID=1 but ${stillOn} not set — ` +\n 'kanban-work will fire from MULTIPLE sources (doubles token spend). ' +\n 'Set both predecessors to 1 for clean hybrid mode.',\n );\n }\n}\nconst BOARD_INJECT_TEMPLATES = new Set(['morning-plan', 'task-update', 'hourly-status', 'end-of-day-summary', 'kanban-work']);\n\n// ENG-4410: Check if the board has work the kanban-work loop will actually act\n// on, used to skip the kanban-work claude -p spawn when there's nothing to do.\n// ENG-5605: `backlog` is deliberately EXCLUDED. The kanban-work loop only works\n// `todo`/`in_progress` cards (it promotes the top `todo`); a card parked in\n// `backlog` is curatorial, not ready. Counting backlog here made an agent with\n// backlog cards spawn claude -p every ~5 min only to return `<no-delivery/>` —\n// no-op subprocess spawns re-paying the full prompt-cache prefix for zero work\n// (observed on maven, agt-aws-1). This now matches HYBRID_ACTIONABLE_STATUSES.\nconst ACTIONABLE_STATUSES = new Set(['todo', 'in_progress']);\nexport function 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; source_type?: string; source_ref?: 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 guardrails?: ProvisionInput['guardrails'];\n // ENG-5380: active kanban tasks payload from /host/refresh. The API\n // caps at 10 and filters to todo/in_progress; here we just thread it\n // through to the adapter behind the feature flag.\n active_tasks?: ProvisionInput['activeTasks'];\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 // ENG-5380: feature-flag gate for the session-start active-tasks\n // injection. AGT_ACTIVE_TASKS_INJECT=1 turns it on; anything else keeps\n // the legacy behaviour where CLAUDE.md doesn't carry an Active Tasks\n // section. Gating here (manager, not adapter) keeps `identity.ts` pure\n // — the adapter just renders whatever the manager hands it.\n const activeTasksInjectEnabled = process.env['AGT_ACTIVE_TASKS_INJECT'] === '1';\n const activeTasksFromRefresh = activeTasksInjectEnabled\n ? (refreshData.active_tasks ?? [])\n : undefined;\n // Log the rendered section's token cost per refresh so we can measure\n // the budget impact across a week before deciding whether to promote\n // this to per-turn injection (ENG-5379 acceptance criterion #3). 0\n // tokens prints too when the list is empty — visibility into the\n // \"flag on, no tasks\" path matters for the rollout decision.\n if (activeTasksInjectEnabled) {\n const tokens = estimateActiveTasksTokens(activeTasksFromRefresh);\n log(\n `[provision/active-tasks] agent=${agent.code_name} count=${activeTasksFromRefresh?.length ?? 0} estimated_tokens=${tokens}`,\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 activeTasks: activeTasksFromRefresh,\n // ENG-5009: org name for the identity-preamble. Only present when\n // /host/refresh ships the field — falls through cleanly for older\n // API payloads that don't carry it.\n organization: (() => {\n // CodeRabbit on PR #941: trim before the length check so a\n // whitespace-only value (`' '`) doesn't render a broken\n // \"in the X team at ** **\" scope in CLAUDE.md.\n const org = (refreshData as unknown as { organization?: { name?: unknown } | null })\n .organization;\n if (typeof org?.name !== 'string') return undefined;\n const name = org.name.trim();\n return name.length > 0 ? { name } : undefined;\n })(),\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 guardrails: refreshData.guardrails,\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 // ENG-5041: extend the inter-poll delay when the last cycle failed, so\n // a sustained API outage doesn't hammer the cloud every few seconds.\n // Returns 0 when consecutivePollFailures is 0, so healthy hosts use\n // the original interval verbatim.\n const backoffMs = currentPollBackoffMs(consecutivePollFailures);\n const delayMs = Math.max(config.intervalMs, backoffMs);\n pollTimer = setTimeout(() => {\n if (restartAfterUpgrade) {\n restartNow();\n return;\n }\n void pollCycle().then(scheduleNext);\n }, delayMs);\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 // ENG-5599: close any in-session scheduled-task runs still tracked in memory.\n // The per-agent stop paths (stopPersistentSessionAndForgetMcpBaseline) cover\n // pause/revoke/restart, but a full manager shutdown/restart can exit without\n // hitting those — without this drain those runs would be left open forever\n // once scheduledRunsByCode is lost. Best-effort (finishRun POSTs in the\n // background under the 15s shutdown budget); the server-side reaper (S2) is\n // the durable backstop.\n for (const codeName of [...scheduledRunsByCode.keys()]) {\n closeScheduledRunsForCode(codeName, 'cancelled', 'manager shutdown');\n }\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 // ENG-5441: rehydrate circuit-breaker trips so a manager restart\n // can't accidentally clear a tripped breaker. An operator who\n // intentionally stopped the loop must be the one to clear it.\n if (parsed.circuitBreakerTrips && typeof parsed.circuitBreakerTrips === 'object') {\n restartBreaker.hydrate(parsed.circuitBreakerTrips);\n const n = Object.keys(parsed.circuitBreakerTrips).length;\n if (n > 0) log(`[startup] rehydrated ${n} circuit-breaker trip(s) — agents will remain paused until manually resumed`);\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 // ENG-5435: warn once at startup if hybrid mode is configured alongside\n // a still-active predecessor (cron or in-session /loop). Both sources\n // would fire and double the token spend.\n warnOnConflictingKanbanModes();\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 dist/mcp/ directory (ENG-5410).\n // The tarball is restricted to dist/ only, so the bundled MCP assets are\n // copied under dist/mcp/ by the build:mcp-assets script.\n const moduleDir = dirname(fileURLToPath(import.meta.url));\n // Dev: src/lib → ../../dist/mcp, Built: dist/lib → ../dist/mcp, npm global: dist/lib → ../dist/mcp\n let mcpSourceDir = '';\n let dir = moduleDir;\n for (let i = 0; i < 6; i++) {\n const candidate = join(dir, 'dist', '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\nimport { createHash } from 'node:crypto';\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/**\n * ENG-5537 — given a content `drift` was detected, decide whether it warrants\n * restarting the running claude session.\n *\n * Only a *membership* change to the declared MCP server set (a server added or\n * removed) requires a restart: the running session connected to a fixed set of\n * servers at launch, and gaining/losing one means the live session's tool\n * surface is wrong until it respawns. A *value-only* drift — the same set of\n * servers with a rotated url or header value, e.g. Composio re-signing its\n * `mcp_url` on most polls — leaves the running session's established\n * connections intact and is picked up by the next natural respawn, so it must\n * NOT restart. Restarting on value-only drift is exactly what flapped agents\n * into the ENG-5441 circuit breaker (phil, agt-aws-1, 2026-05-25).\n *\n * When either key set is unknown (file missing / unparseable on one side), we\n * keep the pre-ENG-5537 safe default: treat it as restart-worthy.\n */\nexport interface McpDriftRestartDecision {\n /** Restart the running session? True only on membership change or unknown. */\n restart: boolean;\n /** Key membership could not be determined (null/undefined on either side). */\n membershipUnknown: boolean;\n /** Server keys added or removed (sorted; empty for value-only or unknown). */\n addedOrRemoved: string[];\n}\n\nexport function decideMcpRestartOnDrift(\n previousKeys: Set<string> | null | undefined,\n currentKeys: Set<string> | null | undefined,\n): McpDriftRestartDecision {\n if (!previousKeys || !currentKeys) {\n return { restart: true, membershipUnknown: true, addedOrRemoved: [] };\n }\n const addedOrRemoved: string[] = [];\n for (const k of currentKeys) if (!previousKeys.has(k)) addedOrRemoved.push(k);\n for (const k of previousKeys) if (!currentKeys.has(k)) addedOrRemoved.push(k);\n addedOrRemoved.sort();\n return { restart: addedOrRemoved.length > 0, membershipUnknown: false, addedOrRemoved };\n}\n\n/**\n * ENG-5537 — structure-only hash of a managed-toolkit desired set, used to gate\n * the convergence-driven session restart independently of the value hash that\n * gates the `.mcp.json` write.\n *\n * Deliberately hashes only each server's id and the SORTED NAMES of its header\n * keys — never the url or header values. A provider rotating a signed `mcp_url`\n * (or a credential-bearing header value) moves the value hash, so `.mcp.json`\n * re-converges to the live credential, but leaves THIS hash unchanged, so the\n * running session is left alone. Adding/removing a toolkit, or a header key\n * appearing/disappearing, does move this hash and triggers the restart.\n */\nexport function managedMcpStructureHash(\n entries: Array<{ serverId: string; headers?: Record<string, string> }>,\n): string {\n const basis = entries\n .slice()\n .sort((a, b) => a.serverId.localeCompare(b.serverId))\n .map((e) => `${e.serverId}|${Object.keys(e.headers ?? {}).sort().join(',')}`)\n .join('\\n');\n return createHash('sha256').update(basis).digest('hex').slice(0, 16);\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// ENG-5344: rotation grace window — when reapStaleMcpChildren SIGTERMs\n// an MCP child, Claude Code's MCP transport only respawns on the next\n// tool call. There is therefore a window (≥ SIGTERM-grace + respawn\n// latency) where the child is dead and presence-reaper would see zero\n// live children for the affected server keys, even though nothing is\n// actually broken. Without coordination, the next manager tick after a\n// rotation kills the whole tmux session — observed on Stirling at ~20\n// min intervals, 54 false-positive restarts/day.\n//\n// We record (codeName, serverKey) → SIGTERM timestamp here and let\n// presence-reaper consult it to skip keys that are still in their\n// post-rotation respawn window.\nconst rotationTimestamps = new Map<string, number>();\n\n/**\n * Default rotation grace window. Sized to cover:\n * - the SIGTERM → SIGKILL grace in reapStaleMcpChildren (5s default)\n * - Claude Code's lazy respawn latency on next tool call\n * - a healthy margin so a slow respawn doesn't trip presence-reaper\n *\n * 15s is comfortably above the observed ~2s race window between\n * stale-reaper and the next manager tick.\n */\nexport const DEFAULT_ROTATION_GRACE_MS = 15_000;\n\nfunction rotationKey(codeName: string, serverKey: string): string {\n return `${codeName}\\x00${serverKey}`;\n}\n\n/**\n * Record that we just SIGTERM'd one or more MCP children for these\n * server keys belonging to <codeName>. Callers should invoke this after\n * SIGTERM (not before): no kill = no in-flight rotation to grace.\n *\n * Idempotent — the latest call wins so back-to-back rotations extend\n * the grace window rather than expire it early.\n */\nexport function recordStaleRotation(\n codeName: string,\n serverKeys: Iterable<string>,\n now: () => number = Date.now,\n): void {\n const ts = now();\n for (const key of serverKeys) {\n rotationTimestamps.set(rotationKey(codeName, key), ts);\n }\n}\n\n/**\n * Was <codeName>:<serverKey> rotated within the last `withinMs`?\n * Returns false if no rotation has been recorded for the pair.\n *\n * Self-cleaning: expired entries are deleted on read so the map stays\n * bounded under steady-state load.\n */\nexport function wasRecentlyRotated(\n codeName: string,\n serverKey: string,\n withinMs: number,\n now: () => number = Date.now,\n): boolean {\n const k = rotationKey(codeName, serverKey);\n const ts = rotationTimestamps.get(k);\n if (ts === undefined) return false;\n if (now() - ts < withinMs) return true;\n rotationTimestamps.delete(k);\n return false;\n}\n\n/**\n * Test-only seam: clear the rotation tracker between tests so a recorded\n * rotation in one case doesn't bleed into another.\n */\nexport function __resetStaleRotationTrackerForTests(): void {\n rotationTimestamps.clear();\n}\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 // ENG-5344: record the rotation so the presence-reaper running on the\n // next manager tick (≥ ~2s later) doesn't see the now-dead children as\n // \"declared MCP missing\" and tear down the whole tmux session. The\n // child won't respawn until Claude Code makes its next tool call, so a\n // grace window is the only correct read here. Only record for keys we\n // actually killed — recording the full `serverKeys` list would grace\n // keys whose env rotated but had no live children to begin with.\n const killedKeys = new Set<string>();\n for (const pid of targets) {\n const argv = byPid.get(pid)?.args ?? '';\n for (const key of serverKeys) {\n const entry = mcpJson?.mcpServers?.[key];\n const matchers = buildArgvMatchersForEntry(key, entry);\n if (matchers.some((re) => re.test(argv))) killedKeys.add(key);\n }\n }\n if (killedKeys.size > 0) recordStaleRotation(codeName, killedKeys);\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-5053: detect MCP children that should be running but aren't, and\n// trigger a session restart so they come back online.\n//\n// Why this exists: an MCP child can die under a live parent claude\n// (transient error during init, OOM, downstream service blip during a\n// tool call). Claude Code's MCP transport advertises that it'll\n// respawn a dead child on the next request — but in practice we've\n// seen agents go silent for hours with a missing MCP and no recovery\n// (Stirling, 2026-05-13, Xero MCP). The agent keeps reporting \"X is\n// still warming up\" indefinitely. Killing the tmux session forces the\n// manager's ensure-session pass to respawn it, which gets the missing\n// child back.\n//\n// This module is the *presence* counterpart to stale-mcp-reaper.ts\n// (which handles env rotation — child alive but holding a stale\n// credential) and orphan-channel-mcp-reaper.ts (which handles dead\n// parents — child alive but its parent claude is gone). The gap they\n// don't cover is the one this fills: parent claude alive, declared\n// MCP child absent, no rotation pending.\n//\n// Pure helpers exposed for tests:\n// • findMissingMcpServers() — given parsed ps rows + the agent's\n// codeName + parsed .mcp.json, return the declared mcpServers\n// keys that have ZERO matching live children belonging to this\n// agent's claude.\n//\n// Side-effecting:\n// • reapMissingMcpSessions() — runs `ps`, checks presence, applies\n// the cold-start grace window, and calls back to stopSession()\n// so the manager's ensure-session pass respawns the agent.\n\nimport { execFileSync } from 'node:child_process';\nimport { parsePsRows, type PsRow } from './orphan-channel-mcp-reaper.js';\nimport {\n DEFAULT_ROTATION_GRACE_MS,\n findMcpChildrenForAgent,\n wasRecentlyRotated,\n type McpConfig,\n} from './stale-mcp-reaper.js';\n\n/**\n * Cold-start grace window — how long after a session starts we wait\n * before flagging a missing MCP. Composio + Xero MCPs spawn via npx\n * which can take 20-30s to install on a fresh box; without this grace\n * a brand-new session would be repeatedly killed mid-init.\n */\nexport const DEFAULT_COLD_START_GRACE_MS = 90_000;\n\n/**\n * ENG-5279: maximum consecutive presence-reaper restarts before we give\n * up on a permanently-broken declared MCP. Set to 3 to match the\n * agent-thrash-monitor threshold — if we keep restarting past that\n * point, the thrash detector pages Slack with a false alarm for a\n * failure the reaper can't fix by restarting (orphan in .mcp.json from\n * a pre-ENG-5277 install, an unpublished MCP package, a transiently\n * failing npx install that's now permanently broken, etc.).\n */\nexport const MAX_PRESENCE_RESTART_ATTEMPTS = 3;\n\n/**\n * Per-(codeName, serverKey) state for the cap logic.\n *\n * `attempts` counts distinct restart cycles where the server never\n * came back live — NOT distinct polls. CodeRabbit on PR #1167: pre-fix\n * the counter incremented on every tick the server was missing, so a\n * slow respawn (3+ polls during a single in-flight restart) would\n * exhaust the cap before three actual session generations had been\n * observed. The `lastAttemptedSessionStartedAt` field is the per-\n * generation gate — we only increment when this differs from the\n * current `sessionStartedAt` (i.e. the manager spawned a fresh\n * session since our last attempt), and after incrementing we record\n * the current generation so subsequent polls within the same\n * generation are no-ops for the counter.\n *\n * `gaveUpLogged` records whether we've already emitted the give-up\n * audit line so subsequent ticks stay silent.\n *\n * Cleared per-agent by `clearPresenceReaperState()` when the manager\n * sees `.mcp.json` content change — that's the signal that something\n * external fixed the config and the give-up state is stale.\n */\ninterface PresenceReaperKeyState {\n attempts: number;\n lastSeenLiveAt: number | null;\n lastAttemptedSessionStartedAt: number | null;\n gaveUpLogged: boolean;\n}\n\nconst presenceReaperState = new Map<string, PresenceReaperKeyState>();\n\nfunction stateKey(codeName: string, serverKey: string): string {\n return `${codeName}\\x00${serverKey}`;\n}\n\n/**\n * Clear every per-server give-up state for `codeName`. Wipes ALL\n * entries for the agent regardless of which keys changed.\n *\n * Prefer {@link clearPresenceReaperStateForKeys} from production code\n * (ENG-5285) — value-only drifts on .mcp.json (e.g. composio rotating\n * a signed proxy URL every ~20 min) shouldn't reset the cap on keys\n * whose presence-classification didn't actually change. This whole-\n * agent wipe was the root cause of Stirling flapping 450× over six\n * days post-ENG-5279: every URL rotation reset the cap before the\n * reaper could give up on the broken xero MCP.\n *\n * Kept exported for tests + any future caller that genuinely needs a\n * full agent-scoped wipe (none in production today).\n */\nexport function clearPresenceReaperState(codeName: string): void {\n const prefix = `${codeName}\\x00`;\n for (const key of presenceReaperState.keys()) {\n if (key.startsWith(prefix)) presenceReaperState.delete(key);\n }\n}\n\n/**\n * ENG-5285: clear give-up state only for the named MCP server keys.\n *\n * Call from the manager-worker's `.mcp.json` drift handler with the\n * set of keys that were ADDED or REMOVED relative to the previous\n * .mcp.json. Keys present in both old and new (even if their values\n * changed — e.g. composio rotated a signed URL) must NOT be cleared,\n * because their presence-classification is unchanged and the cap on\n * a permanently-broken MCP must keep accumulating across config\n * rewrites.\n *\n * No-op when `keys` is empty.\n */\nexport function clearPresenceReaperStateForKeys(\n codeName: string,\n keys: Iterable<string>,\n): void {\n for (const key of keys) {\n presenceReaperState.delete(stateKey(codeName, key));\n }\n}\n\n/**\n * Test-only seam: reset all state. Production code should use\n * `clearPresenceReaperState(codeName)` instead — wiping the whole map\n * would lose give-up state for unrelated agents.\n */\nexport function __resetPresenceReaperStateForTests(): void {\n presenceReaperState.clear();\n}\n\n/**\n * Pure: given parsed ps rows + the agent's codeName + parsed\n * `.mcp.json`, return the declared `mcpServers` keys that have ZERO\n * matching live children belonging to this agent's claude.\n *\n * Reuses `findMcpChildrenForAgent` from stale-mcp-reaper so the argv-\n * matching rules and ppid-walk are shared with the existing reapers\n * (one source of truth for \"which child belongs to which agent + which\n * server key\").\n */\nexport function findMissingMcpServers(args: {\n rows: PsRow[];\n codeName: string;\n mcpJson: McpConfig | null | undefined;\n}): string[] {\n const { rows, codeName, mcpJson } = args;\n const servers = mcpJson?.mcpServers ?? {};\n const declared = Object.keys(servers);\n if (declared.length === 0) return [];\n\n const missing: string[] = [];\n for (const key of declared) {\n // ENG-5075: skip URL-based remote MCP entries (Streamable HTTP /\n // SSE — anything with a `url` field and no `command`). Claude\n // Code dials those endpoints directly; there's no stdio child\n // process to discover via `ps`. Pre-fix, the reaper iterated\n // every declared server and flagged URL-based ones (e.g. Granola\n // post-ENG-5074) as \"missing\" every poll cycle → killed the\n // session every ~3 minutes → Scout flapped indefinitely with the\n // signature \"declared MCP(s) [granola] have no live children\".\n //\n // The presence check is only meaningful for stdio MCPs — those\n // are the ones with a child process that could die under a live\n // parent. URL-based entries can't have that failure mode at the\n // child-process layer (their failure mode is \"HTTP request fails\n // at call time\", which is the MCP transport's job to handle, not\n // the reaper's).\n const entry = (servers as Record<string, unknown>)[key] as\n | { command?: unknown; url?: unknown }\n | undefined;\n const isStdio = typeof entry?.command === 'string';\n if (!isStdio) continue;\n\n const live = findMcpChildrenForAgent({\n rows,\n codeName,\n serverKeys: [key],\n mcpJson,\n });\n if (live.length === 0) missing.push(key);\n }\n return missing;\n}\n\nexport interface ReapMissingMcpSessionsArgs {\n log: (msg: string) => void;\n codeName: string;\n mcpJson: McpConfig | null | undefined;\n /**\n * When the parent claude session was last started (Date.now() ms).\n * Used to apply the cold-start grace window — we don't flag a\n * missing MCP within `graceMs` of startup, because Composio + Xero\n * MCPs can take ~30s to npm-install on first spawn. Pass null when\n * the session start time is unknown — the reaper then *skips* the\n * agent rather than risk a false-positive restart.\n */\n sessionStartedAt: number | null;\n /** Callback that tears the agent's session down so the manager's\n * ensure-session pass respawns it on the next tick.\n *\n * ENG-5547: `ctx.activeKeys` is the set of under-cap missing MCP servers\n * that prompted *this* restart (the same keys logged on the restart line).\n * The manager uses its cardinality to decide whether the restart counts\n * toward the agent-wide circuit breaker: a single persistently-failing\n * server is isolated by its own ENG-5279 give-up cap (→ marked unhealthy →\n * dropped from .mcp.json), so it must NOT pause the whole agent; a restart\n * driven by several servers at once is a genuine storm that should. */\n stopSession: (codeName: string, ctx: { activeKeys: string[] }) => void;\n /** Override the cold-start grace (default: 90s). */\n graceMs?: number;\n /**\n * ENG-5344: post-rotation grace. When stale-mcp-reaper SIGTERMs an MCP\n * child during a credential rotation, the child won't respawn until\n * Claude Code makes the next tool call. Without this grace, the next\n * manager tick (~2s later) sees zero live children for the rotated\n * key and kills the whole tmux session — observed on Stirling at the\n * 20-min xero token rotation cadence (54 false-positive restarts/day).\n *\n * Default: {@link DEFAULT_ROTATION_GRACE_MS} (15s — comfortably above\n * the SIGTERM→SIGKILL grace plus typical respawn latency).\n */\n rotationGraceMs?: number;\n /** Test seam: substitute the `ps` invocation. */\n runPs?: () => string;\n /** Test seam: substitute the wall clock for grace-window checks. */\n now?: () => number;\n /**\n * ENG-5292: invoked EXACTLY ONCE per (codeName, serverKey) when the\n * restart cap (ENG-5279) is first exceeded — i.e. the first poll\n * where we'd give up on the key. Gated by the same `gaveUpLogged`\n * flag that prevents duplicate log lines, so subsequent ticks while\n * the key remains given-up don't re-fire.\n *\n * Production use: manager-worker registers a callback that POSTs to\n * `/host/integration-health` with action=mark_unhealthy. The endpoint\n * flips agent_integrations.status, which makes /host/managed-toolkits\n * drop the broken MCP from its response, which causes the next\n * .mcp.json regeneration to omit it cleanly.\n *\n * Errors thrown by the callback are swallowed (logged) so an API\n * outage doesn't break the reaper itself. The give-up is recorded\n * locally regardless — at worst the integration stays in active\n * state on the DB side and the next give-up tick after the API\n * recovers will retry the call (the cap state still trips immediately\n * since `gaveUpLogged` is local to this process).\n */\n onGiveUp?: (codeName: string, serverKey: string) => void;\n /**\n * ENG-5286: invoked every time the reaper actually triggers a session\n * restart (i.e. just before `stopSession()`). Production use: manager-\n * worker registers a callback that POSTs to /host/mcp-reaper-restart\n * so the API can roll up restart counts per agent into a CloudWatch\n * metric + flapping alarm.\n *\n * Errors thrown by the callback are swallowed (logged) — the reaper's\n * own correctness must not depend on the API call succeeding.\n */\n onRestart?: (\n codeName: string,\n args: { activeKeys: string[]; givenUpKeys: string[] },\n ) => void | Promise<void>;\n}\n\nexport interface ReapMissingMcpSessionsResult {\n /** The declared server keys we found missing. May be empty when\n * everything is healthy, when the session is still in cold-start,\n * or when sessionStartedAt is unknown. */\n missing: string[];\n /** True iff we actually called stopSession() this invocation. */\n restarted: boolean;\n /** Stable reason code for the no-op path, useful in tests. */\n reason?:\n | 'healthy'\n | 'cold-start'\n | 'session-start-unknown'\n | 'no-declared-servers'\n | 'no-mcp-json'\n | 'all-keys-over-cap';\n /** ENG-5279: server keys that were missing but skipped because they\n * hit the consecutive-restart cap. The session would otherwise have\n * been restarted; we held off. Empty in the common case. */\n givenUp?: string[];\n /** ENG-5344: server keys that were missing but skipped because they\n * were rotated by stale-mcp-reaper within the rotation grace window.\n * Treated as in-flight (will respawn on next tool call), not missing. */\n rotationGraced?: string[];\n}\n\n/**\n * Run `ps`, find declared MCP servers that have no live children for\n * <codeName>, and trigger a session restart if any are missing AND the\n * cold-start grace has elapsed. Idempotent — repeated calls when a\n * restart is already in flight are harmless (stopSession is a no-op\n * once the tmux session is already gone).\n */\nexport function reapMissingMcpSessions(\n args: ReapMissingMcpSessionsArgs,\n): ReapMissingMcpSessionsResult {\n const {\n log,\n codeName,\n mcpJson,\n sessionStartedAt,\n stopSession,\n graceMs = DEFAULT_COLD_START_GRACE_MS,\n rotationGraceMs = DEFAULT_ROTATION_GRACE_MS,\n onGiveUp,\n onRestart,\n } = args;\n const now = args.now ?? Date.now;\n const runPs =\n args.runPs ??\n (() => execFileSync('ps', ['-eo', 'pid,ppid,args'], { encoding: 'utf-8', timeout: 5_000 }));\n\n if (!mcpJson?.mcpServers) {\n return { missing: [], restarted: false, reason: 'no-mcp-json' };\n }\n const declaredCount = Object.keys(mcpJson.mcpServers).length;\n if (declaredCount === 0) {\n return { missing: [], restarted: false, reason: 'no-declared-servers' };\n }\n\n // Skip rather than risk a false-positive when we don't know when the\n // session started. The manager-worker passes null for agents whose\n // PersistentSession state hasn't been populated yet.\n if (sessionStartedAt === null) {\n return { missing: [], restarted: false, reason: 'session-start-unknown' };\n }\n\n // Cold-start grace: skip if the session is still in its grace window.\n if (now() - sessionStartedAt < graceMs) {\n return { missing: [], restarted: false, reason: 'cold-start' };\n }\n\n let psOutput: string;\n try {\n psOutput = runPs();\n } catch (err) {\n log(`[mcp-presence-reaper] ps invocation failed for '${codeName}': ${(err as Error).message} — skipping`);\n return { missing: [], restarted: false, reason: 'healthy' };\n }\n\n const rows = parsePsRows(psOutput);\n const missingRaw = findMissingMcpServers({ rows, codeName, mcpJson });\n\n // ENG-5344: stale-mcp-reaper rotation grace. When stale-reaper SIGTERMs\n // an MCP child during credential rotation, the child won't come back\n // until Claude Code makes its next tool call. Within the grace window,\n // treat \"no live children\" for that key as in-flight, not missing —\n // pre-fix this was the entire root cause of Stirling's 54 daily false-\n // positive restarts (and matching kills on don/phil/maven).\n const rotationGraced: string[] = [];\n const missing: string[] = [];\n for (const key of missingRaw) {\n if (wasRecentlyRotated(codeName, key, rotationGraceMs, now)) {\n rotationGraced.push(key);\n } else {\n missing.push(key);\n }\n }\n if (rotationGraced.length > 0) {\n log(\n `[mcp-presence-reaper] '${codeName}': skipping [${rotationGraced.join(', ')}] within rotation grace window (${rotationGraceMs}ms) — child(ren) just SIGTERM'd by stale-mcp-reaper, awaiting respawn on next tool call (ENG-5344)`,\n );\n }\n\n // ENG-5279: update per-(agent, server) recovery state on EVERY tick so\n // a key that came back to life — even briefly — resets its counter.\n // Walk the full declared stdio set (not just `missing`), so transitions\n // from missing→live clear the attempt count immediately. Without this\n // a flapping MCP would accumulate restarts forever.\n //\n // ENG-5344: use the UNFILTERED `missingRaw` to classify liveness — a\n // rotation-graced key has zero live children right now, so it must\n // not reset the cap counter (which would let a chronically broken MCP\n // never reach give-up if it gets rotated frequently).\n const declared = mcpJson.mcpServers ?? {};\n const liveKeys = new Set<string>();\n for (const key of Object.keys(declared)) {\n const entry = (declared as Record<string, unknown>)[key] as { command?: unknown } | undefined;\n if (typeof entry?.command !== 'string') continue; // stdio-only\n if (!missingRaw.includes(key)) liveKeys.add(key);\n }\n for (const key of liveKeys) {\n const state = presenceReaperState.get(stateKey(codeName, key));\n if (state) {\n state.attempts = 0;\n state.lastSeenLiveAt = now();\n state.lastAttemptedSessionStartedAt = null;\n state.gaveUpLogged = false;\n }\n }\n\n if (missing.length === 0) {\n return {\n missing,\n restarted: false,\n reason: 'healthy',\n rotationGraced: rotationGraced.length > 0 ? rotationGraced : undefined,\n };\n }\n\n // Tally attempts and partition missing into active (under cap) vs\n // given-up (at or over cap). Per-generation guard via\n // `lastAttemptedSessionStartedAt`: only count an attempt when the\n // manager has spawned a fresh session since our last increment for\n // this key. A slow respawn (multiple reaper polls between\n // `stopSession()` and the new session actually coming up) must NOT\n // burn the cap — the manager's stop wrapper is idempotent and\n // those extra calls aren't real restart cycles.\n const givenUp: string[] = [];\n const active: string[] = [];\n for (const key of missing) {\n const sk = stateKey(codeName, key);\n const state = presenceReaperState.get(sk) ?? {\n attempts: 0,\n lastSeenLiveAt: null,\n lastAttemptedSessionStartedAt: null,\n gaveUpLogged: false,\n };\n if (state.lastAttemptedSessionStartedAt !== sessionStartedAt) {\n state.attempts += 1;\n state.lastAttemptedSessionStartedAt = sessionStartedAt;\n }\n presenceReaperState.set(sk, state);\n if (state.attempts > MAX_PRESENCE_RESTART_ATTEMPTS) {\n givenUp.push(key);\n if (!state.gaveUpLogged) {\n log(\n `[mcp-presence-reaper] giving up on '${codeName}:${key}' after ${MAX_PRESENCE_RESTART_ATTEMPTS} consecutive failed restarts — declared but never recovers (likely orphaned config; see ENG-5279)`,\n );\n state.gaveUpLogged = true;\n // ENG-5292: fire the give-up callback exactly once per\n // (codeName, serverKey). Manager-worker uses this to POST to\n // /host/integration-health so the broken MCP gets marked\n // unhealthy and dropped from the next .mcp.json regeneration.\n // Swallow errors — the reaper's own correctness must not\n // depend on the callback succeeding.\n if (onGiveUp) {\n try {\n onGiveUp(codeName, key);\n } catch (err) {\n log(\n `[mcp-presence-reaper] onGiveUp callback threw for '${codeName}:${key}' (suppressed; reaper continues): ${(err as Error).message}`,\n );\n }\n }\n }\n } else {\n active.push(key);\n }\n }\n\n if (active.length === 0) {\n // Every missing key has been given up on — don't restart for any\n // of them. Stays quiet on subsequent ticks (gaveUpLogged guard).\n return {\n missing,\n restarted: false,\n reason: 'all-keys-over-cap',\n givenUp,\n rotationGraced: rotationGraced.length > 0 ? rotationGraced : undefined,\n };\n }\n\n // Audit line: emit the missing key(s) so we can spot patterns (\"xero\n // crashes every 4h\") without grepping pane logs. Single line per\n // restart event by design. Mention given-up keys too so the operator\n // sees the full picture.\n const givenUpSuffix = givenUp.length > 0\n ? ` (skipping over-cap: [${givenUp.join(', ')}])`\n : '';\n log(\n `[mcp-presence-reaper] '${codeName}': declared MCP(s) [${active.join(', ')}] have no live children — restarting session${givenUpSuffix}`,\n );\n // ENG-5286: fire the restart-observer callback before stopSession so\n // the API records the event with the exact key set that prompted the\n // restart. Errors are swallowed — reaper correctness wins.\n if (onRestart) {\n try {\n // CodeRabbit on PR #1186: callbacks may be sync OR async.\n // The synchronous try/catch catches throws from sync callbacks\n // and the synchronous part of async ones; the .catch() below\n // catches rejections from async callbacks so they don't escape\n // as unhandled promise rejections.\n const maybePromise = onRestart(codeName, { activeKeys: active, givenUpKeys: givenUp });\n if (\n maybePromise &&\n typeof (maybePromise as PromiseLike<void>).then === 'function'\n ) {\n void (maybePromise as PromiseLike<void>).then(undefined, (err: unknown) => {\n log(\n `[mcp-presence-reaper] onRestart callback rejected for '${codeName}' (suppressed; restart proceeds): ${(err as Error).message}`,\n );\n });\n }\n } catch (err) {\n log(\n `[mcp-presence-reaper] onRestart callback threw for '${codeName}' (suppressed; restart proceeds): ${(err as Error).message}`,\n );\n }\n }\n stopSession(codeName, { activeKeys: active });\n return {\n missing,\n restarted: true,\n givenUp: givenUp.length > 0 ? givenUp : undefined,\n rotationGraced: rotationGraced.length > 0 ? rotationGraced : undefined,\n };\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-5441 — circuit breaker for manager-initiated session restarts.\n *\n * Today's Scout incident on prod-integritylabs-agt-aws-1 respawned an\n * agent every ~55s for hours (~100 spawns/hr) because day-rollover\n * ([ENG-5431]) repeated indefinitely. Each respawn burned subscription\n * budget, flooded pane.log, drowned manager.log signal, and re-floods\n * `/host/direct-chat/poll`. The per-MCP reaper has a give-up cap\n * ([ENG-5279]) and the [ENG-5286] alarm fires when the cap-attempts\n * cross a threshold — but neither *stopped* the whole-agent loop.\n *\n * This breaker sits at the manager's spawn gate. Every manager-initiated\n * stop (day-rollover, auth-tuple change, reaper trigger, hot-reload\n * .mcp.json restart, channel-set restart) records a restart event. When\n * `> AGT_RESTART_BREAKER_MAX` events land inside the sliding\n * `AGT_RESTART_BREAKER_WINDOW_MS` window, the agent's breaker trips:\n * the manager stops respawning until an operator clears the trip (by\n * resuming the agent in the webapp / via the CLI).\n *\n * Pure state, no I/O — the caller (manager-worker.ts) owns serialisation\n * to `manager-state.json` so a manager restart preserves trip state and\n * doesn't accidentally clear a tripped breaker. Tests drive `now()` via\n * the constructor to avoid sleeping in CI.\n */\n\nexport interface RestartBreakerOptions {\n /** Max restart events allowed inside the window; the (max+1)th trips. Default 2 (env: AGT_RESTART_BREAKER_MAX). */\n max?: number;\n /** Sliding window in ms. Default 600_000 = 10 min (env: AGT_RESTART_BREAKER_WINDOW_MS). */\n windowMs?: number;\n /** Injectable clock for tests. */\n now?: () => number;\n}\n\n/** All known restart trigger sources. Adding a new source? Add it here so the trip detail is self-describing. */\nexport type RestartReason =\n | 'day-rollover'\n | 'auth-tuple-change'\n | 'model-change'\n | 'mcp-presence-reaper'\n | 'hot-reload-mcp'\n | 'channel-set-change'\n | 'manual'\n | 'stale-mcp'\n | 'unknown';\n\n/**\n * ENG-5547: decide whether an mcp-presence-reaper restart should count\n * toward the agent-wide circuit breaker.\n *\n * A restart attributable to a SINGLE persistently-failing MCP server is\n * already governed by that server's own ENG-5279 give-up cap (3 cycles →\n * the integration is marked unhealthy via /host/integration-health and\n * dropped from the next .mcp.json), which isolates the one broken tool and\n * lets the agent keep running degraded. Counting it would let the breaker\n * (default max 2) trip on the 3rd restart and pause the ENTIRE agent for one\n * bad integration — racing, and usually beating, the give-up that would have\n * isolated it. That sledgehammer is exactly what auto-paused phil.\n *\n * A restart where MULTIPLE distinct servers are simultaneously missing is a\n * genuine multi-cause storm the per-server cap can't see (the rotating-MCP\n * case ENG-5441 exists for), so it still counts.\n *\n * Returns the {@link RestartReason} to record (the restart counts toward the\n * breaker), or `undefined` to restart WITHOUT counting.\n */\nexport function reaperRestartBreakerReason(\n activeKeys: readonly string[],\n): RestartReason | undefined {\n return activeKeys.length >= 2 ? 'mcp-presence-reaper' : undefined;\n}\n\nexport interface RestartEvent {\n reason: RestartReason;\n at: number;\n}\n\nexport interface TripState {\n trippedAt: number;\n eventsAtTrip: RestartEvent[];\n /** Pre-formatted `status_message` for the agents row + decision-log. */\n statusMessage: string;\n}\n\nexport interface RecordResult {\n /** True on the call that flips the breaker from closed → tripped. */\n tripped: boolean;\n /** Always populated when `tripped` is true. */\n trip?: TripState;\n /** Current count inside the sliding window (post-record). */\n windowCount: number;\n}\n\nconst DEFAULT_MAX = 2;\nconst DEFAULT_WINDOW_MS = 600_000;\n\nexport function readEnvNumber(name: string, fallback: number): number {\n const raw = process.env[name];\n if (!raw) return fallback;\n const parsed = Number(raw);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;\n}\n\n/**\n * Per-agent breaker. One instance per manager, keyed internally by code_name.\n * Held in module-level state in manager-worker.ts so it survives across\n * polls within a worker generation; trip state is mirrored to\n * `manager-state.json` so it survives across worker generations too.\n */\nexport class RestartBreaker {\n private readonly max: number;\n private readonly windowMs: number;\n private readonly now: () => number;\n private readonly events = new Map<string, RestartEvent[]>();\n private readonly trips = new Map<string, TripState>();\n\n constructor(opts: RestartBreakerOptions = {}) {\n this.max = opts.max ?? readEnvNumber('AGT_RESTART_BREAKER_MAX', DEFAULT_MAX);\n this.windowMs = opts.windowMs ?? readEnvNumber('AGT_RESTART_BREAKER_WINDOW_MS', DEFAULT_WINDOW_MS);\n this.now = opts.now ?? Date.now;\n // Reject NaN / Infinity explicitly — `??` treats them as present\n // values, so without these guards an operator who sets\n // `AGT_RESTART_BREAKER_MAX=NaN` (or a programmatic caller passing\n // `Number.NaN`) would silently disable the breaker rather than\n // fall through to the default. CodeRabbit on PR #1302.\n if (!Number.isFinite(this.max) || this.max < 1) {\n throw new Error('restart-breaker max must be a finite number >= 1');\n }\n if (!Number.isFinite(this.windowMs) || this.windowMs < 1000) {\n throw new Error('restart-breaker windowMs must be a finite number >= 1000');\n }\n }\n\n /** True if this agent's breaker is currently tripped (manager must skip spawn). */\n isTripped(codeName: string): boolean {\n return this.trips.has(codeName);\n }\n\n getTrip(codeName: string): TripState | undefined {\n return this.trips.get(codeName);\n }\n\n /**\n * Record a restart event. If recording this event puts the count\n * inside the window above `max`, the breaker trips and the call site\n * should NOT respawn.\n *\n * Idempotent on already-tripped breakers: returns the existing trip\n * without double-counting events. Callers may still record the reason\n * via the decision-log for forensics.\n */\n record(codeName: string, reason: RestartReason): RecordResult {\n const existing = this.trips.get(codeName);\n if (existing) {\n return { tripped: false, trip: existing, windowCount: existing.eventsAtTrip.length };\n }\n\n const at = this.now();\n const cutoff = at - this.windowMs;\n const prior = (this.events.get(codeName) ?? []).filter((e) => e.at >= cutoff);\n prior.push({ reason, at });\n this.events.set(codeName, prior);\n\n if (prior.length > this.max) {\n const trip: TripState = {\n trippedAt: at,\n eventsAtTrip: [...prior],\n statusMessage: formatStatusMessage(prior, this.windowMs),\n };\n this.trips.set(codeName, trip);\n // Free the events log — the trip carries the snapshot from now on.\n this.events.delete(codeName);\n return { tripped: true, trip, windowCount: prior.length };\n }\n\n return { tripped: false, windowCount: prior.length };\n }\n\n /** Operator-initiated reset: drops the trip + the events log for this agent. */\n clear(codeName: string): void {\n this.trips.delete(codeName);\n this.events.delete(codeName);\n }\n\n /** Snapshot tripped agents for `manager-state.json`. */\n serialize(): Record<string, TripState> {\n return Object.fromEntries(this.trips.entries());\n }\n\n /**\n * Rehydrate trip state from `manager-state.json`. Called once at\n * worker startup. Window history is intentionally NOT persisted — only\n * tripped state. A tripped breaker survives manager restart; an\n * un-tripped one starts a fresh window in the new worker.\n */\n hydrate(saved: Record<string, TripState> | null | undefined): void {\n if (!saved) return;\n for (const [codeName, trip] of Object.entries(saved)) {\n if (trip && typeof trip.trippedAt === 'number' && Array.isArray(trip.eventsAtTrip)) {\n this.trips.set(codeName, trip);\n }\n }\n }\n\n /** Test helper — current in-window event count for `codeName`. */\n windowCount(codeName: string): number {\n const cutoff = this.now() - this.windowMs;\n return (this.events.get(codeName) ?? []).filter((e) => e.at >= cutoff).length;\n }\n}\n\nfunction formatStatusMessage(events: RestartEvent[], windowMs: number): string {\n const last = events[events.length - 1]!;\n // Render the window in seconds for sub-minute thresholds (operator-\n // tuned via AGT_RESTART_BREAKER_WINDOW_MS) and in decimal minutes\n // otherwise. Math.round(/60_000) was returning \"0min\" for a 30s\n // tuning, which is unhelpful in the dashboard. CodeRabbit on PR #1302.\n const windowLabel = windowMs < 60_000\n ? `${Math.round(windowMs / 1000)}s`\n : `${(windowMs / 60_000).toFixed(1).replace(/\\.0$/, '')}min`;\n const reasonCounts = new Map<RestartReason, number>();\n for (const e of events) reasonCounts.set(e.reason, (reasonCounts.get(e.reason) ?? 0) + 1);\n const breakdown = Array.from(reasonCounts.entries())\n .map(([r, n]) => `${r}=${n}`)\n .join(', ');\n return (\n `Circuit breaker tripped: ${events.length} restarts in ${windowLabel} ` +\n `(${breakdown}); most recent=${last.reason} at ${new Date(last.at).toISOString()}`\n );\n}\n","// ENG-5635: decide whether a primary_model change should trigger an immediate\n// session respawn.\n//\n// Background: subscription Claude Code agents only pick up their model via the\n// `claude --model <alias>` flag the launcher passes at spawn time (ENG-5631).\n// Changing primary_model rewrites provision/settings.json and the manager's\n// in-memory knownModels map, but does NOT restart the running session — so the\n// new model is invisible until the next natural respawn (daily rotation,\n// channel/MCP/auth change, crash, or manual restart). This predicate gates a\n// proactive hot-reload respawn so the operator's choice applies within a poll.\n//\n// Kept as a pure function so the gating logic is unit-testable without the\n// manager-worker module graph.\n\nexport interface ModelChangeRespawnInput {\n /** The model the manager last saw for this agent (knownModels). Undefined/null on first provision. */\n previousModel: string | null | undefined;\n /** The freshly-resolved model for this poll. */\n primaryModel: string | null | undefined;\n /** The agent's framework id (e.g. 'claude-code', 'openclaw', 'nemoclaw'). */\n framework: string;\n /** Whether a healthy persistent session is currently running for this agent. */\n sessionHealthy: boolean;\n}\n\n/**\n * True when the model change warrants restarting the live session now.\n *\n * Returns false (let the next natural spawn carry the new model) when:\n * - the framework isn't claude-code — `--model` is a Claude Code launch flag;\n * other runtimes apply model changes through their own config/gateway path;\n * - no healthy session is running — the upcoming spawn already reads the new\n * model, so a restart would be pointless (or fight the spawn);\n * - `previousModel` is unset — first provision; the imminent first spawn\n * carries the model, nothing to switch;\n * - `primaryModel` is unset — the model couldn't be resolved this poll;\n * respawning would only drop the agent to the tier default, so leave the\n * live session on its current (known-good) model;\n * - the model is unchanged — no-op, avoids a restart loop.\n */\nexport function shouldRespawnForModelChange(input: ModelChangeRespawnInput): boolean {\n const { previousModel, primaryModel, framework, sessionHealthy } = input;\n if (framework !== 'claude-code') return false;\n if (!sessionHealthy) return false;\n if (!previousModel) return false;\n if (!primaryModel) return false;\n return previousModel !== primaryModel;\n}\n","/**\n * ENG-5389: scrape Claude Code's \"X% of your weekly limit · resets <date>\"\n * banner from each Claude Code agent's pane.log and forward to the API as\n * an observation. Wired into the per-agent processing loop in\n * manager-worker.ts; only Claude Code framework agents are checked.\n *\n * \"Only on change\" semantics:\n * - Keep an in-memory last-observed value per code_name (pct + reset date).\n * - Skip the POST when the next banner read matches it.\n * - This trades a tiny startup-blind window (we don't POST the first\n * observation after restart if it matches what the server already has)\n * for a major drop in write volume. The alert path reads the latest\n * observation per agent and re-derives crossing — the missing duplicate\n * write doesn't change the answer.\n *\n * Per-agent file read is rate-limited to MIN_CHECK_INTERVAL_MS so the\n * manager's normal ~10-30s poll doesn't translate into N file reads/sec for\n * large hosts.\n */\n\nimport { parseUsageBanner } from '@augmented/core';\nimport { readPaneLogTail } from './persistent-session.js';\n\nconst MIN_CHECK_INTERVAL_MS = 60_000;\nconst PANE_TAIL_LINES_FOR_BANNER = 200;\n\ninterface MonitorState {\n /** Last value successfully POSTed to the API (or read at startup). */\n lastPct: number | null;\n /** ISO of the lastPct's week_resets_at. */\n lastWeekResetsAt: string | null;\n /** Last time we actually opened pane.log for this code_name. */\n lastCheckedAt: number;\n}\n\nconst state = new Map<string, MonitorState>();\n\n/**\n * Test helper — reset module state. Not for production use.\n */\nexport function _resetUsageBannerMonitorState(): void {\n state.clear();\n}\n\n/**\n * Minimal API surface the monitor needs. Lets the unit test pass a stub\n * without dragging the real api-client transport in.\n */\nexport interface UsageBannerMonitorApi {\n post(path: string, body: unknown): Promise<unknown>;\n}\n\n/**\n * Read this agent's pane.log, parse the weekly-usage banner, and POST an\n * observation if (and only if) it differs from the last value we saw.\n *\n * Errors are swallowed and reported to `log`. This is a soft observability\n * path; an error here must not break the manager poll cycle.\n */\nexport async function maybeReportUsageBanner(args: {\n api: UsageBannerMonitorApi;\n codeName: string;\n agentId: string;\n log: (msg: string) => void;\n now?: Date;\n}): Promise<void> {\n const { api, codeName, agentId, log } = args;\n const now = args.now ?? new Date();\n const nowMs = now.getTime();\n\n const existing = state.get(codeName);\n if (existing && nowMs - existing.lastCheckedAt < MIN_CHECK_INTERVAL_MS) {\n return;\n }\n\n let tail: string | null;\n try {\n tail = readPaneLogTail(codeName, PANE_TAIL_LINES_FOR_BANNER);\n } catch (err) {\n log(`[usage-banner] readPaneLogTail failed for '${codeName}': ${(err as Error).message}`);\n return;\n }\n\n // Always record the check timestamp so a missing banner doesn't make us\n // re-read the file on every tick.\n const next: MonitorState = {\n lastPct: existing?.lastPct ?? null,\n lastWeekResetsAt: existing?.lastWeekResetsAt ?? null,\n lastCheckedAt: nowMs,\n };\n\n if (!tail) {\n state.set(codeName, next);\n return;\n }\n\n const observation = parseUsageBanner(tail, now);\n if (!observation) {\n state.set(codeName, next);\n return;\n }\n\n const observedAtIso = next.lastWeekResetsAt; // alias for clarity below\n const weekResetsAtIso = observation.weekResetsAt.toISOString();\n if (\n existing &&\n existing.lastPct === observation.pct &&\n observedAtIso === weekResetsAtIso\n ) {\n state.set(codeName, next);\n return;\n }\n\n try {\n await api.post('/host/usage-observations', {\n agent_id: agentId,\n observed_pct: observation.pct,\n week_resets_at: weekResetsAtIso,\n source: 'pane_log',\n });\n next.lastPct = observation.pct;\n next.lastWeekResetsAt = weekResetsAtIso;\n state.set(codeName, next);\n } catch (err) {\n log(`[usage-banner] POST /host/usage-observations failed for '${codeName}': ${(err as Error).message}`);\n // Leave the in-memory state at its previous value so the next tick\n // retries the POST. lastCheckedAt is still updated so we don't hammer\n // the API at every tick on persistent failure.\n state.set(codeName, next);\n }\n}\n","/**\n * ENG-5516: per-agent token-usage reporting.\n *\n * For each Claude Code agent, enumerate every session transcript in the\n * agent's project dir (~/.claude/projects/<encoded-cwd>/*.jsonl), parse the\n * cumulative per-(session, model) token totals, and POST them to\n * /host/token-usage where they're upserted. Wired into the per-agent\n * processing loop in manager-worker.ts alongside maybeReportUsageBanner;\n * Claude Code framework only.\n *\n * Why enumerate ALL transcripts (not just the current session):\n * Post-ENG-5397 every spawn mints a fresh session UUID (--session-id, never\n * --resume), so a single day produces N transcript files per agent — one per\n * respawn — plus separate files for scheduled tasks and direct-chat. They\n * all land in the same project dir. Tracking only daily-session.json's\n * current.sessionId would miss every completed-session tail and every\n * ephemeral session. Enumerating the directory captures all of them.\n *\n * Why cumulative upsert (not byte-offset deltas):\n * Each transcript is re-read in full and its CUMULATIVE totals posted. The\n * API upserts on (agent_id, session_id, model), so re-posting an unchanged\n * session is a no-op and a manager restart can't double-count or lose\n * tokens. To avoid re-reading unchanged files every tick, an in-memory\n * {mtimeMs, size} fingerprint per file gates the read — a restart simply\n * re-reads once (the upsert absorbs it).\n *\n * Errors are swallowed and reported to `log`: this is a soft observability\n * path and must never break the manager poll cycle.\n */\n\nimport { readdirSync, readFileSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { isEmptyTotals, parseTranscriptUsage, attributeTranscriptUsageByRun } from '@augmented/core';\nimport { sessionTranscriptDir } from './daily-session.js';\nimport { getProjectDir } from './persistent-session.js';\n\nconst MIN_CHECK_INTERVAL_MS = 60_000;\n// Only consider transcripts touched within this window. Older sessions were\n// already upserted and won't change; skipping them bounds per-tick I/O and\n// keeps a manager restart's re-read pass small.\nconst TRANSCRIPT_MTIME_WINDOW_MS = 2 * 24 * 60 * 60 * 1000; // 2 days\n// Endpoint caps entries per POST (MAX_TOKEN_USAGE_ENTRIES = 500); stay under.\nconst MAX_ENTRIES_PER_POST = 200;\n\ninterface FileFingerprint {\n mtimeMs: number;\n size: number;\n}\n\ninterface MonitorState {\n /** sessionId (filename minus .jsonl) → fingerprint of the last POSTed read. */\n files: Map<string, FileFingerprint>;\n /** Last time we enumerated the transcript dir for this code_name. */\n lastCheckedAt: number;\n}\n\nconst state = new Map<string, MonitorState>();\n\n/** Test helper — reset module state. Not for production use. */\nexport function _resetTokenUsageMonitorState(): void {\n state.clear();\n}\n\n/** Minimal API surface — lets tests stub without the real transport. */\nexport interface TokenUsageMonitorApi {\n post(path: string, body: unknown): Promise<unknown>;\n}\n\ninterface TokenUsageEntry {\n session_id: string;\n model: string;\n input_tokens: number;\n output_tokens: number;\n cache_creation_tokens: number;\n cache_read_tokens: number;\n session_started_at: string | null;\n last_observed_at: string | null;\n}\n\n/**\n * Enumerate the agent's transcripts, build entries for files that changed\n * since we last reported them, POST in batches, and update fingerprints only\n * for files whose POST succeeded.\n */\nexport async function maybeReportTokenUsage(args: {\n api: TokenUsageMonitorApi;\n codeName: string;\n agentId: string;\n log: (msg: string) => void;\n now?: Date;\n /** Test seam: override the transcript directory. */\n transcriptDir?: string;\n}): Promise<void> {\n const { api, codeName, agentId, log } = args;\n const now = args.now ?? new Date();\n const nowMs = now.getTime();\n\n const existing = state.get(codeName);\n if (existing && nowMs - existing.lastCheckedAt < MIN_CHECK_INTERVAL_MS) {\n return;\n }\n\n const dir = args.transcriptDir ?? sessionTranscriptDir(getProjectDir(codeName));\n const files = existing?.files ?? new Map<string, FileFingerprint>();\n const next: MonitorState = { files, lastCheckedAt: nowMs };\n\n let dirEntries: string[];\n try {\n dirEntries = readdirSync(dir);\n } catch {\n // No transcript dir yet (agent hasn't run) — record the check and move on.\n state.set(codeName, next);\n return;\n }\n\n // Collect entries to POST, plus the fingerprint to commit on success.\n const pending: { entry: TokenUsageEntry; sessionId: string; fp: FileFingerprint }[] = [];\n // ENG-5567: per-run attribution rows derived from the SAME transcript reads,\n // POSTed best-effort to /host/run-token-usage. Independent of the token-usage\n // fingerprint commit below (run_token_usage is cumulative + idempotent, so a\n // failed run POST self-heals on the next change).\n const pendingRuns: {\n run_id: string;\n model: string;\n input_tokens: number;\n output_tokens: number;\n cache_creation_tokens: number;\n cache_read_tokens: number;\n last_observed_at: string | null;\n }[] = [];\n\n for (const name of dirEntries) {\n if (!name.endsWith('.jsonl')) continue;\n const sessionId = name.slice(0, -'.jsonl'.length);\n if (!sessionId) continue;\n\n const path = join(dir, name);\n let st;\n try {\n st = statSync(path);\n } catch {\n continue; // raced deletion / permission — skip\n }\n if (!st.isFile()) continue;\n if (nowMs - st.mtimeMs > TRANSCRIPT_MTIME_WINDOW_MS) continue; // stale, already reported\n\n const fp: FileFingerprint = { mtimeMs: st.mtimeMs, size: st.size };\n const seen = files.get(sessionId);\n if (seen && seen.mtimeMs === fp.mtimeMs && seen.size === fp.size) {\n continue; // unchanged since last successful POST\n }\n\n let content: string;\n try {\n content = readFileSync(path, 'utf-8');\n } catch (err) {\n log(`[token-usage] read failed for '${codeName}/${name}': ${(err as Error).message}`);\n continue;\n }\n\n const parsed = parseTranscriptUsage(content);\n for (const [model, totals] of parsed.byModel) {\n if (isEmptyTotals(totals)) continue;\n pending.push({\n sessionId,\n fp,\n entry: {\n session_id: sessionId,\n model,\n input_tokens: totals.inputTokens,\n output_tokens: totals.outputTokens,\n cache_creation_tokens: totals.cacheCreationTokens,\n cache_read_tokens: totals.cacheReadTokens,\n session_started_at: parsed.sessionStartedAt,\n last_observed_at: parsed.lastObservedAt,\n },\n });\n }\n // ENG-5567: per-run attribution from the same content. Only runs with a\n // marker (runId !== null) and non-empty totals are reported; the\n // unattributed bucket stays in the session-level token_usage total.\n for (const r of attributeTranscriptUsageByRun(content).perRunModel) {\n if (r.runId === null || isEmptyTotals(r.totals)) continue;\n pendingRuns.push({\n run_id: r.runId,\n model: r.model,\n input_tokens: r.totals.inputTokens,\n output_tokens: r.totals.outputTokens,\n cache_creation_tokens: r.totals.cacheCreationTokens,\n cache_read_tokens: r.totals.cacheReadTokens,\n last_observed_at: parsed.lastObservedAt,\n });\n }\n\n // Even when a changed file yields no token rows (e.g. only user turns so\n // far), advance its fingerprint so we don't re-read it every tick until\n // it next changes.\n if (![...parsed.byModel.values()].some((t) => !isEmptyTotals(t))) {\n files.set(sessionId, fp);\n }\n }\n\n if (pending.length === 0) {\n state.set(codeName, next);\n return;\n }\n\n // POST in batches; commit fingerprints only for sessions in batches that\n // succeeded, so a failure retries those sessions next tick.\n let reported = 0;\n for (let i = 0; i < pending.length; i += MAX_ENTRIES_PER_POST) {\n const batch = pending.slice(i, i + MAX_ENTRIES_PER_POST);\n try {\n await api.post('/host/token-usage', {\n agent_id: agentId,\n framework: 'claude-code',\n source: 'transcript_jsonl',\n entries: batch.map((b) => b.entry),\n });\n for (const b of batch) {\n files.set(b.sessionId, b.fp);\n }\n reported += batch.length;\n } catch (err) {\n log(`[token-usage] POST failed for '${codeName}': ${(err as Error).message}`);\n // Leave fingerprints unset for this batch's sessions → retry next tick.\n }\n }\n\n // ENG-5567: best-effort per-run usage POST — independent of the token-usage\n // fingerprint commit above (run_token_usage is cumulative + idempotent, so a\n // failed POST self-heals on the next transcript change). Same batch size.\n for (let i = 0; i < pendingRuns.length; i += MAX_ENTRIES_PER_POST) {\n const batch = pendingRuns.slice(i, i + MAX_ENTRIES_PER_POST);\n try {\n await api.post('/host/run-token-usage', {\n agent_id: agentId,\n framework: 'claude-code',\n source: 'transcript_jsonl',\n entries: batch,\n });\n } catch (err) {\n log(`[token-usage] run-usage POST failed for '${codeName}': ${(err as Error).message}`);\n }\n }\n\n // ENG-5533: emit one success line per flush so a rollout is observable in\n // manager.log without DB access. After the ENG-5516 ship this path stayed\n // silent for 24h because /host/agents never sent `framework`, so the\n // caller's `framework === 'claude-code'` guard meant we never reached here\n // — and since every error path also logs, the *absence* of any\n // [token-usage] line was the only (easily-missed) symptom. Log only when we\n // actually POSTed, not on the every-tick no-op return, to stay quiet.\n if (reported > 0) {\n log(`[token-usage] reported ${reported} entr${reported === 1 ? 'y' : 'ies'} for '${codeName}'`);\n }\n\n state.set(codeName, next);\n}\n","/**\n * ENG-5445: read Claude Code's `~/.claude/stats-cache.json` and forward\n * per-day activity counts to the API. Complements the usage-banner\n * monitor (ENG-5389) — the passive banner only renders at the weekly-\n * limit threshold (~75%+), so quietly-busy hosts under that bar are\n * invisible to the banner path. stats-cache.json is host-wide and\n * captures every session on the machine, giving us volume signal\n * regardless of usage percentage.\n *\n * Host-scoped, not agent-scoped: stats-cache.json aggregates across\n * every Claude Code session on the host, so this module runs once per\n * refresh cycle (not per-agent). The companion table is\n * `claude_code_activity_observations` (ENG-5446), keyed on\n * (host_id, date).\n *\n * \"Only new days\" semantics:\n * - Keep a single in-memory `lastObservedDate` for the host.\n * - On each call, read the cache file, and POST any `dailyActivity`\n * row whose `date` is strictly greater than `lastObservedDate`.\n * - The endpoint's upsert-on-(host_id, date) makes the contract\n * idempotent — re-posting the same day is harmless — but the\n * in-memory gate keeps write volume low at steady state.\n *\n * Rate-limited to MIN_CHECK_INTERVAL_MS so a fast manager poll doesn't\n * translate into N file reads per second on the host.\n *\n * Errors are soft. A missing / unreadable cache, a parser failure, or\n * a 4xx/5xx from the API must NOT break the manager poll cycle — log\n * and move on. The schema-level uniqueness guarantees we'll catch up\n * the next time the file is readable.\n *\n * Probe-feasibility note for the spec-curious reader: the ENG-5443\n * spike attempted a dedicated `/usage` probe session to capture the\n * always-on percentage (vs ENG-5389's threshold-gated banner). That\n * primary path remains unproven — `tmux send-keys` is dropped by the\n * TUI (\"paste-not-submitting\"), `claude -p \"/usage\"` doesn't expand\n * slash commands, and the positional-arg path is blocked by the\n * workspace-trust dialog. This module ships the spike's secondary\n * path (stats-cache.json) on its own.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nconst MIN_CHECK_INTERVAL_MS = 60_000;\nconst STATS_CACHE_PATH = join(homedir(), '.claude', 'stats-cache.json');\nconst ISO_DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/**\n * Shape of `~/.claude/stats-cache.json` that this module cares about.\n * Claude Code includes more fields (totalMessages, modelUsage,\n * hourCounts, etc.); we read only what we forward.\n */\ninterface StatsCacheFile {\n dailyActivity?: Array<{\n date?: unknown;\n messageCount?: unknown;\n sessionCount?: unknown;\n toolCallCount?: unknown;\n }>;\n dailyModelTokens?: Record<string, Record<string, unknown>>;\n lastComputedDate?: unknown;\n}\n\ninterface MonitorState {\n /** Last YYYY-MM-DD we posted (or null if never). */\n lastObservedDate: string | null;\n /** Last time we actually opened the cache file. */\n lastCheckedAt: number;\n}\n\nconst state: MonitorState = { lastObservedDate: null, lastCheckedAt: 0 };\n\n/** Test helper — reset module state. Not for production use. */\nexport function _resetActivityCacheMonitorState(): void {\n state.lastObservedDate = null;\n state.lastCheckedAt = 0;\n}\n\n/** Minimal API surface — lets tests stub a transport. */\nexport interface ActivityCacheMonitorApi {\n post(path: string, body: unknown): Promise<unknown>;\n}\n\n/**\n * Pure helper for tests. Parses the raw file contents and returns the\n * sorted-ascending list of day-rows we'd POST given `lastObservedDate`.\n * Tolerates missing / malformed fields per the file's documented\n * lazy-update behaviour.\n */\nexport function selectNewDailyRows(\n raw: string,\n lastObservedDate: string | null,\n): Array<{\n date: string;\n message_count: number;\n session_count: number;\n tool_call_count: number;\n model_breakdown: Record<string, unknown> | null;\n}> {\n let parsed: StatsCacheFile;\n try {\n parsed = JSON.parse(raw) as StatsCacheFile;\n } catch {\n return [];\n }\n const days = Array.isArray(parsed.dailyActivity) ? parsed.dailyActivity : [];\n\n const nonNegInt = (v: unknown): number | null => {\n if (typeof v !== 'number' || !Number.isFinite(v)) return null;\n const n = Math.trunc(v);\n return n >= 0 ? n : null;\n };\n\n const result: Array<{\n date: string;\n message_count: number;\n session_count: number;\n tool_call_count: number;\n model_breakdown: Record<string, unknown> | null;\n }> = [];\n\n for (const d of days) {\n if (typeof d.date !== 'string' || !ISO_DATE_RE.test(d.date)) continue;\n if (lastObservedDate !== null && d.date <= lastObservedDate) continue;\n const m = nonNegInt(d.messageCount);\n const s = nonNegInt(d.sessionCount);\n const t = nonNegInt(d.toolCallCount);\n if (m === null || s === null || t === null) continue;\n\n // Per-day model breakdown lives in a sibling field (`dailyModelTokens`),\n // keyed by date string. Pass it through verbatim when present; the API\n // accepts `model_breakdown` as an arbitrary jsonb object.\n const breakdownForDay = parsed.dailyModelTokens?.[d.date];\n const model_breakdown =\n breakdownForDay && typeof breakdownForDay === 'object' && !Array.isArray(breakdownForDay)\n ? (breakdownForDay as Record<string, unknown>)\n : null;\n\n result.push({\n date: d.date,\n message_count: m,\n session_count: s,\n tool_call_count: t,\n model_breakdown,\n });\n }\n\n // Ascending by date so the in-memory `lastObservedDate` advances\n // monotonically even if the API call for an earlier day fails.\n result.sort((a, b) => a.date.localeCompare(b.date));\n return result;\n}\n\n/**\n * Read the stats cache, POST any day-rows newer than the in-memory\n * `lastObservedDate`, advance the marker on successful writes.\n *\n * Soft on every error path — this is observability, not the critical\n * path. A persistent failure shows up as stale rows in\n * `claude_code_activity_observations` (which the dashboard can flag),\n * not as a manager outage.\n */\nexport async function maybeReportActivityCache(args: {\n api: ActivityCacheMonitorApi;\n log: (msg: string) => void;\n now?: Date;\n}): Promise<void> {\n const { api, log } = args;\n const now = args.now ?? new Date();\n const nowMs = now.getTime();\n\n if (nowMs - state.lastCheckedAt < MIN_CHECK_INTERVAL_MS) return;\n state.lastCheckedAt = nowMs;\n\n if (!existsSync(STATS_CACHE_PATH)) {\n // Expected on hosts that have never run Claude Code (e.g. brand-new\n // managers waiting on first agent spawn). Quietly bail.\n return;\n }\n\n let raw: string;\n try {\n raw = readFileSync(STATS_CACHE_PATH, 'utf-8');\n } catch (err) {\n log(`[activity-cache] readFileSync failed: ${(err as Error).message}`);\n return;\n }\n\n const rows = selectNewDailyRows(raw, state.lastObservedDate);\n if (rows.length === 0) return;\n\n for (const row of rows) {\n try {\n await api.post('/host/activity-observations', row);\n // Advance the marker only when the POST landed. If a later row\n // in the same batch fails, retry-on-next-tick covers it.\n state.lastObservedDate = row.date;\n } catch (err) {\n log(\n `[activity-cache] POST /host/activity-observations failed for date=${row.date}: ${(err as Error).message}`,\n );\n // Don't advance the marker — next tick retries this row + the\n // ones after it. Stop the batch so we don't paper over a\n // systemic failure with a flood of misordered writes.\n return;\n }\n }\n}\n","/**\n * ENG-5041: pure helpers for the manager's poll-cycle backoff. Extracted\n * from manager-worker.ts so the math is testable without standing up the\n * whole module (which has hundreds of side-effecting imports).\n */\n\nexport const POLL_BACKOFF_BASE_MS = 1_000;\nexport const POLL_BACKOFF_MAX_MS = 60_000;\n\n/**\n * Compute the next inter-poll delay given how many consecutive failures\n * we've already seen. Doubles per failure (1s → 2s → 4s → 8s → ...),\n * capped at POLL_BACKOFF_MAX_MS. Returns 0 when no failures, so the\n * caller defaults to the normal interval.\n */\nexport function currentPollBackoffMs(consecutivePollFailures: number): number {\n if (consecutivePollFailures <= 0) return 0;\n const expDelay = POLL_BACKOFF_BASE_MS * Math.pow(2, consecutivePollFailures - 1);\n return Math.min(POLL_BACKOFF_MAX_MS, expDelay);\n}\n\n/**\n * Bucket the error message into a stable class string for logging. The\n * point is operators can grep \"[poll-backoff] class=5xx\" and correlate\n * with API/CW logs without reading the full Error.message.\n *\n * Precedence (CR on PR #982): check 5xx markers BEFORE auth. The CLI's\n * exchange wrapper produces strings like \"exchange failed: Internal\n * Server Error\", so a naive auth-first match would bury a server-side\n * outage under an 'auth' label — exactly the wrong signal during the\n * 2026-05-12 prod-down. Lowercase first so substring/regex checks\n * don't have to spell every casing the upstream could use.\n */\nexport function classifyPollError(message: string): 'auth' | '5xx' | 'network' | 'other' {\n const msg = message.toLowerCase();\n if (\n msg.includes('api unreachable') ||\n /\\b5\\d\\d\\b/.test(msg) ||\n msg.includes('internal server error') ||\n msg.includes('bad gateway')\n ) return '5xx';\n if (\n msg.includes('exchange failed') ||\n /\\b401\\b/.test(msg) ||\n msg.includes('invalid token')\n ) return 'auth';\n if (\n msg.includes('econn') ||\n msg.includes('etimedout') ||\n msg.includes('fetch failed')\n ) return 'network';\n return 'other';\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 * ENG-5116 — track consecutive ticks where an agent's persistent session\n * is unhealthy AND we didn't attempt a respawn this tick.\n *\n * Vigil's incident: day-rollover stopped the tmux session at 00:02, then\n * for >6 hours the manager polled every ~30s but the spawn branch never\n * fired and never logged why. The bulk-heartbeat update at the API kept\n * `last_heartbeat_at` fresh, so neither the customer nor the cloud\n * heartbeat alarm caught it. This tracker is the local-manager half of\n * the answer: once a session has been unhealthy without a respawn\n * attempt for `threshold` consecutive ticks, log a loud WARN so the\n * operator (or future Ops agent / synthetic prober) can correlate.\n *\n * Pure state held in a Map — no I/O, no timers — so it's safe to unit\n * test and the manager's per-tick code drives it.\n */\n\nexport interface StuckTickInput {\n codeName: string;\n sessionHealthy: boolean;\n spawnAttempted: boolean;\n}\n\nexport interface StuckTickResult {\n /** Number of consecutive ticks the agent has been unhealthy with no spawn. */\n consecutiveTicks: number;\n /** True on the tick we cross the warning threshold (caller logs). */\n shouldWarn: boolean;\n /** True when the counter just reset (caller can log recovery if useful). */\n recovered: boolean;\n}\n\n/**\n * Default WARN threshold: 3 consecutive ticks. At a ~30s poll interval\n * that's ~90s of an agent being silently down, which beats the 6h Vigil\n * outage by several orders of magnitude without being so noisy that\n * single transient blips trigger pages.\n */\nexport const DEFAULT_STUCK_WARN_THRESHOLD = 3;\n\nexport class PersistentSessionStuckTracker {\n private readonly counts = new Map<string, number>();\n private readonly warned = new Set<string>();\n private readonly threshold: number;\n\n constructor(threshold: number = DEFAULT_STUCK_WARN_THRESHOLD) {\n if (threshold < 1) throw new Error('stuck-tracker threshold must be >= 1');\n this.threshold = threshold;\n }\n\n /**\n * Record one tick for one agent. Returns whether the caller should log\n * a stuck-warning this tick. Re-warning only fires when the counter\n * climbs above the threshold *again* after a recovery, so a stuck\n * agent doesn't spam the log every 30s — operators get one WARN per\n * stuck streak.\n */\n record(input: StuckTickInput): StuckTickResult {\n const { codeName, sessionHealthy, spawnAttempted } = input;\n const wasStuck = (this.counts.get(codeName) ?? 0) >= this.threshold;\n\n // Recovery condition: either the session is healthy, OR we made a\n // genuine respawn attempt this tick. Both reset the streak; the\n // attempt path matters because the next tick will observe whether\n // it actually came up.\n if (sessionHealthy || spawnAttempted) {\n this.counts.delete(codeName);\n const wasWarned = this.warned.delete(codeName);\n return {\n consecutiveTicks: 0,\n shouldWarn: false,\n recovered: wasStuck && wasWarned,\n };\n }\n\n const next = (this.counts.get(codeName) ?? 0) + 1;\n this.counts.set(codeName, next);\n\n // Only warn on the first tick that crosses the threshold — set\n // membership in `warned` debounces re-fires until a recovery.\n const crossed = next >= this.threshold && !this.warned.has(codeName);\n if (crossed) this.warned.add(codeName);\n\n return {\n consecutiveTicks: next,\n shouldWarn: crossed,\n recovered: false,\n };\n }\n\n /** Test/debug helper: peek at an agent's current streak. */\n peek(codeName: string): number {\n return this.counts.get(codeName) ?? 0;\n }\n\n /** Test helper: clear all state. */\n reset(): void {\n this.counts.clear();\n this.warned.clear();\n }\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 // ENG-5599: present on the realtime row (postgres_changes sends every\n // column). Lets onTodayItem skip cards a dedicated path already nudged for —\n // e.g. scheduled-task cards driven by routeScheduledTaskViaKanban — so the\n // generic kanban-work trigger doesn't double-nudge them.\n source_type?: string | null;\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\n/**\n * ENG-5614: the *actual* phoenix socket state, distinct from `connected`\n * (our channel-subscription flag set on SUBSCRIBED / cleared on\n * CLOSED·CHANNEL_ERROR·TIMED_OUT). A half-open websocket can leave\n * `connected === true` while the underlying socket is dead — the channel's\n * close callback never fires, so inbound delivery silently stops while\n * `isRealtimeConnected()` still reports healthy. The manager reconciles the\n * two each poll cycle and forces the polling fallback when they diverge.\n * Returns false when there is no client (never started / torn down), which\n * matches `isRealtimeConnected()` so the divergence check stays inert.\n */\nexport function isRealtimeSocketConnected(): boolean {\n try {\n return client?.realtime?.isConnected() ?? false;\n } catch {\n return false;\n }\n}\n\n/**\n * ENG-5614: pure predicate — should the manager force the polling fallback?\n * True ONLY for the half-open divergence: our channel-subscription flag says\n * connected but the underlying socket is actually dead. Extracted as a pure\n * function so the decision is unit-testable without the manager's module\n * state. Note it returns false when both are false (no client) — the normal\n * \"not connected, just poll\" path, not a divergence.\n */\nexport function shouldForcePollFallback(\n channelConnected: boolean,\n socketConnected: boolean,\n): boolean {\n return channelConnected && !socketConnected;\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","statSync","join","homedir","createHash","log","execFileSync","log","execFileSync","state","api","log","MIN_CHECK_INTERVAL_MS","state","api","log","state","MIN_CHECK_INTERVAL_MS","readFileSync","join","MIN_CHECK_INTERVAL_MS","state","api","log","state","MIN_CHECK_INTERVAL_MS","readFileSync","consecutivePollFailures","client","homedir","join","existsSync","readFileSync","join","execFileSync","log","config","envSuffixFor","existsSync","readdirSync","readFileSync","writeFileSync","homedir","join","existsSync","readdirSync","readFileSync","join","config","log","config","log","join","state","readFileSync","createHash","saveChannelHashCache","execFileSync","existsSync","join","homedir","writeFileSync","readFileSync","rmSync","mkdirSync","loadChannelHashCache","saveChannelHashCache","createHash","log","readdirSync","platform","state","trackedFiles","primaryModel","sessionMode","frameworkId","agentFw","statSync","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/mcp-presence-reaper.ts","../../src/lib/channel-restart-decision.ts","../../src/lib/restart-breaker.ts","../../src/lib/model-change-respawn.ts","../../src/lib/connectivity-probe-executor.ts","../../src/lib/connectivity-probe-runner.ts","../../src/lib/mcp-probe-client.ts","../../src/lib/cli-probe.ts","../../src/lib/usage-banner-monitor.ts","../../src/lib/token-usage-monitor.ts","../../src/lib/activity-cache-monitor.ts","../../src/lib/poll-backoff.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/persistent-session-stuck-tracker.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 KANBAN_CHECK_COMMAND,\n formatRunMarker,\n classifyActor,\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 type { ToolkitSourceType } from '@augmented/core/integrations';\nimport type { IntegrationAuthType } from '@augmented/core';\nimport { reapOrphanChannelMcps } from './orphan-channel-mcp-reaper.js';\nimport { decideMcpDriftAction, decideMcpRestartOnDrift, managedMcpStructureHash } from './mcp-config-drift.js';\nimport { computeIntegrationsHash } from './integration-hash.js';\nimport {\n diffEnvIntegrations,\n findMcpServersUsingVars,\n reapStaleMcpChildren,\n} from './stale-mcp-reaper.js';\n// ENG-5053: presence check for declared MCP children — restarts the\n// session when a declared mcpServers entry has no live child under the\n// agent's claude (e.g. xero-mcp-server crashed and Claude Code's\n// transport didn't respawn it). See lib/mcp-presence-reaper.ts.\nimport {\n reapMissingMcpSessions,\n clearPresenceReaperState,\n clearPresenceReaperStateForKeys,\n} from './mcp-presence-reaper.js';\nimport { decideChannelRestart, launchableChannelIds } from './channel-restart-decision.js';\nimport { RestartBreaker, reaperRestartBreakerReason, type RestartReason, type TripState } from './restart-breaker.js';\nimport { shouldRespawnForModelChange } from './model-change-respawn.js';\n// ENG-5641: host-side connectivity probing.\nimport { runConnectivityProbes } from './connectivity-probe-runner.js';\nimport { probeMcpHttp } from './mcp-probe-client.js';\nimport { runCliProbe } from './cli-probe.js';\nimport type { ConnectivityProbeDeps } from './connectivity-probe-executor.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, provisionOrientHook, estimateActiveTasksTokens } from '@augmented/core/provisioning/frameworks/claudecode/index.js';\nimport '@augmented/core/provisioning/frameworks/managed-agents/index.js';\n\nimport { api, getHostId, exchangeApiKey, ApiError } from './api-client.js';\nimport { maybeReportUsageBanner } from './usage-banner-monitor.js';\nimport { maybeReportTokenUsage } from './token-usage-monitor.js';\nimport { maybeReportActivityCache } from './activity-cache-monitor.js';\nimport { classifyPollError, currentPollBackoffMs } from './poll-backoff.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\nexport interface 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 // ENG-5441: serialised circuit-breaker trips keyed by code_name. Only\n // tripped breakers are persisted — the in-window event log is per-\n // worker and rebuilds from scratch on manager restart. A previously-\n // tripped agent stays tripped across a manager restart so an operator\n // can rely on the trip surviving a `systemctl restart agt-manager`.\n circuitBreakerTrips?: Record<string, TripState>;\n}\n\n/**\n * ENG-5008: stamp `lastRestartProcessedAt` on the fresh AgentState rows\n * built this cycle, carrying forward any prior ack from `state.agents`\n * when no new restart signal arrived this poll.\n *\n * Pre-ENG-5008 only `restartAcks` (fresh signals) populated the field, so\n * every cycle where a previously-acked agent had no new signal dropped\n * the ack — and the very next cycle saw `prev?.lastRestartProcessedAt =\n * null`, the timestamp comparison failed, and the manager re-fired the\n * kill. That's the loop that pinned vigil/vera/stirling for hours on\n * 2026-05-12.\n *\n * Exported for unit testing — caller wires it into the poll loop after\n * `processAgent` has filled `agentStates`.\n */\nexport function applyRestartAcks(args: {\n agentStates: AgentState[];\n priorAgents: AgentState[];\n restartAcks: Map<string, string>;\n}): boolean {\n const { agentStates, priorAgents, restartAcks } = args;\n if (agentStates.length === 0) return false;\n\n const priorAcks = new Map<string, string>();\n for (const prev of priorAgents) {\n if (prev.lastRestartProcessedAt) {\n priorAcks.set(prev.agentId, prev.lastRestartProcessedAt);\n }\n }\n // CodeRabbit (PR #953): return whether anything actually changed in\n // this cycle's agentStates rows so the caller can force a crash-safe\n // immediate write on EITHER a fresh ack OR a carry-forward repair.\n // Pre-fix, the immediate write was keyed off `restartAcks.size > 0`,\n // which missed the case where a prior write had failed silently and\n // this cycle is restoring the ack only via carry-forward.\n let changed = false;\n for (const ack of agentStates) {\n const freshAck = restartAcks.get(ack.agentId);\n const carryForward = priorAcks.get(ack.agentId);\n // Fresh ack from this cycle wins (caller only adds to restartAcks\n // when the requested timestamp is strictly newer than any prior).\n // When no fresh ack arrived, preserve the prior ack so the\n // already-acked restart doesn't re-fire next poll.\n if (freshAck) {\n if (ack.lastRestartProcessedAt !== freshAck) changed = true;\n ack.lastRestartProcessedAt = freshAck;\n } else if (carryForward) {\n if (ack.lastRestartProcessedAt !== carryForward) changed = true;\n ack.lastRestartProcessedAt = carryForward;\n }\n }\n return changed;\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\n/**\n * ENG-4970 / ENG-4974: Slack mirror of CharterPeerWithGate. The runtime\n * Slack classifier (ENG-4936) expects entries keyed on `bot_user_id`\n * (Slack `U…` identity), so the manager extractor produces this shape.\n */\nexport interface CharterSlackPeerWithGate {\n code_name: string;\n bot_user_id: string;\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 * ENG-4970 / ENG-4973: Slack `U…` user_ids of same-team agents (from\n * /host/refresh team_peer_slack_user_ids). Parallel to teamPeerBotIds.\n */\n teamPeerSlackUserIds?: ReadonlyArray<string>;\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. ENG-4973 adds the\n * parallel `granted_agent_slack_user_id` so the Slack extractor can\n * match the same way.\n */\n crossTeamGrants?: ReadonlyArray<{\n grant_id: string;\n granted_agent_bot_id: number | null;\n /** ENG-4970 / ENG-4973: Slack `U…` user_id of granted_agent_id. */\n granted_agent_slack_user_id?: string | 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\n/**\n * ENG-4970 / ENG-4974: Slack parallel of `extractCharterTelegramPeers`.\n * Reads `multi_agent.slack_peers[]` from a CHARTER's frontmatter and\n * resolves a gate_path per peer using the same four-state model\n * (same_team / intra_org_unrestricted / grant:<id> / null).\n *\n * Slack-specific differences from the Telegram extractor:\n * - Keyed on `bot_user_id` (string `U…`) instead of `bot_id` (integer)\n * - Same-team detection uses `teamPeerSlackUserIds` from /host/refresh\n * - Grant match compares `granted_agent_slack_user_id` (ENG-4973)\n *\n * Backwards-compat: when `gateContext` is omitted, every peer is admitted\n * as `same_team` — matches the Telegram extractor's pre-ENG-4935\n * behaviour for managers that haven't picked up the new refresh fields.\n */\nexport function extractCharterSlackPeers(\n rawContent: string,\n gateContext?: CharterPeerGateContext,\n): CharterSlackPeerWithGate[] {\n if (!rawContent || rawContent.length === 0) return [];\n try {\n const parsed = extractFrontmatter(rawContent);\n const frontmatter = parsed.frontmatter as\n | {\n multi_agent?: {\n slack_peers?: Array<{\n code_name?: unknown;\n bot_user_id?: unknown;\n cross_team_grant_id?: unknown;\n }>;\n };\n }\n | null;\n const peers = frontmatter?.multi_agent?.slack_peers;\n if (!peers || !Array.isArray(peers)) return [];\n const teamUserIds = new Set(gateContext?.teamPeerSlackUserIds ?? []);\n const grants = gateContext?.crossTeamGrants ?? [];\n const intraOrg = gateContext?.crossTeamPeerIntraOrg ?? 'unrestricted';\n const now = (gateContext?.now ?? (() => new Date()))();\n const out: CharterSlackPeerWithGate[] = [];\n for (const p of peers) {\n if (\n !p ||\n typeof p !== 'object' ||\n typeof p.code_name !== 'string' ||\n typeof p.bot_user_id !== 'string' ||\n p.bot_user_id.length === 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 gate = 'same_team';\n } else if (teamUserIds.has(p.bot_user_id)) {\n // Same-team wins over a stale CHARTER grant_id — same precedence\n // rule as the Telegram extractor (CodeRabbit on PR #868).\n gate = 'same_team';\n } else if (grantId) {\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_slack_user_id ?? null) === p.bot_user_id\n ) {\n gate = `grant:${grantId}` as const;\n } else {\n gate = null;\n }\n } else if (intraOrg === 'unrestricted') {\n gate = 'intra_org_unrestricted';\n } else {\n gate = null;\n }\n out.push({\n code_name: p.code_name,\n bot_user_id: p.bot_user_id,\n agent_id: '',\n gate_path: gate,\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/**\n * ENG-5090: gate the revoked-agent cleanup block in processAgent so it\n * only runs on the first tick after the status flips to `revoked`.\n * Extracted as an exported pure function so it has a regression-pin\n * test without dragging in the full processAgent surface (matches the\n * extract-and-test pattern in extractCharterTelegramPeers etc.).\n *\n * @param previousKnownStatus the value of `knownStatuses.get(agent.agent_id)`\n * captured at the top of the current processAgent call — `undefined`\n * means the manager has never seen this agent before this tick.\n * @returns true when the caller should skip the cleanup side effects\n * (gateway stop, tmux kill, fs cleanup, cache purge) because they\n * already ran on a prior tick.\n */\nexport function shouldSkipRevokedCleanup(previousKnownStatus: string | undefined): boolean {\n return previousKnownStatus === 'revoked';\n}\n\n/**\n * ENG-5090: residual check that runs after `shouldSkipRevokedCleanup`\n * says \"yes, we already cleaned this up on a prior tick\". A couple of\n * the cleanup ops in processAgent's revoked branch — `stopGatewayIfRunning`\n * (manager-worker.ts:2055) and `cleanupAgentFiles` (manager-worker.ts:8272)\n * — wrap their inner work in try/catch and log on failure rather than\n * rethrowing. That means a failure on the first revoked pass can still\n * reach `knownStatuses.set('revoked')` and trip the short-circuit on\n * subsequent ticks while real residuals (running gateway, allocated\n * port, surviving provision dir) remain.\n *\n * Treat the obvious residuals as a signal to fall back into the full\n * cleanup path. The check is cheap: an `isGatewayRunning` adapter call\n * (no-op `false` for adapters without a gateway, e.g. ClaudeCode), an\n * in-memory JSON read for the port file, and a single `existsSync` for\n * the provision dir.\n *\n * `gatewayRunning` is essential because the revoked branch calls\n * `freePort` and `cleanupAgentFiles` *after* `stopGatewayIfRunning`,\n * and those steps succeed even when the stop failed silently. Without\n * the live-gateway check the port and dir residuals are both wiped to\n * false on the next tick and the short-circuit would leave a zombie\n * gateway running forever — the exact bug CodeRabbit on PR #1054 caught.\n */\nexport function hasRevokedResiduals(state: {\n gatewayRunning: boolean;\n portAllocated: boolean;\n provisionDirExists: boolean;\n}): boolean {\n return state.gatewayRunning || state.portAllocated || state.provisionDirExists;\n}\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\n// ENG-5441: per-manager circuit breaker for runaway session restart loops\n// (today's Scout incident: day-rollover repeated every ~55s for hours).\n// Trip state is mirrored into manager-state.json — see saveBreakerState /\n// hydrateBreakerFromState below — so a manager restart can't accidentally\n// clear a tripped breaker and revive the loop the operator stopped.\nconst restartBreaker = new RestartBreaker();\n// Tracks which trips we've already told the API about, so the same trip\n// doesn't POST /host/circuit-breaker/trip on every poll.\nconst reportedTrips = new Map<string, number>();\n\nfunction recordRestartForBreaker(codeName: string, reason: RestartReason): void {\n const result = restartBreaker.record(codeName, reason);\n if (!result.tripped || !result.trip) return;\n const trip = result.trip;\n log(\n `[persistent-session-decision] agent=${codeName} decision=circuit-tripped ` +\n `detail=${JSON.stringify(trip.statusMessage)} (ENG-5441)`,\n );\n // Fire-and-forget — a missed API call leaves the trip in `reportedTrips`\n // unset, so `maybeReportCurrentTrip` will retry on the next poll.\n // The breaker keeps gating spawns locally regardless.\n maybeReportCurrentTrip(codeName);\n}\n\nasync function reportCircuitTripped(codeName: string, trip: TripState): Promise<void> {\n // Skip the API call if we've already reported this exact trip (same\n // trippedAt timestamp). Without this, every subsequent poll would\n // re-POST until the operator clears, spamming the audit log.\n if (reportedTrips.get(codeName) === trip.trippedAt) return;\n // Look up agent_id from the local cache (populated by processAgent on\n // every poll). Cold-start manager that hydrated a trip but hasn't\n // observed the agent in /host/agents yet will hit this — leave\n // `reportedTrips` untouched so `maybeReportCurrentTrip` retries once\n // the agent_id lands. CodeRabbit on PR #1302.\n const agentId = codeNameToAgentId.get(codeName);\n if (!agentId) throw new Error(`no agent_id known yet for '${codeName}' — will retry next poll`);\n // CodeRabbit on PR #1302: do NOT mark `reportedTrips` until the POST\n // succeeds. Pre-fix, a transient API failure (5xx, JWT expiry race)\n // would stamp the timestamp anyway and silently swallow the trip\n // forever; the breaker would still gate spawns locally but the\n // dashboard / status_message would stay stale.\n await api.post('/host/circuit-breaker/trip', {\n agent_id: agentId,\n status_message: trip.statusMessage,\n tripped_at: new Date(trip.trippedAt).toISOString(),\n events: trip.eventsAtTrip.map((e) => ({ reason: e.reason, at: new Date(e.at).toISOString() })),\n });\n reportedTrips.set(codeName, trip.trippedAt);\n}\n\n// Idempotent helper — call after `codeNameToAgentId` is populated for an\n// agent to retry a pending trip report. Used both on the first trip and\n// on the first poll after a manager restart (where the trip was\n// hydrated from manager-state.json before any /host/agents call).\nfunction maybeReportCurrentTrip(codeName: string): void {\n const trip = restartBreaker.getTrip(codeName);\n if (!trip) return;\n if (reportedTrips.get(codeName) === trip.trippedAt) return;\n reportCircuitTripped(codeName, trip).catch((err) => {\n log(`[circuit-breaker] Failed to report trip for '${codeName}' to API: ${(err as Error).message}`);\n });\n}\n\n\nfunction scheduleSessionRestart(\n codeName: string,\n delayMs: number,\n reason: string,\n // ENG-5441: typed restart reason for the circuit breaker. Defaults to\n // 'hot-reload-mcp' since the only callers today are .mcp.json drift and\n // channel-set change (both hot-reload class), but the parameter is\n // explicit so a future caller can't silently bypass the breaker by\n // omitting it.\n breakerReason: RestartReason = 'hot-reload-mcp',\n): 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 // ENG-5441: record into the breaker AFTER the actual stop fires so\n // coalesced restarts (two triggers in the same tick collapsed into\n // one timer) only count as one restart event — matching what the\n // operator sees in manager.log.\n recordRestartForBreaker(codeName, breakerReason);\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\n// ENG-5285: parallel map of the SET of declared mcpServers keys for the\n// running session. Used by the drift handler to compute a precise diff\n// (added/removed keys) so we only reset presence-reaper give-up state\n// for keys whose membership actually changed — not for keys whose value\n// rotated (e.g. composio refreshing a signed proxy URL every ~20 min,\n// which was the root cause of Stirling flapping 450× over six days\n// post-ENG-5279).\nconst runningMcpServerKeys = new Map<string, Set<string>>();\n\nfunction projectMcpHash(_codeName: string, projectDir: string): string | null {\n try {\n // ENG-5071: hash the canonical (key-sorted) JSON, not the raw bytes.\n // Pre-fix this hashed `readFileSync(...)` directly, so two writers\n // producing logically-equivalent files with different key insertion\n // order would trip drift detection every poll — Scout flapped for\n // hours because writeMcpServer emitted `{ url, headers, command,\n // args }` in one order and sanitizeMcpJson rewrote the same logical\n // entry in another. Canonicalising means only real structural\n // changes (new server, removed server, changed values) move the\n // hash; pure shape/order churn no longer fires a restart. Belt-and-\n // braces alongside the sanitize-headers fix in the same PR — the\n // sanitizer was producing semantically-different content for\n // Granola, but byte-level hashing would still bite the next time\n // any writer pair disagreed on key order.\n const raw = readFileSync(join(projectDir, '.mcp.json'), 'utf-8');\n return createHash('sha256').update(canonicalJson(JSON.parse(raw))).digest('hex');\n } catch {\n return null;\n }\n}\n\n/**\n * ENG-5285: read the project `.mcp.json` and return the set of declared\n * `mcpServers` keys. Returns null when the file is missing or unparseable\n * (caller treats that as \"no information\" — falls back to the wholesale\n * give-up-state clear).\n */\nfunction projectMcpKeys(_codeName: string, projectDir: string): Set<string> | null {\n try {\n const raw = readFileSync(join(projectDir, '.mcp.json'), 'utf-8');\n const parsed = JSON.parse(raw);\n const servers = (parsed as { mcpServers?: Record<string, unknown> }).mcpServers;\n if (!servers || typeof servers !== 'object') return new Set();\n return new Set(Object.keys(servers));\n } catch {\n return null;\n }\n}\n\n// ENG-5641: read a streamable-HTTP MCP server's url + headers from the agent's\n// project .mcp.json, for the connectivity probe. Returns null for stdio servers\n// (no url) or when the key/file isn't resolvable — the probe treats that as a\n// non-escalating transient, never a false 'down'.\nfunction readMcpHttpServerConfig(\n projectDir: string,\n serverKey: string,\n): { url: string; headers?: Record<string, string> } | null {\n try {\n const raw = readFileSync(join(projectDir, '.mcp.json'), 'utf-8');\n const servers = (JSON.parse(raw) as {\n mcpServers?: Record<string, { type?: string; url?: string; headers?: Record<string, string> }>;\n }).mcpServers ?? {};\n const entry = servers[serverKey];\n if (entry && typeof entry.url === 'string' && (entry.type === 'http' || entry.type === undefined)) {\n return { url: entry.url, ...(entry.headers ? { headers: entry.headers } : {}) };\n }\n return null;\n } catch {\n return null;\n }\n}\n\n// ENG-5641: run the rolling host-side connectivity probes for one agent and\n// report results to the control plane. Flag-gated by the caller\n// (AGT_CONNECTIVITY_PROBE_ENABLED) — dormant until enabled + host-validated.\n// Composio is intentionally NOT probed here (its management key is server-side);\n// Composio connectivity is probed centrally. Non-fatal: throws are caught by the\n// caller.\nasync function runAgentConnectivityProbes(\n agent: { agent_id: string; code_name: string },\n integrations: Array<{\n id: string;\n definition_id: string;\n scope: string;\n auth_type: string;\n credentials: Record<string, unknown>;\n source_type?: string | null;\n last_connectivity_check_at?: string | null;\n }>,\n projectDir: string,\n): Promise<void> {\n if (integrations.length === 0) return;\n\n const probeDeps: ConnectivityProbeDeps = {\n fetchImpl: fetch,\n runCli: (binary, args) => runCliProbe(binary, args),\n mcpProbe: async (target) => {\n const cfg = readMcpHttpServerConfig(projectDir, target.serverKey);\n if (!cfg) {\n return { status: 'transient_error', message: `MCP server '${target.serverKey}' not resolvable from .mcp.json` };\n }\n return probeMcpHttp(cfg);\n },\n };\n\n const intervalSec = Number(process.env.AGT_CONNECTIVITY_PROBE_INTERVAL_SECONDS) || 3600;\n const result = await runConnectivityProbes(\n integrations.map((i) => ({\n id: i.id,\n definition_id: i.definition_id,\n scope: i.scope as 'agent' | 'team' | 'organization',\n auth_type: i.auth_type as IntegrationAuthType,\n source_type: (i.source_type ?? null) as ToolkitSourceType | null,\n credentials: i.credentials,\n last_connectivity_check_at: i.last_connectivity_check_at ?? null,\n })),\n { probeDeps, intervalMs: intervalSec * 1000 },\n );\n\n if (result.reports.length > 0) {\n await api.post('/host/integration-connectivity', {\n agent_id: agent.agent_id,\n results: result.reports,\n });\n log(\n `Connectivity probe for '${agent.code_name}': probed=${result.probed} reported=${result.reports.length} due=${result.due}`,\n );\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(\n codeName: string,\n // ENG-5441: every call site that represents a *restart loop trigger*\n // (day-rollover, auth-tuple rotation, reaper-driven restart, hot-reload,\n // channel-set change) passes its reason so the breaker can count it.\n // De-provisioning paths (agent paused / revoked / removed from host /\n // operator-initiated dashboard restart) pass undefined — those are\n // intentional and shouldn't accumulate against the breaker.\n breakerReason?: RestartReason,\n): void {\n cancelPendingSessionRestart(codeName);\n stopPersistentSession(codeName, log);\n runningMcpHashes.delete(codeName);\n runningMcpServerKeys.delete(codeName);\n // ENG-5565: a session teardown (pause / revoke / remove / restart trigger)\n // interrupts any in-flight injected kanban run — close it so it doesn't hang\n // open. 'cancelled' since the work was interrupted, not completed.\n closeInjectedRunIfOpen(codeName, 'cancelled', `session stopped (${breakerReason ?? 'deprovision'})`);\n // ENG-5599: likewise close any in-flight scheduled-task runs routed in-session\n // so an interrupted firing doesn't hang open and mis-attribute later turns.\n closeScheduledRunsForCode(codeName, 'cancelled', `session stopped (${breakerReason ?? 'deprovision'})`);\n if (breakerReason) {\n recordRestartForBreaker(codeName, breakerReason);\n }\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 // ENG-5285: also capture the declared mcpServers key set so the\n // next drift event can diff added/removed keys without re-reading\n // the previous file (which is already overwritten by then).\n //\n // ENG-5285 follow-up (CodeRabbit on PR #1178): preserve the\n // \"unknown\" signal from projectMcpKeys — a transient read/parse\n // race should NOT poison the baseline with an empty set, which\n // would make the next drift see every current key as \"newly\n // added\" and reset the cap on every MCP. Treat null as \"no\n // baseline\" — the drift handler falls back to the wholesale\n // clear, which is the safe choice when we don't know what the\n // previous declared set was.\n const baselineKeys = projectMcpKeys(codeName, projectDir);\n if (baselineKeys) {\n runningMcpServerKeys.set(codeName, baselineKeys);\n } else {\n runningMcpServerKeys.delete(codeName);\n }\n return;\n }\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)}) (ENG-4897)`,\n );\n // ENG-5285: clear presence-reaper give-up state ONLY for keys\n // whose membership in the declared set actually changed — i.e.\n // keys that were added or removed. Keys present in both before\n // and after (typical for value-only drift, e.g. composio rotating\n // a signed proxy URL every ~20 min) keep their cap accumulated.\n // Pre-fix this wholesale-cleared the agent's reaper state on\n // every URL rotation, which was why Stirling flapped 450× over\n // six days despite ENG-5279's restart cap — the cap was reset\n // before it could trip.\n // ENG-5285 follow-up (CodeRabbit on PR #1178): preserve the\n // \"unknown\" signal from projectMcpKeys. Coercing either side to\n // an empty set would either poison the comparison (every current\n // key looks \"newly added\") or hide the drift (both empty looks\n // like \"no membership change\"). When either side is unknown,\n // fall back to the wholesale clear — the safe behaviour we had\n // pre-ENG-5285, used here as the fallback rather than the\n // default.\n const previousKeys = runningMcpServerKeys.get(codeName);\n const currentKeys = projectMcpKeys(codeName, projectDir);\n // ENG-5537: only a *membership* change (server added/removed) warrants\n // restarting the running session. A value-only drift — the same set of\n // servers with a rotated url/header value, e.g. composio re-signing\n // `mcp_url` every poll — leaves the running session's established MCP\n // connections intact, and a future respawn reads the freshly-written\n // file anyway. Pre-fix this scheduled a restart on EVERY drift, so a\n // flapping signed url tripped the ENG-5441 breaker and auto-paused the\n // agent within minutes. When key membership is unknown we keep the\n // pre-ENG-5537 safe default (treat as a restart-worthy change).\n const decision = decideMcpRestartOnDrift(previousKeys, currentKeys);\n if (decision.membershipUnknown) {\n clearPresenceReaperState(codeName);\n log(\n `[hot-reload] .mcp.json key membership unavailable for '${codeName}' — clearing full presence-reaper state and restarting (ENG-5285/ENG-5537)`,\n );\n } else if (decision.restart) {\n clearPresenceReaperStateForKeys(codeName, new Set(decision.addedOrRemoved));\n log(\n `[hot-reload] .mcp.json membership changed for '${codeName}': ` +\n `${decision.addedOrRemoved.length} key(s) added/removed [${decision.addedOrRemoved.join(', ')}] — reaper state cleared for those keys only (ENG-5285)`,\n );\n } else {\n log(\n `[hot-reload] .mcp.json value-only drift for '${codeName}' — preserving presence-reaper state and NOT restarting (ENG-5285/ENG-5537)`,\n );\n }\n\n if (decision.restart) {\n scheduleSessionRestart(codeName, 0, '.mcp.json content change (ENG-4897)');\n // Drop the entries — the next poll after respawn captures the\n // new hash + key set as baseline. Keeping the old values would\n // re-fire every poll until the respawn timer triggers.\n runningMcpHashes.delete(codeName);\n runningMcpServerKeys.delete(codeName);\n } else {\n // ENG-5537: value-only drift — adopt the new content as the baseline\n // so we don't re-log/re-evaluate every poll, but leave the running\n // session untouched. currentKeys is non-null here (membershipUnknown\n // would have set decision.restart=true), so the key set stays in sync.\n runningMcpHashes.set(codeName, action.current);\n if (currentKeys) runningMcpServerKeys.set(codeName, currentKeys);\n }\n return;\n }\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>();\n// ENG-5537: structure-only hash of the same desired set — serverId membership\n// + header KEY names, excluding url and header VALUES. Composio (and peers)\n// rotate a freshly-signed `mcp_url` on most polls, which moves the value hash\n// above (so .mcp.json re-converges to the live credential) but must NOT\n// restart a healthy session. The session restart is gated on THIS hash so a\n// pure credential-URL rotation no longer flaps the agent into the ENG-5441\n// circuit breaker (phil, agt-aws-1, 2026-05-25).\nconst knownManagedMcpStructure = new Map<string, string>();\nconst knownSkillHashes = new Map<string, string>();\n// ENG-5292: reverse mapping from (agent_id, serverId) to toolkit_id.\n// The serverId is the .mcp.json key — the lowercased-non-alnum-to-\n// underscore form of toolkit_id that writeMcpServer uses. The reaper's\n// onGiveUp callback consults this so it can tell the API which toolkit\n// to mark unhealthy.\n//\n// CodeRabbit on PR #1184: scoped per-agent (composite key) — NOT keyed\n// by serverId alone. Two different agents can declare the same\n// toolkit (same serverId), and stale entries from a now-removed agent\n// must not misattribute a give-up on another agent's MCP to the wrong\n// toolkit_id. The composite key also lets us prune per-agent on agent\n// removal without a full scan.\nconst managedToolkitIdByAgentAndServerId = new Map<string, string>();\n\nfunction managedToolkitMapKey(agentId: string, serverId: string): string {\n return `${agentId}\\x00${serverId}`;\n}\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// ENG-5408: separate set for the structured stuck-task watchdog — we want\n// one log line per stuck row per occurrence so CloudWatch metric filters\n// can count them, but we still don't want to spam every poll tick for\n// the same row. Cleared in lockstep with alertedStaleItems below.\nconst stuckKanbanLogged = new Set<string>();\n// Track agents with known API key errors (avoid spamming the API)\nconst apiKeyStatusCache = new Map<string, boolean>();\n// ENG-5408: threshold env var, falls back to 30 min. The existing\n// auto-fail path (around manager-worker.ts:5042) and the new structured\n// stuck-task watchdog log line both honour this — operators can tighten\n// or loosen the budget without a code change.\nconst STALE_TASK_THRESHOLD_MS = (() => {\n const env = process.env['AUGMENTED_STUCK_TASK_THRESHOLD_MINUTES'];\n const minutes = env ? Number.parseInt(env, 10) : NaN;\n if (Number.isFinite(minutes) && minutes > 0) return minutes * 60 * 1000;\n return 30 * 60 * 1000;\n})();\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 knownManagedMcpStructure.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 // CodeRabbit on PR #1318: drop the per-agent dedupe so a re-enrolled\n // agent's first banner observation isn't suppressed by the previous\n // generation's cached count.\n lastMcpFailedBannerCount.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// ENG-5399: Tier 1 responsiveness probe — pane.log freshness reported\n// per agent on a slow cadence. Throttled here because pollCycle runs\n// every 10s but we only need this once every few minutes (the existing\n// SyntheticReplyAgeSeconds cron runs every 15 min as the deeper check).\nlet lastResponsivenessProbeAt = 0;\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 * Detect whether the `claude` binary is installed on this host. Mirrors\n * resolveBrewPath's three-platform-fallback shape: Linuxbrew (EC2 default),\n * Apple-silicon macOS, Intel macOS — then `which claude` as a last resort\n * for unusual install locations.\n *\n * Pre-CodeRabbit fix (PR #1226 review): only the Linuxbrew path was\n * checked before falling back to `which`, which fails on macOS launchd\n * with a sparse PATH. The manager would then treat Claude as missing,\n * stamp the upgrade marker, and skip the upgrade — silently never\n * refreshing Claude Code on dev macs.\n */\nfunction claudeBinaryInstalled(\n execFileSync: typeof import('node:child_process').execFileSync,\n): boolean {\n const canonical = [\n '/home/linuxbrew/.linuxbrew/bin/claude',\n '/opt/homebrew/bin/claude',\n '/usr/local/bin/claude',\n ];\n if (canonical.some((path) => existsSync(path))) return true;\n try {\n execFileSync('which', ['claude'], { timeout: 5_000 });\n return true;\n } catch {\n return false;\n }\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 const claudeExists = claudeBinaryInstalled(execFileSync);\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\n // ENG-5357: fresh install is by definition the latest version — stamp\n // the upgrade marker so the next poll's maybeUpgradeClaudeCode() won't\n // immediately fire a redundant `brew upgrade`. Same disk-persisted\n // throttle described next to maybeUpgradeClaudeCode().\n stampClaudeCodeUpgradeMarker();\n }\n // ENG-5357: upgrade for existing installs is no longer kicked off here.\n // The in-memory `frameworkBinaryChecked` guard above is process-scope,\n // so on alyve-host-1 a restart storm fired N concurrent `brew upgrade\n // --cask claude-code` jobs and pegged the host until it wedged. Upgrade\n // now lives in maybeUpgradeClaudeCode() which is called from pollCycle\n // and throttled via an on-disk 24h marker — restarts can no longer\n // re-trigger it.\n\n // Check if Claude Code is authenticated. The binary that's already on\n // disk works regardless of upgrade state.\n agentRuntimeAuthenticated = await checkClaudeAuth();\n}\n\n// ---------------------------------------------------------------------------\n// Claude Code background upgrade (ENG-5357)\n// ---------------------------------------------------------------------------\n//\n// Called from pollCycle on every tick. Returns immediately when the disk\n// marker is fresh — only the once-per-day actual `brew upgrade` does any\n// real work. This is the same disk-marker pattern as the agt CLI self-\n// update path (checkAndUpdateCli); the window is just longer because\n// Claude Code releases on a much slower cadence than the agt CLI.\n//\n// The marker is stamped BEFORE the fire-and-forget upgrade kicks off so\n// concurrent poll cycles inside the 30-120s upgrade window cannot double-\n// trigger. Without that pre-stamp, every poll tick during a slow upgrade\n// would see \"no marker yet\" and queue another brew job behind the lock —\n// the same restart-loop pathology this issue was created to fix.\n\nconst CLAUDE_CODE_UPGRADE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24h\nlet claudeCodeUpgradeInFlight = false;\n\n// Exported for unit testing — internal callers should use the wrappers below.\nexport function claudeCodeUpgradeMarkerPath(): string {\n return join(homedir(), '.augmented', '.last-claude-code-upgrade-check');\n}\n\nexport function stampClaudeCodeUpgradeMarker(): void {\n try {\n writeFileSync(claudeCodeUpgradeMarkerPath(), String(Date.now()));\n } catch { /* non-fatal — next poll will try again */ }\n}\n\nexport function claudeCodeUpgradeThrottled(): boolean {\n try {\n const lastCheck = parseInt(readFileSync(claudeCodeUpgradeMarkerPath(), 'utf-8').trim(), 10);\n if (!Number.isFinite(lastCheck)) return false;\n return Date.now() - lastCheck < CLAUDE_CODE_UPGRADE_CHECK_INTERVAL_MS;\n } catch {\n // No marker yet → not throttled → first call writes it.\n return false;\n }\n}\n\nexport async function maybeUpgradeClaudeCode(): Promise<void> {\n if (claudeCodeUpgradeInFlight) return;\n if (claudeCodeUpgradeThrottled()) return;\n\n // Stamp BEFORE kicking off the upgrade so concurrent polls inside the\n // upgrade window see \"throttled\" and bail. Without this, every poll\n // tick during the 30-120s brew run would queue another job. The\n // in-flight flag is a second belt for the rare case where two polls\n // race past the disk read in the same event-loop tick.\n claudeCodeUpgradeInFlight = true;\n stampClaudeCodeUpgradeMarker();\n\n const { execFileSync } = await import('node:child_process');\n const brewPath = resolveBrewPath(execFileSync);\n if (!brewPath) {\n claudeCodeUpgradeInFlight = false;\n return;\n }\n\n // Only upgrade if the binary already exists; install lives in\n // ensureFrameworkBinary's hot path (it's a hard prerequisite, not a\n // refresh, and shouldn't be deferred to the poll loop).\n if (!claudeBinaryInstalled(execFileSync)) {\n claudeCodeUpgradeInFlight = false;\n return;\n }\n\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 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 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 .finally(() => { claudeCodeUpgradeInFlight = false; });\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 // Release channel — `latest` (prod default) or a gated channel like `test`\n // (set on the test environment's hosts via AGT_CLI_RELEASE_CHANNEL). The\n // channel is the npm dist-tag we track: prod hosts follow `latest`,\n // test hosts follow `test`, same registry. Unset ⇒ `latest`, so prod\n // behaviour is unchanged.\n const channel = process.env.AGT_CLI_RELEASE_CHANNEL || 'latest';\n\n // Query the npm registry's channel 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/${encodeURIComponent(channel)}`,\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} (channel=${channel})`);\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 // `latest` channel: never move backwards — only upgrade to a strictly\n // newer semver (prod-safe; ignores prerelease tags entirely). A gated\n // channel is a moving pointer to a specific build, so track it exactly:\n // install whenever the tag points somewhere other than what we run,\n // including prereleases that isNewerSemver can't compare.\n const shouldUpdate =\n channel === 'latest' ? isNewerSemver(agtCliVersion, latest) : latest !== agtCliVersion;\n if (!shouldUpdate) {\n if (!selfUpdateUpToDateLogged) {\n log(`[self-update] agt CLI is up to date (npm, channel=${channel}, ${agtCliVersion})`);\n selfUpdateUpToDateLogged = true;\n }\n return;\n }\n\n log(`[self-update] agt CLI update available: ${agtCliVersion} → ${latest} (channel=${channel}). 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\n// ENG-5041: in-process backoff for /host/exchange and poll-cycle failures.\n// Pre-fix the manager would `process.exit(1)` on any \"exchange failed\" or\n// \"401\" error and rely on the systemd/launchd watchdog to restart it,\n// which during a sustained API outage (e.g. the S3 SDK prod-down on\n// 2026-05-12 that returned 500 for ~30 min) caused a restart storm and\n// blackholed the agent for the duration. Now we stay running, log each\n// attempt, and double the delay up to a 60s cap. A successful cycle\n// resets the counter so we go straight back to the normal interval.\n// Math + classifier extracted to ./poll-backoff.ts so they're testable\n// without dragging in this whole module's import graph.\nlet consecutivePollFailures = 0;\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 // ENG-5441: restart-flags are written by channel MCPs (slack/\n // telegram/direct-chat) when they hit an unrecoverable error\n // they can't fix in-place. If a single channel keeps writing\n // restart flags, it counts against the breaker — same protection\n // as a hot-reload loop.\n stopPersistentSessionAndForgetMcpBaseline(codeName, 'channel-set-change');\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 // ENG-5357: same fire-and-forget shape for the Claude Code framework\n // binary. Throttled to once per 24h via an on-disk marker so a restart\n // storm (e.g. transient API 5xx during a deploy) cannot fire N\n // concurrent `brew upgrade --cask claude-code` jobs the way it could\n // pre-fix — that pathology pegged alyve-host-1 until reboot.\n maybeUpgradeClaudeCode().catch((err) => log(`[claude-code-upgrade] 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 // ENG-5614: inbound-loop liveness. The actual phoenix socket state\n // behind the direct-chat Realtime subscription. When this is false\n // but the manager keeps heartbeating, inbound is being served by the\n // polling fallback (healRealtimeSocketDivergence forces it). Surfaced\n // so a sustained socket-down window is observable rather than hidden\n // behind a fresh heartbeat.\n realtime_socket_connected: isRealtimeSocketConnected(),\n });\n } catch (err) {\n log(`Heartbeat failed: ${(err as Error).message}`);\n }\n\n // 1d. ENG-5399 Tier 1 responsiveness probe — pane.log freshness per\n // agent. Reports `PaneActivityAgeSeconds` on a slow cadence (default\n // 5 min via AUGMENTED_RESPONSIVENESS_INTERVAL_MS). Complements the\n // existing 15-min agent-synthetic-probe cron with a faster, zero-\n // token canary that catches silent agents before the 35-min\n // SyntheticReplyAgeSeconds alarm window.\n try {\n const {\n collectResponsivenessProbes,\n getResponsivenessIntervalMs,\n } = await import('./responsiveness-probe.js');\n const probeIntervalMs = getResponsivenessIntervalMs();\n if (now - lastResponsivenessProbeAt > probeIntervalMs) {\n const probeCodeNames = [...persistentSessionAgents];\n if (probeCodeNames.length > 0) {\n const probes = collectResponsivenessProbes(probeCodeNames);\n if (probes.length > 0) {\n // Fire-and-forget — never block pollCycle on CloudWatch\n // latency, and a transient API error here mustn't pause\n // the rest of the cycle.\n void api\n .post('/host/responsiveness-probe', { host_id: hostId, probes })\n .catch((err: unknown) => {\n log(`[responsiveness-probe] post failed: ${(err as Error).message}`);\n });\n }\n }\n lastResponsivenessProbeAt = now;\n }\n } catch (err) {\n log(`[responsiveness-probe] collection failed: ${(err as Error).message}`);\n }\n\n // 1e. ENG-5459 — scrape \"N MCP servers failed · /mcp\" from each\n // active agent's pane.log and POST to the API so the webapp can\n // surface a \"needs attention\" signal alongside the existing\n // needsReconnect indicator. Catches the broken-but-process-alive\n // case mcp-presence-reaper can't see (Composio MCP whose URL\n // 401s on every tool call — process up, API dead).\n //\n // Fire-and-forget per agent. Only POSTs when the count differs\n // from the last observation (cheap dedupe — most ticks the count\n // is stable). Skipped entirely for agents with no pane.log.\n try {\n const { scrapeMcpFailedBannerCount } = await import('./pane-mcp-banner-scraper.js');\n const observations: Array<{ agent_id: string; count: number }> = [];\n // CodeRabbit on PR #1318: commit dedupe-cache only after POST\n // succeeds, otherwise a transient API failure permanently\n // suppresses future POSTs of the same value. Build a parallel\n // (codeName, count) list while we walk agents so the success\n // handler can apply the cache without a reverse lookup.\n const pendingCacheCommits: Array<{ codeName: string; count: number }> = [];\n for (const codeName of persistentSessionAgents) {\n const agentId = codeNameToAgentId.get(codeName);\n if (!agentId) continue;\n // 40 lines is enough to catch the banner — it renders on\n // every frame, so the last few KB always contain it.\n const tail = readPaneLogTail(codeName, 40);\n if (!tail) continue;\n const observed = scrapeMcpFailedBannerCount(tail);\n // Null = no banner detected this tick. Treat as 0 — Claude\n // Code omits the banner when all MCPs are healthy. Persisting\n // 0 also lets the alarm transition back to OK on recovery.\n const count = observed ?? 0;\n const last = lastMcpFailedBannerCount.get(codeName);\n if (last === count) continue; // dedupe — no change since last poll\n observations.push({ agent_id: agentId, count });\n pendingCacheCommits.push({ codeName, count });\n }\n if (observations.length > 0) {\n // CodeRabbit on PR #1318 round 4: only commit the cache for\n // observations the API actually accepted. The endpoint\n // returns `{ results: [{ agent_id, ok, error? }] }` — a row\n // that failed (e.g. host not assigned anymore) should be\n // retried on the next tick, not silently suppressed.\n void api\n .post<{ results?: Array<{ agent_id: string; ok: boolean }> }>(\n '/host/mcp-banner-observation',\n { observations },\n )\n .then((resp) => {\n const accepted = new Set(\n (resp?.results ?? [])\n .filter((r) => r.ok)\n .map((r) => r.agent_id),\n );\n for (const { codeName, count } of pendingCacheCommits) {\n const agentId = codeNameToAgentId.get(codeName);\n if (agentId && accepted.has(agentId)) {\n lastMcpFailedBannerCount.set(codeName, count);\n }\n }\n })\n .catch((err: unknown) => {\n log(`[mcp-banner] post failed: ${(err as Error).message}`);\n });\n }\n } catch (err) {\n log(`[mcp-banner] collection 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-5445: forward Claude Code's host-wide activity counts from\n // `~/.claude/stats-cache.json` to /host/activity-observations. Once\n // per cycle (not per-agent) — the cache is host-scoped, every\n // session on the machine contributes to the same numbers. The\n // monitor handles its own rate-limiting and the API endpoint\n // upserts on (host_id, date), so calling here every cycle is\n // safe. Fire-and-forget — observability writes must not slow the\n // poll cycle.\n void maybeReportActivityCache({ api, log });\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 //\n // ENG-5008: carry forward `lastRestartProcessedAt` from `state.agents`\n // (the prior cycle's state) for every agent in this cycle's\n // agentStates, regardless of whether `restartAcks` has a fresh\n // timestamp this poll. `processAgent` rebuilds AgentState from scratch\n // on every successful path (line 4416+), so without this copy-forward\n // the field gets dropped any cycle where a previously-acked agent\n // doesn't have a brand-new restart signal — and the very next cycle\n // sees `prev?.lastRestartProcessedAt = null`, the timestamp comparison\n // fails, and the manager re-fires the kill. That's the loop that\n // pinned vigil/vera/stirling for hours on 2026-05-12.\n // CodeRabbit (PR #953): key the immediate write off whether any\n // ack state actually changed this cycle, not just fresh signals.\n // A carry-forward repair after a prior write-failure should also\n // persist immediately to close the same crash-replay window.\n const restartAckStateChanged = applyRestartAcks({\n agentStates,\n priorAgents: state.agents,\n restartAcks,\n });\n if (restartAckStateChanged) {\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 // ENG-5614: reconcile the realtime socket vs the channel-subscription\n // flag BEFORE deciding whether to poll. A half-open socket leaves the\n // flag stale-true, which would otherwise skip the poll fallback and\n // silently drop inbound direct-chat. On divergence this forces the\n // flag false (and a reconnect next tick) so the poll fallback runs now.\n healRealtimeSocketDivergence();\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 // ENG-5441: serialise trip state on every poll so manager restarts\n // never silently clear a tripped breaker. Cheap — only tripped\n // entries are stored, and the typical state is zero entries.\n circuitBreakerTrips: restartBreaker.serialize(),\n };\n\n // ENG-5041: cycle completed — clear the backoff counter so the next\n // tick fires at the normal interval. Logging only on the transition\n // (n>0 → 0) so a healthy host doesn't spam recovery messages.\n if (consecutivePollFailures > 0) {\n log(`[poll-backoff] recovered after ${consecutivePollFailures} failure(s), resuming normal interval`);\n consecutivePollFailures = 0;\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 // ENG-5041: stay running and back off in-process instead of exiting\n // for the watchdog. Auth and 5xx failures are usually transient (key\n // rotation, API restart, prod outage) and the watchdog restart loop\n // was both slow and noisy — see the 2026-05-12 prod-down where the\n // S3 SDK regression made every /host/exchange return 500 for ~30\n // min and agt-aws-1 thrashed the whole time.\n consecutivePollFailures += 1;\n // CR (CodeRabbit on PR #982): log the *actual* scheduled delay (which\n // scheduleNext computes as max(intervalMs, backoffMs)) rather than the\n // raw backoff value, so the log line agrees with what really happens.\n // Use `_ms` suffix instead of seconds-with-s so downstream parsers\n // (Loki, awk, etc.) don't have to strip units.\n const scheduledDelayMs = Math.max(\n config?.intervalMs ?? 0,\n currentPollBackoffMs(consecutivePollFailures),\n );\n const errorClass = classifyPollError(message);\n log(`[poll-backoff] attempt=${consecutivePollFailures} class=${errorClass} next_retry_ms=${scheduledDelayMs}`);\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 // ENG-5080: the per-agent banner used to fire here unconditionally —\n // one line per agent per poll-cycle (10s default), so an idle host with\n // three agents wrote ~26k pure-decoration lines per day to CloudWatch.\n // Every nested log() in this function carries `'${agent.code_name}'`,\n // so removing the banner doesn't cost any forensic context. Keep the\n // map writes; they're cheap and other call sites depend on them.\n //\n // The status-branches below (draft / paused / revoked) similarly used\n // to log unconditionally; they now edge-trigger on `knownStatuses`,\n // matching the existing \"status changed\" pattern further down.\n const previousKnownStatus = knownStatuses.get(agent.agent_id);\n agentDisplayNames.set(agent.code_name, agent.display_name);\n codeNameToAgentId.set(agent.code_name, agent.agent_id);\n // ENG-5441 + CodeRabbit on PR #1302: if a trip was hydrated from\n // `manager-state.json` (manager restart with a pre-existing trip),\n // `codeNameToAgentId` wasn't populated when the breaker's record path\n // first tried to report. Retry now that we know the agent_id.\n // Idempotent — `reportedTrips` short-circuits already-reported trips.\n maybeReportCurrentTrip(agent.code_name);\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 // ENG-5389: scrape the Claude Code \"X% of your weekly limit\" banner from\n // pane.log and forward observations to the API. Claude Code only — the\n // banner doesn't exist in other framework runtimes. The monitor handles\n // its own rate-limiting and dedupes against an in-memory last value, so\n // calling it every cycle for every active CC agent is safe. Fire-and-\n // forget — observability writes must not slow the poll cycle.\n if (agent.framework === 'claude-code' && agent.status === 'active') {\n void maybeReportUsageBanner({\n api,\n codeName: agent.code_name,\n agentId: agent.agent_id,\n log,\n });\n\n // ENG-5516: read per-agent token usage from the session transcripts and\n // upsert it via /host/token-usage so usage is comparable across agents.\n // Same fire-and-forget contract as the banner monitor: self-rate-limited,\n // self-deduped, errors swallowed — must not slow the poll cycle.\n void maybeReportTokenUsage({\n api,\n codeName: agent.code_name,\n agentId: agent.agent_id,\n log,\n });\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 if (previousKnownStatus !== agent.status) {\n log(`Agent '${agent.code_name}' is ${agent.status}, skipping provisioning`);\n }\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 // ENG-5080: needed for the edge-triggered log above. The branch\n // returns before the canonical knownStatuses.set near the end of\n // processAgent, so we'd otherwise log \"is draft, skipping\" every\n // 10s for the lifetime of the draft.\n knownStatuses.set(agent.agent_id, agent.status);\n return;\n }\n\n if (agent.status === 'revoked') {\n // ENG-5090: short-circuit on the second-and-later tick. All cleanup\n // below is idempotent — gateway already stopped, tmux session\n // already dead, files already removed, caches already cleared — so\n // re-running it every 10s for the lifetime of a revoked agent is\n // pure waste (child-process spawn for tmux, fs walks, Map churn).\n // ENG-5080 silenced the log line; this short-circuits the work.\n //\n // Re-enrolment safety: if the agent ever flips off `revoked` (e.g.\n // an operator un-revokes — rare but possible), the canonical\n // `status changed: prev → curr` branch a few lines below picks up\n // the transition because we still keep `knownStatuses` accurate.\n if (shouldSkipRevokedCleanup(previousKnownStatus)) {\n // CodeRabbit on PR #1052: stopGatewayIfRunning and cleanupAgentFiles\n // swallow internal errors and log them, so a failed first-pass\n // cleanup can still set knownStatuses['revoked'] and silently\n // skip retries. If obvious residuals remain (gateway process\n // alive, port still in the gateway-ports file, provision dir\n // still on disk), fall through to retry instead of short-circuiting.\n //\n // Gateway liveness is checked first because `freePort` and\n // `cleanupAgentFiles` run *after* `stopGatewayIfRunning` and\n // succeed even when the stop failed silently. Without this check\n // the port + dir residuals would both be false on the next tick\n // while the gateway process kept running forever (CodeRabbit on\n // PR #1054).\n const ports = loadGatewayPorts();\n const gatewayLiveness = adapter.isGatewayRunning\n ? await adapter.isGatewayRunning(agent.code_name).catch(() => ({ running: false }))\n : { running: false };\n const residuals = {\n gatewayRunning: gatewayLiveness.running,\n portAllocated: Object.prototype.hasOwnProperty.call(ports, agent.code_name),\n provisionDirExists: existsSync(agentDir),\n };\n if (!hasRevokedResiduals(residuals)) {\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 // Residuals detected — note it once (this branch only triggers\n // when the first-pass cleanup partially failed, which is rare)\n // and fall through to the full cleanup block below for retry.\n log(\n `[revoked] residuals for '${agent.code_name}': ` +\n `gateway=${residuals.gatewayRunning} ` +\n `dir=${residuals.provisionDirExists} ` +\n `port=${residuals.portAllocated ? ports[agent.code_name] : 'none'} ` +\n `— retrying cleanup`,\n );\n }\n\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 // ENG-5441: paused → active means the operator cleared the trip\n // (either through the webapp resume or `agt agent resume`). Drop the\n // breaker state so the agent can spawn on this tick. We don't clear\n // on every status-change because draft/revoked transitions don't\n // imply operator intent to recover from a trip.\n if (previousStatus === 'paused' && agent.status === 'active' && restartBreaker.isTripped(agent.code_name)) {\n restartBreaker.clear(agent.code_name);\n reportedTrips.delete(agent.code_name);\n log(`[circuit-breaker] Cleared trip for '${agent.code_name}' on operator resume (paused → active)`);\n }\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 * Effective guardrails for this agent, pre-resolved server-side\n * (org → team → agent + overrides) and joined with their\n * definitions. Threaded straight into `provisionInput.guardrails`\n * for the framework adapter to render. Omitted by older API\n * builds; downstream code treats `undefined` and `[]` the same.\n */\n guardrails?: ProvisionInput['guardrails'];\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 // ENG-5635: a model change only takes effect on the next spawn — the\n // launcher derives `--model <alias>` from primary_model at launch\n // (ENG-5631); the settings.json write above is inert for claude-code at\n // runtime. So for a live claude-code session, proactively respawn now\n // (via the coalescing scheduler, which preserves the presence-reaper\n // baseline since channels/MCP are unchanged) instead of leaving the\n // agent on its old model until the next natural restart. The gate skips\n // first-provision (no previousModel — the imminent first spawn already\n // carries the model) and the no-session case (next spawn picks it up).\n if (\n shouldRespawnForModelChange({\n previousModel,\n primaryModel,\n framework: frameworkId,\n sessionHealthy: isSessionHealthy(agent.code_name),\n })\n ) {\n const restartNotice =\n `Your model was changed to ${primaryModel}. Claude Code applies the model at launch, ` +\n `so your manager will restart your session shortly to switch.`;\n const delivered = await injectMessage(\n agent.code_name, 'system', restartNotice, { task_name: 'model-update' }, log,\n ).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 log(`[hot-reload] Model changed for '${agent.code_name}': ${previousModel} → ${primaryModel} — restarting session`);\n scheduleSessionRestart(agent.code_name, delay, 'model change', 'model-change');\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 // ENG-5363: team timezone propagates into the spawned channel\n // MCP child as the `TZ` env var so progress-message timestamps\n // (\"last update: HH:MM:SS TZ\") render in the agent's local\n // time instead of the host system zone (UTC on Lambda/EC2).\n // Folded into the hash so a team timezone change invalidates\n // the cache and triggers a credential rewrite.\n const agentTimezone = (() => {\n const tz = teamSettings?.['timezone'];\n return typeof tz === 'string' && tz.trim() !== '' ? tz.trim() : undefined;\n })();\n const teamSettingsForHash =\n channelId === 'telegram' || channelId === 'slack' || channelId === 'direct-chat'\n ? { peer_disabled: peerDisabledMode, timezone: agentTimezone ?? null }\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?: {\n inbound?: ReadonlyArray<{\n grant_id: string;\n granted_agent_bot_id: number | null;\n /** ENG-4970 / ENG-4973: Slack `U…` user_id of granted_agent_id. */\n granted_agent_slack_user_id?: string | null;\n revoked_at: string | null;\n expires_at: string | null;\n }>;\n };\n team_peer_bot_ids?: ReadonlyArray<number>;\n /** ENG-4970 / ENG-4973: Slack `U…` user_ids of same-team agents. */\n team_peer_slack_user_ids?: ReadonlyArray<string>;\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 //\n // CodeRabbit on PR #934: pick the discriminator per-channel.\n // The previous all-Telegram discriminator would short-circuit\n // the Slack backwards-compat fallback on managers running\n // against a /host/refresh that has the Telegram fields but\n // not yet the Slack ones (mid-rollout). Without this, same-team\n // Slack peers would get downgraded to intra_org_unrestricted /\n // null because gateContext is \"present but with empty Slack\n // arrays\" — not the same as \"absent entirely\".\n const refreshHasCrossTeamFields =\n channelId === 'telegram'\n ? Array.isArray(crossTeamData.team_peer_bot_ids)\n : channelId === 'slack'\n ? Array.isArray(crossTeamData.team_peer_slack_user_ids)\n : false;\n const gateContext: CharterPeerGateContext | undefined =\n refreshHasCrossTeamFields\n ? {\n teamPeerBotIds: crossTeamData.team_peer_bot_ids ?? [],\n teamPeerSlackUserIds: crossTeamData.team_peer_slack_user_ids ?? [],\n crossTeamGrants: crossTeamData.cross_team_grants?.inbound ?? [],\n crossTeamPeerIntraOrg:\n crossTeamData.organization?.cross_team_peer_intra_org ?? 'unrestricted',\n }\n : undefined;\n // Peer roster folded into the hash so a CHARTER edit / grant\n // flip invalidates the cache. Telegram + Slack each carry\n // their own peer arrays in CHARTER; whichever channel we're\n // rendering picks the matching one.\n const peersForHash =\n channelId === 'telegram'\n ? extractCharterTelegramPeers(refreshData.charter?.raw_content ?? '', gateContext)\n : channelId === 'slack'\n ? extractCharterSlackPeers(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 // v3 — ENG-4970 / ENG-4974: Slack MCP child gets the same\n // AGT auth trio + SLACK_PEERS / SLACK_PEERS_GATE /\n // SLACK_PEER_AGENT_MODE / SLACK_PEER_GROUP_IDS env so\n // cross-team peer collab finally works on Slack at\n // runtime (previously the classifier had an empty peer\n // list).\n // v5 — ENG-5363: fold `agentTimezone` (from\n // team.settings.timezone) into the hash + emit `TZ` env\n // var on telegram / slack MCP children so the\n // in-place progress heartbeat's \"last update: HH:MM:SS\"\n // footer renders in the agent's local timezone instead\n // of UTC.\n // v4 — ENG-5155: fold `session_mode` into the hash. The\n // rendered slack/direct-chat blocks branch on\n // `isPersistent` (`claudecode/index.ts:2149`), so a\n // oneshot→persistent flip changes the output of\n // `writeChannelCredentials` — but pre-v4 the hash\n // didn't notice and the rewrite was skipped forever.\n // Sherlock (2026-05-17) hit this: created as oneshot\n // before #1108, then flipped to persistent, but his\n // `.mcp.json` kept the broken `npx -y @augmented/\n // claude-code-channel-slack` fallback until we cleared\n // the cache by hand. The version bump itself forces\n // one rewrite per agent on first poll after upgrade,\n // which auto-repairs any other agents in the same\n // stuck state.\n const sessionModeForHash =\n (refreshData.agent as Record<string, unknown>).session_mode as string | undefined;\n const CHANNEL_WRITE_VERSION = 5;\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 sessionMode: sessionModeForHash ?? null,\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 // ENG-4970 / ENG-4974: Slack mirror. Parallel resolution\n // from CHARTER multi_agent.slack_peers — same gate-path\n // model, keyed on bot_user_id.\n const slackPeers =\n channelId === 'slack'\n ? extractCharterSlackPeers(refreshData.charter?.raw_content ?? '', gateContext)\n : undefined;\n frameworkAdapter.writeChannelCredentials(\n agent.code_name,\n channelId,\n entry.config as Record<string, unknown>,\n {\n sessionMode,\n agentId: agent.agent_id,\n telegramPeerDisabled,\n peerDisabled,\n telegramPeers,\n slackPeers,\n agentTimezone,\n },\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 // ENG-5363: team timezone propagates as `TZ` so the direct-chat\n // progress-heartbeat's \"last update\" footer renders in the agent's\n // local time. Mirror of the Slack/Telegram fix in\n // claudecode/index.ts. The current block only writes on first\n // install (`!mcpConfig.mcpServers['direct-chat']`), so existing\n // agents will pick up TZ the next time their `.mcp.json` is reset\n // — acceptable for a hotfix; a follow-up could re-evaluate the\n // env block on each refresh.\n const directChatTeamSettings = (refreshData.team as { settings?: Record<string, unknown> } | undefined)?.settings;\n const directChatTz = (() => {\n const tz = directChatTeamSettings?.['timezone'];\n return typeof tz === 'string' && tz.trim() !== '' ? tz.trim() : undefined;\n })();\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 ...(directChatTz ? { TZ: directChatTz } : {}),\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 // ENG-5641: connectivity-probe inputs (enriched by /host/agent-integrations).\n source_type?: string | null;\n last_connectivity_check_at?: string | null;\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-i.5 ENG-5641: rolling host-side connectivity probes — FLAG-GATED, default\n // off. Probes each integration ~hourly (rolling due-selection) using the\n // agent's real creds/MCP and reports to /host/integration-connectivity, where\n // the central evaluator escalates (also flag-off). Dormant until\n // AGT_CONNECTIVITY_PROBE_ENABLED is set and validated on a live host.\n if (process.env.AGT_CONNECTIVITY_PROBE_ENABLED === 'true') {\n try {\n const probeProjectDir = join(homedir(), '.augmented', agent.code_name, 'project');\n await runAgentConnectivityProbes(agent, integrations, probeProjectDir);\n } catch (err) {\n log(`Connectivity probe failed for '${agent.code_name}': ${(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 // ENG-5292: record reverse mapping so the reaper's give-up\n // callback (later in this file) can resolve a serverId back\n // to the toolkit_id when POSTing to /host/integration-health.\n managedToolkitIdByAgentAndServerId.set(\n managedToolkitMapKey(agent.agent_id, serverId),\n tk.toolkit_id,\n );\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 // ENG-5537: structure-only hash — serverId membership + header KEY\n // names, deliberately excluding the url and header VALUES. The write\n // below still keys off `mcpHash` (so .mcp.json converges to the live\n // signed url), but the session RESTART is gated on this hash so a\n // value-only rotation (composio re-signing `mcp_url` every poll)\n // refreshes the file without churning the running session. Only a\n // real structural change — toolkit added/removed, or a header key\n // appearing/disappearing — restarts the session.\n const structureHash = managedMcpStructureHash(desiredEntries);\n const prevStructureHash = knownManagedMcpStructure.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 knownManagedMcpStructure.set(agent.agent_id, structureHash);\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 //\n // ENG-5537: additionally gate on a *structural* change\n // (`structureHash`), not just any `mcpHash` change. A value-only\n // rotation (composio re-signing `mcp_url`) writes the fresh file\n // above but leaves `structureHash` identical — the running session's\n // established MCP connections are unaffected, so we skip the restart\n // and the inject-notice. `prevStructureHash === undefined` is the\n // first-convergence case (no running session to disturb).\n if (\n prevStructureHash !== undefined &&\n structureHash !== prevStructureHash &&\n fwForMcp === 'claude-code' &&\n isSessionHealthy(agent.code_name)\n ) {\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 // 7e. ENG-5435 hybrid kanban-work + ENG-5599/5600/5601 scheduled-task\n // reconcile & delivery. Both need a fresh board snapshot for a claude-code\n // persistent agent (oneshot agents have no live REPL; openclaw / nemoclaw use\n // the cron / gateway pipeline), so they share one fetch — but they're gated\n // by SEPARATE flags and must not be coupled:\n // - reconcile + delivery (ENG-5601) runs whenever scheduled-via-kanban is\n // on. It's the PRIMARY delivery trigger; gating it behind the hybrid flag\n // would silently drop every scheduled-task delivery when an operator\n // enables AGT_SCHEDULED_VIA_KANBAN alone. (Harmless no-op when nothing is\n // tracked in scheduledRunsByCode.)\n // - the hybrid kanban-work inject (ENG-5435) runs only when the hybrid flag\n // is on. Uses the tighter HYBRID_ACTIONABLE_STATUSES predicate\n // (todo / in_progress — NOT backlog) per\n // docs/research/eng-5435-hybrid-kanban-inject.md.\n const wantsHybridInject = isKanbanHybridEnabled();\n const wantsScheduledReconcile = isScheduledViaKanbanEnabled();\n if ((wantsHybridInject || wantsScheduledReconcile) && agentFw === 'claude-code' && sessionMode === 'persistent') {\n // CR (CodeRabbit on PR #1293): the kanbanBoardCache is process-wide\n // and persists across poll ticks. The previous `!hybridBoard` short\n // circuit meant that once we populated the cache, we'd read stale\n // data forever for agents without a board-aware scheduled template\n // (step 7d only fetches when `hasBoardTemplates`). Reuse step 7d's\n // freshly-fetched board when it ran this tick; otherwise fetch our\n // own and update the cache. Falls back to whatever cached snapshot\n // exists on transport failure rather than treating the agent as\n // having an empty board.\n let hybridBoard = hasBoardTemplates ? boardItems : undefined;\n if (!hybridBoard) {\n try {\n const boardData = await api.post<{ items?: BoardItem[] }>(\n '/host/my-kanban',\n { agent_id: agent.agent_id },\n );\n hybridBoard = (boardData.items ?? []).map(sanitizeBoardItem);\n kanbanBoardCache.set(agent.code_name, hybridBoard);\n } catch {\n hybridBoard = kanbanBoardCache.get(agent.code_name) ?? [];\n }\n }\n // ENG-5600/5601: reconcile in-flight scheduled-task runs against this fresh\n // board snapshot — deliver the agent's result from the done card and close\n // the per-card run. Done here because we already have a current board and\n // the agent self-completing its card means the realtime onCompletion path\n // is suppressed by isSelfCompletion (so this poll is the primary trigger).\n // Fire-and-forget: it awaits delivery per card internally but the poll loop\n // doesn't block on it.\n void reconcileScheduledRuns(agent.code_name, agent.agent_id, hybridBoard);\n if (wantsHybridInject) {\n if (hasHybridActionableItems(hybridBoard)) {\n const actionableCount = hybridBoard.filter(isHybridActionable).length;\n // Fire-and-forget per design-doc Q2 — the 10s waitForPromptReady\n // cap would otherwise serialise across N agents and starve the\n // poll cycle. Caller doesn't await.\n void maybeInjectKanbanCheck(agent.code_name, agent.agent_id, actionableCount);\n } else {\n // ENG-5565: board drained (no todo/in_progress) — the agent has worked\n // through its injected kanban items, so close any open injected run so\n // it doesn't hang open and absorb the agent's subsequent non-kanban turns.\n closeInjectedRunIfOpen(agent.code_name, 'completed', 'kanban board drained');\n }\n }\n }\n\n if (agentFw === 'claude-code' && sessionMode === 'persistent') {\n // Claude Code persistent mode: manage long-running session with channels.\n //\n // ENG-5116: wrap the per-agent call in try/catch so an exception\n // affecting one agent's evaluation can't silently take that agent\n // down. Pre-fix, a throw inside ensurePersistentSession bypassed\n // every log statement and the operator only learned about it when\n // a user reported the agent unresponsive in Slack (Vigil, 2026-05-16).\n // Snapshot health *before* the call so an exception thrown after the\n // session is already healthy doesn't get misreported as down — that\n // would start a bogus stuck streak for an agent still serving traffic\n // (CodeRabbit PR #1070).\n const wasSessionHealthy = isSessionHealthy(agent.code_name);\n let psResult: PersistentSessionResult;\n try {\n psResult = await ensurePersistentSession(agent, tasks, boardItems, refreshData);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n log(`[persistent-session] EXCEPTION evaluating '${agent.code_name}': ${msg}`);\n // Construct a synthetic result so the decision-summary + stuck\n // tracker still see this tick. Carry forward the pre-call health\n // observation — the throw doesn't kill the tmux session, only our\n // evaluation of it.\n psResult = {\n decision: 'skipped-auth-resolve-failed',\n spawnAttempted: false,\n sessionHealthyAfter: wasSessionHealthy,\n detail: `exception: ${msg}`,\n };\n }\n\n // One structured decision-summary line per agent per tick. This is\n // the line that would have shown Vigil silently stuck — pre-fix only\n // the happy path logged. `detail` is JSON-stringified so embedded\n // quotes/newlines from exception messages can't break single-line\n // parsing downstream (CodeRabbit PR #1070).\n const normalizedDetail = psResult.detail?.replace(/\\s+/g, ' ').trim();\n const detailSuffix = normalizedDetail\n ? ` detail=${JSON.stringify(normalizedDetail)}`\n : '';\n log(\n `[persistent-session-decision] agent=${agent.code_name} decision=${psResult.decision} ` +\n `spawn_attempted=${psResult.spawnAttempted} session_healthy_after=${psResult.sessionHealthyAfter}${detailSuffix}`,\n );\n\n const stuck = persistentSessionStuckTracker.record({\n codeName: agent.code_name,\n sessionHealthy: psResult.sessionHealthyAfter,\n spawnAttempted: psResult.spawnAttempted,\n });\n\n // ENG-5404: retry the in-session /loop arm on every healthy tick. The\n // spawn-time arm is best-effort (it can race the prompt-ready check);\n // this catches the rare \"spawn succeeded but arm injection failed\"\n // case so the agent doesn't sit unarmed until the next manual respawn.\n // ensureKanbanLoopArmed short-circuits when already armed (cheap), so\n // it's safe to call every tick.\n if (psResult.sessionHealthyAfter) {\n ensureKanbanLoopArmed(agent.code_name, log).catch(() => { /* idempotent — next tick retries */ });\n }\n if (stuck.shouldWarn) {\n log(\n `[persistent-session-stuck] WARN: agent=${agent.code_name} unhealthy with no spawn attempt for ${stuck.consecutiveTicks} consecutive ticks ` +\n `(last_decision=${psResult.decision}) — manager-worker may need restart, or upstream guard is mis-firing (see ENG-5116)`,\n );\n } else if (stuck.recovered) {\n log(`[persistent-session-stuck] agent=${agent.code_name} recovered from stuck state`);\n }\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\n // ENG-5053: presence check — flag declared MCP servers that have\n // no live children under this agent's claude (e.g. xero-mcp-server\n // crashed silently) and kill the session so the next ensure pass\n // respawns it. Skipped within the cold-start grace window so the\n // initial 20-30s npm-install of @composio/mcp + xero-mcp-server\n // doesn't trip a false-positive restart.\n try {\n const sess = getSessionState(agent.code_name);\n let mcpJsonParsed: import('./stale-mcp-reaper.js').McpConfig | null = null;\n try {\n const mcpPath = join(psGetProjectDir(agent.code_name), '.mcp.json');\n mcpJsonParsed = JSON.parse(readFileSync(mcpPath, 'utf-8'));\n } catch {\n // Missing/unreadable .mcp.json — nothing to check. The drift\n // check above already logs unparseable configs; we silently\n // skip presence so we don't double-log on a transient read.\n }\n reapMissingMcpSessions({\n log,\n codeName: agent.code_name,\n mcpJson: mcpJsonParsed,\n sessionStartedAt: sess?.startedAt ?? null,\n // ENG-5441 / ENG-5547: a mcp-presence-reaper restart counts against\n // the agent-wide breaker ONLY when several distinct MCP servers are\n // missing at once — a thrashing collection that rotates restarts of\n // the whole session, which the per-MCP cap can't see. A restart\n // driven by a SINGLE persistently-failing server is left to that\n // server's own ENG-5279 give-up cap (→ marked unhealthy via onGiveUp\n // → dropped from the next .mcp.json), so one bad integration is\n // isolated and the agent keeps running degraded instead of being\n // paused wholesale. reaperRestartBreakerReason() encodes that\n // single-vs-multi decision; undefined means \"restart, don't count\".\n stopSession: (codeName, ctx) =>\n stopPersistentSessionAndForgetMcpBaseline(codeName, reaperRestartBreakerReason(ctx.activeKeys)),\n // ENG-5292: when the reaper gives up on a managed MCP (cap from\n // ENG-5279 + state-preservation from ENG-5285 both said \"this\n // MCP keeps failing after 3 restart cycles\"), mark the matching\n // integration unhealthy server-side. The next /host/managed-\n // toolkits poll drops it from the response → next writeMcpServer\n // omits it → broken MCP is cleanly gone instead of declared-but-\n // dead.\n //\n // Async fire-and-forget — the reaper's correctness must not\n // depend on the API call succeeding. Errors are caught inside\n // the .then/.catch chain and logged.\n onGiveUp: (codeName, serverKey) => {\n const toolkitId = managedToolkitIdByAgentAndServerId.get(\n managedToolkitMapKey(agent.agent_id, serverKey),\n );\n if (!toolkitId) {\n log(\n `[mcp-presence-reaper] give-up on '${codeName}:${serverKey}' — not a managed toolkit (no toolkit_id mapping), skipping integration-health API call`,\n );\n return;\n }\n const agentId = agent.agent_id;\n api.post('/host/integration-health', {\n agent_id: agentId,\n toolkit_id: toolkitId,\n action: 'mark_unhealthy',\n reason: `mcp-presence-reaper cap (${3} consecutive failed restart cycles) tripped on '${serverKey}'`,\n }).then((res) => {\n log(\n `[mcp-presence-reaper] marked integration unhealthy for '${codeName}' toolkit '${toolkitId}' (ENG-5292): ${JSON.stringify(res).slice(0, 200)}`,\n );\n }).catch((err) => {\n log(\n `[mcp-presence-reaper] failed to mark integration unhealthy for '${codeName}' toolkit '${toolkitId}' (ENG-5292): ${(err as Error).message} — local give-up state still recorded; will retry on next agent provisioning cycle if .mcp.json changes`,\n );\n });\n },\n // ENG-5286: record the restart event with the API so the\n // agent-heartbeat-monitor cron can aggregate per-agent counts\n // into the McpReaperRestartsHourly CloudWatch metric. Fire-and-\n // forget — the reaper's correctness must not depend on this.\n onRestart: (codeName, { activeKeys, givenUpKeys }) => {\n api.post('/host/mcp-reaper-restart', {\n agent_id: agent.agent_id,\n code_name: codeName,\n server_keys: activeKeys,\n given_up_keys: givenUpKeys,\n }).catch((err) => {\n log(\n `[mcp-presence-reaper] failed to record restart event for '${codeName}' (ENG-5286): ${(err as Error).message} — local restart still proceeded; CloudWatch metric will under-count this event`,\n );\n });\n },\n });\n } catch (err) {\n log(`[mcp-presence-reaper] presence 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 // ENG-5405: include the top todo item's id in the inject so\n // the agent can kanban_move(id, \"in_progress\") without a\n // round-trip via kanban_list — saves ~one MCP call per\n // trigger. Falls back to a board-list prompt when there's\n // no obvious top item (empty board, all-blocked, etc).\n const todayItem = boardItems.find((b) => b.status === 'todo');\n // CodeRabbit PR #1275: titles are user-authored; JSON.stringify\n // so embedded quotes/newlines can't reshape the injected\n // prompt and mis-direct the agent to a different item.\n const triggerMessage = todayItem\n ? `New work triggered. Top item: id=${todayItem.id} title=${JSON.stringify(todayItem.title)} priority=${todayItem.priority}. Call kanban_move(\"${todayItem.id}\", \"in_progress\") and start working — no need to kanban_list first.`\n : 'New work triggered. Check your kanban board with kanban_list and pick up the next item.';\n injectMessage(agent.code_name, 'task', triggerMessage, {\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 // ENG-5408: structured stuck-task watchdog line. Lands BEFORE\n // the existing auto-fail so CloudWatch metric filters can\n // count occurrences even when the auto-fail succeeds. Same\n // shape as `[persistent-session-stuck]` (ENG-5116) so the\n // alerting/monitoring pipeline can pattern-match without a\n // new parser. One line per row per occurrence — `stuckKanbanLogged`\n // dedupes within the session.\n const stuckKey = `${agent.code_name}:${item.id}`;\n if (!stuckKanbanLogged.has(stuckKey)) {\n log(\n `[persistent-session-stuck-kanban] agent=${agent.code_name} ` +\n `item_id=${item.id} title=${JSON.stringify(item.title)} ` +\n `status=in_progress age_minutes=${age} ` +\n `threshold_minutes=${Math.round(STALE_TASK_THRESHOLD_MS / 60_000)}`,\n );\n stuckKanbanLogged.add(stuckKey);\n }\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 // ENG-5408: clear the structured-log dedupe set in lockstep, so an\n // item that gets cancelled / fixed / re-spawned can re-trigger the\n // watchdog if it goes stuck again later in this session.\n for (const key of stuckKanbanLogged) {\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 stuckKanbanLogged.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 a kanban card tied to this run. The card id comes back so\n // finishRun can close it. initial_status (ENG-5599) selects the opening\n // column — default 'in_progress' (legacy oneshot), 'todo' for the in-session\n // route where the persistent session picks the card up itself.\n materialize_kanban?: {\n title: string;\n description?: string;\n priority?: number;\n initial_status?: 'todo' | 'in_progress';\n };\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\n// ---------------------------------------------------------------------------\n// ENG-5599 (Slice 1 of ENG-5576): route scheduled tasks through a todo card +\n// in-session nudge instead of a `claude -p` oneshot.\n//\n// Anthropic's billing change on `claude -p` makes a per-task oneshot\n// cost-prohibitive for persistent-session agents that already have a live\n// tmux session. Instead we materialise the firing as a TODO kanban card and\n// nudge the live session to work it in-session — the board becomes the work\n// queue (generalising the ENG-5404/5435 kanban-work cutover to all scheduled\n// tasks).\n//\n// Cost attribution (the ENG-5557 seam): the API stamps the per-card run_id\n// onto the materialised card (agent_kanban_items.run_id), and the nudge\n// carries that run's marker (formatRunMarker). The per-turn transcript parser\n// (ENG-5566) bounds the card's in-session turns by that marker, so\n// scheduled_task_cost_windows keeps attributing cost per task exactly as it\n// did for the oneshot — one run = one card = one firing — with no claude -p\n// process. A board-level inject run (ENG-5565) would NOT satisfy this: the\n// todo card would carry no run_id and a shared nudge run would over-attribute\n// to every due task. Hence the per-card run grain here.\n// ---------------------------------------------------------------------------\n\nexport function isScheduledViaKanbanEnabled(): boolean {\n const v = process.env['AGT_SCHEDULED_VIA_KANBAN'];\n return v === '1' || v?.toLowerCase() === 'true';\n}\n\n// codeName → (kanbanItemId → run_id) for scheduled-task runs opened on the\n// in-session route. A session teardown closes these so an interrupted run\n// doesn't hang open and mis-attribute the agent's later turns. Board-drain /\n// card-done close (the normal happy-path close) lands in S2 (ENG-5600).\nconst scheduledRunsByCode = new Map<string, Map<string, string>>();\n\n// ENG-5601: cardId delivery dedupe across the poll path (reconcileScheduledRuns)\n// and the realtime onCompletion handler, which can BOTH fire for the same done\n// card. Two states, keyed on the cardId alone — cardIds are globally-unique\n// UUIDs, so no per-agent bucket is needed, and that sidesteps the\n// poll-vs-onCompletion keying mismatch (poll has code_name, onCompletion can\n// fall back to the agent UUID when the agent isn't in the active set):\n// - `claimedScheduledCards`: a card someone is actively delivering (in-flight).\n// - `completedScheduledCards`: a card whose delivery RESOLVED (delivered or\n// terminally not-deliverable). The claim alone can't tell \"in-flight\" from\n// \"done\" — and conflating them lets the poll path close (and drop the retry\n// handle for) a run while the other path's delivery is still in flight and\n// might yet fail. So delivery returns a 4-state result and only a RESOLVED\n// state lets the caller close the run.\n// Deliberately NOT cleared on session teardown: a done card lingers in the\n// board's 24h done window and the run map (the retry handle) is dropped on\n// close, so these must outlive both to stop a re-delivery within the process.\n// (Cross-process restart can't be deduped without durable state — a known,\n// narrow limitation: a human re-completing an already-delivered card after a\n// manager restart could re-send. Bounded growth: one UUID per delivered card\n// over manager uptime.)\nconst claimedScheduledCards = new Set<string>();\nconst completedScheduledCards = new Set<string>();\n\n/** Outcome of a delivery attempt, telling the caller whether the run can close. */\ntype ScheduledDeliveryResult =\n | 'delivered' // we delivered it this call — caller may close the run\n | 'terminal' // not deliverable (wrong status/source/template, no task) OR already\n // delivered by the other path — caller may close the run\n | 'retry' // transient failure (claim released) — caller MUST keep the run open\n | 'in_flight'; // another path holds the claim and hasn't resolved — caller must\n // keep the run open; a later tick sees 'terminal' once it resolves\n\n// Claim a card for delivery. Returns true the first time (caller should\n// deliver), false if it's already claimed (in-flight or resolved).\nfunction claimScheduledCardDelivery(cardId: string): boolean {\n if (claimedScheduledCards.has(cardId)) return false;\n claimedScheduledCards.add(cardId);\n return true;\n}\n\n// Release a claim so a later attempt can retry — used when the delivery\n// fetch/post throws BEFORE processClaudeTaskResult ran (that helper swallows\n// its own errors, so a thrown error here always means delivery didn't happen).\nfunction releaseScheduledCardDelivery(cardId: string): void {\n claimedScheduledCards.delete(cardId);\n}\n\n// Mark a card's delivery as RESOLVED (delivered or terminal). The claim stays\n// held; completion is what lets a concurrent caller's `in_flight` settle to\n// `terminal` on its next look.\nfunction markScheduledCardDeliveryComplete(cardId: string): void {\n completedScheduledCards.add(cardId);\n}\n\n// Test-only: clear the delivery dedupe sets so a cardId constant reused across\n// test cases doesn't leak a claim between them. No production caller.\nexport function __resetScheduledDeliveryDedupeForTest(): void {\n claimedScheduledCards.clear();\n completedScheduledCards.clear();\n}\n\nfunction trackScheduledRun(codeName: string, cardId: string, runId: string): void {\n let m = scheduledRunsByCode.get(codeName);\n if (!m) {\n m = new Map();\n scheduledRunsByCode.set(codeName, m);\n }\n m.set(cardId, runId);\n}\n\n// True if this card was materialised by the in-session route\n// (routeScheduledTaskViaKanban) and still has an open per-card run. cardIds are\n// globally unique, so scanning every code's map is unambiguous — and it avoids\n// depending on a resolved codeName (the realtime path can only fall back to the\n// agent UUID when the agent isn't in the active set). Used to keep the\n// onCompletion delivery off oneshot/fallback scheduled_task cards (those are\n// materialised by executeAndProcessClaudeTask, which already self-delivers and\n// never records the card here).\nexport function isScheduledCardTracked(cardId: string): boolean {\n for (const m of scheduledRunsByCode.values()) {\n if (m.has(cardId)) return true;\n }\n return false;\n}\n\nfunction closeScheduledRunsForCode(\n codeName: string,\n outcome: 'completed' | 'failed' | 'cancelled' | 'timeout',\n reason: string,\n): void {\n const m = scheduledRunsByCode.get(codeName);\n if (!m || m.size === 0) return;\n scheduledRunsByCode.delete(codeName);\n for (const runId of m.values()) {\n void finishRun(runId, outcome, { outcomeMessage: reason });\n }\n}\n\n/** Full, untruncated delivery fields for one card (from POST /host/kanban-item). */\ninterface ScheduledCardForDelivery {\n id: string;\n title: string;\n status: string;\n result: string | null;\n deliverable: string | null;\n source_type: string | null;\n source_ref: string | null;\n last_actor_id: string | null;\n completed_at: string | null;\n}\n\n// ENG-5601: manager-side delivery from a done scheduled-task card. Centralises\n// delivery + suppression manager-side (the locked 2026-05-26 decision): the\n// in-session agent records its result on the card and marks it done WITHOUT\n// sending anything itself; the manager then reads the full result and delivers\n// via the existing processClaudeTaskResult path (deliveryMode/channel/to +\n// `<no-delivery/>` handling).\n//\n// Called from two places against a shared idempotency claim:\n// - reconcileScheduledRuns (poll path) — the PRIMARY trigger, because the\n// agent self-completes the card and realtime onCompletion is therefore\n// suppressed by isSelfCompletion.\n// - the realtime onCompletion handler — only for non-self (human) completions\n// of a scheduled-task card.\n//\n// Returns a ScheduledDeliveryResult so the caller knows whether it may close the\n// run. Only 'delivered'/'terminal' authorise closing it; 'retry' (transient\n// fetch failure, claim released) and 'in_flight' (another path holds an\n// unresolved claim) keep the run open so the retry handle survives.\nexport async function deliverScheduledCardResult(\n codeName: string,\n agentId: string,\n cardId: string,\n): Promise<ScheduledDeliveryResult> {\n if (!claimScheduledCardDelivery(cardId)) {\n // Someone else owns the claim. If their delivery already RESOLVED, this is\n // terminal (close the run); if it's still in flight, the caller must keep\n // the run open — closing it now would drop the retry handle should that\n // in-flight attempt fail and release the claim.\n return completedScheduledCards.has(cardId) ? 'terminal' : 'in_flight';\n }\n let card: ScheduledCardForDelivery | undefined;\n try {\n const resp = await api.post<{ item?: ScheduledCardForDelivery }>('/host/kanban-item', {\n agent_id: agentId,\n item_id: cardId,\n });\n card = resp.item;\n } catch (err) {\n // A 4xx is terminal: a 404 means the card was deleted between the board read\n // and this fetch (or a 400/403 — none retryable), so resolve it and let the\n // caller close the run rather than re-fetching a gone card every poll until\n // the stale-run reaper steps in. Only a 5xx / network/transport error is\n // transient: release the claim and signal retry so the run stays open.\n if (err instanceof ApiError && err.status >= 400 && err.status < 500) {\n log(`[scheduled-kanban] delivery: card ${cardId} on '${codeName}' fetch returned ${err.status} (${err.message}) — treating as terminal, not retrying`);\n markScheduledCardDeliveryComplete(cardId);\n return 'terminal';\n }\n releaseScheduledCardDelivery(cardId);\n log(`[scheduled-kanban] delivery fetch failed for card ${cardId} on '${codeName}': ${(err as Error).message} — will retry`);\n return 'retry';\n }\n\n // Terminal non-deliverable cases (resolve + let the caller close the run): a\n // failed/cancelled card has no deliverable; a non-scheduled_task card isn't\n // ours to deliver.\n if (!card || card.status !== 'done' || card.source_type !== 'scheduled_task' || !card.source_ref) {\n markScheduledCardDeliveryComplete(cardId);\n return 'terminal';\n }\n const state = claudeSchedulerStates.get(codeName) ?? loadSchedulerState(codeName);\n const task = state.tasks[card.source_ref];\n if (!task) {\n log(`[scheduled-kanban] delivery: no scheduler task for source_ref=${card.source_ref} on '${codeName}' — skipping`);\n markScheduledCardDeliveryComplete(cardId);\n return 'terminal';\n }\n // ENG-5601: only the \"plain\" templates route in-session (isPlainScheduledTemplate\n // gates routeScheduledTaskViaKanban). Guard the delivery path too — a human\n // completing a NON-plain scheduled_task card (e.g. a standup card the oneshot\n // path materialised) must not run processClaudeTaskResult's template handler\n // and parse free text as standup/plan JSON into /host/agent-status or the board.\n if (!isPlainScheduledTemplate(task.templateId)) {\n log(`[scheduled-kanban] delivery: card ${cardId} template '${task.templateId}' on '${codeName}' is not a plain scheduled template — skipping manager delivery`);\n markScheduledCardDeliveryComplete(cardId);\n return 'terminal';\n }\n // processClaudeTaskResult swallows its own errors internally, so this won't\n // throw; the card is delivered (or suppressed via <no-delivery/>) exactly once.\n await processClaudeTaskResult(codeName, agentId, task.templateId, card.result ?? '', {\n mode: task.deliveryMode,\n channel: task.deliveryChannel,\n to: task.deliveryTo,\n taskId: task.taskId,\n });\n markScheduledCardDeliveryComplete(cardId);\n log(`[scheduled-kanban] Delivered result from card ${cardId} (task '${task.name}') for '${codeName}'`);\n return 'delivered';\n}\n\n// ENG-5600: happy-path close. Given a fresh board snapshot, close the per-card\n// run for any tracked scheduled-task card the session has finished, and drop\n// the map entry — completing the todo → in_progress → done lifecycle without\n// leaking an open run. A card still todo/in_progress is left open; a card\n// absent from this snapshot is left for the server-side reaper rather than\n// guessed (it may just be filtered out of the board's done window). Idempotent:\n// only acts on cards we're tracking, and deletes the entry on close so a\n// repeated snapshot is a no-op.\nexport async function reconcileScheduledRuns(codeName: string, agentId: string, board: BoardItem[]): Promise<void> {\n const m = scheduledRunsByCode.get(codeName);\n if (!m || m.size === 0) return;\n const byId = new Map(board.map((i) => [i.id, i]));\n for (const [cardId, runId] of [...m.entries()]) {\n const card = byId.get(cardId);\n if (!card) continue; // not in snapshot — leave for the reaper, don't guess\n if (card.status === 'done') {\n // ENG-5601: deliver the agent's result, THEN close the run. This is the\n // PRIMARY delivery path — the agent self-completes the card so realtime\n // onCompletion is suppressed by isSelfCompletion and never fires here.\n // Critically, only drop the run-map entry once delivery has RESOLVED\n // ('delivered'/'terminal'). On a transient failure ('retry') or while\n // another path's claim is still in flight ('in_flight') the entry stays so\n // a later poll retries — the map IS the only retry handle, so deleting it\n // before a resolved delivery could silently drop the result.\n const result = await deliverScheduledCardResult(codeName, agentId, cardId);\n if (result === 'retry' || result === 'in_flight') continue;\n m.delete(cardId);\n void finishRun(runId, 'completed', { outcomeMessage: 'scheduled-task card done' });\n } else if (card.status === 'failed') {\n m.delete(cardId);\n void finishRun(runId, 'failed', { outcomeMessage: 'scheduled-task card failed' });\n }\n }\n if (m.size === 0) scheduledRunsByCode.delete(codeName);\n}\n\n// Materialise a due scheduled task as a TODO card bound to a per-card run, then\n// nudge the live session to work it. Returns true when the card was created\n// AND the nudge was delivered; false otherwise so the caller can fall back to\n// the `claude -p` oneshot (keeps the cutover reversible and never drops a\n// firing). Assumes the caller has already confirmed the session is healthy.\nexport async function routeScheduledTaskViaKanban(\n codeName: string,\n agentId: string,\n task: SchedulerTaskState,\n prompt: string,\n): Promise<boolean> {\n // Remaining limitation (delivery itself landed in S3 / ENG-5601 — the manager\n // now delivers from the done card via deliverScheduledCardResult): the card\n // description is still the raw task prompt. The oneshot path additionally\n // wraps it via wrapScheduledTaskPrompt — prior-runs context\n // (fetchPriorScheduledRuns, the last 24h of outputs so the agent reports only\n // what changed) and the [NOW] time anchor. The in-session agent inherits live\n // session context, but recurring \"report what's new\" tasks will re-report\n // until that wrapper is re-homed onto the card. Tracked as a follow-up.\n //\n // Open the run and materialise the card together. The card carries the task\n // prompt as its description (the in-session work spec) and starts in 'todo'\n // so the session promotes it itself.\n const { run_id, kanban_item_id } = 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, via: 'kanban-inject' },\n materialize_kanban: {\n title: task.name,\n description: prompt,\n priority: 2,\n initial_status: 'todo',\n },\n });\n\n if (!run_id || !kanban_item_id) {\n log(`[scheduled-kanban] materialise incomplete for task '${task.name}' on '${codeName}' (run_id=${run_id ? 'ok' : 'null'}, card=${kanban_item_id ? 'ok' : 'null'}) — falling back to claude -p`);\n // Close a half-opened run + card so the fallback oneshot doesn't leave a\n // phantom open run behind it.\n if (run_id) void finishRun(run_id, 'failed', { outcomeMessage: 'kanban materialise incomplete', completeKanbanItemId: kanban_item_id });\n return false;\n }\n\n // Targeted, per-card nudge. Plain text (not a slash command) so acpx delivers\n // it as a user turn; the run marker is appended so ENG-5566's per-turn parser\n // attributes this card's turns to this run. Title is JSON.stringify'd because\n // it's user-authored and could otherwise break the injected prompt shape\n // (same hardening as the realtime kanban-work trigger).\n // ENG-5601: the agent records its result on the card and marks it done, but\n // must NOT send/post the result itself — the manager delivers from the done\n // card (deliveryMode/channel/to honored centrally). Without this instruction\n // the card description (the raw task prompt) may tell the agent to \"send me\n // X\", which would double up with the manager's delivery.\n const nudge =\n `You have a new scheduled task on your kanban board: id=${kanban_item_id} ` +\n `title=${JSON.stringify(task.name)}. Call kanban_move(\"${kanban_item_id}\", \"in_progress\"), ` +\n `do the work described on the card, then write the finished result onto the card and mark it done. ` +\n `Do NOT send, post, or message the result to anyone yourself — recording it on the card is enough; ` +\n `delivery to the recipient is handled for you.\\n` +\n formatRunMarker(run_id);\n\n // Use the status-returning variant: a bare `false` from injectMessage is\n // ambiguous — tmux send-keys returns it even after the keystroke landed. If\n // we fell back to the oneshot on that, a non-acpx host would BOTH nudge the\n // live session and spawn claude -p, double-executing the task. So only the\n // \"neither delivered nor sent\" case is a real failure that warrants fallback.\n // A rejection here (vs the normal {delivered,fallbackUsed} resolve) would\n // exit after the run + card were materialised, and the caller would fall back\n // to claude -p — orphaning the card/run and double-executing the firing.\n // Catch it: close the card + run and return false (fall back cleanly).\n let injectStatus: import('./persistent-session.js').InjectResult;\n try {\n injectStatus = await injectMessageWithStatus(codeName, 'task', nudge, { task_name: 'scheduled-task' }, log);\n } catch (err) {\n log(`[scheduled-kanban] nudge injection threw for task '${task.name}' on '${codeName}': ${(err as Error).message} — closing card + falling back to claude -p`);\n void finishRun(run_id, 'failed', { outcomeMessage: 'scheduled-task nudge delivery threw', completeKanbanItemId: kanban_item_id });\n return false;\n }\n const { delivered, fallbackUsed } = injectStatus;\n if (!delivered && !fallbackUsed) {\n log(`[scheduled-kanban] nudge delivery failed for task '${task.name}' on '${codeName}' — closing card + falling back to claude -p`);\n // Close the card + run so the fallback oneshot doesn't double up on a card\n // the session never picked up.\n void finishRun(run_id, 'failed', { outcomeMessage: 'scheduled-task nudge delivery failed', completeKanbanItemId: kanban_item_id });\n return false;\n }\n if (!delivered && fallbackUsed) {\n // Sent via tmux send-keys (submission unverified). Treat as committed so we\n // don't also spawn the oneshot; if the keystroke didn't submit, the card\n // sits in 'todo' and recovery is the S2 reaper's job — preferable to the\n // double-billing this epic exists to eliminate.\n log(`[scheduled-kanban] nudge sent via send-keys (unverified) for task '${task.name}' on '${codeName}' — proceeding without oneshot fallback`);\n }\n\n trackScheduledRun(codeName, kanban_item_id, run_id);\n log(`[scheduled-kanban] Routed task '${task.name}' for '${codeName}' via todo card ${kanban_item_id} run_id=${run_id}`);\n return true;\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 injectMessageWithStatus,\n isSessionHealthy,\n getSessionState,\n resetRestartCount,\n stopAllSessions,\n stopAllSessionsAndWait,\n getProjectDir as psGetProjectDir,\n resolveClaudeBinary,\n getLastFailureContext,\n prepareForRespawn,\n takeZombieDetection,\n ensureKanbanLoopArmed,\n isKanbanLoopArmedForCurrentSession,\n sendToAgent,\n isAgentPromptReady,\n isKanbanLoopDisabled,\n readPaneLogTail,\n} from './persistent-session.js';\nimport { isAgentIdle, isStaleForToday, peekCurrentSession } from './daily-session.js';\nimport { processRestartFlags } from './restart-handler.js';\nimport { PersistentSessionStuckTracker } from './persistent-session-stuck-tracker.js';\n\n// Track which agents have persistent sessions started\nconst persistentSessionAgents = new Set<string>();\n\n// ENG-5459: per-agent dedupe for the pane.log MCP-failure-banner\n// observation. Manager only POSTs when the count differs from the last\n// reported value, so a stable healthy agent (banner-not-present →\n// count 0) emits at most one POST per fresh worker.\nconst lastMcpFailedBannerCount = new Map<string, number>();\n\n/**\n * ENG-5116 — discriminator for the *one decision* a persistent-session\n * tick made for a given agent. Every code path inside\n * `ensurePersistentSession` records exactly one of these so the caller\n * can emit a single structured `[persistent-session-decision]` log line\n * even when the path took an early return. Before this, only the happy\n * path logged (\"Starting tmux session ...\") and silent skips were\n * effectively invisible — see the Vigil incident (2026-05-16).\n */\ntype PersistentSessionDecision =\n | 'spawn' // startPersistentSession() invoked this tick\n | 'healthy' // session is up; tasks may have synced\n | 'restart-pending' // stopped this tick (auth tuple changed); next tick respawns\n | 'day-rollover-restart' // stopped this tick for date rollover; next tick respawns\n | 'day-rollover-deferred' // stale but agent still active mid-task; will retry\n | 'circuit-tripped' // ENG-5441: breaker tripped — no spawn until operator resumes\n | 'skipped-no-api-key'\n | 'skipped-auth-resolve-failed'\n | 'skipped-not-authenticated'\n | 'skipped-api-key-mode-no-key';\n\ninterface PersistentSessionResult {\n decision: PersistentSessionDecision;\n /** True when `startPersistentSession` was called this tick. */\n spawnAttempted: boolean;\n /** Session health observed AFTER the tick's work — what the next tick will see. */\n sessionHealthyAfter: boolean;\n /** Optional human-readable suffix for the decision log line. */\n detail?: string;\n}\n\n/**\n * ENG-5116 — once an agent has been unhealthy without a respawn for\n * `threshold` consecutive ticks, emit a loud WARN exactly once until\n * the streak breaks. See `persistent-session-stuck-tracker.ts`.\n */\nconst persistentSessionStuckTracker = new PersistentSessionStuckTracker();\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<PersistentSessionResult> {\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 // ENG-5441: circuit breaker — if this agent's session restart count\n // crossed the threshold inside the sliding window, refuse to spawn\n // until an operator clears the trip (currently: webapp resume / agt\n // agent resume flips the agent's status from paused → active, which\n // the breaker observes on the next poll via clearBreakerOnResume).\n if (restartBreaker.isTripped(codeName)) {\n const trip = restartBreaker.getTrip(codeName)!;\n return {\n decision: 'circuit-tripped',\n spawnAttempted: false,\n sessionHealthyAfter: isSessionHealthy(codeName),\n detail: trip.statusMessage,\n };\n }\n\n // ENG-5371: resolve the agent's IANA timezone from team settings (same\n // source as ENG-5363's channel-MCP TZ env var). Used to scope the\n // daily-session day rollover so it fires at the agent's midnight\n // rather than host UTC. `undefined` keeps the original ENG-4642\n // host-local behaviour, which is what we want for teams that never\n // configured a timezone.\n const teamSettingsForTz = (\n refreshData.team as { settings?: Record<string, unknown> } | undefined\n )?.settings;\n const agentTimezone = (() => {\n const tz = teamSettingsForTz?.['timezone'];\n return typeof tz === 'string' && tz.trim() !== '' ? tz.trim() : undefined;\n })();\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 decision: 'skipped-no-api-key',\n spawnAttempted: false,\n sessionHealthyAfter: isSessionHealthy(codeName),\n };\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 decision: 'skipped-auth-resolve-failed',\n spawnAttempted: false,\n sessionHealthyAfter: isSessionHealthy(codeName),\n detail: msg,\n };\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 decision: 'skipped-not-authenticated',\n spawnAttempted: false,\n sessionHealthyAfter: isSessionHealthy(codeName),\n };\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 decision: 'skipped-api-key-mode-no-key',\n spawnAttempted: false,\n sessionHealthyAfter: isSessionHealthy(codeName),\n };\n }\n\n // ENG-5116: track which reason (if any) triggered a session stop on this\n // tick. Used at the end of the function to distinguish \"we stopped and\n // also respawned\" (decision=spawn) from the silent-bug case \"we stopped\n // but didn't respawn this tick\" (decision=restart-pending /\n // day-rollover-restart, with sessionHealthyAfter=true exposing the\n // false-positive that took Vigil down).\n let restartTrigger: 'auth-tuple' | 'day-rollover' | null = null;\n let dayRolloverDeferred = false;\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, 'auth-tuple-change');\n persistentSessionAgents.delete(codeName);\n restartTrigger = 'auth-tuple';\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, new Date(), agentTimezone) && 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, 'day-rollover');\n persistentSessionAgents.delete(codeName);\n restartTrigger = 'day-rollover';\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 dayRolloverDeferred = true;\n }\n }\n }\n\n // ENG-5441 + CodeRabbit on PR #1302: re-check the breaker after the\n // auth-tuple-change and day-rollover blocks. Either of those can call\n // `recordRestartForBreaker` for the (max+1)th time *inside this tick*,\n // and without a re-gate the spawn block below would still respawn.\n // The initial gate at the top of this function catches *already-tripped*\n // breakers; this second check catches the case where this tick is the\n // one that trips it.\n if (restartBreaker.isTripped(codeName)) {\n const trip = restartBreaker.getTrip(codeName)!;\n return {\n decision: 'circuit-tripped',\n spawnAttempted: false,\n sessionHealthyAfter: isSessionHealthy(codeName),\n detail: trip.statusMessage,\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 // ENG-5391: if this unhealthy detection was the dead-claude-in-live-tmux\n // case, ship an audit event so we can graph occurrences. Fire-and-forget —\n // a failed POST must not block the respawn that follows.\n const zombie = takeZombieDetection(codeName);\n if (zombie) {\n log(\n `[persistent-session] ZOMBIE DETECTED for '${codeName}' — tmux session ` +\n `${zombie.tmuxSession} alive but no claude process inside; killed shell, will respawn`,\n );\n getHostId()\n .then((hostId) => {\n if (!hostId) return;\n // CodeRabbit: pane output is untrusted (echoed user prompts,\n // tool output, secrets). Same redaction policy as the manager\n // log — ship a sha256 prefix the operator can correlate with\n // ~/.augmented/<codeName>/pane.log instead of the raw text.\n const paneTailHash = zombie.paneTail\n ? `sha256:${createHash('sha256').update(zombie.paneTail).digest('hex').slice(0, 12)}`\n : null;\n return api.post('/host/events', {\n host_id: hostId,\n agent_code_name: codeName,\n event_type: 'manager.agent_zombie_detected',\n payload: {\n agent_code_name: codeName,\n agent_id: agent.agent_id,\n tmux_session: zombie.tmuxSession,\n detected_at: new Date(zombie.detectedAt).toISOString(),\n pane_tail_hash: paneTailHash,\n pane_tail_redacted: zombie.paneTail !== null,\n },\n timestamp: new Date(zombie.detectedAt).toISOString(),\n });\n })\n .catch((err) => {\n log(`[persistent-session] Failed to forward zombie audit event for '${codeName}': ${(err as Error).message}`);\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 // ENG-5397: provision SessionStart orient hook (idempotent). Replaces\n // --resume's transcript continuity with a fresh-session + injected\n // orientation context model.\n try {\n provisionOrientHook(codeName);\n } catch (err) {\n log(`[persistent-session] Failed to provision orient hook for '${codeName}': ${(err as Error).message}`);\n }\n\n // ENG-5051: mint a run UUID for the persistent session so Claude\n // Code's `${AGT_RUN_ID}` substitution in .mcp.json resolves to a\n // real value. Without this the cloud-broker MCP boots with the\n // literal `${AGT_RUN_ID}` placeholder, fails its startup guard,\n // and every aws_request_access 400s. Best-effort: if startRun\n // fails, we fall through to the legacy behaviour (placeholder\n // leaks; agent has to mint manually). Tied to the SESSION boot,\n // not per-tool-call — one run UUID covers everything inside this\n // long-running session, which matches the cloud-broker's\n // single-startup-guard semantics.\n const sessionRunResult = await startRun({\n agent_id: agent.agent_id,\n source_type: 'system',\n metadata: { type: 'persistent_session_boot' },\n });\n\n // ENG-5631: resolve the agent's model (agent override → org → platform\n // default) and hand it to the launcher so it can pass `--model <alias>`.\n // Same resolution as provisioning/registration above (resolveModelChain).\n const spawnPrimaryModel =\n resolveModelChain(refreshData).primary ??\n ((refreshData.agent as Record<string, unknown> | undefined)?.['primary_model'] as\n | string\n | null\n | undefined) ??\n null;\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 primaryModel: spawnPrimaryModel,\n runId: sessionRunResult.run_id,\n agentTimezone,\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 {\n decision: 'spawn',\n spawnAttempted: true,\n sessionHealthyAfter: isSessionHealthy(codeName),\n detail: restartTrigger ? `trigger=${restartTrigger}` : undefined,\n };\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-5405: when the in-session `/loop` is doing the board polling\n // (armed by ENG-5404 at session bootstrap), the external `claude -p\n // kanban-work` cron is redundant — and the redundancy is noisy:\n // subprocess spawn cost ~every 5 minutes per agent, plus the\n // parallel control path that ENG-5408's policy is moving off.\n // Flag-on => skip; flag-off (default) => legacy cron continues so\n // the cutover is reversible by toggling the env var.\n //\n // CodeRabbit PR #1275: gate the skip on the loop ACTUALLY being\n // armed for the current session UUID — not just on the env var.\n // The arm path is asynchronous (waitForPromptReady + injectMessage),\n // so on the first healthy tick after spawn or on a failed arm we\n // can land here with the cron suppressed but no loop running. Fall\n // back to firing the legacy task in that window so the board is\n // never left unattended.\n if (\n KANBAN_WORK_TEMPLATES.has(task.templateId) &&\n isKanbanWorkCronDisabled() &&\n isKanbanLoopArmedForCurrentSession(codeName)\n ) {\n // Mark the task as fired so the scheduler doesn't keep treating\n // it as \"ready\" every tick (otherwise the ready-log would spam\n // with the same skipped task every poll).\n const updated = markTaskFired(codeName, task.taskId, 'ok');\n claudeSchedulerStates.set(codeName, updated);\n continue;\n }\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-5599 (S1 of ENG-5576): for a healthy persistent session, route the\n // firing through a todo card + in-session nudge instead of a `claude -p`\n // oneshot (Anthropic's headless billing change). Excluded for S1:\n // - kanban-work (own in-session loop, ENG-5404/5408); and\n // - templates whose claude -p result drives side effects beyond message\n // delivery — standup → /host/agent-status, plan/task-update → board\n // writes in processClaudeTaskResult. Re-homing those side effects\n // in-session is S3 (ENG-5601); until then they keep the oneshot.\n // Falls back to claude -p when the flag is off, the template is excluded,\n // or the route can't materialise/deliver — the cutover stays reversible\n // and a firing is never dropped. We're already inside the isSessionHealthy\n // branch, so no extra health check is needed.\n if (isScheduledViaKanbanEnabled() && isPlainScheduledTemplate(task.templateId)) {\n let routed = false;\n try {\n routed = await routeScheduledTaskViaKanban(codeName, agent.agent_id, task, prompt);\n } catch (err) {\n // The route threw before committing (no card/nudge). Don't leak the\n // in-flight/concurrency guard — fall through to the oneshot, which\n // holds the guard and releases it in its own .finally().\n log(`[scheduled-kanban] route threw for '${task.name}' on '${codeName}': ${(err as Error).message}`);\n routed = false;\n }\n if (routed) {\n // Commit point: the firing has happened in-session (card materialised\n // + nudge delivered), so we must NOT also spawn the oneshot. Advance\n // the schedule, but guard markTaskFired's state write so a disk error\n // can't leak the in-flight/concurrency guard (which would wedge the\n // task until restart). A failed advance just risks a re-fire next\n // cycle — idempotent pickup is S2 (ENG-5600).\n try {\n const updated = markTaskFired(codeName, task.taskId, 'ok');\n claudeSchedulerStates.set(codeName, updated);\n } catch (err) {\n log(`[scheduled-kanban] markTaskFired failed for '${task.name}' on '${codeName}': ${(err as Error).message}`);\n }\n if (task.scheduleKind === 'at') {\n api.post('/host/schedules/disable', { agent_id: agent.agent_id, task_id: task.taskId }).catch((err) =>\n log(`[scheduled-kanban] Failed to disable one-off task '${task.name}': ${(err as Error).message}`),\n );\n }\n // No long-running subprocess on the routed path — release the guard.\n inFlightClaudeTasks.delete(task.taskId);\n claudeTaskConcurrency.set(codeName, Math.max(0, (claudeTaskConcurrency.get(codeName) ?? 1) - 1));\n // One task per cycle in persistent mode (mirrors the oneshot path).\n break;\n }\n // routed === false → fall through to the claude -p oneshot below.\n }\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 // ENG-5116: we reach here only when the spawn branch was NOT entered,\n // i.e. `isSessionHealthy(codeName)` returned true at line ~5719.\n // - restartTrigger set ⇒ we stopped the session earlier on this tick\n // but somehow it still looks healthy. That's the Vigil silent-bug\n // signature: surface it explicitly so the caller can warn after N\n // consecutive ticks instead of waiting hours for a customer to\n // notice. (Pre-ENG-5116 this collapsed into the same \"tasks synced\"\n // log as a normal healthy tick.)\n // - dayRolloverDeferred set ⇒ stale-for-today but agent mid-turn;\n // benign, will retry next tick.\n // - otherwise ⇒ ordinary healthy steady state.\n const finalDecision: PersistentSessionDecision =\n restartTrigger === 'auth-tuple'\n ? 'restart-pending'\n : restartTrigger === 'day-rollover'\n ? 'day-rollover-restart'\n : dayRolloverDeferred\n ? 'day-rollover-deferred'\n : 'healthy';\n return {\n decision: finalDecision,\n spawnAttempted: false,\n sessionHealthyAfter: isSessionHealthy(codeName),\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 isRealtimeSocketConnected,\n shouldForcePollFallback,\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\n/**\n * ENG-5614: reconcile the realtime channel-subscription flag against the\n * actual phoenix socket state. A half-open websocket can leave\n * `isRealtimeConnected()` reporting `true` while the socket is dead and the\n * channel's CLOSED/CHANNEL_ERROR callback never fired — inbound direct-chat\n * delivery silently stops, yet the manager keeps skipping the polling\n * fallback (it only polls `if (!isRealtimeConnected())`). The manager\n * heartbeat stays fresh the whole time (this same poll cycle posts it), so\n * nothing else catches it.\n *\n * On divergence we tear the realtime client down (which clears the flag and\n * nulls the client) and reset the subscription flags so `ensureRealtime\n * Started()` rebuilds a fresh client+subscription next tick. Because the flag\n * is now false, the polling fallback runs THIS cycle — inbound delivery is\n * restored immediately rather than waiting on the ~125-min synthetic-probe\n * backstop. Returns true when a heal was performed (for heartbeat reporting).\n */\nfunction healRealtimeSocketDivergence(): boolean {\n if (!shouldForcePollFallback(isRealtimeConnected(), isRealtimeSocketConnected())) return false;\n log('[realtime] Socket divergence — channel flag connected but socket dead; forcing poll fallback + reconnect');\n stopRealtimeChat();\n realtimeStarted = false;\n realtimeDriftStarted = false;\n realtimeAssignStarted = false;\n realtimeConfigStarted = false;\n realtimeKanbanStarted = false;\n subscribedIntegrationContextAgentIds = new Set();\n return true;\n}\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 // ENG-5599: scheduled-task cards are already nudged by\n // routeScheduledTaskViaKanban with their own per-card run marker. The\n // generic kanban-work trigger here would double-nudge them (and the\n // second nudge carries no marker, so any turns it provokes are\n // unattributed) — skip them.\n if (item.source_type === 'scheduled_task') return;\n\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 // ENG-5405: include the item's id in the inject so the agent\n // can call kanban_move(id, \"in_progress\") directly instead of\n // having to kanban_list first to look up its UUID.\n // CodeRabbit PR #1275: JSON.stringify the title — see the\n // ensurePersistentSession poll-driven trigger above for the\n // same hardening rationale (user-authored content can break\n // the injected prompt shape).\n injectMessage(agent.codeName, 'task', `New task added to your board: id=${item.id} title=${JSON.stringify(item.title)} priority=${item.priority}. Call kanban_move(\"${item.id}\", \"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 // ENG-5601: secondary delivery path, for a HUMAN completing the agent's\n // scheduled-task card from the console. Three gates, all load-bearing:\n // 1. Behind the rollout flags. Manager-side delivery is the new model;\n // when neither flag is on, the legacy claude -p oneshot owns\n // delivery. The poll path is already flag-gated upstream; mirror it.\n // 2. `user:` actor only. Agent self-completion (the in-session happy\n // path) is suppressed upstream by isSelfCompletion and handled by\n // the poll path; the oneshot's own `manager:scheduled-task` closure\n // classifies as 'unknown' and is excluded.\n // 3. Manager-ROUTED cards only (isScheduledCardTracked). The flag +\n // actor gates alone aren't enough: executeAndProcessClaudeTask (the\n // oneshot/fallback path) ALSO materialises plain scheduled_task\n // cards and already self-delivers, but never records them in\n // scheduledRunsByCode. Restricting to tracked cards keeps this off\n // those — matching the poll path, which only ever iterates tracked\n // cards — so a human completing an oneshot card can't double-send.\n // deliverScheduledCardResult shares the idempotency claim with the poll\n // path, so a human closing a card the poll already delivered is a no-op.\n if (\n event.status === 'done' &&\n (isScheduledViaKanbanEnabled() || isKanbanHybridEnabled()) &&\n classifyActor(event.last_actor_id, event.agent_id) === 'user' &&\n isScheduledCardTracked(event.item_id)\n ) {\n void deliverScheduledCardResult(codeName, event.agent_id, event.item_id);\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('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&apos;');\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']);\n\n// ENG-5599 (S1 of ENG-5576): a \"plain\" scheduled task is one whose claude -p\n// result only needs delivering — no template-specific side effects in\n// processClaudeTaskResult (standup posts to /host/agent-status; plan /\n// task-update write board items) and not the kanban-work loop. Only plain\n// tasks are eligible for the in-session kanban route in S1; the rest keep the\n// oneshot until their side effects are re-homed in-session (S3, ENG-5601).\nexport function isPlainScheduledTemplate(templateId: string): boolean {\n return (\n !STANDUP_TEMPLATES.has(templateId) &&\n !TASK_UPDATE_TEMPLATES.has(templateId) &&\n !PLAN_TEMPLATES.has(templateId) &&\n !KANBAN_WORK_TEMPLATES.has(templateId)\n );\n}\n\n// ENG-5405: opt-in cutover from external `claude -p kanban-work` cron to\n// the in-session `/loop kanban-work` armed at bootstrap by ENG-5404. When\n// truthy, persistent-mode Claude Code agents stop firing kanban-work\n// subprocesses — the live REPL handles board polling via the loop.\n// Non-persistent (oneshot) agents are unaffected: they keep firing\n// kanban-work via `claude -p` because they have no live REPL to inject\n// into. The flag is read every poll tick so operators can toggle without\n// a restart. Truthy = literal '1' or 'true' (case-insensitive); anything\n// else preserves the legacy cron behaviour.\nexport function isKanbanWorkCronDisabled(): boolean {\n const v = process.env['AGT_DISABLE_KANBAN_WORK_CRON'];\n return v === '1' || v?.toLowerCase() === 'true';\n}\n\n// ENG-5435: hybrid kanban-work — manager-gated edge-triggered injection.\n// Trigger lives in step 7e of the per-agent poll. Helpers below.\n//\n// Layering with the two predecessor env vars (see operator matrix in\n// docs/research/eng-5435-hybrid-kanban-inject.md):\n// AGT_DISABLE_KANBAN_WORK_CRON=1 — legacy cron off (ENG-5405)\n// AGT_DISABLE_KANBAN_LOOP=1 — in-session /loop off (ENG-5433)\n// AGT_KANBAN_HYBRID=1 — manager-gated inject on ← this\n// Set all three for clean hybrid mode. `warnOnConflictingKanbanModes`\n// fires a startup WARN if HYBRID is on without both predecessors off,\n// because both kanban sources running at once doubles token spend.\n\nexport function isKanbanHybridEnabled(): boolean {\n const v = process.env['AGT_KANBAN_HYBRID'];\n return v === '1' || v?.toLowerCase() === 'true';\n}\n\nexport function isKanbanHybridDryRun(): boolean {\n const v = process.env['AGT_KANBAN_HYBRID_DRY_RUN'];\n return v === '1' || v?.toLowerCase() === 'true';\n}\n\n// Same status set as ACTIONABLE_STATUSES (both exclude `backlog` as of\n// ENG-5605 — backlog is curatorial, not \"pick this up now\"; the board owner\n// promotes backlog → todo when ready). The hybrid path's *additional* narrowing\n// over the plain kanban-work gate is the scheduled_task exclusion in\n// isHybridActionable below, not the status set.\nconst HYBRID_ACTIONABLE_STATUSES = new Set(['todo', 'in_progress']);\n\n// ENG-5600: a board item the generic hybrid nudge should act on. Excludes\n// scheduled_task cards — those are driven by routeScheduledTaskViaKanban's own\n// per-card nudge + run (ENG-5599). Letting the board-level nudge also fire for\n// them opens a competing source_type='kanban' run whose marker would absorb the\n// card's turns, mis-attributing the scheduled task's cost (and double-nudging).\nexport function isHybridActionable(item: BoardItem): boolean {\n return HYBRID_ACTIONABLE_STATUSES.has(item.status) && item.source_type !== 'scheduled_task';\n}\n\nexport function hasHybridActionableItems(items: BoardItem[]): boolean {\n return items.some(isHybridActionable);\n}\n\n// Module-level debounce state. See design-doc Q3 for the\n// \"no manager-state.json persistence\" rationale.\nconst lastKanbanInjectAt = new Map<string, number>();\n\n// ENG-5565: the currently-open injected `runs` row per agent (codeName →\n// run_id). Each manager-injected kanban nudge opens a run; the next inject\n// (the agent is idle again, so the prior work is done) closes it. The run_id\n// is also written into the injected prompt as a marker (see formatRunMarker)\n// so the per-turn transcript parser can attribute that run's token usage.\n// At most one open injected run per agent; the last one stays open until the\n// next inject (or session end) — fine, since attribution is marker-bounded.\nconst openInjectedRunByCode = new Map<string, string>();\n\n// acpx delivers a file-loaded user message downstream of the REPL slash\n// parser, so slash commands must go via tmux send-keys (and carry no marker).\n// Plain-text nudges (e.g. the kanban check) go via acpx + a run marker.\nfunction isSlashCommand(command: string): boolean {\n return command.trimStart().startsWith('/');\n}\n\n// ENG-5565: close an agent's open injected run if one exists. Fire-and-forget\n// (the map is drained synchronously; finishRun POSTs in the background). Called\n// when the board drains (kanban work done), the next inject supersedes it, or\n// the session is torn down — so a run never hangs open past its real\n// prompt-ready boundary, which would mis-attribute later turns. A manager\n// restart that loses this in-memory map can't close in-process; a server-side\n// stale-run reaper is the backstop there (tracked for B3/hardening).\nfunction closeInjectedRunIfOpen(\n codeName: string,\n outcome: 'completed' | 'failed' | 'cancelled' | 'timeout',\n outcomeMessage: string,\n): void {\n const runId = openInjectedRunByCode.get(codeName);\n if (!runId) return;\n openInjectedRunByCode.delete(codeName);\n void finishRun(runId, outcome, { outcomeMessage });\n}\n\n// Floor at 10s — anything tighter risks pummelling the REPL when the\n// agent's first response itself adds new actionable items.\n// CR (CodeRabbit on PR #1293): clamp sub-10 overrides to 10s rather\n// than discarding them and falling back to the 60s default. An operator\n// who sets `=5` is asking for \"as fast as you can safely go\", not \"give\n// me a slow default.\"\nconst KANBAN_HYBRID_DEBOUNCE_MS = (() => {\n const raw = process.env['AGT_KANBAN_HYBRID_DEBOUNCE_SECONDS'];\n const parsed = raw ? parseInt(raw, 10) : NaN;\n if (Number.isFinite(parsed) && parsed > 0) return Math.max(parsed, 10) * 1000;\n return 60_000;\n})();\n\n/**\n * Per-agent inject decision: debounce-then-idle-then-send. Stamps the\n * debounce BEFORE the idle check (design-doc Q3 mitigation) so two\n * back-to-back ticks don't both proceed into a double send. Idle skip\n * is a soft-fail: the agent gets another shot on the next tick.\n *\n * Fire-and-forget — caller MUST NOT await the outer promise, or a\n * busy agent's 10s `waitForPromptReady` cap serialises every later\n * agent in the same poll cycle.\n */\nexport async function maybeInjectKanbanCheck(\n codeName: string,\n agentId: string,\n actionableCount: number,\n): Promise<void> {\n const lastAt = lastKanbanInjectAt.get(codeName) ?? 0;\n if (Date.now() - lastAt < KANBAN_HYBRID_DEBOUNCE_MS) {\n // Quiet log — every 5s poll tick on a chatty board would otherwise\n // emit a debounce line per agent. Only log every ~10th skip via a\n // counter would be nicer; for v1 we just log nothing on debounce-\n // skip and let the next successful inject mark the cadence.\n return;\n }\n // Stamp NOW so a concurrent tick can't race past the debounce. A\n // failed send (idle skip, tmux gone) leaves the stamp in place for\n // one debounce window — acceptable: the next inject just waits one\n // window before retrying.\n lastKanbanInjectAt.set(codeName, Date.now());\n\n const tmuxSession = `agt-${codeName}`;\n const ready = await isAgentPromptReady(tmuxSession);\n if (!ready) {\n log(`[manager-worker] kanban inject skipped — agent busy ('${codeName}')`);\n return;\n }\n\n if (isKanbanHybridDryRun()) {\n log(\n `[manager-worker] kanban inject DRY-RUN — would inject for '${codeName}' (${actionableCount} actionable items)`,\n );\n // Roll back the stamp so the next tick can DRY-RUN-log again\n // immediately. Without this, dry-run output would only appear once\n // per debounce window per agent and be hard to use as a verifier.\n lastKanbanInjectAt.delete(codeName);\n return;\n }\n\n // ENG-5565: the agent is idle (ready), so any previously-injected run is\n // finished — close it before opening the next. Marker-bounded attribution\n // means the prior run's turns are everything up to this new marker.\n closeInjectedRunIfOpen(codeName, 'completed', 'kanban inject superseded by next inject');\n\n // Open a run for this injection so its token usage is attributable. A\n // start failure (run_id null) is non-fatal: we still nudge, just without\n // a marker this cycle (the work counts toward the agent total, unattributed).\n const { run_id } = await startRun({\n agent_id: agentId,\n source_type: 'kanban',\n metadata: { injected: 'kanban-check', actionable_count: actionableCount },\n });\n\n // Deliver the nudge. Plain text → acpx file-inject with the run marker\n // appended as an inert trailing line (acpx isn't paste-limited and lands a\n // user turn the per-turn parser can read). A slash command would break\n // under acpx, so it falls back to tmux send-keys and carries no marker.\n let delivered: boolean;\n if (isSlashCommand(KANBAN_CHECK_COMMAND)) {\n delivered = sendToAgent(tmuxSession, KANBAN_CHECK_COMMAND);\n } else {\n const content = run_id\n ? `${KANBAN_CHECK_COMMAND}\\n${formatRunMarker(run_id)}`\n : KANBAN_CHECK_COMMAND;\n delivered = await injectMessage(codeName, 'system', content, undefined, log);\n }\n\n if (delivered) {\n if (run_id) openInjectedRunByCode.set(codeName, run_id);\n log(\n `[manager-worker] Injected kanban check for '${codeName}' (${actionableCount} actionable items)${run_id ? ` run_id=${run_id}` : ''}`,\n );\n } else {\n // Delivery failed — don't leave a phantom open run for a nudge that\n // never landed. Close it so it isn't billed turns it didn't cause.\n if (run_id) {\n void finishRun(run_id, 'failed', { outcomeMessage: 'kanban inject delivery failed' });\n }\n log(`[manager-worker] kanban inject failed for '${codeName}'`);\n }\n}\n\n/**\n * Single WARN at manager start when AGT_KANBAN_HYBRID is on but a\n * predecessor isn't disabled — both sources would fire simultaneously\n * and double-spend tokens. We warn rather than refuse to start so a\n * bad env push can't strand the host.\n */\nexport function warnOnConflictingKanbanModes(): void {\n if (!isKanbanHybridEnabled()) return;\n const cronOff = isKanbanWorkCronDisabled();\n const loopOff = isKanbanLoopDisabled();\n if (!cronOff || !loopOff) {\n const stillOn = !cronOff && !loopOff\n ? 'AGT_DISABLE_KANBAN_WORK_CRON and AGT_DISABLE_KANBAN_LOOP are'\n : !cronOff\n ? 'AGT_DISABLE_KANBAN_WORK_CRON is'\n : 'AGT_DISABLE_KANBAN_LOOP is';\n log(\n `[manager-worker] WARN AGT_KANBAN_HYBRID=1 but ${stillOn} not set — ` +\n 'kanban-work will fire from MULTIPLE sources (doubles token spend). ' +\n 'Set both predecessors to 1 for clean hybrid mode.',\n );\n }\n}\nconst BOARD_INJECT_TEMPLATES = new Set(['morning-plan', 'task-update', 'hourly-status', 'end-of-day-summary', 'kanban-work']);\n\n// ENG-4410: Check if the board has work the kanban-work loop will actually act\n// on, used to skip the kanban-work claude -p spawn when there's nothing to do.\n// ENG-5605: `backlog` is deliberately EXCLUDED. The kanban-work loop only works\n// `todo`/`in_progress` cards (it promotes the top `todo`); a card parked in\n// `backlog` is curatorial, not ready. Counting backlog here made an agent with\n// backlog cards spawn claude -p every ~5 min only to return `<no-delivery/>` —\n// no-op subprocess spawns re-paying the full prompt-cache prefix for zero work\n// (observed on maven, agt-aws-1). This now matches HYBRID_ACTIONABLE_STATUSES.\nconst ACTIONABLE_STATUSES = new Set(['todo', 'in_progress']);\nexport function 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; source_type?: string; source_ref?: 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 guardrails?: ProvisionInput['guardrails'];\n // ENG-5380: active kanban tasks payload from /host/refresh. The API\n // caps at 10 and filters to todo/in_progress; here we just thread it\n // through to the adapter behind the feature flag.\n active_tasks?: ProvisionInput['activeTasks'];\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 // ENG-5380: feature-flag gate for the session-start active-tasks\n // injection. AGT_ACTIVE_TASKS_INJECT=1 turns it on; anything else keeps\n // the legacy behaviour where CLAUDE.md doesn't carry an Active Tasks\n // section. Gating here (manager, not adapter) keeps `identity.ts` pure\n // — the adapter just renders whatever the manager hands it.\n const activeTasksInjectEnabled = process.env['AGT_ACTIVE_TASKS_INJECT'] === '1';\n const activeTasksFromRefresh = activeTasksInjectEnabled\n ? (refreshData.active_tasks ?? [])\n : undefined;\n // Log the rendered section's token cost per refresh so we can measure\n // the budget impact across a week before deciding whether to promote\n // this to per-turn injection (ENG-5379 acceptance criterion #3). 0\n // tokens prints too when the list is empty — visibility into the\n // \"flag on, no tasks\" path matters for the rollout decision.\n if (activeTasksInjectEnabled) {\n const tokens = estimateActiveTasksTokens(activeTasksFromRefresh);\n log(\n `[provision/active-tasks] agent=${agent.code_name} count=${activeTasksFromRefresh?.length ?? 0} estimated_tokens=${tokens}`,\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 activeTasks: activeTasksFromRefresh,\n // ENG-5009: org name for the identity-preamble. Only present when\n // /host/refresh ships the field — falls through cleanly for older\n // API payloads that don't carry it.\n organization: (() => {\n // CodeRabbit on PR #941: trim before the length check so a\n // whitespace-only value (`' '`) doesn't render a broken\n // \"in the X team at ** **\" scope in CLAUDE.md.\n const org = (refreshData as unknown as { organization?: { name?: unknown } | null })\n .organization;\n if (typeof org?.name !== 'string') return undefined;\n const name = org.name.trim();\n return name.length > 0 ? { name } : undefined;\n })(),\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 guardrails: refreshData.guardrails,\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 // ENG-5041: extend the inter-poll delay when the last cycle failed, so\n // a sustained API outage doesn't hammer the cloud every few seconds.\n // Returns 0 when consecutivePollFailures is 0, so healthy hosts use\n // the original interval verbatim.\n const backoffMs = currentPollBackoffMs(consecutivePollFailures);\n const delayMs = Math.max(config.intervalMs, backoffMs);\n pollTimer = setTimeout(() => {\n if (restartAfterUpgrade) {\n restartNow();\n return;\n }\n void pollCycle().then(scheduleNext);\n }, delayMs);\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 // ENG-5599: close any in-session scheduled-task runs still tracked in memory.\n // The per-agent stop paths (stopPersistentSessionAndForgetMcpBaseline) cover\n // pause/revoke/restart, but a full manager shutdown/restart can exit without\n // hitting those — without this drain those runs would be left open forever\n // once scheduledRunsByCode is lost. Best-effort (finishRun POSTs in the\n // background under the 15s shutdown budget); the server-side reaper (S2) is\n // the durable backstop.\n for (const codeName of [...scheduledRunsByCode.keys()]) {\n closeScheduledRunsForCode(codeName, 'cancelled', 'manager shutdown');\n }\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 // ENG-5441: rehydrate circuit-breaker trips so a manager restart\n // can't accidentally clear a tripped breaker. An operator who\n // intentionally stopped the loop must be the one to clear it.\n if (parsed.circuitBreakerTrips && typeof parsed.circuitBreakerTrips === 'object') {\n restartBreaker.hydrate(parsed.circuitBreakerTrips);\n const n = Object.keys(parsed.circuitBreakerTrips).length;\n if (n > 0) log(`[startup] rehydrated ${n} circuit-breaker trip(s) — agents will remain paused until manually resumed`);\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 // ENG-5435: warn once at startup if hybrid mode is configured alongside\n // a still-active predecessor (cron or in-session /loop). Both sources\n // would fire and double the token spend.\n warnOnConflictingKanbanModes();\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 dist/mcp/ directory (ENG-5410).\n // The tarball is restricted to dist/ only, so the bundled MCP assets are\n // copied under dist/mcp/ by the build:mcp-assets script.\n const moduleDir = dirname(fileURLToPath(import.meta.url));\n // Dev: src/lib → ../../dist/mcp, Built: dist/lib → ../dist/mcp, npm global: dist/lib → ../dist/mcp\n let mcpSourceDir = '';\n let dir = moduleDir;\n for (let i = 0; i < 6; i++) {\n const candidate = join(dir, 'dist', '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\nimport { createHash } from 'node:crypto';\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/**\n * ENG-5537 — given a content `drift` was detected, decide whether it warrants\n * restarting the running claude session.\n *\n * Only a *membership* change to the declared MCP server set (a server added or\n * removed) requires a restart: the running session connected to a fixed set of\n * servers at launch, and gaining/losing one means the live session's tool\n * surface is wrong until it respawns. A *value-only* drift — the same set of\n * servers with a rotated url or header value, e.g. Composio re-signing its\n * `mcp_url` on most polls — leaves the running session's established\n * connections intact and is picked up by the next natural respawn, so it must\n * NOT restart. Restarting on value-only drift is exactly what flapped agents\n * into the ENG-5441 circuit breaker (phil, agt-aws-1, 2026-05-25).\n *\n * When either key set is unknown (file missing / unparseable on one side), we\n * keep the pre-ENG-5537 safe default: treat it as restart-worthy.\n */\nexport interface McpDriftRestartDecision {\n /** Restart the running session? True only on membership change or unknown. */\n restart: boolean;\n /** Key membership could not be determined (null/undefined on either side). */\n membershipUnknown: boolean;\n /** Server keys added or removed (sorted; empty for value-only or unknown). */\n addedOrRemoved: string[];\n}\n\nexport function decideMcpRestartOnDrift(\n previousKeys: Set<string> | null | undefined,\n currentKeys: Set<string> | null | undefined,\n): McpDriftRestartDecision {\n if (!previousKeys || !currentKeys) {\n return { restart: true, membershipUnknown: true, addedOrRemoved: [] };\n }\n const addedOrRemoved: string[] = [];\n for (const k of currentKeys) if (!previousKeys.has(k)) addedOrRemoved.push(k);\n for (const k of previousKeys) if (!currentKeys.has(k)) addedOrRemoved.push(k);\n addedOrRemoved.sort();\n return { restart: addedOrRemoved.length > 0, membershipUnknown: false, addedOrRemoved };\n}\n\n/**\n * ENG-5537 — structure-only hash of a managed-toolkit desired set, used to gate\n * the convergence-driven session restart independently of the value hash that\n * gates the `.mcp.json` write.\n *\n * Deliberately hashes only each server's id and the SORTED NAMES of its header\n * keys — never the url or header values. A provider rotating a signed `mcp_url`\n * (or a credential-bearing header value) moves the value hash, so `.mcp.json`\n * re-converges to the live credential, but leaves THIS hash unchanged, so the\n * running session is left alone. Adding/removing a toolkit, or a header key\n * appearing/disappearing, does move this hash and triggers the restart.\n */\nexport function managedMcpStructureHash(\n entries: Array<{ serverId: string; headers?: Record<string, string> }>,\n): string {\n const basis = entries\n .slice()\n .sort((a, b) => a.serverId.localeCompare(b.serverId))\n .map((e) => `${e.serverId}|${Object.keys(e.headers ?? {}).sort().join(',')}`)\n .join('\\n');\n return createHash('sha256').update(basis).digest('hex').slice(0, 16);\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// ENG-5344: rotation grace window — when reapStaleMcpChildren SIGTERMs\n// an MCP child, Claude Code's MCP transport only respawns on the next\n// tool call. There is therefore a window (≥ SIGTERM-grace + respawn\n// latency) where the child is dead and presence-reaper would see zero\n// live children for the affected server keys, even though nothing is\n// actually broken. Without coordination, the next manager tick after a\n// rotation kills the whole tmux session — observed on Stirling at ~20\n// min intervals, 54 false-positive restarts/day.\n//\n// We record (codeName, serverKey) → SIGTERM timestamp here and let\n// presence-reaper consult it to skip keys that are still in their\n// post-rotation respawn window.\nconst rotationTimestamps = new Map<string, number>();\n\n/**\n * Default rotation grace window. Sized to cover:\n * - the SIGTERM → SIGKILL grace in reapStaleMcpChildren (5s default)\n * - Claude Code's lazy respawn latency on next tool call\n * - a healthy margin so a slow respawn doesn't trip presence-reaper\n *\n * 15s is comfortably above the observed ~2s race window between\n * stale-reaper and the next manager tick.\n */\nexport const DEFAULT_ROTATION_GRACE_MS = 15_000;\n\nfunction rotationKey(codeName: string, serverKey: string): string {\n return `${codeName}\\x00${serverKey}`;\n}\n\n/**\n * Record that we just SIGTERM'd one or more MCP children for these\n * server keys belonging to <codeName>. Callers should invoke this after\n * SIGTERM (not before): no kill = no in-flight rotation to grace.\n *\n * Idempotent — the latest call wins so back-to-back rotations extend\n * the grace window rather than expire it early.\n */\nexport function recordStaleRotation(\n codeName: string,\n serverKeys: Iterable<string>,\n now: () => number = Date.now,\n): void {\n const ts = now();\n for (const key of serverKeys) {\n rotationTimestamps.set(rotationKey(codeName, key), ts);\n }\n}\n\n/**\n * Was <codeName>:<serverKey> rotated within the last `withinMs`?\n * Returns false if no rotation has been recorded for the pair.\n *\n * Self-cleaning: expired entries are deleted on read so the map stays\n * bounded under steady-state load.\n */\nexport function wasRecentlyRotated(\n codeName: string,\n serverKey: string,\n withinMs: number,\n now: () => number = Date.now,\n): boolean {\n const k = rotationKey(codeName, serverKey);\n const ts = rotationTimestamps.get(k);\n if (ts === undefined) return false;\n if (now() - ts < withinMs) return true;\n rotationTimestamps.delete(k);\n return false;\n}\n\n/**\n * Test-only seam: clear the rotation tracker between tests so a recorded\n * rotation in one case doesn't bleed into another.\n */\nexport function __resetStaleRotationTrackerForTests(): void {\n rotationTimestamps.clear();\n}\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 // ENG-5344: record the rotation so the presence-reaper running on the\n // next manager tick (≥ ~2s later) doesn't see the now-dead children as\n // \"declared MCP missing\" and tear down the whole tmux session. The\n // child won't respawn until Claude Code makes its next tool call, so a\n // grace window is the only correct read here. Only record for keys we\n // actually killed — recording the full `serverKeys` list would grace\n // keys whose env rotated but had no live children to begin with.\n const killedKeys = new Set<string>();\n for (const pid of targets) {\n const argv = byPid.get(pid)?.args ?? '';\n for (const key of serverKeys) {\n const entry = mcpJson?.mcpServers?.[key];\n const matchers = buildArgvMatchersForEntry(key, entry);\n if (matchers.some((re) => re.test(argv))) killedKeys.add(key);\n }\n }\n if (killedKeys.size > 0) recordStaleRotation(codeName, killedKeys);\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-5053: detect MCP children that should be running but aren't, and\n// trigger a session restart so they come back online.\n//\n// Why this exists: an MCP child can die under a live parent claude\n// (transient error during init, OOM, downstream service blip during a\n// tool call). Claude Code's MCP transport advertises that it'll\n// respawn a dead child on the next request — but in practice we've\n// seen agents go silent for hours with a missing MCP and no recovery\n// (Stirling, 2026-05-13, Xero MCP). The agent keeps reporting \"X is\n// still warming up\" indefinitely. Killing the tmux session forces the\n// manager's ensure-session pass to respawn it, which gets the missing\n// child back.\n//\n// This module is the *presence* counterpart to stale-mcp-reaper.ts\n// (which handles env rotation — child alive but holding a stale\n// credential) and orphan-channel-mcp-reaper.ts (which handles dead\n// parents — child alive but its parent claude is gone). The gap they\n// don't cover is the one this fills: parent claude alive, declared\n// MCP child absent, no rotation pending.\n//\n// Pure helpers exposed for tests:\n// • findMissingMcpServers() — given parsed ps rows + the agent's\n// codeName + parsed .mcp.json, return the declared mcpServers\n// keys that have ZERO matching live children belonging to this\n// agent's claude.\n//\n// Side-effecting:\n// • reapMissingMcpSessions() — runs `ps`, checks presence, applies\n// the cold-start grace window, and calls back to stopSession()\n// so the manager's ensure-session pass respawns the agent.\n\nimport { execFileSync } from 'node:child_process';\nimport { parsePsRows, type PsRow } from './orphan-channel-mcp-reaper.js';\nimport {\n DEFAULT_ROTATION_GRACE_MS,\n findMcpChildrenForAgent,\n wasRecentlyRotated,\n type McpConfig,\n} from './stale-mcp-reaper.js';\n\n/**\n * Cold-start grace window — how long after a session starts we wait\n * before flagging a missing MCP. Composio + Xero MCPs spawn via npx\n * which can take 20-30s to install on a fresh box; without this grace\n * a brand-new session would be repeatedly killed mid-init.\n */\nexport const DEFAULT_COLD_START_GRACE_MS = 90_000;\n\n/**\n * ENG-5279: maximum consecutive presence-reaper restarts before we give\n * up on a permanently-broken declared MCP. Set to 3 to match the\n * agent-thrash-monitor threshold — if we keep restarting past that\n * point, the thrash detector pages Slack with a false alarm for a\n * failure the reaper can't fix by restarting (orphan in .mcp.json from\n * a pre-ENG-5277 install, an unpublished MCP package, a transiently\n * failing npx install that's now permanently broken, etc.).\n */\nexport const MAX_PRESENCE_RESTART_ATTEMPTS = 3;\n\n/**\n * Per-(codeName, serverKey) state for the cap logic.\n *\n * `attempts` counts distinct restart cycles where the server never\n * came back live — NOT distinct polls. CodeRabbit on PR #1167: pre-fix\n * the counter incremented on every tick the server was missing, so a\n * slow respawn (3+ polls during a single in-flight restart) would\n * exhaust the cap before three actual session generations had been\n * observed. The `lastAttemptedSessionStartedAt` field is the per-\n * generation gate — we only increment when this differs from the\n * current `sessionStartedAt` (i.e. the manager spawned a fresh\n * session since our last attempt), and after incrementing we record\n * the current generation so subsequent polls within the same\n * generation are no-ops for the counter.\n *\n * `gaveUpLogged` records whether we've already emitted the give-up\n * audit line so subsequent ticks stay silent.\n *\n * Cleared per-agent by `clearPresenceReaperState()` when the manager\n * sees `.mcp.json` content change — that's the signal that something\n * external fixed the config and the give-up state is stale.\n */\ninterface PresenceReaperKeyState {\n attempts: number;\n lastSeenLiveAt: number | null;\n lastAttemptedSessionStartedAt: number | null;\n gaveUpLogged: boolean;\n}\n\nconst presenceReaperState = new Map<string, PresenceReaperKeyState>();\n\nfunction stateKey(codeName: string, serverKey: string): string {\n return `${codeName}\\x00${serverKey}`;\n}\n\n/**\n * Clear every per-server give-up state for `codeName`. Wipes ALL\n * entries for the agent regardless of which keys changed.\n *\n * Prefer {@link clearPresenceReaperStateForKeys} from production code\n * (ENG-5285) — value-only drifts on .mcp.json (e.g. composio rotating\n * a signed proxy URL every ~20 min) shouldn't reset the cap on keys\n * whose presence-classification didn't actually change. This whole-\n * agent wipe was the root cause of Stirling flapping 450× over six\n * days post-ENG-5279: every URL rotation reset the cap before the\n * reaper could give up on the broken xero MCP.\n *\n * Kept exported for tests + any future caller that genuinely needs a\n * full agent-scoped wipe (none in production today).\n */\nexport function clearPresenceReaperState(codeName: string): void {\n const prefix = `${codeName}\\x00`;\n for (const key of presenceReaperState.keys()) {\n if (key.startsWith(prefix)) presenceReaperState.delete(key);\n }\n}\n\n/**\n * ENG-5285: clear give-up state only for the named MCP server keys.\n *\n * Call from the manager-worker's `.mcp.json` drift handler with the\n * set of keys that were ADDED or REMOVED relative to the previous\n * .mcp.json. Keys present in both old and new (even if their values\n * changed — e.g. composio rotated a signed URL) must NOT be cleared,\n * because their presence-classification is unchanged and the cap on\n * a permanently-broken MCP must keep accumulating across config\n * rewrites.\n *\n * No-op when `keys` is empty.\n */\nexport function clearPresenceReaperStateForKeys(\n codeName: string,\n keys: Iterable<string>,\n): void {\n for (const key of keys) {\n presenceReaperState.delete(stateKey(codeName, key));\n }\n}\n\n/**\n * Test-only seam: reset all state. Production code should use\n * `clearPresenceReaperState(codeName)` instead — wiping the whole map\n * would lose give-up state for unrelated agents.\n */\nexport function __resetPresenceReaperStateForTests(): void {\n presenceReaperState.clear();\n}\n\n/**\n * Pure: given parsed ps rows + the agent's codeName + parsed\n * `.mcp.json`, return the declared `mcpServers` keys that have ZERO\n * matching live children belonging to this agent's claude.\n *\n * Reuses `findMcpChildrenForAgent` from stale-mcp-reaper so the argv-\n * matching rules and ppid-walk are shared with the existing reapers\n * (one source of truth for \"which child belongs to which agent + which\n * server key\").\n */\nexport function findMissingMcpServers(args: {\n rows: PsRow[];\n codeName: string;\n mcpJson: McpConfig | null | undefined;\n}): string[] {\n const { rows, codeName, mcpJson } = args;\n const servers = mcpJson?.mcpServers ?? {};\n const declared = Object.keys(servers);\n if (declared.length === 0) return [];\n\n const missing: string[] = [];\n for (const key of declared) {\n // ENG-5075: skip URL-based remote MCP entries (Streamable HTTP /\n // SSE — anything with a `url` field and no `command`). Claude\n // Code dials those endpoints directly; there's no stdio child\n // process to discover via `ps`. Pre-fix, the reaper iterated\n // every declared server and flagged URL-based ones (e.g. Granola\n // post-ENG-5074) as \"missing\" every poll cycle → killed the\n // session every ~3 minutes → Scout flapped indefinitely with the\n // signature \"declared MCP(s) [granola] have no live children\".\n //\n // The presence check is only meaningful for stdio MCPs — those\n // are the ones with a child process that could die under a live\n // parent. URL-based entries can't have that failure mode at the\n // child-process layer (their failure mode is \"HTTP request fails\n // at call time\", which is the MCP transport's job to handle, not\n // the reaper's).\n const entry = (servers as Record<string, unknown>)[key] as\n | { command?: unknown; url?: unknown }\n | undefined;\n const isStdio = typeof entry?.command === 'string';\n if (!isStdio) continue;\n\n const live = findMcpChildrenForAgent({\n rows,\n codeName,\n serverKeys: [key],\n mcpJson,\n });\n if (live.length === 0) missing.push(key);\n }\n return missing;\n}\n\nexport interface ReapMissingMcpSessionsArgs {\n log: (msg: string) => void;\n codeName: string;\n mcpJson: McpConfig | null | undefined;\n /**\n * When the parent claude session was last started (Date.now() ms).\n * Used to apply the cold-start grace window — we don't flag a\n * missing MCP within `graceMs` of startup, because Composio + Xero\n * MCPs can take ~30s to npm-install on first spawn. Pass null when\n * the session start time is unknown — the reaper then *skips* the\n * agent rather than risk a false-positive restart.\n */\n sessionStartedAt: number | null;\n /** Callback that tears the agent's session down so the manager's\n * ensure-session pass respawns it on the next tick.\n *\n * ENG-5547: `ctx.activeKeys` is the set of under-cap missing MCP servers\n * that prompted *this* restart (the same keys logged on the restart line).\n * The manager uses its cardinality to decide whether the restart counts\n * toward the agent-wide circuit breaker: a single persistently-failing\n * server is isolated by its own ENG-5279 give-up cap (→ marked unhealthy →\n * dropped from .mcp.json), so it must NOT pause the whole agent; a restart\n * driven by several servers at once is a genuine storm that should. */\n stopSession: (codeName: string, ctx: { activeKeys: string[] }) => void;\n /** Override the cold-start grace (default: 90s). */\n graceMs?: number;\n /**\n * ENG-5344: post-rotation grace. When stale-mcp-reaper SIGTERMs an MCP\n * child during a credential rotation, the child won't respawn until\n * Claude Code makes the next tool call. Without this grace, the next\n * manager tick (~2s later) sees zero live children for the rotated\n * key and kills the whole tmux session — observed on Stirling at the\n * 20-min xero token rotation cadence (54 false-positive restarts/day).\n *\n * Default: {@link DEFAULT_ROTATION_GRACE_MS} (15s — comfortably above\n * the SIGTERM→SIGKILL grace plus typical respawn latency).\n */\n rotationGraceMs?: number;\n /** Test seam: substitute the `ps` invocation. */\n runPs?: () => string;\n /** Test seam: substitute the wall clock for grace-window checks. */\n now?: () => number;\n /**\n * ENG-5292: invoked EXACTLY ONCE per (codeName, serverKey) when the\n * restart cap (ENG-5279) is first exceeded — i.e. the first poll\n * where we'd give up on the key. Gated by the same `gaveUpLogged`\n * flag that prevents duplicate log lines, so subsequent ticks while\n * the key remains given-up don't re-fire.\n *\n * Production use: manager-worker registers a callback that POSTs to\n * `/host/integration-health` with action=mark_unhealthy. The endpoint\n * flips agent_integrations.status, which makes /host/managed-toolkits\n * drop the broken MCP from its response, which causes the next\n * .mcp.json regeneration to omit it cleanly.\n *\n * Errors thrown by the callback are swallowed (logged) so an API\n * outage doesn't break the reaper itself. The give-up is recorded\n * locally regardless — at worst the integration stays in active\n * state on the DB side and the next give-up tick after the API\n * recovers will retry the call (the cap state still trips immediately\n * since `gaveUpLogged` is local to this process).\n */\n onGiveUp?: (codeName: string, serverKey: string) => void;\n /**\n * ENG-5286: invoked every time the reaper actually triggers a session\n * restart (i.e. just before `stopSession()`). Production use: manager-\n * worker registers a callback that POSTs to /host/mcp-reaper-restart\n * so the API can roll up restart counts per agent into a CloudWatch\n * metric + flapping alarm.\n *\n * Errors thrown by the callback are swallowed (logged) — the reaper's\n * own correctness must not depend on the API call succeeding.\n */\n onRestart?: (\n codeName: string,\n args: { activeKeys: string[]; givenUpKeys: string[] },\n ) => void | Promise<void>;\n}\n\nexport interface ReapMissingMcpSessionsResult {\n /** The declared server keys we found missing. May be empty when\n * everything is healthy, when the session is still in cold-start,\n * or when sessionStartedAt is unknown. */\n missing: string[];\n /** True iff we actually called stopSession() this invocation. */\n restarted: boolean;\n /** Stable reason code for the no-op path, useful in tests. */\n reason?:\n | 'healthy'\n | 'cold-start'\n | 'session-start-unknown'\n | 'no-declared-servers'\n | 'no-mcp-json'\n | 'all-keys-over-cap';\n /** ENG-5279: server keys that were missing but skipped because they\n * hit the consecutive-restart cap. The session would otherwise have\n * been restarted; we held off. Empty in the common case. */\n givenUp?: string[];\n /** ENG-5344: server keys that were missing but skipped because they\n * were rotated by stale-mcp-reaper within the rotation grace window.\n * Treated as in-flight (will respawn on next tool call), not missing. */\n rotationGraced?: string[];\n}\n\n/**\n * Run `ps`, find declared MCP servers that have no live children for\n * <codeName>, and trigger a session restart if any are missing AND the\n * cold-start grace has elapsed. Idempotent — repeated calls when a\n * restart is already in flight are harmless (stopSession is a no-op\n * once the tmux session is already gone).\n */\nexport function reapMissingMcpSessions(\n args: ReapMissingMcpSessionsArgs,\n): ReapMissingMcpSessionsResult {\n const {\n log,\n codeName,\n mcpJson,\n sessionStartedAt,\n stopSession,\n graceMs = DEFAULT_COLD_START_GRACE_MS,\n rotationGraceMs = DEFAULT_ROTATION_GRACE_MS,\n onGiveUp,\n onRestart,\n } = args;\n const now = args.now ?? Date.now;\n const runPs =\n args.runPs ??\n (() => execFileSync('ps', ['-eo', 'pid,ppid,args'], { encoding: 'utf-8', timeout: 5_000 }));\n\n if (!mcpJson?.mcpServers) {\n return { missing: [], restarted: false, reason: 'no-mcp-json' };\n }\n const declaredCount = Object.keys(mcpJson.mcpServers).length;\n if (declaredCount === 0) {\n return { missing: [], restarted: false, reason: 'no-declared-servers' };\n }\n\n // Skip rather than risk a false-positive when we don't know when the\n // session started. The manager-worker passes null for agents whose\n // PersistentSession state hasn't been populated yet.\n if (sessionStartedAt === null) {\n return { missing: [], restarted: false, reason: 'session-start-unknown' };\n }\n\n // Cold-start grace: skip if the session is still in its grace window.\n if (now() - sessionStartedAt < graceMs) {\n return { missing: [], restarted: false, reason: 'cold-start' };\n }\n\n let psOutput: string;\n try {\n psOutput = runPs();\n } catch (err) {\n log(`[mcp-presence-reaper] ps invocation failed for '${codeName}': ${(err as Error).message} — skipping`);\n return { missing: [], restarted: false, reason: 'healthy' };\n }\n\n const rows = parsePsRows(psOutput);\n const missingRaw = findMissingMcpServers({ rows, codeName, mcpJson });\n\n // ENG-5344: stale-mcp-reaper rotation grace. When stale-reaper SIGTERMs\n // an MCP child during credential rotation, the child won't come back\n // until Claude Code makes its next tool call. Within the grace window,\n // treat \"no live children\" for that key as in-flight, not missing —\n // pre-fix this was the entire root cause of Stirling's 54 daily false-\n // positive restarts (and matching kills on don/phil/maven).\n const rotationGraced: string[] = [];\n const missing: string[] = [];\n for (const key of missingRaw) {\n if (wasRecentlyRotated(codeName, key, rotationGraceMs, now)) {\n rotationGraced.push(key);\n } else {\n missing.push(key);\n }\n }\n if (rotationGraced.length > 0) {\n log(\n `[mcp-presence-reaper] '${codeName}': skipping [${rotationGraced.join(', ')}] within rotation grace window (${rotationGraceMs}ms) — child(ren) just SIGTERM'd by stale-mcp-reaper, awaiting respawn on next tool call (ENG-5344)`,\n );\n }\n\n // ENG-5279: update per-(agent, server) recovery state on EVERY tick so\n // a key that came back to life — even briefly — resets its counter.\n // Walk the full declared stdio set (not just `missing`), so transitions\n // from missing→live clear the attempt count immediately. Without this\n // a flapping MCP would accumulate restarts forever.\n //\n // ENG-5344: use the UNFILTERED `missingRaw` to classify liveness — a\n // rotation-graced key has zero live children right now, so it must\n // not reset the cap counter (which would let a chronically broken MCP\n // never reach give-up if it gets rotated frequently).\n const declared = mcpJson.mcpServers ?? {};\n const liveKeys = new Set<string>();\n for (const key of Object.keys(declared)) {\n const entry = (declared as Record<string, unknown>)[key] as { command?: unknown } | undefined;\n if (typeof entry?.command !== 'string') continue; // stdio-only\n if (!missingRaw.includes(key)) liveKeys.add(key);\n }\n for (const key of liveKeys) {\n const state = presenceReaperState.get(stateKey(codeName, key));\n if (state) {\n state.attempts = 0;\n state.lastSeenLiveAt = now();\n state.lastAttemptedSessionStartedAt = null;\n state.gaveUpLogged = false;\n }\n }\n\n if (missing.length === 0) {\n return {\n missing,\n restarted: false,\n reason: 'healthy',\n rotationGraced: rotationGraced.length > 0 ? rotationGraced : undefined,\n };\n }\n\n // Tally attempts and partition missing into active (under cap) vs\n // given-up (at or over cap). Per-generation guard via\n // `lastAttemptedSessionStartedAt`: only count an attempt when the\n // manager has spawned a fresh session since our last increment for\n // this key. A slow respawn (multiple reaper polls between\n // `stopSession()` and the new session actually coming up) must NOT\n // burn the cap — the manager's stop wrapper is idempotent and\n // those extra calls aren't real restart cycles.\n const givenUp: string[] = [];\n const active: string[] = [];\n for (const key of missing) {\n const sk = stateKey(codeName, key);\n const state = presenceReaperState.get(sk) ?? {\n attempts: 0,\n lastSeenLiveAt: null,\n lastAttemptedSessionStartedAt: null,\n gaveUpLogged: false,\n };\n if (state.lastAttemptedSessionStartedAt !== sessionStartedAt) {\n state.attempts += 1;\n state.lastAttemptedSessionStartedAt = sessionStartedAt;\n }\n presenceReaperState.set(sk, state);\n if (state.attempts > MAX_PRESENCE_RESTART_ATTEMPTS) {\n givenUp.push(key);\n if (!state.gaveUpLogged) {\n log(\n `[mcp-presence-reaper] giving up on '${codeName}:${key}' after ${MAX_PRESENCE_RESTART_ATTEMPTS} consecutive failed restarts — declared but never recovers (likely orphaned config; see ENG-5279)`,\n );\n state.gaveUpLogged = true;\n // ENG-5292: fire the give-up callback exactly once per\n // (codeName, serverKey). Manager-worker uses this to POST to\n // /host/integration-health so the broken MCP gets marked\n // unhealthy and dropped from the next .mcp.json regeneration.\n // Swallow errors — the reaper's own correctness must not\n // depend on the callback succeeding.\n if (onGiveUp) {\n try {\n onGiveUp(codeName, key);\n } catch (err) {\n log(\n `[mcp-presence-reaper] onGiveUp callback threw for '${codeName}:${key}' (suppressed; reaper continues): ${(err as Error).message}`,\n );\n }\n }\n }\n } else {\n active.push(key);\n }\n }\n\n if (active.length === 0) {\n // Every missing key has been given up on — don't restart for any\n // of them. Stays quiet on subsequent ticks (gaveUpLogged guard).\n return {\n missing,\n restarted: false,\n reason: 'all-keys-over-cap',\n givenUp,\n rotationGraced: rotationGraced.length > 0 ? rotationGraced : undefined,\n };\n }\n\n // Audit line: emit the missing key(s) so we can spot patterns (\"xero\n // crashes every 4h\") without grepping pane logs. Single line per\n // restart event by design. Mention given-up keys too so the operator\n // sees the full picture.\n const givenUpSuffix = givenUp.length > 0\n ? ` (skipping over-cap: [${givenUp.join(', ')}])`\n : '';\n log(\n `[mcp-presence-reaper] '${codeName}': declared MCP(s) [${active.join(', ')}] have no live children — restarting session${givenUpSuffix}`,\n );\n // ENG-5286: fire the restart-observer callback before stopSession so\n // the API records the event with the exact key set that prompted the\n // restart. Errors are swallowed — reaper correctness wins.\n if (onRestart) {\n try {\n // CodeRabbit on PR #1186: callbacks may be sync OR async.\n // The synchronous try/catch catches throws from sync callbacks\n // and the synchronous part of async ones; the .catch() below\n // catches rejections from async callbacks so they don't escape\n // as unhandled promise rejections.\n const maybePromise = onRestart(codeName, { activeKeys: active, givenUpKeys: givenUp });\n if (\n maybePromise &&\n typeof (maybePromise as PromiseLike<void>).then === 'function'\n ) {\n void (maybePromise as PromiseLike<void>).then(undefined, (err: unknown) => {\n log(\n `[mcp-presence-reaper] onRestart callback rejected for '${codeName}' (suppressed; restart proceeds): ${(err as Error).message}`,\n );\n });\n }\n } catch (err) {\n log(\n `[mcp-presence-reaper] onRestart callback threw for '${codeName}' (suppressed; restart proceeds): ${(err as Error).message}`,\n );\n }\n }\n stopSession(codeName, { activeKeys: active });\n return {\n missing,\n restarted: true,\n givenUp: givenUp.length > 0 ? givenUp : undefined,\n rotationGraced: rotationGraced.length > 0 ? rotationGraced : undefined,\n };\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-5441 — circuit breaker for manager-initiated session restarts.\n *\n * Today's Scout incident on prod-integritylabs-agt-aws-1 respawned an\n * agent every ~55s for hours (~100 spawns/hr) because day-rollover\n * ([ENG-5431]) repeated indefinitely. Each respawn burned subscription\n * budget, flooded pane.log, drowned manager.log signal, and re-floods\n * `/host/direct-chat/poll`. The per-MCP reaper has a give-up cap\n * ([ENG-5279]) and the [ENG-5286] alarm fires when the cap-attempts\n * cross a threshold — but neither *stopped* the whole-agent loop.\n *\n * This breaker sits at the manager's spawn gate. Every manager-initiated\n * stop (day-rollover, auth-tuple change, reaper trigger, hot-reload\n * .mcp.json restart, channel-set restart) records a restart event. When\n * `> AGT_RESTART_BREAKER_MAX` events land inside the sliding\n * `AGT_RESTART_BREAKER_WINDOW_MS` window, the agent's breaker trips:\n * the manager stops respawning until an operator clears the trip (by\n * resuming the agent in the webapp / via the CLI).\n *\n * Pure state, no I/O — the caller (manager-worker.ts) owns serialisation\n * to `manager-state.json` so a manager restart preserves trip state and\n * doesn't accidentally clear a tripped breaker. Tests drive `now()` via\n * the constructor to avoid sleeping in CI.\n */\n\nexport interface RestartBreakerOptions {\n /** Max restart events allowed inside the window; the (max+1)th trips. Default 2 (env: AGT_RESTART_BREAKER_MAX). */\n max?: number;\n /** Sliding window in ms. Default 600_000 = 10 min (env: AGT_RESTART_BREAKER_WINDOW_MS). */\n windowMs?: number;\n /** Injectable clock for tests. */\n now?: () => number;\n}\n\n/** All known restart trigger sources. Adding a new source? Add it here so the trip detail is self-describing. */\nexport type RestartReason =\n | 'day-rollover'\n | 'auth-tuple-change'\n | 'model-change'\n | 'mcp-presence-reaper'\n | 'hot-reload-mcp'\n | 'channel-set-change'\n | 'manual'\n | 'stale-mcp'\n | 'unknown';\n\n/**\n * ENG-5547: decide whether an mcp-presence-reaper restart should count\n * toward the agent-wide circuit breaker.\n *\n * A restart attributable to a SINGLE persistently-failing MCP server is\n * already governed by that server's own ENG-5279 give-up cap (3 cycles →\n * the integration is marked unhealthy via /host/integration-health and\n * dropped from the next .mcp.json), which isolates the one broken tool and\n * lets the agent keep running degraded. Counting it would let the breaker\n * (default max 2) trip on the 3rd restart and pause the ENTIRE agent for one\n * bad integration — racing, and usually beating, the give-up that would have\n * isolated it. That sledgehammer is exactly what auto-paused phil.\n *\n * A restart where MULTIPLE distinct servers are simultaneously missing is a\n * genuine multi-cause storm the per-server cap can't see (the rotating-MCP\n * case ENG-5441 exists for), so it still counts.\n *\n * Returns the {@link RestartReason} to record (the restart counts toward the\n * breaker), or `undefined` to restart WITHOUT counting.\n */\nexport function reaperRestartBreakerReason(\n activeKeys: readonly string[],\n): RestartReason | undefined {\n return activeKeys.length >= 2 ? 'mcp-presence-reaper' : undefined;\n}\n\nexport interface RestartEvent {\n reason: RestartReason;\n at: number;\n}\n\nexport interface TripState {\n trippedAt: number;\n eventsAtTrip: RestartEvent[];\n /** Pre-formatted `status_message` for the agents row + decision-log. */\n statusMessage: string;\n}\n\nexport interface RecordResult {\n /** True on the call that flips the breaker from closed → tripped. */\n tripped: boolean;\n /** Always populated when `tripped` is true. */\n trip?: TripState;\n /** Current count inside the sliding window (post-record). */\n windowCount: number;\n}\n\nconst DEFAULT_MAX = 2;\nconst DEFAULT_WINDOW_MS = 600_000;\n\nexport function readEnvNumber(name: string, fallback: number): number {\n const raw = process.env[name];\n if (!raw) return fallback;\n const parsed = Number(raw);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;\n}\n\n/**\n * Per-agent breaker. One instance per manager, keyed internally by code_name.\n * Held in module-level state in manager-worker.ts so it survives across\n * polls within a worker generation; trip state is mirrored to\n * `manager-state.json` so it survives across worker generations too.\n */\nexport class RestartBreaker {\n private readonly max: number;\n private readonly windowMs: number;\n private readonly now: () => number;\n private readonly events = new Map<string, RestartEvent[]>();\n private readonly trips = new Map<string, TripState>();\n\n constructor(opts: RestartBreakerOptions = {}) {\n this.max = opts.max ?? readEnvNumber('AGT_RESTART_BREAKER_MAX', DEFAULT_MAX);\n this.windowMs = opts.windowMs ?? readEnvNumber('AGT_RESTART_BREAKER_WINDOW_MS', DEFAULT_WINDOW_MS);\n this.now = opts.now ?? Date.now;\n // Reject NaN / Infinity explicitly — `??` treats them as present\n // values, so without these guards an operator who sets\n // `AGT_RESTART_BREAKER_MAX=NaN` (or a programmatic caller passing\n // `Number.NaN`) would silently disable the breaker rather than\n // fall through to the default. CodeRabbit on PR #1302.\n if (!Number.isFinite(this.max) || this.max < 1) {\n throw new Error('restart-breaker max must be a finite number >= 1');\n }\n if (!Number.isFinite(this.windowMs) || this.windowMs < 1000) {\n throw new Error('restart-breaker windowMs must be a finite number >= 1000');\n }\n }\n\n /** True if this agent's breaker is currently tripped (manager must skip spawn). */\n isTripped(codeName: string): boolean {\n return this.trips.has(codeName);\n }\n\n getTrip(codeName: string): TripState | undefined {\n return this.trips.get(codeName);\n }\n\n /**\n * Record a restart event. If recording this event puts the count\n * inside the window above `max`, the breaker trips and the call site\n * should NOT respawn.\n *\n * Idempotent on already-tripped breakers: returns the existing trip\n * without double-counting events. Callers may still record the reason\n * via the decision-log for forensics.\n */\n record(codeName: string, reason: RestartReason): RecordResult {\n const existing = this.trips.get(codeName);\n if (existing) {\n return { tripped: false, trip: existing, windowCount: existing.eventsAtTrip.length };\n }\n\n const at = this.now();\n const cutoff = at - this.windowMs;\n const prior = (this.events.get(codeName) ?? []).filter((e) => e.at >= cutoff);\n prior.push({ reason, at });\n this.events.set(codeName, prior);\n\n if (prior.length > this.max) {\n const trip: TripState = {\n trippedAt: at,\n eventsAtTrip: [...prior],\n statusMessage: formatStatusMessage(prior, this.windowMs),\n };\n this.trips.set(codeName, trip);\n // Free the events log — the trip carries the snapshot from now on.\n this.events.delete(codeName);\n return { tripped: true, trip, windowCount: prior.length };\n }\n\n return { tripped: false, windowCount: prior.length };\n }\n\n /** Operator-initiated reset: drops the trip + the events log for this agent. */\n clear(codeName: string): void {\n this.trips.delete(codeName);\n this.events.delete(codeName);\n }\n\n /** Snapshot tripped agents for `manager-state.json`. */\n serialize(): Record<string, TripState> {\n return Object.fromEntries(this.trips.entries());\n }\n\n /**\n * Rehydrate trip state from `manager-state.json`. Called once at\n * worker startup. Window history is intentionally NOT persisted — only\n * tripped state. A tripped breaker survives manager restart; an\n * un-tripped one starts a fresh window in the new worker.\n */\n hydrate(saved: Record<string, TripState> | null | undefined): void {\n if (!saved) return;\n for (const [codeName, trip] of Object.entries(saved)) {\n if (trip && typeof trip.trippedAt === 'number' && Array.isArray(trip.eventsAtTrip)) {\n this.trips.set(codeName, trip);\n }\n }\n }\n\n /** Test helper — current in-window event count for `codeName`. */\n windowCount(codeName: string): number {\n const cutoff = this.now() - this.windowMs;\n return (this.events.get(codeName) ?? []).filter((e) => e.at >= cutoff).length;\n }\n}\n\nfunction formatStatusMessage(events: RestartEvent[], windowMs: number): string {\n const last = events[events.length - 1]!;\n // Render the window in seconds for sub-minute thresholds (operator-\n // tuned via AGT_RESTART_BREAKER_WINDOW_MS) and in decimal minutes\n // otherwise. Math.round(/60_000) was returning \"0min\" for a 30s\n // tuning, which is unhelpful in the dashboard. CodeRabbit on PR #1302.\n const windowLabel = windowMs < 60_000\n ? `${Math.round(windowMs / 1000)}s`\n : `${(windowMs / 60_000).toFixed(1).replace(/\\.0$/, '')}min`;\n const reasonCounts = new Map<RestartReason, number>();\n for (const e of events) reasonCounts.set(e.reason, (reasonCounts.get(e.reason) ?? 0) + 1);\n const breakdown = Array.from(reasonCounts.entries())\n .map(([r, n]) => `${r}=${n}`)\n .join(', ');\n return (\n `Circuit breaker tripped: ${events.length} restarts in ${windowLabel} ` +\n `(${breakdown}); most recent=${last.reason} at ${new Date(last.at).toISOString()}`\n );\n}\n","// ENG-5635: decide whether a primary_model change should trigger an immediate\n// session respawn.\n//\n// Background: subscription Claude Code agents only pick up their model via the\n// `claude --model <alias>` flag the launcher passes at spawn time (ENG-5631).\n// Changing primary_model rewrites provision/settings.json and the manager's\n// in-memory knownModels map, but does NOT restart the running session — so the\n// new model is invisible until the next natural respawn (daily rotation,\n// channel/MCP/auth change, crash, or manual restart). This predicate gates a\n// proactive hot-reload respawn so the operator's choice applies within a poll.\n//\n// Kept as a pure function so the gating logic is unit-testable without the\n// manager-worker module graph.\n\nexport interface ModelChangeRespawnInput {\n /** The model the manager last saw for this agent (knownModels). Undefined/null on first provision. */\n previousModel: string | null | undefined;\n /** The freshly-resolved model for this poll. */\n primaryModel: string | null | undefined;\n /** The agent's framework id (e.g. 'claude-code', 'openclaw', 'nemoclaw'). */\n framework: string;\n /** Whether a healthy persistent session is currently running for this agent. */\n sessionHealthy: boolean;\n}\n\n/**\n * True when the model change warrants restarting the live session now.\n *\n * Returns false (let the next natural spawn carry the new model) when:\n * - the framework isn't claude-code — `--model` is a Claude Code launch flag;\n * other runtimes apply model changes through their own config/gateway path;\n * - no healthy session is running — the upcoming spawn already reads the new\n * model, so a restart would be pointless (or fight the spawn);\n * - `previousModel` is unset — first provision; the imminent first spawn\n * carries the model, nothing to switch;\n * - `primaryModel` is unset — the model couldn't be resolved this poll;\n * respawning would only drop the agent to the tier default, so leave the\n * live session on its current (known-good) model;\n * - the model is unchanged — no-op, avoids a restart loop.\n */\nexport function shouldRespawnForModelChange(input: ModelChangeRespawnInput): boolean {\n const { previousModel, primaryModel, framework, sessionHealthy } = input;\n if (framework !== 'claude-code') return false;\n if (!sessionHealthy) return false;\n if (!previousModel) return false;\n if (!primaryModel) return false;\n return previousModel !== primaryModel;\n}\n","/**\n * ENG-5641 — host-side connectivity probe executor (manager CLI).\n *\n * Given an installed integration, resolves its probe strategy (shared core\n * resolver) and executes the corresponding read-only probe, returning a\n * ConnectivityProbeOutcome to report up via POST /host/integration-connectivity.\n *\n * Runs in the manager because that is where the agent's real credentials,\n * network egress and live MCP servers are — the only vantage point that can\n * honestly reach all four source types.\n *\n * PURE + INJECTABLE: every side-effecting capability (HTTP, CLI exec, the MCP\n * handshake, the Composio account check) is injected, so dispatch + outcome\n * mapping is unit-testable without a live host. The real implementations of\n * `mcpProbe` (a true MCP `initialize → tools/list` client), `runCli` and\n * `composioProbe` are wired into the poll loop separately and exercised against\n * live hosts — this module owns the routing and the read-only invariant.\n *\n * Returns `null` when there is no probe to run (kind 'unsupported', or an\n * injectable capability the caller didn't supply yet) — callers skip reporting\n * rather than inventing a status.\n */\n\nimport {\n resolveConnectivityProbe,\n probeHttpProvider,\n type ConnectivityProbeOutcome,\n type ToolkitSourceType,\n} from '@augmented/core/integrations';\nimport type { IntegrationAuthType } from '@augmented/core';\n\nexport interface McpProbeTarget {\n /** Server key / name as wired in the agent's .mcp.json. */\n serverKey: string;\n definitionId: string;\n}\n\nexport interface ConnectivityProbeDeps {\n /** Injected fetch for the HTTP-provider probes (defaults to global fetch). */\n fetchImpl?: typeof fetch;\n /** Run a read-only CLI command (e.g. `<bin> --version`); resolves an outcome. */\n runCli?: (binary: string, args: string[]) => Promise<ConnectivityProbeOutcome>;\n /** Real MCP client: initialize → tools/list against the agent's wired server. */\n mcpProbe?: (target: McpProbeTarget) => Promise<ConnectivityProbeOutcome>;\n /** Composio connected-account check (account ACTIVE + bound to the runtime user_id). */\n composioProbe?: (\n definitionId: string,\n credentials: Record<string, unknown>,\n ) => Promise<ConnectivityProbeOutcome>;\n}\n\nexport interface ConnectivityProbeTarget {\n definitionId: string;\n sourceType?: ToolkitSourceType | null;\n authType?: IntegrationAuthType | null;\n /** Decrypted credentials (the manager already holds these from /host/agent-integrations). */\n credentials: Record<string, unknown>;\n /** CLI binary name on PATH, when this is a cli_tool integration. */\n cliBinary?: string;\n /** MCP server key in the agent's .mcp.json, when this is an mcp_server integration. */\n mcpServerKey?: string;\n}\n\n/**\n * Execute the connectivity probe for one integration. Returns the outcome, or\n * `null` when no probe is available / wired (caller should not report a status).\n */\nexport async function executeConnectivityProbe(\n target: ConnectivityProbeTarget,\n deps: ConnectivityProbeDeps = {},\n): Promise<ConnectivityProbeOutcome | null> {\n const descriptor = resolveConnectivityProbe({\n definitionId: target.definitionId,\n sourceType: target.sourceType,\n authType: target.authType,\n });\n\n // The resolver only ever returns read-only strategies; assert it so a future\n // mistake fails loud rather than letting a mutating probe through.\n if (!descriptor.readOnly) {\n throw new Error(`Refusing non-read-only probe for ${target.definitionId}`);\n }\n\n switch (descriptor.kind) {\n case 'http_provider':\n // null → unknown provider; surface as \"no probe\" rather than a fake status.\n return probeHttpProvider(target.definitionId, target.credentials, deps.fetchImpl ?? fetch);\n\n case 'composio_account':\n if (!deps.composioProbe) return null;\n return deps.composioProbe(target.definitionId, target.credentials);\n\n case 'mcp_tools_list':\n if (!deps.mcpProbe) return null;\n return deps.mcpProbe({\n serverKey: target.mcpServerKey ?? target.definitionId,\n definitionId: target.definitionId,\n });\n\n case 'cli_command':\n if (!deps.runCli) return null;\n return deps.runCli(target.cliBinary ?? target.definitionId, descriptor.cliArgs ?? ['--version']);\n\n case 'builtin':\n // Built-in/in-process modules are reachable when installed; a deeper\n // per-module check is a follow-up. Report healthy.\n return { status: 'ok', message: `${target.definitionId}: built-in` };\n\n case 'unsupported':\n default:\n return null;\n }\n}\n","/**\n * ENG-5641 — rolling connectivity-probe orchestration (manager CLI).\n *\n * Given an agent's installed integrations, selects the ones DUE for a probe\n * (last_connectivity_check_at older than the interval, NULLS first), bounds the\n * batch so we don't probe everything at once (the rolling cadence: wake every\n * 5 min, probe each integration ~hourly), runs each probe via the executor, and\n * returns the report batch to POST to /host/integration-connectivity.\n *\n * Pure + injectable (executor, clock, interval, batch cap) so due-selection +\n * batching + report-mapping are unit-testable. The real probe capabilities\n * (fetch / runCli / mcpProbe) and the POST live in the poll-loop wiring.\n */\n\nimport {\n executeConnectivityProbe as defaultExecuteProbe,\n type ConnectivityProbeDeps,\n type ConnectivityProbeTarget,\n} from './connectivity-probe-executor.js';\nimport type { ConnectivityProbeOutcome, ToolkitSourceType } from '@augmented/core/integrations';\nimport type { IntegrationAuthType } from '@augmented/core';\n\n/** One installed integration as returned (enriched) by /host/agent-integrations. */\nexport interface ProbeRunnerIntegration {\n id: string;\n definition_id: string;\n scope: 'agent' | 'team' | 'organization';\n auth_type?: IntegrationAuthType | null;\n source_type?: ToolkitSourceType | null;\n credentials?: Record<string, unknown>;\n last_connectivity_check_at?: string | null;\n /** CLI binary name (when this is a cli_tool integration). */\n cli_binary?: string;\n /** MCP server key in the agent's .mcp.json (when this is an mcp_server integration). */\n mcp_server_key?: string;\n}\n\nexport interface ConnectivityReport {\n integration_id: string;\n scope: 'agent' | 'team' | 'organization';\n status: ConnectivityProbeOutcome['status'];\n message?: string;\n}\n\nexport interface ProbeRunnerOptions {\n now?: () => Date;\n /** A row is due when its last check is older than this (default 1h). */\n intervalMs?: number;\n /** Max probes per run — bounds the rolling batch (default 25). */\n maxPerRun?: number;\n /** Probe capabilities forwarded to the executor (fetch / runCli / mcpProbe / composioProbe). */\n probeDeps?: ConnectivityProbeDeps;\n /** Override the executor (tests). */\n executeProbe?: typeof defaultExecuteProbe;\n}\n\nexport interface ProbeRunnerResult {\n /** Reports to POST up (only integrations that produced an outcome). */\n reports: ConnectivityReport[];\n /** How many integrations were due this run. */\n due: number;\n /** How many were actually probed (bounded by maxPerRun). */\n probed: number;\n /** Probed but produced no outcome (unsupported / un-wired capability). */\n skipped: number;\n}\n\nconst DEFAULT_INTERVAL_MS = 60 * 60 * 1000; // 1 hour\nconst DEFAULT_MAX_PER_RUN = 25;\n\nfunction isDue(lastCheck: string | null | undefined, now: number, intervalMs: number): boolean {\n if (!lastCheck) return true; // never probed\n const t = Date.parse(lastCheck);\n if (Number.isNaN(t)) return true; // unparseable → treat as due\n return now - t >= intervalMs;\n}\n\nexport async function runConnectivityProbes(\n integrations: ProbeRunnerIntegration[],\n options: ProbeRunnerOptions = {},\n): Promise<ProbeRunnerResult> {\n const now = (options.now?.() ?? new Date()).getTime();\n const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;\n const maxPerRun = options.maxPerRun ?? DEFAULT_MAX_PER_RUN;\n const probeDeps = options.probeDeps ?? {};\n const execute = options.executeProbe ?? defaultExecuteProbe;\n\n // Due rows, oldest-first (NULLS first) so the rolling cursor clears the\n // backlog fairly and load spreads across ticks.\n const due = integrations\n .filter((i) => isDue(i.last_connectivity_check_at, now, intervalMs))\n .sort((a, b) => {\n const ta = a.last_connectivity_check_at ? Date.parse(a.last_connectivity_check_at) : 0;\n const tb = b.last_connectivity_check_at ? Date.parse(b.last_connectivity_check_at) : 0;\n return ta - tb;\n });\n\n const batch = due.slice(0, maxPerRun);\n const reports: ConnectivityReport[] = [];\n let skipped = 0;\n\n for (const integ of batch) {\n const target: ConnectivityProbeTarget = {\n definitionId: integ.definition_id,\n sourceType: integ.source_type ?? null,\n authType: integ.auth_type ?? null,\n credentials: integ.credentials ?? {},\n cliBinary: integ.cli_binary,\n mcpServerKey: integ.mcp_server_key,\n };\n let outcome: ConnectivityProbeOutcome | null;\n try {\n outcome = await execute(target, probeDeps);\n } catch (err) {\n // A probe blowing up must not fail the batch — record nothing for it.\n console.warn(`[connectivity-probe] ${integ.definition_id} threw: ${(err as Error).message}`);\n outcome = null;\n }\n if (!outcome) {\n skipped += 1;\n continue;\n }\n reports.push({\n integration_id: integ.id,\n scope: integ.scope,\n status: outcome.status,\n ...(outcome.message ? { message: outcome.message } : {}),\n });\n }\n\n return { reports, due: due.length, probed: batch.length, skipped };\n}\n","/**\n * ENG-5641 — real MCP connectivity probe client (manager CLI).\n *\n * Does a genuine MCP handshake against the agent's wired streamable-HTTP MCP\n * server — initialize → notifications/initialized → tools/list — and maps the\n * result to a ConnectivityProbeOutcome. tools/list is the cheapest read that\n * proves the server is reachable AND speaking MCP (not just that a TCP port is\n * open). Framing mirrors packages/api/src/routes/mcp-integration-tools.ts.\n *\n * Streamable-HTTP only for now (remote-MCP providers like granola, and HTTP\n * proxies). Stdio MCP servers (spawned from the agent's .mcp.json `command`)\n * are a follow-up — the manager would spawn the process with the agent's env\n * and speak JSON-RPC over stdio; flagged rather than half-built here.\n *\n * `fetch` is injected so the handshake + status mapping is unit-testable. Read-\n * only: initialize + a tools listing; never calls a tool.\n *\n * Status mapping:\n * - 'ok' tools/list returned a result\n * - 'down' 401/403 (auth) or a JSON-RPC error from the server\n * - 'transient_error' 5xx / network / timeout — retryable\n */\n\nimport type { ConnectivityProbeOutcome } from '@augmented/core/integrations';\n\nconst MCP_ACCEPT = 'application/json, text/event-stream';\nconst DEFAULT_TIMEOUT_MS = 10_000;\n\nexport interface McpHttpProbeConfig {\n url: string;\n headers?: Record<string, string>;\n timeoutMs?: number;\n}\n\n/** Extract the JSON-RPC response from a JSON or SSE (text/event-stream) body. */\nasync function parseRpc(res: Response, expectedId: number): Promise<Record<string, unknown> | null> {\n const ct = res.headers.get('content-type') ?? '';\n if (ct.includes('text/event-stream')) {\n const text = await res.text();\n let dataLines: string[] = [];\n for (const rawLine of text.split(/\\r?\\n/)) {\n if (rawLine.startsWith('data:')) {\n dataLines.push(rawLine.slice(5).trimStart());\n continue;\n }\n if (rawLine === '' && dataLines.length > 0) {\n try {\n const msg = JSON.parse(dataLines.join('\\n')) as Record<string, unknown>;\n if (('result' in msg || 'error' in msg) && msg['id'] === expectedId) return msg;\n } catch { /* skip malformed frame */ }\n dataLines = [];\n }\n }\n return null;\n }\n const msg = (await res.json().catch(() => null)) as Record<string, unknown> | null;\n if (msg && ('result' in msg || 'error' in msg) && msg['id'] === expectedId) return msg;\n return msg;\n}\n\nfunction httpStatusOutcome(status: number, step: string): ConnectivityProbeOutcome {\n if (status === 401 || status === 403) {\n return { status: 'down', message: `MCP ${step} unauthorized (${status}) — reconnect required` };\n }\n if (status >= 500) {\n return { status: 'transient_error', message: `MCP ${step} returned ${status}` };\n }\n return { status: 'down', message: `MCP ${step} returned ${status}` };\n}\n\n/**\n * Probe a streamable-HTTP MCP server. Returns the connectivity outcome.\n */\nexport async function probeMcpHttp(\n config: McpHttpProbeConfig,\n fetchImpl: typeof fetch = fetch,\n): Promise<ConnectivityProbeOutcome> {\n const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const baseHeaders: Record<string, string> = {\n ...(config.headers ?? {}),\n 'Content-Type': 'application/json',\n Accept: MCP_ACCEPT,\n };\n\n try {\n // 1. initialize\n const initRes = await fetchImpl(config.url, {\n method: 'POST',\n headers: baseHeaders,\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: 1,\n method: 'initialize',\n params: {\n protocolVersion: '2025-03-26',\n capabilities: {},\n clientInfo: { name: 'augmented-connectivity-probe', version: '1.0.0' },\n },\n }),\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (!initRes.ok) return httpStatusOutcome(initRes.status, 'initialize');\n\n const sessionId = initRes.headers.get('mcp-session-id');\n await parseRpc(initRes, 1);\n const sessionHeaders = { ...baseHeaders, ...(sessionId ? { 'Mcp-Session-Id': sessionId } : {}) };\n\n // 2. notifications/initialized\n const initializedRes = await fetchImpl(config.url, {\n method: 'POST',\n headers: sessionHeaders,\n body: JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' }),\n signal: AbortSignal.timeout(5_000),\n });\n if (!initializedRes.ok) return httpStatusOutcome(initializedRes.status, 'initialized');\n await initializedRes.text().catch(() => '');\n\n // 3. tools/list — the cheap read that proves MCP reachability.\n const listRes = await fetchImpl(config.url, {\n method: 'POST',\n headers: sessionHeaders,\n body: JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'tools/list' }),\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (!listRes.ok) return httpStatusOutcome(listRes.status, 'tools/list');\n\n const rpc = await parseRpc(listRes, 2);\n if (rpc && 'error' in rpc) {\n const err = rpc['error'] as { message?: string } | undefined;\n return { status: 'down', message: `MCP tools/list error: ${err?.message ?? 'unknown'}` };\n }\n const result = rpc?.['result'] as { tools?: unknown[] } | undefined;\n const toolCount = Array.isArray(result?.tools) ? result!.tools!.length : undefined;\n return {\n status: 'ok',\n message: toolCount !== undefined ? `${toolCount} tools` : 'reachable',\n ...(toolCount !== undefined ? { details: { toolCount } } : {}),\n };\n } catch (err) {\n const isAbort = (err as Error)?.name === 'TimeoutError' || (err as Error)?.name === 'AbortError';\n return {\n status: 'transient_error',\n message: isAbort ? `MCP handshake timed out after ${timeoutMs / 1000}s` : `MCP handshake failed: ${(err as Error).message}`,\n };\n }\n}\n","/**\n * ENG-5641 — read-only CLI connectivity probe (manager CLI).\n *\n * Runs a single read-only command (e.g. `<bin> --version`, `gcloud version`)\n * and maps the result to a ConnectivityProbeOutcome. The manager already\n * installs/manages these CLI tools, so this just confirms the binary is on\n * PATH and answers — the cheapest \"is this tool usable\" signal.\n *\n * - exit 0 → 'ok'\n * - ENOENT (missing) → 'down' (not installed / not on PATH)\n * - non-zero exit → 'down' (present but erroring — needs attention)\n * - timeout → 'transient_error'\n *\n * Read-only by contract: callers pass only inspection args (the resolver's\n * cliArgs, defaulting to `--version`). Never runs a mutating subcommand.\n */\n\nimport { execFile } from 'node:child_process';\nimport type { ConnectivityProbeOutcome } from '@augmented/core/integrations';\n\nconst DEFAULT_TIMEOUT_MS = 8_000;\n\nexport interface CliProbeOptions {\n timeoutMs?: number;\n /** Extra env for the child (e.g. the integration's API key var). */\n env?: NodeJS.ProcessEnv;\n}\n\nexport function runCliProbe(\n binary: string,\n args: string[],\n opts: CliProbeOptions = {},\n): Promise<ConnectivityProbeOutcome> {\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n return new Promise((resolve) => {\n execFile(\n binary,\n args,\n { timeout: timeoutMs, env: opts.env ?? process.env, windowsHide: true },\n (err, stdout) => {\n if (!err) {\n const firstLine = String(stdout).split(/\\r?\\n/, 1)[0]?.trim();\n resolve({ status: 'ok', message: firstLine ? `${binary}: ${firstLine}` : `${binary}: ok` });\n return;\n }\n const e = err as NodeJS.ErrnoException & { killed?: boolean; signal?: string };\n if (e.code === 'ENOENT') {\n resolve({ status: 'down', message: `${binary} not found on PATH (not installed)` });\n return;\n }\n if (e.killed || e.signal === 'SIGTERM') {\n resolve({ status: 'transient_error', message: `${binary} probe timed out after ${timeoutMs / 1000}s` });\n return;\n }\n resolve({ status: 'down', message: `${binary} exited non-zero: ${e.message}` });\n },\n );\n });\n}\n","/**\n * ENG-5389: scrape Claude Code's \"X% of your weekly limit · resets <date>\"\n * banner from each Claude Code agent's pane.log and forward to the API as\n * an observation. Wired into the per-agent processing loop in\n * manager-worker.ts; only Claude Code framework agents are checked.\n *\n * \"Only on change\" semantics:\n * - Keep an in-memory last-observed value per code_name (pct + reset date).\n * - Skip the POST when the next banner read matches it.\n * - This trades a tiny startup-blind window (we don't POST the first\n * observation after restart if it matches what the server already has)\n * for a major drop in write volume. The alert path reads the latest\n * observation per agent and re-derives crossing — the missing duplicate\n * write doesn't change the answer.\n *\n * Per-agent file read is rate-limited to MIN_CHECK_INTERVAL_MS so the\n * manager's normal ~10-30s poll doesn't translate into N file reads/sec for\n * large hosts.\n */\n\nimport { parseUsageBanner } from '@augmented/core';\nimport { readPaneLogTail } from './persistent-session.js';\n\nconst MIN_CHECK_INTERVAL_MS = 60_000;\nconst PANE_TAIL_LINES_FOR_BANNER = 200;\n\ninterface MonitorState {\n /** Last value successfully POSTed to the API (or read at startup). */\n lastPct: number | null;\n /** ISO of the lastPct's week_resets_at. */\n lastWeekResetsAt: string | null;\n /** Last time we actually opened pane.log for this code_name. */\n lastCheckedAt: number;\n}\n\nconst state = new Map<string, MonitorState>();\n\n/**\n * Test helper — reset module state. Not for production use.\n */\nexport function _resetUsageBannerMonitorState(): void {\n state.clear();\n}\n\n/**\n * Minimal API surface the monitor needs. Lets the unit test pass a stub\n * without dragging the real api-client transport in.\n */\nexport interface UsageBannerMonitorApi {\n post(path: string, body: unknown): Promise<unknown>;\n}\n\n/**\n * Read this agent's pane.log, parse the weekly-usage banner, and POST an\n * observation if (and only if) it differs from the last value we saw.\n *\n * Errors are swallowed and reported to `log`. This is a soft observability\n * path; an error here must not break the manager poll cycle.\n */\nexport async function maybeReportUsageBanner(args: {\n api: UsageBannerMonitorApi;\n codeName: string;\n agentId: string;\n log: (msg: string) => void;\n now?: Date;\n}): Promise<void> {\n const { api, codeName, agentId, log } = args;\n const now = args.now ?? new Date();\n const nowMs = now.getTime();\n\n const existing = state.get(codeName);\n if (existing && nowMs - existing.lastCheckedAt < MIN_CHECK_INTERVAL_MS) {\n return;\n }\n\n let tail: string | null;\n try {\n tail = readPaneLogTail(codeName, PANE_TAIL_LINES_FOR_BANNER);\n } catch (err) {\n log(`[usage-banner] readPaneLogTail failed for '${codeName}': ${(err as Error).message}`);\n return;\n }\n\n // Always record the check timestamp so a missing banner doesn't make us\n // re-read the file on every tick.\n const next: MonitorState = {\n lastPct: existing?.lastPct ?? null,\n lastWeekResetsAt: existing?.lastWeekResetsAt ?? null,\n lastCheckedAt: nowMs,\n };\n\n if (!tail) {\n state.set(codeName, next);\n return;\n }\n\n const observation = parseUsageBanner(tail, now);\n if (!observation) {\n state.set(codeName, next);\n return;\n }\n\n const observedAtIso = next.lastWeekResetsAt; // alias for clarity below\n const weekResetsAtIso = observation.weekResetsAt.toISOString();\n if (\n existing &&\n existing.lastPct === observation.pct &&\n observedAtIso === weekResetsAtIso\n ) {\n state.set(codeName, next);\n return;\n }\n\n try {\n await api.post('/host/usage-observations', {\n agent_id: agentId,\n observed_pct: observation.pct,\n week_resets_at: weekResetsAtIso,\n source: 'pane_log',\n });\n next.lastPct = observation.pct;\n next.lastWeekResetsAt = weekResetsAtIso;\n state.set(codeName, next);\n } catch (err) {\n log(`[usage-banner] POST /host/usage-observations failed for '${codeName}': ${(err as Error).message}`);\n // Leave the in-memory state at its previous value so the next tick\n // retries the POST. lastCheckedAt is still updated so we don't hammer\n // the API at every tick on persistent failure.\n state.set(codeName, next);\n }\n}\n","/**\n * ENG-5516: per-agent token-usage reporting.\n *\n * For each Claude Code agent, enumerate every session transcript in the\n * agent's project dir (~/.claude/projects/<encoded-cwd>/*.jsonl), parse the\n * cumulative per-(session, model) token totals, and POST them to\n * /host/token-usage where they're upserted. Wired into the per-agent\n * processing loop in manager-worker.ts alongside maybeReportUsageBanner;\n * Claude Code framework only.\n *\n * Why enumerate ALL transcripts (not just the current session):\n * Post-ENG-5397 every spawn mints a fresh session UUID (--session-id, never\n * --resume), so a single day produces N transcript files per agent — one per\n * respawn — plus separate files for scheduled tasks and direct-chat. They\n * all land in the same project dir. Tracking only daily-session.json's\n * current.sessionId would miss every completed-session tail and every\n * ephemeral session. Enumerating the directory captures all of them.\n *\n * Why cumulative upsert (not byte-offset deltas):\n * Each transcript is re-read in full and its CUMULATIVE totals posted. The\n * API upserts on (agent_id, session_id, model), so re-posting an unchanged\n * session is a no-op and a manager restart can't double-count or lose\n * tokens. To avoid re-reading unchanged files every tick, an in-memory\n * {mtimeMs, size} fingerprint per file gates the read — a restart simply\n * re-reads once (the upsert absorbs it).\n *\n * Errors are swallowed and reported to `log`: this is a soft observability\n * path and must never break the manager poll cycle.\n */\n\nimport { readdirSync, readFileSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { isEmptyTotals, parseTranscriptUsage, attributeTranscriptUsageByRun } from '@augmented/core';\nimport { sessionTranscriptDir } from './daily-session.js';\nimport { getProjectDir } from './persistent-session.js';\n\nconst MIN_CHECK_INTERVAL_MS = 60_000;\n// Only consider transcripts touched within this window. Older sessions were\n// already upserted and won't change; skipping them bounds per-tick I/O and\n// keeps a manager restart's re-read pass small.\nconst TRANSCRIPT_MTIME_WINDOW_MS = 2 * 24 * 60 * 60 * 1000; // 2 days\n// Endpoint caps entries per POST (MAX_TOKEN_USAGE_ENTRIES = 500); stay under.\nconst MAX_ENTRIES_PER_POST = 200;\n\ninterface FileFingerprint {\n mtimeMs: number;\n size: number;\n}\n\ninterface MonitorState {\n /** sessionId (filename minus .jsonl) → fingerprint of the last POSTed read. */\n files: Map<string, FileFingerprint>;\n /** Last time we enumerated the transcript dir for this code_name. */\n lastCheckedAt: number;\n}\n\nconst state = new Map<string, MonitorState>();\n\n/** Test helper — reset module state. Not for production use. */\nexport function _resetTokenUsageMonitorState(): void {\n state.clear();\n}\n\n/** Minimal API surface — lets tests stub without the real transport. */\nexport interface TokenUsageMonitorApi {\n post(path: string, body: unknown): Promise<unknown>;\n}\n\ninterface TokenUsageEntry {\n session_id: string;\n model: string;\n input_tokens: number;\n output_tokens: number;\n cache_creation_tokens: number;\n cache_read_tokens: number;\n session_started_at: string | null;\n last_observed_at: string | null;\n}\n\n/**\n * Enumerate the agent's transcripts, build entries for files that changed\n * since we last reported them, POST in batches, and update fingerprints only\n * for files whose POST succeeded.\n */\nexport async function maybeReportTokenUsage(args: {\n api: TokenUsageMonitorApi;\n codeName: string;\n agentId: string;\n log: (msg: string) => void;\n now?: Date;\n /** Test seam: override the transcript directory. */\n transcriptDir?: string;\n}): Promise<void> {\n const { api, codeName, agentId, log } = args;\n const now = args.now ?? new Date();\n const nowMs = now.getTime();\n\n const existing = state.get(codeName);\n if (existing && nowMs - existing.lastCheckedAt < MIN_CHECK_INTERVAL_MS) {\n return;\n }\n\n const dir = args.transcriptDir ?? sessionTranscriptDir(getProjectDir(codeName));\n const files = existing?.files ?? new Map<string, FileFingerprint>();\n const next: MonitorState = { files, lastCheckedAt: nowMs };\n\n let dirEntries: string[];\n try {\n dirEntries = readdirSync(dir);\n } catch {\n // No transcript dir yet (agent hasn't run) — record the check and move on.\n state.set(codeName, next);\n return;\n }\n\n // Collect entries to POST, plus the fingerprint to commit on success.\n const pending: { entry: TokenUsageEntry; sessionId: string; fp: FileFingerprint }[] = [];\n // ENG-5567: per-run attribution rows derived from the SAME transcript reads,\n // POSTed best-effort to /host/run-token-usage. Independent of the token-usage\n // fingerprint commit below (run_token_usage is cumulative + idempotent, so a\n // failed run POST self-heals on the next change).\n const pendingRuns: {\n run_id: string;\n model: string;\n input_tokens: number;\n output_tokens: number;\n cache_creation_tokens: number;\n cache_read_tokens: number;\n last_observed_at: string | null;\n }[] = [];\n\n for (const name of dirEntries) {\n if (!name.endsWith('.jsonl')) continue;\n const sessionId = name.slice(0, -'.jsonl'.length);\n if (!sessionId) continue;\n\n const path = join(dir, name);\n let st;\n try {\n st = statSync(path);\n } catch {\n continue; // raced deletion / permission — skip\n }\n if (!st.isFile()) continue;\n if (nowMs - st.mtimeMs > TRANSCRIPT_MTIME_WINDOW_MS) continue; // stale, already reported\n\n const fp: FileFingerprint = { mtimeMs: st.mtimeMs, size: st.size };\n const seen = files.get(sessionId);\n if (seen && seen.mtimeMs === fp.mtimeMs && seen.size === fp.size) {\n continue; // unchanged since last successful POST\n }\n\n let content: string;\n try {\n content = readFileSync(path, 'utf-8');\n } catch (err) {\n log(`[token-usage] read failed for '${codeName}/${name}': ${(err as Error).message}`);\n continue;\n }\n\n const parsed = parseTranscriptUsage(content);\n for (const [model, totals] of parsed.byModel) {\n if (isEmptyTotals(totals)) continue;\n pending.push({\n sessionId,\n fp,\n entry: {\n session_id: sessionId,\n model,\n input_tokens: totals.inputTokens,\n output_tokens: totals.outputTokens,\n cache_creation_tokens: totals.cacheCreationTokens,\n cache_read_tokens: totals.cacheReadTokens,\n session_started_at: parsed.sessionStartedAt,\n last_observed_at: parsed.lastObservedAt,\n },\n });\n }\n // ENG-5567: per-run attribution from the same content. Only runs with a\n // marker (runId !== null) and non-empty totals are reported; the\n // unattributed bucket stays in the session-level token_usage total.\n for (const r of attributeTranscriptUsageByRun(content).perRunModel) {\n if (r.runId === null || isEmptyTotals(r.totals)) continue;\n pendingRuns.push({\n run_id: r.runId,\n model: r.model,\n input_tokens: r.totals.inputTokens,\n output_tokens: r.totals.outputTokens,\n cache_creation_tokens: r.totals.cacheCreationTokens,\n cache_read_tokens: r.totals.cacheReadTokens,\n last_observed_at: parsed.lastObservedAt,\n });\n }\n\n // Even when a changed file yields no token rows (e.g. only user turns so\n // far), advance its fingerprint so we don't re-read it every tick until\n // it next changes.\n if (![...parsed.byModel.values()].some((t) => !isEmptyTotals(t))) {\n files.set(sessionId, fp);\n }\n }\n\n if (pending.length === 0) {\n state.set(codeName, next);\n return;\n }\n\n // POST in batches; commit fingerprints only for sessions in batches that\n // succeeded, so a failure retries those sessions next tick.\n let reported = 0;\n for (let i = 0; i < pending.length; i += MAX_ENTRIES_PER_POST) {\n const batch = pending.slice(i, i + MAX_ENTRIES_PER_POST);\n try {\n await api.post('/host/token-usage', {\n agent_id: agentId,\n framework: 'claude-code',\n source: 'transcript_jsonl',\n entries: batch.map((b) => b.entry),\n });\n for (const b of batch) {\n files.set(b.sessionId, b.fp);\n }\n reported += batch.length;\n } catch (err) {\n log(`[token-usage] POST failed for '${codeName}': ${(err as Error).message}`);\n // Leave fingerprints unset for this batch's sessions → retry next tick.\n }\n }\n\n // ENG-5567: best-effort per-run usage POST — independent of the token-usage\n // fingerprint commit above (run_token_usage is cumulative + idempotent, so a\n // failed POST self-heals on the next transcript change). Same batch size.\n for (let i = 0; i < pendingRuns.length; i += MAX_ENTRIES_PER_POST) {\n const batch = pendingRuns.slice(i, i + MAX_ENTRIES_PER_POST);\n try {\n await api.post('/host/run-token-usage', {\n agent_id: agentId,\n framework: 'claude-code',\n source: 'transcript_jsonl',\n entries: batch,\n });\n } catch (err) {\n log(`[token-usage] run-usage POST failed for '${codeName}': ${(err as Error).message}`);\n }\n }\n\n // ENG-5533: emit one success line per flush so a rollout is observable in\n // manager.log without DB access. After the ENG-5516 ship this path stayed\n // silent for 24h because /host/agents never sent `framework`, so the\n // caller's `framework === 'claude-code'` guard meant we never reached here\n // — and since every error path also logs, the *absence* of any\n // [token-usage] line was the only (easily-missed) symptom. Log only when we\n // actually POSTed, not on the every-tick no-op return, to stay quiet.\n if (reported > 0) {\n log(`[token-usage] reported ${reported} entr${reported === 1 ? 'y' : 'ies'} for '${codeName}'`);\n }\n\n state.set(codeName, next);\n}\n","/**\n * ENG-5445: read Claude Code's `~/.claude/stats-cache.json` and forward\n * per-day activity counts to the API. Complements the usage-banner\n * monitor (ENG-5389) — the passive banner only renders at the weekly-\n * limit threshold (~75%+), so quietly-busy hosts under that bar are\n * invisible to the banner path. stats-cache.json is host-wide and\n * captures every session on the machine, giving us volume signal\n * regardless of usage percentage.\n *\n * Host-scoped, not agent-scoped: stats-cache.json aggregates across\n * every Claude Code session on the host, so this module runs once per\n * refresh cycle (not per-agent). The companion table is\n * `claude_code_activity_observations` (ENG-5446), keyed on\n * (host_id, date).\n *\n * \"Only new days\" semantics:\n * - Keep a single in-memory `lastObservedDate` for the host.\n * - On each call, read the cache file, and POST any `dailyActivity`\n * row whose `date` is strictly greater than `lastObservedDate`.\n * - The endpoint's upsert-on-(host_id, date) makes the contract\n * idempotent — re-posting the same day is harmless — but the\n * in-memory gate keeps write volume low at steady state.\n *\n * Rate-limited to MIN_CHECK_INTERVAL_MS so a fast manager poll doesn't\n * translate into N file reads per second on the host.\n *\n * Errors are soft. A missing / unreadable cache, a parser failure, or\n * a 4xx/5xx from the API must NOT break the manager poll cycle — log\n * and move on. The schema-level uniqueness guarantees we'll catch up\n * the next time the file is readable.\n *\n * Probe-feasibility note for the spec-curious reader: the ENG-5443\n * spike attempted a dedicated `/usage` probe session to capture the\n * always-on percentage (vs ENG-5389's threshold-gated banner). That\n * primary path remains unproven — `tmux send-keys` is dropped by the\n * TUI (\"paste-not-submitting\"), `claude -p \"/usage\"` doesn't expand\n * slash commands, and the positional-arg path is blocked by the\n * workspace-trust dialog. This module ships the spike's secondary\n * path (stats-cache.json) on its own.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nconst MIN_CHECK_INTERVAL_MS = 60_000;\nconst STATS_CACHE_PATH = join(homedir(), '.claude', 'stats-cache.json');\nconst ISO_DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/**\n * Shape of `~/.claude/stats-cache.json` that this module cares about.\n * Claude Code includes more fields (totalMessages, modelUsage,\n * hourCounts, etc.); we read only what we forward.\n */\ninterface StatsCacheFile {\n dailyActivity?: Array<{\n date?: unknown;\n messageCount?: unknown;\n sessionCount?: unknown;\n toolCallCount?: unknown;\n }>;\n dailyModelTokens?: Record<string, Record<string, unknown>>;\n lastComputedDate?: unknown;\n}\n\ninterface MonitorState {\n /** Last YYYY-MM-DD we posted (or null if never). */\n lastObservedDate: string | null;\n /** Last time we actually opened the cache file. */\n lastCheckedAt: number;\n}\n\nconst state: MonitorState = { lastObservedDate: null, lastCheckedAt: 0 };\n\n/** Test helper — reset module state. Not for production use. */\nexport function _resetActivityCacheMonitorState(): void {\n state.lastObservedDate = null;\n state.lastCheckedAt = 0;\n}\n\n/** Minimal API surface — lets tests stub a transport. */\nexport interface ActivityCacheMonitorApi {\n post(path: string, body: unknown): Promise<unknown>;\n}\n\n/**\n * Pure helper for tests. Parses the raw file contents and returns the\n * sorted-ascending list of day-rows we'd POST given `lastObservedDate`.\n * Tolerates missing / malformed fields per the file's documented\n * lazy-update behaviour.\n */\nexport function selectNewDailyRows(\n raw: string,\n lastObservedDate: string | null,\n): Array<{\n date: string;\n message_count: number;\n session_count: number;\n tool_call_count: number;\n model_breakdown: Record<string, unknown> | null;\n}> {\n let parsed: StatsCacheFile;\n try {\n parsed = JSON.parse(raw) as StatsCacheFile;\n } catch {\n return [];\n }\n const days = Array.isArray(parsed.dailyActivity) ? parsed.dailyActivity : [];\n\n const nonNegInt = (v: unknown): number | null => {\n if (typeof v !== 'number' || !Number.isFinite(v)) return null;\n const n = Math.trunc(v);\n return n >= 0 ? n : null;\n };\n\n const result: Array<{\n date: string;\n message_count: number;\n session_count: number;\n tool_call_count: number;\n model_breakdown: Record<string, unknown> | null;\n }> = [];\n\n for (const d of days) {\n if (typeof d.date !== 'string' || !ISO_DATE_RE.test(d.date)) continue;\n if (lastObservedDate !== null && d.date <= lastObservedDate) continue;\n const m = nonNegInt(d.messageCount);\n const s = nonNegInt(d.sessionCount);\n const t = nonNegInt(d.toolCallCount);\n if (m === null || s === null || t === null) continue;\n\n // Per-day model breakdown lives in a sibling field (`dailyModelTokens`),\n // keyed by date string. Pass it through verbatim when present; the API\n // accepts `model_breakdown` as an arbitrary jsonb object.\n const breakdownForDay = parsed.dailyModelTokens?.[d.date];\n const model_breakdown =\n breakdownForDay && typeof breakdownForDay === 'object' && !Array.isArray(breakdownForDay)\n ? (breakdownForDay as Record<string, unknown>)\n : null;\n\n result.push({\n date: d.date,\n message_count: m,\n session_count: s,\n tool_call_count: t,\n model_breakdown,\n });\n }\n\n // Ascending by date so the in-memory `lastObservedDate` advances\n // monotonically even if the API call for an earlier day fails.\n result.sort((a, b) => a.date.localeCompare(b.date));\n return result;\n}\n\n/**\n * Read the stats cache, POST any day-rows newer than the in-memory\n * `lastObservedDate`, advance the marker on successful writes.\n *\n * Soft on every error path — this is observability, not the critical\n * path. A persistent failure shows up as stale rows in\n * `claude_code_activity_observations` (which the dashboard can flag),\n * not as a manager outage.\n */\nexport async function maybeReportActivityCache(args: {\n api: ActivityCacheMonitorApi;\n log: (msg: string) => void;\n now?: Date;\n}): Promise<void> {\n const { api, log } = args;\n const now = args.now ?? new Date();\n const nowMs = now.getTime();\n\n if (nowMs - state.lastCheckedAt < MIN_CHECK_INTERVAL_MS) return;\n state.lastCheckedAt = nowMs;\n\n if (!existsSync(STATS_CACHE_PATH)) {\n // Expected on hosts that have never run Claude Code (e.g. brand-new\n // managers waiting on first agent spawn). Quietly bail.\n return;\n }\n\n let raw: string;\n try {\n raw = readFileSync(STATS_CACHE_PATH, 'utf-8');\n } catch (err) {\n log(`[activity-cache] readFileSync failed: ${(err as Error).message}`);\n return;\n }\n\n const rows = selectNewDailyRows(raw, state.lastObservedDate);\n if (rows.length === 0) return;\n\n for (const row of rows) {\n try {\n await api.post('/host/activity-observations', row);\n // Advance the marker only when the POST landed. If a later row\n // in the same batch fails, retry-on-next-tick covers it.\n state.lastObservedDate = row.date;\n } catch (err) {\n log(\n `[activity-cache] POST /host/activity-observations failed for date=${row.date}: ${(err as Error).message}`,\n );\n // Don't advance the marker — next tick retries this row + the\n // ones after it. Stop the batch so we don't paper over a\n // systemic failure with a flood of misordered writes.\n return;\n }\n }\n}\n","/**\n * ENG-5041: pure helpers for the manager's poll-cycle backoff. Extracted\n * from manager-worker.ts so the math is testable without standing up the\n * whole module (which has hundreds of side-effecting imports).\n */\n\nexport const POLL_BACKOFF_BASE_MS = 1_000;\nexport const POLL_BACKOFF_MAX_MS = 60_000;\n\n/**\n * Compute the next inter-poll delay given how many consecutive failures\n * we've already seen. Doubles per failure (1s → 2s → 4s → 8s → ...),\n * capped at POLL_BACKOFF_MAX_MS. Returns 0 when no failures, so the\n * caller defaults to the normal interval.\n */\nexport function currentPollBackoffMs(consecutivePollFailures: number): number {\n if (consecutivePollFailures <= 0) return 0;\n const expDelay = POLL_BACKOFF_BASE_MS * Math.pow(2, consecutivePollFailures - 1);\n return Math.min(POLL_BACKOFF_MAX_MS, expDelay);\n}\n\n/**\n * Bucket the error message into a stable class string for logging. The\n * point is operators can grep \"[poll-backoff] class=5xx\" and correlate\n * with API/CW logs without reading the full Error.message.\n *\n * Precedence (CR on PR #982): check 5xx markers BEFORE auth. The CLI's\n * exchange wrapper produces strings like \"exchange failed: Internal\n * Server Error\", so a naive auth-first match would bury a server-side\n * outage under an 'auth' label — exactly the wrong signal during the\n * 2026-05-12 prod-down. Lowercase first so substring/regex checks\n * don't have to spell every casing the upstream could use.\n */\nexport function classifyPollError(message: string): 'auth' | '5xx' | 'network' | 'other' {\n const msg = message.toLowerCase();\n if (\n msg.includes('api unreachable') ||\n /\\b5\\d\\d\\b/.test(msg) ||\n msg.includes('internal server error') ||\n msg.includes('bad gateway')\n ) return '5xx';\n if (\n msg.includes('exchange failed') ||\n /\\b401\\b/.test(msg) ||\n msg.includes('invalid token')\n ) return 'auth';\n if (\n msg.includes('econn') ||\n msg.includes('etimedout') ||\n msg.includes('fetch failed')\n ) return 'network';\n return 'other';\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 * ENG-5116 — track consecutive ticks where an agent's persistent session\n * is unhealthy AND we didn't attempt a respawn this tick.\n *\n * Vigil's incident: day-rollover stopped the tmux session at 00:02, then\n * for >6 hours the manager polled every ~30s but the spawn branch never\n * fired and never logged why. The bulk-heartbeat update at the API kept\n * `last_heartbeat_at` fresh, so neither the customer nor the cloud\n * heartbeat alarm caught it. This tracker is the local-manager half of\n * the answer: once a session has been unhealthy without a respawn\n * attempt for `threshold` consecutive ticks, log a loud WARN so the\n * operator (or future Ops agent / synthetic prober) can correlate.\n *\n * Pure state held in a Map — no I/O, no timers — so it's safe to unit\n * test and the manager's per-tick code drives it.\n */\n\nexport interface StuckTickInput {\n codeName: string;\n sessionHealthy: boolean;\n spawnAttempted: boolean;\n}\n\nexport interface StuckTickResult {\n /** Number of consecutive ticks the agent has been unhealthy with no spawn. */\n consecutiveTicks: number;\n /** True on the tick we cross the warning threshold (caller logs). */\n shouldWarn: boolean;\n /** True when the counter just reset (caller can log recovery if useful). */\n recovered: boolean;\n}\n\n/**\n * Default WARN threshold: 3 consecutive ticks. At a ~30s poll interval\n * that's ~90s of an agent being silently down, which beats the 6h Vigil\n * outage by several orders of magnitude without being so noisy that\n * single transient blips trigger pages.\n */\nexport const DEFAULT_STUCK_WARN_THRESHOLD = 3;\n\nexport class PersistentSessionStuckTracker {\n private readonly counts = new Map<string, number>();\n private readonly warned = new Set<string>();\n private readonly threshold: number;\n\n constructor(threshold: number = DEFAULT_STUCK_WARN_THRESHOLD) {\n if (threshold < 1) throw new Error('stuck-tracker threshold must be >= 1');\n this.threshold = threshold;\n }\n\n /**\n * Record one tick for one agent. Returns whether the caller should log\n * a stuck-warning this tick. Re-warning only fires when the counter\n * climbs above the threshold *again* after a recovery, so a stuck\n * agent doesn't spam the log every 30s — operators get one WARN per\n * stuck streak.\n */\n record(input: StuckTickInput): StuckTickResult {\n const { codeName, sessionHealthy, spawnAttempted } = input;\n const wasStuck = (this.counts.get(codeName) ?? 0) >= this.threshold;\n\n // Recovery condition: either the session is healthy, OR we made a\n // genuine respawn attempt this tick. Both reset the streak; the\n // attempt path matters because the next tick will observe whether\n // it actually came up.\n if (sessionHealthy || spawnAttempted) {\n this.counts.delete(codeName);\n const wasWarned = this.warned.delete(codeName);\n return {\n consecutiveTicks: 0,\n shouldWarn: false,\n recovered: wasStuck && wasWarned,\n };\n }\n\n const next = (this.counts.get(codeName) ?? 0) + 1;\n this.counts.set(codeName, next);\n\n // Only warn on the first tick that crosses the threshold — set\n // membership in `warned` debounces re-fires until a recovery.\n const crossed = next >= this.threshold && !this.warned.has(codeName);\n if (crossed) this.warned.add(codeName);\n\n return {\n consecutiveTicks: next,\n shouldWarn: crossed,\n recovered: false,\n };\n }\n\n /** Test/debug helper: peek at an agent's current streak. */\n peek(codeName: string): number {\n return this.counts.get(codeName) ?? 0;\n }\n\n /** Test helper: clear all state. */\n reset(): void {\n this.counts.clear();\n this.warned.clear();\n }\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 // ENG-5599: present on the realtime row (postgres_changes sends every\n // column). Lets onTodayItem skip cards a dedicated path already nudged for —\n // e.g. scheduled-task cards driven by routeScheduledTaskViaKanban — so the\n // generic kanban-work trigger doesn't double-nudge them.\n source_type?: string | null;\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\n/**\n * ENG-5614: the *actual* phoenix socket state, distinct from `connected`\n * (our channel-subscription flag set on SUBSCRIBED / cleared on\n * CLOSED·CHANNEL_ERROR·TIMED_OUT). A half-open websocket can leave\n * `connected === true` while the underlying socket is dead — the channel's\n * close callback never fires, so inbound delivery silently stops while\n * `isRealtimeConnected()` still reports healthy. The manager reconciles the\n * two each poll cycle and forces the polling fallback when they diverge.\n * Returns false when there is no client (never started / torn down), which\n * matches `isRealtimeConnected()` so the divergence check stays inert.\n */\nexport function isRealtimeSocketConnected(): boolean {\n try {\n return client?.realtime?.isConnected() ?? false;\n } catch {\n return false;\n }\n}\n\n/**\n * ENG-5614: pure predicate — should the manager force the polling fallback?\n * True ONLY for the half-open divergence: our channel-subscription flag says\n * connected but the underlying socket is actually dead. Extracted as a pure\n * function so the decision is unit-testable without the manager's module\n * state. Note it returns false when both are false (no client) — the normal\n * \"not connected, just poll\" path, not a divergence.\n */\nexport function shouldForcePollFallback(\n channelConnected: boolean,\n socketConnected: boolean,\n): boolean {\n return channelConnected && !socketConnected;\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","statSync","join","homedir","createHash","log","execFileSync","log","execFileSync","state","msg","config","DEFAULT_TIMEOUT_MS","api","log","MIN_CHECK_INTERVAL_MS","state","api","log","state","MIN_CHECK_INTERVAL_MS","readFileSync","join","MIN_CHECK_INTERVAL_MS","state","api","log","state","MIN_CHECK_INTERVAL_MS","readFileSync","consecutivePollFailures","client","homedir","join","execFile","existsSync","readFileSync","join","execFileSync","log","config","envSuffixFor","existsSync","readdirSync","readFileSync","writeFileSync","homedir","join","existsSync","readdirSync","readFileSync","join","config","log","config","log","join","state","readFileSync","createHash","saveChannelHashCache","execFileSync","existsSync","join","homedir","writeFileSync","readFileSync","rmSync","mkdirSync","loadChannelHashCache","saveChannelHashCache","createHash","log","readdirSync","platform","state","trackedFiles","primaryModel","sessionMode","frameworkId","agentFw","statSync","getProjectDir"]}