@overdeck/core 0.49.0 → 0.49.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{build-info-Ck2TzOSQ.js → build-info-Bts-0E0B.js} +3 -3
- package/dist/{build-info-Ck2TzOSQ.js.map → build-info-Bts-0E0B.js.map} +1 -1
- package/dist/build-info-CD0O1MvT.js +2 -0
- package/dist/cli/index.js +4 -4
- package/dist/dashboard/{build-info-DXfOjbg3.js → build-info-DTX8JwVP.js} +3 -3
- package/dist/dashboard/{build-info-DXfOjbg3.js.map → build-info-DTX8JwVP.js.map} +1 -1
- package/dist/dashboard/build-info-PQlh_Rhb.js +2 -0
- package/dist/dashboard/{deacon-CeqVPw0o.js → deacon-BEH-IY0v.js} +2 -2
- package/dist/dashboard/{deacon-CQxv6Qwv.js → deacon-TPYssFYt.js} +8 -8
- package/dist/dashboard/{deacon-CQxv6Qwv.js.map → deacon-TPYssFYt.js.map} +1 -1
- package/dist/dashboard/{deacon-merge-ugZ7jekt.js → deacon-merge-BOGFEV8_.js} +5 -5
- package/dist/dashboard/{deacon-merge-ugZ7jekt.js.map → deacon-merge-BOGFEV8_.js.map} +1 -1
- package/dist/dashboard/{deacon-pending-lifecycle-Cxu6Rezs.js → deacon-pending-lifecycle-DVHjOLje.js} +2 -2
- package/dist/dashboard/{deacon-pending-lifecycle-Cxu6Rezs.js.map → deacon-pending-lifecycle-DVHjOLje.js.map} +1 -1
- package/dist/dashboard/deacon.js +2 -2
- package/dist/dashboard/{deploy-patrol-DxnpZcnl.js → deploy-patrol-BXjlvoV3.js} +2 -2
- package/dist/dashboard/{deploy-patrol-DxnpZcnl.js.map → deploy-patrol-BXjlvoV3.js.map} +1 -1
- package/dist/dashboard/{identity-BnxKo9-z.js → identity-Bwh6VGeL.js} +2 -2
- package/dist/dashboard/{identity-BnxKo9-z.js.map → identity-Bwh6VGeL.js.map} +1 -1
- package/dist/dashboard/{lifecycle-ozA4GZNs.js → lifecycle-B0rL38D3.js} +2 -2
- package/dist/dashboard/{lifecycle-ozA4GZNs.js.map → lifecycle-B0rL38D3.js.map} +1 -1
- package/dist/dashboard/{memory-governor-W8994ASc.js → memory-governor-A4O6vI9d.js} +3 -3
- package/dist/dashboard/{memory-governor-W8994ASc.js.map → memory-governor-A4O6vI9d.js.map} +1 -1
- package/dist/dashboard/{memory-pressure-patrol-D_T_QLFC.js → memory-pressure-patrol-BWlWywmA.js} +2 -2
- package/dist/dashboard/{memory-pressure-patrol-D_T_QLFC.js.map → memory-pressure-patrol-BWlWywmA.js.map} +1 -1
- package/dist/dashboard/{merge-agent-B8YsWRDu.js → merge-agent-BRSz26Oh.js} +1 -1
- package/dist/dashboard/{merge-agent-DxecsTre.js → merge-agent-GWhmnxFk.js} +2 -2
- package/dist/dashboard/{merge-agent-DxecsTre.js.map → merge-agent-GWhmnxFk.js.map} +1 -1
- package/dist/dashboard/merge-ops-DtHx17Vd.js +2 -0
- package/dist/dashboard/{post-merge-lifecycle-worker-DC-i1kqz.js → post-merge-lifecycle-worker-KK1RTKRE.js} +2 -2
- package/dist/dashboard/{post-merge-lifecycle-worker-DC-i1kqz.js.map → post-merge-lifecycle-worker-KK1RTKRE.js.map} +1 -1
- package/dist/dashboard/{resolver-BdHzKhNL.js → resolver-C8rooxoz.js} +2 -2
- package/dist/dashboard/{resolver-BdHzKhNL.js.map → resolver-C8rooxoz.js.map} +1 -1
- package/dist/dashboard/server.js +28 -28
- package/dist/dashboard/{service-0hbSVeHz.js → service-BLb_CyPL.js} +6 -6
- package/dist/dashboard/{service-0hbSVeHz.js.map → service-BLb_CyPL.js.map} +1 -1
- package/dist/dashboard/{specialists-C2ZVszIW.js → specialists-DocAK-7A.js} +5 -5
- package/dist/dashboard/{specialists-C2ZVszIW.js.map → specialists-DocAK-7A.js.map} +1 -1
- package/dist/dashboard/{specialists-DhLEuxVX.js → specialists-xXcJHAyg.js} +1 -1
- package/dist/dashboard/{stall-sweeper-o4-q0vqY.js → stall-sweeper-DOyIfeLR.js} +2 -2
- package/dist/dashboard/{stall-sweeper-o4-q0vqY.js.map → stall-sweeper-DOyIfeLR.js.map} +1 -1
- package/dist/dashboard/{uat-train-fK0FbCx7.js → uat-train-COSy-vEX.js} +1 -1
- package/dist/dashboard/{uat-train-CSOval9z.js → uat-train-DVUNW03P.js} +0 -0
- package/dist/dashboard/{uat-train-CSOval9z.js.map → uat-train-DVUNW03P.js.map} +1 -1
- package/dist/dashboard/{workflows-BnNc1QlT.js → workflows-BYwR2aIN.js} +2 -2
- package/dist/dashboard/{workflows-BnNc1QlT.js.map → workflows-BYwR2aIN.js.map} +1 -1
- package/dist/dashboard/workflows-Cf4LAnhg.js +2 -0
- package/dist/dashboard/{workspaces-CqeaoIK6.js → workspaces-BlpbOPZ7.js} +11 -11
- package/dist/dashboard/{workspaces-CqeaoIK6.js.map → workspaces-BlpbOPZ7.js.map} +1 -1
- package/dist/{lifecycle-Bhu9mEsV.js → lifecycle-BhAciIXh.js} +1 -1
- package/dist/{lifecycle-CMRdm3mR.js → lifecycle-D8m3w850.js} +2 -2
- package/dist/{lifecycle-CMRdm3mR.js.map → lifecycle-D8m3w850.js.map} +1 -1
- package/dist/{merge-agent-DQ7DOXE2.js → merge-agent-CUnIrr8R.js} +2 -2
- package/dist/{merge-agent-DQ7DOXE2.js.map → merge-agent-CUnIrr8R.js.map} +1 -1
- package/dist/{merge-agent-D33x_y_T.js → merge-agent-DwfVId_E.js} +1 -1
- package/package.json +1 -1
- package/dist/build-info-B9bbKbXz.js +0 -2
- package/dist/dashboard/build-info-CrBYH-L2.js +0 -2
- package/dist/dashboard/merge-ops-C9wpFz1G.js +0 -2
- package/dist/dashboard/workflows-C6MkcBR_.js +0 -2
package/dist/dashboard/{memory-pressure-patrol-D_T_QLFC.js → memory-pressure-patrol-BWlWywmA.js}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as emitActivityEntrySync, c as init_activity_logger } from "./activity-logger-BAVLrkR7.js";
|
|
2
2
|
import { _ as logDeaconEventSync, h as init_persistent_logger } from "./agents-CqQJW75N.js";
|
|
3
3
|
import { xn as init_runtime_census, yn as getRuntimeCensus } from "./agent-state-Bb3UUxQm.js";
|
|
4
|
-
import { i as readGovernorWatchReserveBytes, r as readGovernorReserves, t as assessMemoryPressure } from "./memory-governor-
|
|
4
|
+
import { i as readGovernorWatchReserveBytes, r as readGovernorReserves, t as assessMemoryPressure } from "./memory-governor-A4O6vI9d.js";
|
|
5
5
|
import { resolve } from "path";
|
|
6
6
|
import { homedir } from "os";
|
|
7
7
|
import { execFile } from "child_process";
|
|
@@ -271,4 +271,4 @@ function parseOomKills(journalText) {
|
|
|
271
271
|
//#endregion
|
|
272
272
|
export { patrolMemoryPressure };
|
|
273
273
|
|
|
274
|
-
//# sourceMappingURL=memory-pressure-patrol-
|
|
274
|
+
//# sourceMappingURL=memory-pressure-patrol-BWlWywmA.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-pressure-patrol-D_T_QLFC.js","names":[],"sources":["../../src/lib/cloister/memory-pressure-patrol.ts"],"sourcesContent":["/**\n * PAN-3550: Activity-feed signals for memory pressure. Runs every 15 seconds\n * in the deacon child, independent of the 60s patrol, to warn before the kernel acts.\n *\n * Three levels: ok → watch (warn), holding (soft band, warn), shedding (hard band, error).\n * Transition-only emit: one row per level change, never repeats while the level persists.\n * No frontend changes needed — activity.entry already renders in ActivityPanel.\n */\n\nimport { MemoryPressureBand, MemoryVerdict, assessMemoryPressure, readGovernorReserves, readGovernorWatchReserveBytes } from './memory-governor.js';\nimport { RuntimeCensus, getRuntimeCensus } from '../runtime-census.js';\nimport { emitActivityEntrySync, EmitActivityOptions } from '../activity-logger.js';\nimport { logDeaconEventSync } from '../persistent-logger.js';\nimport { execFile } from 'child_process';\nimport { promisify } from 'util';\nimport { homedir } from 'os';\nimport { resolve } from 'path';\n\nexport type MemoryFeedLevel = 'ok' | 'watch' | 'holding' | 'shedding';\n\n/**\n * Pure: fold the governor's band and the WATCH reserve into one feed level.\n * The band already encodes SOFT/HARD hysteresis; WATCH only applies while the\n * governor is still admitting.\n */\nexport function memoryFeedLevel(\n band: MemoryPressureBand,\n availableBytes: number,\n watchBytes: number,\n): MemoryFeedLevel {\n if (band === 'hard') return 'shedding';\n if (band === 'soft') return 'holding';\n return availableBytes < watchBytes ? 'watch' : 'ok';\n}\n\n/**\n * Format bytes as human-readable GiB string, e.g., \"7.2 GiB\"\n */\nfunction formatGib(bytes: number): string {\n const gib = bytes / (1024 ** 3);\n return `${gib.toFixed(1)} GiB`;\n}\n\n/**\n * Format a multi-line memory details string for activity entry.\n */\nfunction buildMemoryDetails(\n availableBytes: number,\n watchBytes: number,\n softBytes: number,\n hardBytes: number,\n recoveryBytes: number,\n): string {\n // For now, just the basic reserve info. We'll add swap and PSI data later in WI-3 and WI-4.\n return [\n `MemAvailable: ${formatGib(availableBytes)}`,\n `Watch reserve: ${formatGib(watchBytes)} | Soft: ${formatGib(softBytes)} | Hard: ${formatGib(hardBytes)} | Recovery: ${formatGib(recoveryBytes)}`,\n `Swap free: unavailable | PSI full avg10: unavailable`,\n ].join('\\n');\n}\n\nexport interface MemoryPressurePatrolDeps {\n assess: () => Promise<MemoryVerdict>;\n readWatchReserveBytes: () => number;\n readSoftReserveBytes: () => number;\n readHardReserveBytes: () => number;\n readRecoveryReserveBytes: () => number;\n census: () => Promise<RuntimeCensus>;\n readNewKernelJournal: () => Promise<string>;\n emit: (entry: EmitActivityOptions) => void;\n}\n\n// Module-level state for transition-only emission\nlet lastLevel: MemoryFeedLevel | null = null;\n\n// Module-level state for OOM canary: disabled if journal reading fails\nlet oomCanaryDisabled = false;\n\n/**\n * WI-4: Read new kernel journal entries via journalctl with cursor-file persistence.\n * On first call (cursor file absent), uses -n 0 to initialize and returns empty string.\n * On ANY error, disables the canary permanently and returns empty string.\n */\nasync function readNewKernelJournal(): Promise<string> {\n if (oomCanaryDisabled) return '';\n\n try {\n const overdeckHome = process.env.OVERDECK_HOME || resolve(homedir(), '.overdeck');\n const cursorFile = resolve(overdeckHome, 'oom-canary.cursor');\n\n // Check if cursor file exists to determine if we should use -n 0\n const fs = await import('fs/promises');\n let cursorExists = false;\n try {\n await fs.stat(cursorFile);\n cursorExists = true;\n } catch {\n // Cursor doesn't exist yet\n }\n\n const execFileAsync = promisify(execFile);\n const args = ['-k', '--cursor-file', cursorFile, '--no-pager', '-o', 'cat'];\n\n // On first run (no cursor), use -n 0 to initialize without replaying\n if (!cursorExists) {\n args.push('-n', '0');\n }\n\n const result = await execFileAsync('journalctl', args, { timeout: 10000 });\n return result.stdout;\n } catch (err) {\n console.warn('[memory-pressure-patrol] Journal reader failed, disabling OOM canary:', err);\n oomCanaryDisabled = true;\n return '';\n }\n}\n\n/**\n * Patrol memory pressure once and emit a transition-only activity entry if the level changed.\n * Returns a human-readable list of actions taken (for deacon log).\n */\nexport async function patrolMemoryPressure(deps: Partial<MemoryPressurePatrolDeps> = {}): Promise<string[]> {\n // Fill in defaults\n const d: MemoryPressurePatrolDeps = {\n assess: deps.assess || (() => assessMemoryPressure()),\n readWatchReserveBytes: deps.readWatchReserveBytes || (() => readGovernorWatchReserveBytes()),\n readSoftReserveBytes: deps.readSoftReserveBytes || (() => readGovernorReserves().softBytes),\n readHardReserveBytes: deps.readHardReserveBytes || (() => readGovernorReserves().hardBytes),\n readRecoveryReserveBytes: deps.readRecoveryReserveBytes || (() => readGovernorReserves().recoveryBytes),\n census: deps.census || (() => getRuntimeCensus()),\n readNewKernelJournal: deps.readNewKernelJournal || readNewKernelJournal,\n emit: deps.emit || emitActivityEntrySync,\n };\n\n const verdict = await d.assess();\n const watchBytes = d.readWatchReserveBytes();\n const softBytes = d.readSoftReserveBytes();\n const hardBytes = d.readHardReserveBytes();\n const recoveryBytes = d.readRecoveryReserveBytes();\n const actions: string[] = [];\n\n // WI-4: Check for new OOM kills in the journal (runs even if level unchanged)\n const journalText = await d.readNewKernelJournal();\n const oomKills = parseOomKills(journalText);\n\n // Emit OOM kills with backpressure: aggregate burst into one event per level\n // to avoid unbounded durable writes when the machine is memory-constrained\n if (oomKills.length > 0) {\n const overdeckKills = oomKills.filter((k) => k.inOverdeckTree);\n const hostKills = oomKills.filter((k) => !k.inOverdeckTree);\n\n // Emit Overdeck-tree kills as error\n if (overdeckKills.length > 0) {\n const plural = overdeckKills.length === 1 ? '' : 's';\n const message =\n overdeckKills.length === 1\n ? `Kernel OOM: Killed process ${overdeckKills[0].pid} (${overdeckKills[0].comm}), ${formatGib(overdeckKills[0].rssBytes)} RSS in Overdeck tmux-server`\n : `Kernel OOM: Killed ${overdeckKills.length} process${plural} in Overdeck tmux-server (${overdeckKills.map((k) => `${k.pid}/${k.comm}`).join(', ')})`;\n\n const details = overdeckKills\n .map((k) => ` PID ${k.pid} (${k.comm}): ${formatGib(k.rssBytes)} RSS | Cgroup: ${k.cgroup || '(unknown)'}`)\n .join('\\n');\n\n d.emit({\n level: 'error',\n source: 'cloister',\n link: '/resources',\n message,\n details: `OOM kills in Overdeck tree:\\n${details}\\n\\n${buildMemoryDetails(verdict.availableBytes, watchBytes, softBytes, hardBytes, recoveryBytes)}`,\n desktop: true,\n });\n\n const action = `memory-pressure-patrol: oom-kills (${overdeckKills.length} in Overdeck tree)`;\n actions.push(action);\n logDeaconEventSync(`[deacon] ${action}`);\n }\n\n // Emit host kills as warn (less urgent)\n if (hostKills.length > 0) {\n const plural = hostKills.length === 1 ? '' : 's';\n const message =\n hostKills.length === 1\n ? `Kernel OOM: Killed process ${hostKills[0].pid} (${hostKills[0].comm}), ${formatGib(hostKills[0].rssBytes)} RSS outside Overdeck`\n : `Kernel OOM: Killed ${hostKills.length} process${plural} outside Overdeck (${hostKills.map((k) => `${k.pid}/${k.comm}`).join(', ')})`;\n\n const details = hostKills\n .map((k) => ` PID ${k.pid} (${k.comm}): ${formatGib(k.rssBytes)} RSS | Cgroup: ${k.cgroup || '(unknown)'}`)\n .join('\\n');\n\n d.emit({\n level: 'warn',\n source: 'cloister',\n link: '/resources',\n message,\n details: `OOM kills outside Overdeck:\\n${details}\\n\\n${buildMemoryDetails(verdict.availableBytes, watchBytes, softBytes, hardBytes, recoveryBytes)}`,\n desktop: false,\n });\n\n const action = `memory-pressure-patrol: oom-kills (${hostKills.length} outside Overdeck)`;\n actions.push(action);\n logDeaconEventSync(`[deacon] ${action}`);\n }\n }\n\n const level = memoryFeedLevel(verdict.band, verdict.availableBytes, watchBytes);\n\n // Transition-only: if level hasn't changed, emit nothing for level transition\n if (level === lastLevel) {\n return oomKills.length > 0 ? actions : [];\n }\n\n lastLevel = level;\n\n // Fetch the runtime census for top-consumer attribution\n const census = await d.census();\n\n // Build details with the base reserves and top consumers\n let details = buildMemoryDetails(\n verdict.availableBytes,\n watchBytes,\n softBytes,\n hardBytes,\n recoveryBytes,\n );\n\n // WI-3: Append top memory consumers to details\n if (!census.processAvailable) {\n details += '\\n\\nTop memory consumers: unavailable (process census could not be read)';\n } else {\n const topConsumers = topMemoryConsumers(census, 5);\n if (topConsumers.length > 0) {\n const consumerLines = topConsumers.map(\n (c) =>\n ` ${formatGib(c.rssBytes)} | pid ${c.pid} (${c.comm}) in session ${c.sessionName || '(host)'}`,\n );\n details += '\\n\\nTop memory consumers:\\n' + consumerLines.join('\\n');\n }\n }\n\n // Build the activity entry based on level\n if (level === 'watch') {\n const message =\n `Memory is getting tight — ${formatGib(verdict.availableBytes)} available, below the ${formatGib(watchBytes)} watch reserve. ` +\n `Overdeck is still admitting new agents; it will stop admitting if available memory falls below the ${formatGib(softBytes)} soft reserve.`;\n\n d.emit({\n level: 'warn',\n source: 'cloister',\n link: '/resources',\n message,\n details,\n desktop: false,\n });\n\n const action = `memory-pressure-patrol: watch-level (${formatGib(verdict.availableBytes)} available)`;\n actions.push(action);\n logDeaconEventSync(`[deacon] ${action}`);\n } else if (level === 'holding') {\n const message =\n `Memory pressure: Overdeck has stopped admitting work — ${formatGib(verdict.availableBytes)} available is under the ${formatGib(softBytes)} soft reserve. ` +\n `Queued agent resumes and review/test dispatches will wait until available memory climbs back above the ${formatGib(recoveryBytes)} recovery reserve. ` +\n `Nothing has been stopped or killed.`;\n\n d.emit({\n level: 'warn',\n source: 'cloister',\n link: '/resources',\n message,\n details,\n desktop: false,\n });\n\n const action = `memory-pressure-patrol: admission-hold (${formatGib(verdict.availableBytes)} available)`;\n actions.push(action);\n logDeaconEventSync(`[deacon] ${action}`);\n } else if (level === 'shedding') {\n const message =\n `Memory critical — ${formatGib(verdict.availableBytes)} available is under the ${formatGib(hardBytes)} hard reserve. ` +\n `Overdeck admits nothing and the kernel may start killing processes. ` +\n `Free memory on this host now; automatic shedding is not wired, so Overdeck will not reclaim anything on its own.`;\n\n d.emit({\n level: 'error',\n source: 'cloister',\n link: '/resources',\n message,\n details,\n desktop: true,\n });\n\n const action = `memory-pressure-patrol: shedding-level (${formatGib(verdict.availableBytes)} available)`;\n actions.push(action);\n logDeaconEventSync(`[deacon] ${action}`);\n } else if (level === 'ok') {\n const message =\n `Memory pressure cleared — ${formatGib(verdict.availableBytes)} available, above the ${formatGib(watchBytes)} watch reserve. ` +\n `Overdeck is admitting work again.`;\n\n d.emit({\n level: 'info',\n source: 'cloister',\n link: '/resources',\n message,\n details,\n desktop: false,\n });\n\n const action = `memory-pressure-patrol: recovered (${formatGib(verdict.availableBytes)} available)`;\n actions.push(action);\n logDeaconEventSync(`[deacon] ${action}`);\n }\n\n return actions;\n}\n\n/**\n * Test-only: reset the module-level state between test cases.\n */\nexport function __resetMemoryPressurePatrolState(): void {\n lastLevel = null;\n oomCanaryDisabled = false;\n}\n\n/**\n * WI-3: Top consumer attribution\n */\nexport interface TopConsumer {\n pid: number;\n rssBytes: number;\n comm: string;\n sessionName: string | null;\n}\n\n/**\n * Pure: the N largest-RSS processes in the census, each attributed to a tmux session.\n */\nexport function topMemoryConsumers(census: RuntimeCensus, limit: number): TopConsumer[] {\n if (!census.processAvailable) return [];\n \n const sessionByPanePid = new Map<number, string>();\n for (const [sessionName, panes] of census.panesBySession) {\n for (const pane of panes) {\n sessionByPanePid.set(pane.panePid, sessionName);\n }\n }\n\n const topProcs = [...census.processesByPid.values()]\n .sort((a, b) => b.rssBytes - a.rssBytes)\n .slice(0, limit);\n\n return topProcs.map((proc) => {\n const visited = new Set<number>();\n let current = proc.pid;\n let sessionName: string | null = null;\n\n while (current > 1 && !visited.has(current)) {\n visited.add(current);\n if (sessionByPanePid.has(current)) {\n sessionName = sessionByPanePid.get(current)!;\n break;\n }\n const parent = census.processesByPid.get(current);\n if (!parent?.ppid) break;\n current = parent.ppid;\n }\n\n return {\n pid: proc.pid,\n rssBytes: proc.rssBytes,\n comm: proc.comm,\n sessionName,\n };\n });\n}\n\n/**\n * WI-4: OOM kill detection\n */\nexport interface OomKillRecord {\n pid: number;\n comm: string;\n rssBytes: number;\n cgroup: string | null;\n inOverdeckTree: boolean;\n}\n\nconst OOM_OVERDECK_TREE_REGEX = /overdeck-tmux-server\\.service|overdeck-/;\n\nexport function parseOomKills(journalText: string): OomKillRecord[] {\n const kills = new Map<number, OomKillRecord>();\n\n const oomkillPattern = /task_memcg=(\\S+?),task=([^,]+),pid=(\\d+)/g;\n let match;\n while ((match = oomkillPattern.exec(journalText)) !== null) {\n const [_, cgroup, comm, pidStr] = match;\n const pid = parseInt(pidStr, 10);\n kills.set(pid, {\n pid,\n comm,\n rssBytes: 0,\n cgroup,\n inOverdeckTree: OOM_OVERDECK_TREE_REGEX.test(cgroup),\n });\n }\n\n const victimPattern = /Out of memory: Killed process (\\d+) \\(([^)]+)\\)(.*?)$/gm;\n while ((match = victimPattern.exec(journalText)) !== null) {\n const [_, pidStr, comm, tail] = match;\n const pid = parseInt(pidStr, 10);\n\n let rssBytes = 0;\n const rssPattern = /(?:anon|file|shmem)-rss:(\\d+)kB/g;\n let rssMatch;\n while ((rssMatch = rssPattern.exec(tail)) !== null) {\n rssBytes += parseInt(rssMatch[1], 10) * 1024;\n }\n\n if (kills.has(pid)) {\n kills.get(pid)!.rssBytes = rssBytes;\n } else {\n kills.set(pid, {\n pid,\n comm,\n rssBytes,\n cgroup: null,\n inOverdeckTree: false,\n });\n }\n }\n\n return Array.from(kills.values());\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"memory-pressure-patrol-BWlWywmA.js","names":[],"sources":["../../src/lib/cloister/memory-pressure-patrol.ts"],"sourcesContent":["/**\n * PAN-3550: Activity-feed signals for memory pressure. Runs every 15 seconds\n * in the deacon child, independent of the 60s patrol, to warn before the kernel acts.\n *\n * Three levels: ok → watch (warn), holding (soft band, warn), shedding (hard band, error).\n * Transition-only emit: one row per level change, never repeats while the level persists.\n * No frontend changes needed — activity.entry already renders in ActivityPanel.\n */\n\nimport { MemoryPressureBand, MemoryVerdict, assessMemoryPressure, readGovernorReserves, readGovernorWatchReserveBytes } from './memory-governor.js';\nimport { RuntimeCensus, getRuntimeCensus } from '../runtime-census.js';\nimport { emitActivityEntrySync, EmitActivityOptions } from '../activity-logger.js';\nimport { logDeaconEventSync } from '../persistent-logger.js';\nimport { execFile } from 'child_process';\nimport { promisify } from 'util';\nimport { homedir } from 'os';\nimport { resolve } from 'path';\n\nexport type MemoryFeedLevel = 'ok' | 'watch' | 'holding' | 'shedding';\n\n/**\n * Pure: fold the governor's band and the WATCH reserve into one feed level.\n * The band already encodes SOFT/HARD hysteresis; WATCH only applies while the\n * governor is still admitting.\n */\nexport function memoryFeedLevel(\n band: MemoryPressureBand,\n availableBytes: number,\n watchBytes: number,\n): MemoryFeedLevel {\n if (band === 'hard') return 'shedding';\n if (band === 'soft') return 'holding';\n return availableBytes < watchBytes ? 'watch' : 'ok';\n}\n\n/**\n * Format bytes as human-readable GiB string, e.g., \"7.2 GiB\"\n */\nfunction formatGib(bytes: number): string {\n const gib = bytes / (1024 ** 3);\n return `${gib.toFixed(1)} GiB`;\n}\n\n/**\n * Format a multi-line memory details string for activity entry.\n */\nfunction buildMemoryDetails(\n availableBytes: number,\n watchBytes: number,\n softBytes: number,\n hardBytes: number,\n recoveryBytes: number,\n): string {\n // For now, just the basic reserve info. We'll add swap and PSI data later in WI-3 and WI-4.\n return [\n `MemAvailable: ${formatGib(availableBytes)}`,\n `Watch reserve: ${formatGib(watchBytes)} | Soft: ${formatGib(softBytes)} | Hard: ${formatGib(hardBytes)} | Recovery: ${formatGib(recoveryBytes)}`,\n `Swap free: unavailable | PSI full avg10: unavailable`,\n ].join('\\n');\n}\n\nexport interface MemoryPressurePatrolDeps {\n assess: () => Promise<MemoryVerdict>;\n readWatchReserveBytes: () => number;\n readSoftReserveBytes: () => number;\n readHardReserveBytes: () => number;\n readRecoveryReserveBytes: () => number;\n census: () => Promise<RuntimeCensus>;\n readNewKernelJournal: () => Promise<string>;\n emit: (entry: EmitActivityOptions) => void;\n}\n\n// Module-level state for transition-only emission\nlet lastLevel: MemoryFeedLevel | null = null;\n\n// Module-level state for OOM canary: disabled if journal reading fails\nlet oomCanaryDisabled = false;\n\n/**\n * WI-4: Read new kernel journal entries via journalctl with cursor-file persistence.\n * On first call (cursor file absent), uses -n 0 to initialize and returns empty string.\n * On ANY error, disables the canary permanently and returns empty string.\n */\nasync function readNewKernelJournal(): Promise<string> {\n if (oomCanaryDisabled) return '';\n\n try {\n const overdeckHome = process.env.OVERDECK_HOME || resolve(homedir(), '.overdeck');\n const cursorFile = resolve(overdeckHome, 'oom-canary.cursor');\n\n // Check if cursor file exists to determine if we should use -n 0\n const fs = await import('fs/promises');\n let cursorExists = false;\n try {\n await fs.stat(cursorFile);\n cursorExists = true;\n } catch {\n // Cursor doesn't exist yet\n }\n\n const execFileAsync = promisify(execFile);\n const args = ['-k', '--cursor-file', cursorFile, '--no-pager', '-o', 'cat'];\n\n // On first run (no cursor), use -n 0 to initialize without replaying\n if (!cursorExists) {\n args.push('-n', '0');\n }\n\n const result = await execFileAsync('journalctl', args, { timeout: 10000 });\n return result.stdout;\n } catch (err) {\n console.warn('[memory-pressure-patrol] Journal reader failed, disabling OOM canary:', err);\n oomCanaryDisabled = true;\n return '';\n }\n}\n\n/**\n * Patrol memory pressure once and emit a transition-only activity entry if the level changed.\n * Returns a human-readable list of actions taken (for deacon log).\n */\nexport async function patrolMemoryPressure(deps: Partial<MemoryPressurePatrolDeps> = {}): Promise<string[]> {\n // Fill in defaults\n const d: MemoryPressurePatrolDeps = {\n assess: deps.assess || (() => assessMemoryPressure()),\n readWatchReserveBytes: deps.readWatchReserveBytes || (() => readGovernorWatchReserveBytes()),\n readSoftReserveBytes: deps.readSoftReserveBytes || (() => readGovernorReserves().softBytes),\n readHardReserveBytes: deps.readHardReserveBytes || (() => readGovernorReserves().hardBytes),\n readRecoveryReserveBytes: deps.readRecoveryReserveBytes || (() => readGovernorReserves().recoveryBytes),\n census: deps.census || (() => getRuntimeCensus()),\n readNewKernelJournal: deps.readNewKernelJournal || readNewKernelJournal,\n emit: deps.emit || emitActivityEntrySync,\n };\n\n const verdict = await d.assess();\n const watchBytes = d.readWatchReserveBytes();\n const softBytes = d.readSoftReserveBytes();\n const hardBytes = d.readHardReserveBytes();\n const recoveryBytes = d.readRecoveryReserveBytes();\n const actions: string[] = [];\n\n // WI-4: Check for new OOM kills in the journal (runs even if level unchanged)\n const journalText = await d.readNewKernelJournal();\n const oomKills = parseOomKills(journalText);\n\n // Emit OOM kills with backpressure: aggregate burst into one event per level\n // to avoid unbounded durable writes when the machine is memory-constrained\n if (oomKills.length > 0) {\n const overdeckKills = oomKills.filter((k) => k.inOverdeckTree);\n const hostKills = oomKills.filter((k) => !k.inOverdeckTree);\n\n // Emit Overdeck-tree kills as error\n if (overdeckKills.length > 0) {\n const plural = overdeckKills.length === 1 ? '' : 's';\n const message =\n overdeckKills.length === 1\n ? `Kernel OOM: Killed process ${overdeckKills[0].pid} (${overdeckKills[0].comm}), ${formatGib(overdeckKills[0].rssBytes)} RSS in Overdeck tmux-server`\n : `Kernel OOM: Killed ${overdeckKills.length} process${plural} in Overdeck tmux-server (${overdeckKills.map((k) => `${k.pid}/${k.comm}`).join(', ')})`;\n\n const details = overdeckKills\n .map((k) => ` PID ${k.pid} (${k.comm}): ${formatGib(k.rssBytes)} RSS | Cgroup: ${k.cgroup || '(unknown)'}`)\n .join('\\n');\n\n d.emit({\n level: 'error',\n source: 'cloister',\n link: '/resources',\n message,\n details: `OOM kills in Overdeck tree:\\n${details}\\n\\n${buildMemoryDetails(verdict.availableBytes, watchBytes, softBytes, hardBytes, recoveryBytes)}`,\n desktop: true,\n });\n\n const action = `memory-pressure-patrol: oom-kills (${overdeckKills.length} in Overdeck tree)`;\n actions.push(action);\n logDeaconEventSync(`[deacon] ${action}`);\n }\n\n // Emit host kills as warn (less urgent)\n if (hostKills.length > 0) {\n const plural = hostKills.length === 1 ? '' : 's';\n const message =\n hostKills.length === 1\n ? `Kernel OOM: Killed process ${hostKills[0].pid} (${hostKills[0].comm}), ${formatGib(hostKills[0].rssBytes)} RSS outside Overdeck`\n : `Kernel OOM: Killed ${hostKills.length} process${plural} outside Overdeck (${hostKills.map((k) => `${k.pid}/${k.comm}`).join(', ')})`;\n\n const details = hostKills\n .map((k) => ` PID ${k.pid} (${k.comm}): ${formatGib(k.rssBytes)} RSS | Cgroup: ${k.cgroup || '(unknown)'}`)\n .join('\\n');\n\n d.emit({\n level: 'warn',\n source: 'cloister',\n link: '/resources',\n message,\n details: `OOM kills outside Overdeck:\\n${details}\\n\\n${buildMemoryDetails(verdict.availableBytes, watchBytes, softBytes, hardBytes, recoveryBytes)}`,\n desktop: false,\n });\n\n const action = `memory-pressure-patrol: oom-kills (${hostKills.length} outside Overdeck)`;\n actions.push(action);\n logDeaconEventSync(`[deacon] ${action}`);\n }\n }\n\n const level = memoryFeedLevel(verdict.band, verdict.availableBytes, watchBytes);\n\n // Transition-only: if level hasn't changed, emit nothing for level transition\n if (level === lastLevel) {\n return oomKills.length > 0 ? actions : [];\n }\n\n lastLevel = level;\n\n // Fetch the runtime census for top-consumer attribution\n const census = await d.census();\n\n // Build details with the base reserves and top consumers\n let details = buildMemoryDetails(\n verdict.availableBytes,\n watchBytes,\n softBytes,\n hardBytes,\n recoveryBytes,\n );\n\n // WI-3: Append top memory consumers to details\n if (!census.processAvailable) {\n details += '\\n\\nTop memory consumers: unavailable (process census could not be read)';\n } else {\n const topConsumers = topMemoryConsumers(census, 5);\n if (topConsumers.length > 0) {\n const consumerLines = topConsumers.map(\n (c) =>\n ` ${formatGib(c.rssBytes)} | pid ${c.pid} (${c.comm}) in session ${c.sessionName || '(host)'}`,\n );\n details += '\\n\\nTop memory consumers:\\n' + consumerLines.join('\\n');\n }\n }\n\n // Build the activity entry based on level\n if (level === 'watch') {\n const message =\n `Memory is getting tight — ${formatGib(verdict.availableBytes)} available, below the ${formatGib(watchBytes)} watch reserve. ` +\n `Overdeck is still admitting new agents; it will stop admitting if available memory falls below the ${formatGib(softBytes)} soft reserve.`;\n\n d.emit({\n level: 'warn',\n source: 'cloister',\n link: '/resources',\n message,\n details,\n desktop: false,\n });\n\n const action = `memory-pressure-patrol: watch-level (${formatGib(verdict.availableBytes)} available)`;\n actions.push(action);\n logDeaconEventSync(`[deacon] ${action}`);\n } else if (level === 'holding') {\n const message =\n `Memory pressure: Overdeck has stopped admitting work — ${formatGib(verdict.availableBytes)} available is under the ${formatGib(softBytes)} soft reserve. ` +\n `Queued agent resumes and review/test dispatches will wait until available memory climbs back above the ${formatGib(recoveryBytes)} recovery reserve. ` +\n `Nothing has been stopped or killed.`;\n\n d.emit({\n level: 'warn',\n source: 'cloister',\n link: '/resources',\n message,\n details,\n desktop: false,\n });\n\n const action = `memory-pressure-patrol: admission-hold (${formatGib(verdict.availableBytes)} available)`;\n actions.push(action);\n logDeaconEventSync(`[deacon] ${action}`);\n } else if (level === 'shedding') {\n const message =\n `Memory critical — ${formatGib(verdict.availableBytes)} available is under the ${formatGib(hardBytes)} hard reserve. ` +\n `Overdeck admits nothing and the kernel may start killing processes. ` +\n `Free memory on this host now; automatic shedding is not wired, so Overdeck will not reclaim anything on its own.`;\n\n d.emit({\n level: 'error',\n source: 'cloister',\n link: '/resources',\n message,\n details,\n desktop: true,\n });\n\n const action = `memory-pressure-patrol: shedding-level (${formatGib(verdict.availableBytes)} available)`;\n actions.push(action);\n logDeaconEventSync(`[deacon] ${action}`);\n } else if (level === 'ok') {\n const message =\n `Memory pressure cleared — ${formatGib(verdict.availableBytes)} available, above the ${formatGib(watchBytes)} watch reserve. ` +\n `Overdeck is admitting work again.`;\n\n d.emit({\n level: 'info',\n source: 'cloister',\n link: '/resources',\n message,\n details,\n desktop: false,\n });\n\n const action = `memory-pressure-patrol: recovered (${formatGib(verdict.availableBytes)} available)`;\n actions.push(action);\n logDeaconEventSync(`[deacon] ${action}`);\n }\n\n return actions;\n}\n\n/**\n * Test-only: reset the module-level state between test cases.\n */\nexport function __resetMemoryPressurePatrolState(): void {\n lastLevel = null;\n oomCanaryDisabled = false;\n}\n\n/**\n * WI-3: Top consumer attribution\n */\nexport interface TopConsumer {\n pid: number;\n rssBytes: number;\n comm: string;\n sessionName: string | null;\n}\n\n/**\n * Pure: the N largest-RSS processes in the census, each attributed to a tmux session.\n */\nexport function topMemoryConsumers(census: RuntimeCensus, limit: number): TopConsumer[] {\n if (!census.processAvailable) return [];\n \n const sessionByPanePid = new Map<number, string>();\n for (const [sessionName, panes] of census.panesBySession) {\n for (const pane of panes) {\n sessionByPanePid.set(pane.panePid, sessionName);\n }\n }\n\n const topProcs = [...census.processesByPid.values()]\n .sort((a, b) => b.rssBytes - a.rssBytes)\n .slice(0, limit);\n\n return topProcs.map((proc) => {\n const visited = new Set<number>();\n let current = proc.pid;\n let sessionName: string | null = null;\n\n while (current > 1 && !visited.has(current)) {\n visited.add(current);\n if (sessionByPanePid.has(current)) {\n sessionName = sessionByPanePid.get(current)!;\n break;\n }\n const parent = census.processesByPid.get(current);\n if (!parent?.ppid) break;\n current = parent.ppid;\n }\n\n return {\n pid: proc.pid,\n rssBytes: proc.rssBytes,\n comm: proc.comm,\n sessionName,\n };\n });\n}\n\n/**\n * WI-4: OOM kill detection\n */\nexport interface OomKillRecord {\n pid: number;\n comm: string;\n rssBytes: number;\n cgroup: string | null;\n inOverdeckTree: boolean;\n}\n\nconst OOM_OVERDECK_TREE_REGEX = /overdeck-tmux-server\\.service|overdeck-/;\n\nexport function parseOomKills(journalText: string): OomKillRecord[] {\n const kills = new Map<number, OomKillRecord>();\n\n const oomkillPattern = /task_memcg=(\\S+?),task=([^,]+),pid=(\\d+)/g;\n let match;\n while ((match = oomkillPattern.exec(journalText)) !== null) {\n const [_, cgroup, comm, pidStr] = match;\n const pid = parseInt(pidStr, 10);\n kills.set(pid, {\n pid,\n comm,\n rssBytes: 0,\n cgroup,\n inOverdeckTree: OOM_OVERDECK_TREE_REGEX.test(cgroup),\n });\n }\n\n const victimPattern = /Out of memory: Killed process (\\d+) \\(([^)]+)\\)(.*?)$/gm;\n while ((match = victimPattern.exec(journalText)) !== null) {\n const [_, pidStr, comm, tail] = match;\n const pid = parseInt(pidStr, 10);\n\n let rssBytes = 0;\n const rssPattern = /(?:anon|file|shmem)-rss:(\\d+)kB/g;\n let rssMatch;\n while ((rssMatch = rssPattern.exec(tail)) !== null) {\n rssBytes += parseInt(rssMatch[1], 10) * 1024;\n }\n\n if (kills.has(pid)) {\n kills.get(pid)!.rssBytes = rssBytes;\n } else {\n kills.set(pid, {\n pid,\n comm,\n rssBytes,\n cgroup: null,\n inOverdeckTree: false,\n });\n }\n }\n\n return Array.from(kills.values());\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { i as resetPostMergeState } from "./post-merge-state-_Lj3LDLh.js";
|
|
2
|
-
import { _ as syncMainIntoWorkspace, a as isAutoCommitExcludedPath, c as logMergeHistory, d as postMergeLifecycle, f as runProjectQualityGates, g as sendMessageToAgent, h as scanGitPatterns, i as captureTmuxOutput, l as notifyTldrDaemon, m as scanForConflictMarkers, n as GIT_PATTERNS, o as isMergeAgentRunning, p as salvageStrandedMerge, r as autoCommitWorkspaceChangesBeforeSync, s as isSyncMainMainPreferredPath, t as AUTO_COMMIT_EXCLUDED_PATHS, u as parseAgentOutput, v as triggerPostMergeReleaseIfConfigured } from "./merge-agent-
|
|
2
|
+
import { _ as syncMainIntoWorkspace, a as isAutoCommitExcludedPath, c as logMergeHistory, d as postMergeLifecycle, f as runProjectQualityGates, g as sendMessageToAgent, h as scanGitPatterns, i as captureTmuxOutput, l as notifyTldrDaemon, m as scanForConflictMarkers, n as GIT_PATTERNS, o as isMergeAgentRunning, p as salvageStrandedMerge, r as autoCommitWorkspaceChangesBeforeSync, s as isSyncMainMainPreferredPath, t as AUTO_COMMIT_EXCLUDED_PATHS, u as parseAgentOutput, v as triggerPostMergeReleaseIfConfigured } from "./merge-agent-GWhmnxFk.js";
|
|
3
3
|
export { AUTO_COMMIT_EXCLUDED_PATHS, GIT_PATTERNS, autoCommitWorkspaceChangesBeforeSync, captureTmuxOutput, isAutoCommitExcludedPath, isMergeAgentRunning, isSyncMainMainPreferredPath, logMergeHistory, notifyTldrDaemon, parseAgentOutput, postMergeLifecycle, resetPostMergeState, runProjectQualityGates, salvageStrandedMerge, scanForConflictMarkers, scanGitPatterns, sendMessageToAgent, syncMainIntoWorkspace, triggerPostMergeReleaseIfConfigured };
|
|
@@ -27,7 +27,7 @@ init_activity_logger();
|
|
|
27
27
|
init_tmux();
|
|
28
28
|
async function shouldRestartForPostMerge(repoRoot, dependencies = {}) {
|
|
29
29
|
if ((await (dependencies.computeStaleness ?? (async () => {
|
|
30
|
-
const [{ getBuildInfo }, { computeBuildStaleness }] = await Promise.all([import("./build-info-
|
|
30
|
+
const [{ getBuildInfo }, { computeBuildStaleness }] = await Promise.all([import("./build-info-PQlh_Rhb.js"), import("./staleness-BjOAq9-d.js")]);
|
|
31
31
|
return computeBuildStaleness({
|
|
32
32
|
repoRoot,
|
|
33
33
|
buildCommit: getBuildInfo().buildCommit
|
|
@@ -1724,4 +1724,4 @@ async function runProjectQualityGates(projectPath, phase) {
|
|
|
1724
1724
|
//#endregion
|
|
1725
1725
|
export { ensureSyncGitQuiescent as S, syncMainIntoWorkspace as _, isAutoCommitExcludedPath as a, gitPush as b, logMergeHistory as c, postMergeLifecycle as d, runProjectQualityGates as f, sendMessageToAgent as g, scanGitPatterns as h, captureTmuxOutput as i, notifyTldrDaemon as l, scanForConflictMarkers as m, GIT_PATTERNS as n, isMergeAgentRunning as o, salvageStrandedMerge as p, autoCommitWorkspaceChangesBeforeSync as r, isSyncMainMainPreferredPath as s, AUTO_COMMIT_EXCLUDED_PATHS as t, parseAgentOutput as u, triggerPostMergeReleaseIfConfigured as v, listGitOperationsSync as x, MainDivergedError as y };
|
|
1726
1726
|
|
|
1727
|
-
//# sourceMappingURL=merge-agent-
|
|
1727
|
+
//# sourceMappingURL=merge-agent-GWhmnxFk.js.map
|