Integrate canonical architecture: two-process backend + LiveKit agent

Promote all 12 staged canonical files from docs/generated/files/ to their
live paths, creating the full PodMan architecture:

Backend:
- server.ts: HTTP service (token mint, sync-PR, outcome recording, /health, WS relay)
- agent.ts: worker joining LiveKit room, grabbing screenshare frames at ~1fps
- agent/podman.ts: orchestrator loop (vision -> collision detection -> intervention)
- env.ts: flat env var accessors replacing nested stub
- vision/gemini.ts: JPEG -> Gemini vision -> EngineerContext (real implementation)
- collision/detector.ts: fused vision+GitHub collision detection (the moat)
- github/client.ts: Octokit wrapper with caching + sync PR creation
- memory/store.ts: extended with recordObservation/recordCollision/recordIntervention/recordOutcome helpers
- memory/vectors.ts: stub for Voyage+Atlas vector recall (Loop A)
- memory/policy.ts: stub for intervention policy gate (Loop B)
- voice/live.ts: stub for Gemini Live TTS voice output

Shared:
- messages.ts: LiveKit data-channel wire protocol (DataMessage, InterventionOutcome, TeamModel, LocalGitReport)
- index.ts: re-exports messages module

Frontend:
- livekit/useScreenPublish.ts: hook for joining pod and publishing screenshare
- livekit/useInterventions.ts: hook for receiving collision cards and responding
- lib/api.ts: fetchToken + postOutcome HTTP helpers

Database:
- database/init.ts: MongoDB Atlas collections + indexes + vector search index

Infra:
- infra/.do/app.yaml: DO App Platform spec (static_site + service + worker)

Retire stubs superseded by canonical decomposition:
- backend/src/index.ts (replaced by server.ts)
- backend/src/intervention/engine.ts (logic now in agent/podman.ts)
- backend/src/livekit/token.ts (token minting now in server.ts)

Install missing dependencies: @livekit/rtc-node, sharp, mongodb, ws, @types/ws

Type error fixes:
- vision/gemini.ts: use MediaResolution.MEDIA_RESOLUTION_LOW enum value (not string literal)
- agent/podman.ts: wrap SuggestedActionKind into { kind: action } SuggestedAction object

All packages pass pnpm -r typecheck and pnpm -r build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kartikeya
2026-06-27 14:27:14 -07:00
parent 253c7438fb
commit da5b2622b2
23 changed files with 1369 additions and 190 deletions
+22 -32
View File
@@ -1,50 +1,40 @@
import type { Collision, EngineerContext, GithubStateSnapshot } from '@podman/shared';
import type { EngineerContext, Collision, GithubStateSnapshot } from '@podman/shared';
function normalize(path?: string): string | undefined {
if (!path) return undefined;
return path.replace(/^\.?\/?(src\/)?/, 'src/').toLowerCase();
}
/**
* Fuse live engineer contexts (from vision) with GitHub state to find overlaps:
* two or more engineers editing the same file — especially when unpushed.
*
* Pure function: easy to unit-test, no I/O. Callers supply the GitHub snapshot.
*/
export function detectCollisions(
contexts: EngineerContext[],
githubStateByFile: Record<string, GithubStateSnapshot> = {},
now: string = new Date().toISOString(),
github: GithubStateSnapshot,
): Collision[] {
const byFile = new Map<string, EngineerContext[]>();
for (const ctx of contexts) {
if (!ctx.currentFile) continue;
const list = byFile.get(ctx.currentFile) ?? [];
list.push(ctx);
byFile.set(ctx.currentFile, list);
for (const c of contexts) {
const f = normalize(c.currentFile);
if (!f) continue;
(byFile.get(f) ?? byFile.set(f, []).get(f)!).push(c);
}
const collisions: Collision[] = [];
const out: Collision[] = [];
for (const [file, group] of byFile) {
if (group.length < 2) continue;
const engineers = [...new Set(group.map((g) => g.engineerId))];
if (engineers.length < 2) continue;
const github = githubStateByFile[file];
const unpushed = group.some((g) => g.hasUnpushedChanges) || github?.unpushed === true;
const sharedSymbol = group.every(
(g) => g.currentSymbol && g.currentSymbol === group[0]!.currentSymbol,
)
? group[0]!.currentSymbol
: undefined;
const anyUnpushed =
group.some((g) => g.hasUnpushedChanges) || github.unpushed === true;
if (!anyUnpushed) continue; // the crux GitHub alone cannot answer
collisions.push({
id: `${group[0]!.podId}:${file}:${engineers.sort().join(',')}`,
out.push({
id: `col_${file}_${Date.now()}`,
podId: group[0]!.podId,
file,
symbol: sharedSymbol,
symbol: group.find((g) => g.currentSymbol)?.currentSymbol,
engineers,
severity: unpushed ? 'critical' : sharedSymbol ? 'warn' : 'info',
githubState: github,
detectedAt: now,
severity: 'warn',
githubState: { ...github, unpushed: anyUnpushed },
detectedAt: new Date().toISOString(),
});
}
return collisions;
return out;
}