Files
podman/backend/src/memory/db.ts
T
Ramis 6a6fbca9e0 fix: stop agent crashing on Mongo errors during live loop
A Mongo auth/connection failure in getGitStates escaped uncaught and
killed the agent process on the first screen frame, so collision
detection never ran. Make git-state fusion best-effort (degrade to
vision-only) and wrap the whole onScreenFrame loop so no per-frame
Gemini/GitHub/Mongo error can crash the long-running agent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AaCFWMkYQmTcuPsxaaACft
2026-06-27 20:31:38 -07:00

121 lines
4.2 KiB
TypeScript

import { MongoClient, type Db, type Collection } from 'mongodb';
import type {
EngineerContext,
Collision,
Intervention,
InterventionOutcome,
Pod,
} from '@podman/shared';
import { env } from '../env.js';
let clientPromise: Promise<MongoClient> | null = null;
function getClient(): Promise<MongoClient> {
if (!clientPromise) {
const client = new MongoClient(env.MONGODB_URI, { serverSelectionTimeoutMS: 3000 });
clientPromise = client.connect();
}
return clientPromise;
}
/** The PodMan database (name comes from the MONGODB_URI path, e.g. `podman`). */
export async function getDb(): Promise<Db> {
const client = await getClient();
return client.db();
}
export async function closeMemory(): Promise<void> {
if (!clientPromise) return;
const client = await clientPromise;
clientPromise = null;
await client.close();
}
export interface PodCollections {
pods: Collection<Pod>;
observations: Collection<EngineerContext>;
collisions: Collection<Collision>;
interventions: Collection<Intervention>;
outcomes: Collection<InterventionOutcome>;
}
export async function collections(): Promise<PodCollections> {
const db = await getDb();
return {
pods: db.collection<Pod>('pods'),
observations: db.collection<EngineerContext>('observations'),
collisions: db.collection<Collision>('collisions'),
interventions: db.collection<Intervention>('interventions'),
outcomes: db.collection<InterventionOutcome>('outcomes'),
};
}
export interface GitState {
changedFiles: string[];
branch: string | null;
recentCommit: string | null;
gitUpdatedAt: Date | null;
}
/** Fetch latest git state per engineer for a pod from the engineer_states collection.
* Returns a map keyed by engineer name (matches --name arg used in podman-agent.mjs). */
export async function getGitStates(podId: string): Promise<Map<string, GitState>> {
const db = await getDb();
const col = db.collection<{
_id: string;
name: string;
changedFiles?: string[];
branch?: string | null;
recentCommit?: string | null;
gitUpdatedAt?: Date;
}>('engineer_states');
const map = new Map<string, GitState>();
try {
const docs = await col.find({ podId }).toArray();
for (const doc of docs) {
map.set(doc.name, {
changedFiles: doc.changedFiles ?? [],
branch: doc.branch ?? null,
recentCommit: doc.recentCommit ?? null,
gitUpdatedAt: doc.gitUpdatedAt ?? null,
});
}
} catch (err) {
// Best-effort: a Mongo hiccup degrades git fusion, it must not crash the
// live agent loop. Detection falls back to vision-only signals.
console.warn(`[memory] getGitStates failed: ${(err as Error).message}`);
}
return map;
}
/** Connect, verify reachability, and create helpful indexes. Call once on startup. */
export async function initMemory(): Promise<void> {
const db = await getDb();
await db.command({ ping: 1 });
const c = await collections();
// Create each index independently so one failure (e.g. the unique pods index
// failing on pre-existing duplicate ids) doesn't abort the others or block
// seeding. Failures are logged loudly rather than silently swallowed.
const indexes: Array<[string, () => Promise<unknown>]> = [
['pods.id (unique)', () => c.pods.createIndex({ id: 1 }, { unique: true })],
['observations.podId', () => c.observations.createIndex({ podId: 1, observedAt: -1 })],
['observations.engineerId', () => c.observations.createIndex({ engineerId: 1 })],
['collisions.podId', () => c.collisions.createIndex({ podId: 1, detectedAt: -1 })],
[
'collisions.memorySignature',
() => c.collisions.createIndex({ podId: 1, memorySignature: 1 }),
],
['collisions.file', () => c.collisions.createIndex({ podId: 1, file: 1, detectedAt: -1 })],
['interventions.collisionId', () => c.interventions.createIndex({ collisionId: 1 })],
['outcomes.interventionId', () => c.outcomes.createIndex({ interventionId: 1 })],
];
for (const [name, make] of indexes) {
try {
await make();
} catch (err) {
console.error(`[memory] index "${name}" failed: ${(err as Error).message}`);
}
}
console.log(`[memory] mongo connected -> ${db.databaseName}`);
}