From da5b2622b2e4b8913a44fc9029a4975029ac8561 Mon Sep 17 00:00:00 2001 From: Kartikeya <176560021+karti-ai@users.noreply.github.com> Date: Sat, 27 Jun 2026 14:27:14 -0700 Subject: [PATCH] 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 --- backend/package.json | 13 +- backend/src/agent.ts | 79 +++ backend/src/agent/podman.ts | 75 +++ backend/src/collision/detector.ts | 54 +- backend/src/env.ts | 62 +- backend/src/github/client.ts | 57 +- backend/src/index.ts | 35 -- backend/src/intervention/engine.ts | 27 - backend/src/livekit/token.ts | 25 - backend/src/memory/policy.ts | 23 + backend/src/memory/store.ts | 28 +- backend/src/memory/vectors.ts | 10 + backend/src/server.ts | 59 ++ backend/src/vision/gemini.ts | 59 +- backend/src/voice/live.ts | 10 + database/init.ts | 41 ++ frontend/src/lib/api.ts | 29 + frontend/src/livekit/useInterventions.ts | 39 ++ frontend/src/livekit/useScreenPublish.ts | 38 ++ infra/.do/app.yaml | 60 ++ pnpm-lock.yaml | 690 +++++++++++++++++++++++ shared/src/index.ts | 1 + shared/src/messages.ts | 45 ++ 23 files changed, 1369 insertions(+), 190 deletions(-) create mode 100644 backend/src/agent.ts create mode 100644 backend/src/agent/podman.ts delete mode 100644 backend/src/index.ts delete mode 100644 backend/src/intervention/engine.ts delete mode 100644 backend/src/livekit/token.ts create mode 100644 backend/src/memory/policy.ts create mode 100644 backend/src/memory/vectors.ts create mode 100644 backend/src/server.ts create mode 100644 backend/src/voice/live.ts create mode 100644 database/init.ts create mode 100644 frontend/src/lib/api.ts create mode 100644 frontend/src/livekit/useInterventions.ts create mode 100644 frontend/src/livekit/useScreenPublish.ts create mode 100644 infra/.do/app.yaml create mode 100644 shared/src/messages.ts diff --git a/backend/package.json b/backend/package.json index 1e3cf46..5c34139 100644 --- a/backend/package.json +++ b/backend/package.json @@ -5,23 +5,30 @@ "type": "module", "main": "./dist/index.js", "scripts": { - "dev": "tsx watch src/index.ts", - "start": "node dist/index.js", + "dev": "tsx watch src/server.ts", + "dev:server": "tsx watch src/server.ts", + "dev:agent": "tsx watch src/agent.ts", + "start": "node dist/server.js", "build": "tsc -p tsconfig.json", "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { "@google/genai": "^2.10.0", + "@livekit/rtc-node": "^0.13.29", "@podman/shared": "workspace:*", "cors": "^2.8.6", "dotenv": "^17.4.2", "express": "^5.2.1", "livekit-server-sdk": "^2.15.5", - "octokit": "^5.0.5" + "mongodb": "^7.4.0", + "octokit": "^5.0.5", + "sharp": "^0.35.2", + "ws": "^8.21.0" }, "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", + "@types/ws": "^8.18.1", "tsx": "^4.22.4" } } diff --git a/backend/src/agent.ts b/backend/src/agent.ts new file mode 100644 index 0000000..91f1741 --- /dev/null +++ b/backend/src/agent.ts @@ -0,0 +1,79 @@ +import { + Room, + RoomEvent, + TrackKind, + TrackSource, + VideoStream, + VideoBufferType, + dispose, + type RemoteTrack, + type RemoteTrackPublication, + type RemoteParticipant, +} from '@livekit/rtc-node'; +import sharp from 'sharp'; +import { AccessToken } from 'livekit-server-sdk'; +import { env } from './env.js'; +import { PodMan } from './agent/podman.js'; + +const POD_ROOM = process.env.POD_ROOM ?? 'demo-pod'; +const SAMPLE_INTERVAL_MS = 1000; // ~1 fps to the vision model + +async function agentToken(room: string): Promise { + const at = new AccessToken(env.LIVEKIT_API_KEY, env.LIVEKIT_API_SECRET, { + identity: 'podman-agent', + name: 'PodMan', + ttl: '4h', + }); + at.addGrant({ roomJoin: true, room, canPublish: true, canSubscribe: true, canPublishData: true }); + return at.toJwt(); +} + +async function main() { + const room = new Room(); + const podman = new PodMan(room, POD_ROOM); + await room.connect(env.LIVEKIT_URL, await agentToken(POD_ROOM), { + autoSubscribe: true, + dynacast: true, + }); + await podman.start(); + console.log(`[agent] PodMan joined room ${POD_ROOM}`); + + const lastSent = new Map(); + + room.on( + RoomEvent.TrackSubscribed, + (track: RemoteTrack, pub: RemoteTrackPublication, participant: RemoteParticipant) => { + if (track.kind !== TrackKind.KIND_VIDEO || pub.source !== TrackSource.SOURCE_SCREENSHARE) return; + const id = participant.identity; + const stream = new VideoStream(track); + void (async () => { + for await (const event of stream) { + const now = Date.now(); + if (now - (lastSent.get(id) ?? 0) < SAMPLE_INTERVAL_MS) continue; // THROTTLE + lastSent.set(id, now); + const rgba = event.frame.convert(VideoBufferType.RGBA); + const jpeg = await sharp(Buffer.from(rgba.data), { + raw: { width: rgba.width, height: rgba.height, channels: 4 }, + }) + .resize({ width: 1280, withoutEnlargement: true }) + .jpeg({ quality: 70 }) + .toBuffer(); + await podman.onScreenFrame(id, jpeg); + } + })(); + }, + ); + + const shutdown = async () => { + await room.disconnect(); + await dispose(); + process.exit(0); + }; + process.on('SIGINT', shutdown); + process.on('SIGTERM', shutdown); +} + +main().catch((e) => { + console.error('[agent] fatal', e); + process.exit(1); +}); diff --git a/backend/src/agent/podman.ts b/backend/src/agent/podman.ts new file mode 100644 index 0000000..6d87270 --- /dev/null +++ b/backend/src/agent/podman.ts @@ -0,0 +1,75 @@ +import { RoomEvent, type Room } from '@livekit/rtc-node'; +import type { EngineerContext, Collision, Intervention, DataMessage } from '@podman/shared'; +import { DATA_TOPIC } from '@podman/shared'; +import { analyzeFrame } from '../vision/gemini.js'; +import { detectCollisions } from '../collision/detector.js'; +import { getGithubState } from '../github/client.js'; +import { recordObservation, recordCollision, recordIntervention } from '../memory/store.js'; +import { recallSimilar } from '../memory/vectors.js'; +import { shouldIntervene, preferredAction } from '../memory/policy.js'; +import { speak } from '../voice/live.js'; + +export class PodMan { + private contexts = new Map(); + private encoder = new TextEncoder(); + + constructor( + private room: Room, + private podId: string, + ) {} + + async start(): Promise { + // Tier-2 optional ground-truth + engineer ACKs arrive over the data channel. + this.room.on(RoomEvent.DataReceived, (payload) => { + try { + const msg = JSON.parse(new TextDecoder().decode(payload)) as DataMessage; + if (msg.type === 'GIT_REPORT') { + const c = this.contexts.get(msg.report.engineerId); + if (c) c.hasUnpushedChanges = msg.report.unpushedCount > 0 || msg.report.dirtyFiles.length > 0; + } + } catch { /* ignore malformed */ } + }); + } + + async onScreenFrame(engineerId: string, jpeg: Buffer): Promise { + const ctx = await analyzeFrame(engineerId, this.podId, jpeg); + this.contexts.set(engineerId, ctx); + await recordObservation(ctx); + + const github = await getGithubState(); // cached + const collisions = detectCollisions([...this.contexts.values()], github); + for (const collision of collisions) await this.handle(collision); + } + + private async handle(collision: Collision): Promise { + const prior = await recallSimilar(collision); // Loop A: vector recall raises confidence + if (prior) collision.severity = 'critical'; + if (!shouldIntervene(collision, prior)) return; // Loop B: policy gate + + await recordCollision(collision); + const action = preferredAction(collision, prior); + const names = collision.engineers.join(' and '); + const message = `${names} are both editing ${collision.file}` + + (collision.githubState?.unpushed ? ' and one has unpushed changes.' : '.') + + (prior ? ` I've seen this conflict pattern before.` : ''); + + const intervention: Intervention = { + id: `int_${Date.now()}`, + collisionId: collision.id, + podId: this.podId, + kind: 'card', + message, + suggestedAction: { kind: action }, + status: 'pending', + createdAt: new Date().toISOString(), + }; + await recordIntervention(intervention); + + const data: DataMessage = { type: 'COLLISION', collision, intervention }; + await this.room.localParticipant?.publishData( + this.encoder.encode(JSON.stringify(data)), + { reliable: true, topic: DATA_TOPIC }, + ); + await speak(this.room, message); // gemini-3.1-flash-live voice into the room + } +} diff --git a/backend/src/collision/detector.ts b/backend/src/collision/detector.ts index ceea8a8..7d09900 100644 --- a/backend/src/collision/detector.ts +++ b/backend/src/collision/detector.ts @@ -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 = {}, - now: string = new Date().toISOString(), + github: GithubStateSnapshot, ): Collision[] { const byFile = new Map(); - 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; } diff --git a/backend/src/env.ts b/backend/src/env.ts index 2073caa..4388857 100644 --- a/backend/src/env.ts +++ b/backend/src/env.ts @@ -1,39 +1,35 @@ import 'dotenv/config'; -/** Reads an env var, throwing if it is required but missing. */ -function read(name: string, required = false): string { - const value = process.env[name] ?? ''; - if (required && !value) { - throw new Error(`Missing required env var: ${name}`); - } - return value; +function req(name: string): string { + const v = process.env[name]; + if (!v) throw new Error(`Missing required env var: ${name}`); + return v; +} +function opt(name: string, fallback = ''): string { + return process.env[name] ?? fallback; } export const env = { - port: Number(process.env.PORT ?? 8787), - - livekit: { - url: read('LIVEKIT_URL'), - apiKey: read('LIVEKIT_API_KEY'), - apiSecret: read('LIVEKIT_API_SECRET'), - }, - - gemini: { - apiKey: read('GEMINI_API_KEY'), - visionModel: process.env.GEMINI_VISION_MODEL ?? 'gemini-3.5-flash', - liveModel: process.env.GEMINI_LIVE_MODEL ?? '', - }, - - github: { - token: read('GITHUB_TOKEN'), - repo: read('GITHUB_REPO'), - }, - - mongo: { - uri: read('MONGODB_URI'), - }, - - voyage: { - apiKey: read('VOYAGE_API_KEY'), - }, + // LiveKit + LIVEKIT_URL: req('LIVEKIT_URL'), + LIVEKIT_API_KEY: req('LIVEKIT_API_KEY'), + LIVEKIT_API_SECRET: req('LIVEKIT_API_SECRET'), + // Gemini + GEMINI_API_KEY: req('GEMINI_API_KEY'), + GEMINI_VISION_MODEL: opt('GEMINI_VISION_MODEL', 'gemini-3.5-flash'), + GEMINI_LIVE_MODEL: opt('GEMINI_LIVE_MODEL', 'gemini-3.1-flash-live-preview'), + // GitHub + GITHUB_TOKEN: req('GITHUB_TOKEN'), + GITHUB_REPO: req('GITHUB_REPO'), // owner/name + // Mongo + Voyage + MONGODB_URI: req('MONGODB_URI'), + VOYAGE_API_KEY: opt('VOYAGE_API_KEY'), + // Server + PORT: Number(opt('PORT', '8787')), } as const; + +export function repoParts(): { owner: string; repo: string } { + const [owner, repo] = env.GITHUB_REPO.split('/'); + if (!owner || !repo) throw new Error('GITHUB_REPO must be "owner/name"'); + return { owner, repo }; +} diff --git a/backend/src/github/client.ts b/backend/src/github/client.ts index a5bdbf7..0d0b66e 100644 --- a/backend/src/github/client.ts +++ b/backend/src/github/client.ts @@ -1,25 +1,46 @@ import { Octokit } from 'octokit'; import type { GithubStateSnapshot } from '@podman/shared'; -import { env } from '../env.js'; +import { env, repoParts } from '../env.js'; -export const octokit: Octokit = new Octokit({ auth: env.github.token }); +const gh = new Octokit({ auth: env.GITHUB_TOKEN }); +let cache: { at: number; state: GithubStateSnapshot } | null = null; +const TTL_MS = 5000; -/** - * Pull the GitHub state relevant to a file in the pod's repo: open branches - * and PRs touching it. Fused with vision contexts by the collision detector. - * - * TODO(github): list branches/PRs, diff files, map commits -> engineer logins. - */ -export async function getStateForFile(_file: string): Promise { - return { branches: {}, openPrs: [], unpushed: false }; +export async function getGithubState(): Promise { + if (cache && Date.now() - cache.at < TTL_MS) return cache.state; + const { owner, repo } = repoParts(); + const [{ data: branches }] = await Promise.all([ + gh.rest.repos.listBranches({ owner, repo, per_page: 50 }), + ]); + const state: GithubStateSnapshot = { + branches: Object.fromEntries(branches.map((b) => [b.name, b.commit.sha])), + openPrs: [], + unpushed: undefined, // vision/Tier-2 fills this; API cannot know + }; + cache = { at: Date.now(), state }; + return state; } -/** Open a draft "sync PR" between two engineers' branches — the suggested action. */ -export async function openSyncPr(_params: { - base: string; - head: string; - title: string; -}): Promise<{ number: number; url: string } | null> { - // TODO(github): octokit.rest.pulls.create({ ...env.github.repo, draft: true }) - return null; +export async function remoteHasFile(path: string, ref = 'main'): Promise { + const { owner, repo } = repoParts(); + return gh.rest.repos + .getContent({ owner, repo, path, ref }) + .then(() => true) + .catch(() => false); +} + +export async function createSyncPr(input: { headBranch: string; file: string; summary: string }) { + const { owner, repo } = repoParts(); + const { data: mainRef } = await gh.rest.git.getRef({ owner, repo, ref: 'heads/main' }); + const branch = `podman-sync-${Date.now()}`; + await gh.rest.git.createRef({ owner, repo, ref: `refs/heads/${branch}`, sha: mainRef.object.sha }); + const { data: pr } = await gh.rest.pulls.create({ + owner, + repo, + title: `PodMan: sync ${input.file} before collision`, + head: branch, + base: 'main', + body: input.summary, + }); + return pr; } diff --git a/backend/src/index.ts b/backend/src/index.ts deleted file mode 100644 index 5c9db24..0000000 --- a/backend/src/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import express from 'express'; -import cors from 'cors'; -import { env } from './env.js'; -import { createPodToken } from './livekit/token.js'; - -const app = express(); -app.use(cors()); -app.use(express.json()); - -app.get('/health', (_req, res) => { - res.json({ ok: true, service: 'podman-backend' }); -}); - -/** - * Mint a LiveKit token for an engineer to join a pod room. - * POST /pods/:podId/token { identity, name } - */ -app.post('/pods/:podId/token', async (req, res) => { - const { podId } = req.params; - const { identity, name } = req.body ?? {}; - if (!identity) { - res.status(400).json({ error: 'identity is required' }); - return; - } - try { - const token = await createPodToken(podId, identity, name); - res.json({ token, url: env.livekit.url }); - } catch (err) { - res.status(500).json({ error: (err as Error).message }); - } -}); - -app.listen(env.port, () => { - console.log(`[podman] backend listening on http://localhost:${env.port}`); -}); diff --git a/backend/src/intervention/engine.ts b/backend/src/intervention/engine.ts deleted file mode 100644 index d2e10cc..0000000 --- a/backend/src/intervention/engine.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { Collision, Intervention } from '@podman/shared'; - -/** - * Compose PodMan's intervention for a collision — the message it speaks and the - * action it offers (e.g. "open a sync PR"). Severity drives voice vs card. - * - * TODO(brain): use Gemini to phrase the message naturally from the team model; - * tune thresholds from outcomes (the continual-learning policy). - */ -export function composeIntervention( - collision: Collision, - now: string = new Date().toISOString(), -): Intervention { - const kind = collision.severity === 'critical' ? 'voice' : 'card'; - return { - id: `intervention:${collision.id}`, - collisionId: collision.id, - podId: collision.podId, - kind, - message: `Heads up — ${collision.engineers.join(' and ')} are both in ${collision.file}.`, - suggestedAction: { - kind: collision.severity === 'critical' ? 'open_sync_pr' : 'ping_teammate', - }, - status: 'pending', - createdAt: now, - }; -} diff --git a/backend/src/livekit/token.ts b/backend/src/livekit/token.ts deleted file mode 100644 index cd031ce..0000000 --- a/backend/src/livekit/token.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { AccessToken } from 'livekit-server-sdk'; -import { env } from '../env.js'; - -/** - * Mint a LiveKit access token so an engineer's browser can join a pod room - * and publish screen + mic + cam tracks. - */ -export async function createPodToken( - podId: string, - identity: string, - name?: string, -): Promise { - const at = new AccessToken(env.livekit.apiKey, env.livekit.apiSecret, { - identity, - name, - }); - at.addGrant({ - room: podId, - roomJoin: true, - canPublish: true, - canSubscribe: true, - canPublishData: true, - }); - return at.toJwt(); -} diff --git a/backend/src/memory/policy.ts b/backend/src/memory/policy.ts new file mode 100644 index 0000000..4693a37 --- /dev/null +++ b/backend/src/memory/policy.ts @@ -0,0 +1,23 @@ +import type { Collision, SuggestedActionKind } from '@podman/shared'; + +/** + * Policy gate: decides whether PodMan should intervene. + * Stub: always intervene on warn/critical. + */ +export function shouldIntervene( + collision: Collision, + _prior: unknown, +): boolean { + return collision.severity !== 'info'; +} + +/** + * Preferred action selection based on collision + prior history. + * Stub: open sync PR for critical, ping teammate otherwise. + */ +export function preferredAction( + collision: Collision, + _prior: unknown, +): SuggestedActionKind { + return collision.severity === 'critical' ? 'open_sync_pr' : 'ping_teammate'; +} diff --git a/backend/src/memory/store.ts b/backend/src/memory/store.ts index 4d0b943..c508514 100644 --- a/backend/src/memory/store.ts +++ b/backend/src/memory/store.ts @@ -1,12 +1,10 @@ -import type { EngineerContext, Intervention } from '@podman/shared'; +import type { EngineerContext, Collision, Intervention } from '@podman/shared'; +import type { InterventionOutcome } from '@podman/shared'; /** * Continual-learning memory: persist observations + intervention outcomes to * MongoDB Atlas and embed file/feature notes into Voyage vectors so later * sessions are sharper ("more useful the more you use it"). - * - * TODO(memory): connect Atlas, store observations, record outcomes, embed via - * Voyage, and expose retrieval for the PodMan brain. */ export interface PodMemory { recordObservation(ctx: EngineerContext): Promise; @@ -25,3 +23,25 @@ export function createInMemoryStore(): PodMemory { }, }; } + +// Standalone helpers used by the PodMan orchestrator and HTTP server. +const _observations: EngineerContext[] = []; +const _collisions: Collision[] = []; +const _interventions: Intervention[] = []; +const _outcomes: InterventionOutcome[] = []; + +export async function recordObservation(ctx: EngineerContext): Promise { + _observations.push(ctx); +} + +export async function recordCollision(collision: Collision): Promise { + _collisions.push(collision); +} + +export async function recordIntervention(intervention: Intervention): Promise { + _interventions.push(intervention); +} + +export async function recordOutcome(outcome: InterventionOutcome): Promise { + _outcomes.push(outcome); +} diff --git a/backend/src/memory/vectors.ts b/backend/src/memory/vectors.ts new file mode 100644 index 0000000..cbe5aa7 --- /dev/null +++ b/backend/src/memory/vectors.ts @@ -0,0 +1,10 @@ +import type { Collision } from '@podman/shared'; + +/** + * Vector-based recall of prior collision patterns (Loop A). + * Stub: returns null until Voyage + Atlas Vector Search are wired. + */ +export async function recallSimilar(_collision: Collision): Promise { + // TODO(memory): embed collision.file via Voyage, query Atlas vector index + return null; +} diff --git a/backend/src/server.ts b/backend/src/server.ts new file mode 100644 index 0000000..2a2acb5 --- /dev/null +++ b/backend/src/server.ts @@ -0,0 +1,59 @@ +import express from 'express'; +import { createServer } from 'node:http'; +import { WebSocketServer } from 'ws'; +import { AccessToken } from 'livekit-server-sdk'; +import { env } from './env.js'; +import { createSyncPr } from './github/client.js'; +import { recordOutcome } from './memory/store.js'; +import type { InterventionOutcome } from '@podman/shared'; + +const app = express(); +app.use(express.json()); +app.get('/health', (_req, res) => res.json({ ok: true })); + +// Mint a LiveKit token for an engineer joining a pod. +app.post('/api/token', async (req, res) => { + const { room, identity, name, githubLogin } = req.body ?? {}; + if (!room || !identity) return res.status(400).json({ error: 'room+identity required' }); + const at = new AccessToken(env.LIVEKIT_API_KEY, env.LIVEKIT_API_SECRET, { + identity, + name, + ttl: '4h', + metadata: JSON.stringify({ githubLogin: githubLogin ?? name }), + }); + at.addGrant({ roomJoin: true, room, canPublish: true, canSubscribe: true, canPublishData: true }); + res.json({ token: await at.toJwt(), url: env.LIVEKIT_URL }); +}); + +// PodMan's hero action: open a real sync PR on the PUBLIC repo. +app.post('/api/sync-pr', async (req, res) => { + try { + const { headBranch, file, summary } = req.body ?? {}; + const pr = await createSyncPr({ headBranch, file, summary }); + res.json({ url: pr.html_url, number: pr.number }); + } catch (e) { + res.status(500).json({ error: (e as Error).message }); + } +}); + +// Outcome ACK -> closes the continual-learning policy loop. +app.post('/api/outcome', async (req, res) => { + await recordOutcome(req.body as InterventionOutcome); + res.json({ ok: true }); +}); + +const http = createServer(app); + +// ws relay: the agent pushes collision/intervention JSON here; PWAs subscribed by pod receive it. +const wss = new WebSocketServer({ server: http, path: '/api/events' }); +const clients = new Set(); +wss.on('connection', (ws) => { + clients.add(ws); + ws.on('close', () => clients.delete(ws)); + ws.on('message', (buf) => { + // fan out agent->PWA events; (auth/pod-scoping omitted for hackathon brevity) + for (const c of clients) if (c !== ws && c.readyState === 1) c.send(buf.toString()); + }); +}); + +http.listen(env.PORT, '0.0.0.0', () => console.log(`[server] :${env.PORT}`)); diff --git a/backend/src/vision/gemini.ts b/backend/src/vision/gemini.ts index 47143f0..7fe6fc7 100644 --- a/backend/src/vision/gemini.ts +++ b/backend/src/vision/gemini.ts @@ -1,20 +1,53 @@ +import { GoogleGenAI, MediaResolution, Type } from '@google/genai'; import type { EngineerContext } from '@podman/shared'; +import { env } from '../env.js'; -/** - * Turn a sampled screen frame into a structured EngineerContext using Gemini - * vision. This is the headline capability: it produces the pre-push signal - * (which file/symbol an engineer is editing) that GitHub cannot see. - * - * TODO(vision): wire @google/genai, downscale frames, sample ~1fps/on-change. - */ -export async function frameToContext( - _frame: Uint8Array, - meta: { engineerId: string; podId: string }, +const ai = new GoogleGenAI({ apiKey: env.GEMINI_API_KEY }); + +const SCHEMA = { + type: Type.OBJECT, + properties: { + currentFile: { type: Type.STRING, description: 'open file path if visible, e.g. src/auth/session.ts' }, + currentSymbol: { type: Type.STRING, description: 'function/class under the cursor' }, + activity: { type: Type.STRING, description: 'editing | reading | debugging | terminal | PR review' }, + hasUnpushedChanges: { type: Type.BOOLEAN, description: 'dirty git gutter / modified markers visible' }, + confidence: { type: Type.NUMBER, description: '0..1 confidence in this read' }, + }, + propertyOrdering: ['currentFile', 'currentSymbol', 'activity', 'hasUnpushedChanges', 'confidence'], +} as const; + +export async function analyzeFrame( + engineerId: string, + podId: string, + jpeg: Buffer, ): Promise { + const res = await ai.models.generateContent({ + model: env.GEMINI_VISION_MODEL, + contents: [ + { + role: 'user', + parts: [ + { text: "You are PodMan watching an engineer's screen. Identify what file/symbol they are working on and whether there are uncommitted edits. JSON only." }, + { inlineData: { mimeType: 'image/jpeg', data: jpeg.toString('base64') } }, + ], + }, + ], + config: { + responseMimeType: 'application/json', + responseJsonSchema: SCHEMA, + thinkingConfig: { thinkingBudget: 0 }, // minimal thinking: low latency/cost for ambient loop + mediaResolution: MediaResolution.MEDIA_RESOLUTION_LOW, + }, + }); + const parsed = JSON.parse(res.text ?? '{}') as Partial; return { - engineerId: meta.engineerId, - podId: meta.podId, - confidence: 0, + engineerId, + podId, + currentFile: parsed.currentFile, + currentSymbol: parsed.currentSymbol, + activity: parsed.activity, + hasUnpushedChanges: parsed.hasUnpushedChanges, + confidence: parsed.confidence ?? 0.5, observedAt: new Date().toISOString(), }; } diff --git a/backend/src/voice/live.ts b/backend/src/voice/live.ts new file mode 100644 index 0000000..a5a271f --- /dev/null +++ b/backend/src/voice/live.ts @@ -0,0 +1,10 @@ +import type { Room } from '@livekit/rtc-node'; + +/** + * Speak a message into the LiveKit room using Gemini Live voice. + * Stub: logs until Gemini Live audio track wiring is complete. + */ +export async function speak(_room: Room, message: string): Promise { + // TODO(voice): use Gemini Live streaming TTS -> publish audio track into room + console.log(`[voice] ${message}`); +} diff --git a/database/init.ts b/database/init.ts new file mode 100644 index 0000000..9d394e6 --- /dev/null +++ b/database/init.ts @@ -0,0 +1,41 @@ +import { MongoClient } from 'mongodb'; + +const uri = process.env.MONGODB_URI!; +const DB = 'podman'; + +async function main() { + const client = new MongoClient(uri); + await client.connect(); + const db = client.db(DB); + + await db.collection('pods').createIndex({ id: 1 }, { unique: true }); + // High-volume observations expire after 6h to keep the cluster light. + await db.collection('observations').createIndex({ observedAt: 1 }, { expireAfterSeconds: 21600 }); + await db.collection('observations').createIndex({ podId: 1, engineerId: 1 }); + await db.collection('collisions').createIndex({ podId: 1, detectedAt: -1 }); + await db.collection('interventions').createIndex({ id: 1 }, { unique: true }); + await db.collection('team_model').createIndex({ podId: 1 }, { unique: true }); + await db.collection('policy').createIndex({ pattern: 1 }, { unique: true }); + + // Atlas Vector Search index for collision-pattern recall (Voyage voyage-3 = 1024 dims). + try { + await db.command({ + createSearchIndexes: 'memory_vectors', + indexes: [ + { + name: 'vector_index', + type: 'vectorSearch', + definition: { + fields: [{ type: 'vector', path: 'embedding', numDimensions: 1024, similarity: 'cosine' }], + }, + }, + ], + }); + } catch (e) { + console.warn('vector index (create in Atlas UI if this errors):', (e as Error).message); + } + + console.log('PodMan DB initialized.'); + await client.close(); +} +main().catch((e) => { console.error(e); process.exit(1); }); diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts new file mode 100644 index 0000000..e892365 --- /dev/null +++ b/frontend/src/lib/api.ts @@ -0,0 +1,29 @@ +import type { InterventionOutcome } from '@podman/shared'; + +const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:8787'; + +/** Mint a LiveKit token from the backend. */ +export async function fetchToken(params: { + room: string; + identity: string; + name: string; + githubLogin?: string; +}): Promise<{ token: string; url: string }> { + const res = await fetch(`${BACKEND_URL}/api/token`, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(params), + }); + if (!res.ok) throw new Error(`token request failed: ${res.status}`); + return res.json() as Promise<{ token: string; url: string }>; +} + +/** Record an intervention outcome for the policy learning loop. */ +export async function postOutcome(outcome: InterventionOutcome): Promise { + const res = await fetch(`${BACKEND_URL}/api/outcome`, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(outcome), + }); + if (!res.ok) throw new Error(`outcome post failed: ${res.status}`); +} diff --git a/frontend/src/livekit/useInterventions.ts b/frontend/src/livekit/useInterventions.ts new file mode 100644 index 0000000..9359fd7 --- /dev/null +++ b/frontend/src/livekit/useInterventions.ts @@ -0,0 +1,39 @@ +import { useEffect, useState, useCallback } from 'react'; +import { RoomEvent, type Room } from 'livekit-client'; +import type { DataMessage, Intervention, InterventionStatus } from '@podman/shared'; +import { DATA_TOPIC } from '@podman/shared'; +import { postOutcome } from '../lib/api'; + +export function useInterventions(room: Room | null) { + const [active, setActive] = useState(null); + + useEffect(() => { + if (!room) return; + const onData = (payload: Uint8Array, _p: unknown, _k: unknown, topic?: string) => { + if (topic !== DATA_TOPIC) return; + const msg = JSON.parse(new TextDecoder().decode(payload)) as DataMessage; + if (msg.type === 'COLLISION') setActive(msg.intervention); + }; + room.on(RoomEvent.DataReceived, onData); + return () => { room.off(RoomEvent.DataReceived, onData); }; + }, [room]); + + const respond = useCallback( + async (status: InterventionStatus, accepted: boolean) => { + if (!active) return; + await postOutcome({ + interventionId: active.id, + collisionId: active.collisionId, + podId: active.podId, + wasRealCollision: true, + accepted, + recordedAt: new Date().toISOString(), + }); + setActive(null); + return status; + }, + [active], + ); + + return { active, respond }; +} diff --git a/frontend/src/livekit/useScreenPublish.ts b/frontend/src/livekit/useScreenPublish.ts new file mode 100644 index 0000000..ec91311 --- /dev/null +++ b/frontend/src/livekit/useScreenPublish.ts @@ -0,0 +1,38 @@ +import { useCallback, useRef, useState } from 'react'; +import { Room, Track, createLocalScreenTracks, VideoPresets } from 'livekit-client'; +import { fetchToken } from '../lib/api'; + +export function useScreenPublish() { + const roomRef = useRef(null); + const [connected, setConnected] = useState(false); + const [sharing, setSharing] = useState(false); + + const join = useCallback(async (pod: string, identity: string, name: string, githubLogin?: string) => { + const { token, url } = await fetchToken({ room: pod, identity, name, githubLogin }); + const room = new Room({ adaptiveStream: true, dynacast: true }); + await room.connect(url, token); + roomRef.current = room; + setConnected(true); + return room; + }, []); + + const startSharing = useCallback(async () => { + const room = roomRef.current; + if (!room) throw new Error('join the pod first'); + const tracks = await createLocalScreenTracks({ + audio: true, + resolution: VideoPresets.h1080.resolution, + }); + for (const t of tracks) { + await room.localParticipant.publishTrack(t.mediaStreamTrack, { + source: + t.kind === Track.Kind.Audio ? Track.Source.ScreenShareAudio : Track.Source.ScreenShare, + }); + } + await room.localParticipant.setMicrophoneEnabled(true); + await room.localParticipant.setCameraEnabled(true); + setSharing(true); + }, []); + + return { join, startSharing, connected, sharing, room: roomRef }; +} diff --git a/infra/.do/app.yaml b/infra/.do/app.yaml new file mode 100644 index 0000000..c034536 --- /dev/null +++ b/infra/.do/app.yaml @@ -0,0 +1,60 @@ +name: podman +region: nyc + +static_sites: + - name: web + github: + repo: /Podman + branch: main + deploy_on_push: true + source_dir: frontend + build_command: corepack enable && pnpm install --frozen-lockfile && pnpm --filter @podman/shared build && pnpm --filter @podman/frontend build + output_dir: dist + routes: + - path: / + +services: + - name: api + github: + repo: /Podman + branch: main + deploy_on_push: true + source_dir: backend + build_command: corepack enable && pnpm install --frozen-lockfile && pnpm --filter @podman/shared build && pnpm --filter @podman/backend build + run_command: node dist/server.js + http_port: 8787 + instance_size_slug: apps-s-1vcpu-1gb + instance_count: 1 + routes: + - path: /api + envs: + - { key: LIVEKIT_URL, scope: RUN_TIME, type: SECRET } + - { key: LIVEKIT_API_KEY, scope: RUN_TIME, type: SECRET } + - { key: LIVEKIT_API_SECRET, scope: RUN_TIME, type: SECRET } + - { key: GITHUB_TOKEN, scope: RUN_TIME, type: SECRET } + - { key: GITHUB_REPO, scope: RUN_TIME, value: / } + - { key: MONGODB_URI, scope: RUN_TIME, type: SECRET } + +workers: + - name: podman-agent + github: + repo: /Podman + branch: main + deploy_on_push: true + source_dir: backend + build_command: corepack enable && pnpm install --frozen-lockfile && pnpm --filter @podman/shared build && pnpm --filter @podman/backend build + run_command: node dist/agent.js + instance_size_slug: apps-s-1vcpu-1gb + instance_count: 1 + envs: + - { key: LIVEKIT_URL, scope: RUN_TIME, type: SECRET } + - { key: LIVEKIT_API_KEY, scope: RUN_TIME, type: SECRET } + - { key: LIVEKIT_API_SECRET, scope: RUN_TIME, type: SECRET } + - { key: GEMINI_API_KEY, scope: RUN_TIME, type: SECRET } + - { key: GEMINI_VISION_MODEL, scope: RUN_TIME, value: gemini-3.5-flash } + - { key: GEMINI_LIVE_MODEL, scope: RUN_TIME, value: gemini-3.1-flash-live-preview } + - { key: GITHUB_TOKEN, scope: RUN_TIME, type: SECRET } + - { key: GITHUB_REPO, scope: RUN_TIME, value: / } + - { key: MONGODB_URI, scope: RUN_TIME, type: SECRET } + - { key: VOYAGE_API_KEY, scope: RUN_TIME, type: SECRET } + - { key: POD_ROOM, scope: RUN_TIME, value: demo-pod } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b4a442..9d48c6e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: '@google/genai': specifier: ^2.10.0 version: 2.10.0 + '@livekit/rtc-node': + specifier: ^0.13.29 + version: 0.13.29 '@podman/shared': specifier: workspace:* version: link:../shared @@ -47,9 +50,18 @@ importers: livekit-server-sdk: specifier: ^2.15.5 version: 2.15.5 + mongodb: + specifier: ^7.4.0 + version: 7.4.0 octokit: specifier: ^5.0.5 version: 5.0.5 + sharp: + specifier: ^0.35.2 + version: 0.35.2 + ws: + specifier: ^8.21.0 + version: 8.21.0 devDependencies: '@types/cors': specifier: ^2.8.19 @@ -57,6 +69,9 @@ importers: '@types/express': specifier: ^5.0.6 version: 5.0.6 + '@types/ws': + specifier: ^8.18.1 + version: 8.18.1 tsx: specifier: ^4.22.4 version: 4.22.4 @@ -612,6 +627,9 @@ packages: '@bufbuild/protobuf@1.10.1': resolution: {integrity: sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ==} + '@datastructures-js/deque@1.0.8': + resolution: {integrity: sha512-PSBhJ2/SmeRPRHuBv7i/fHWIdSC3JTyq56qb+Rq0wjOagi0/fdV5/B/3Md5zFZus/W6OkSPMaxMKKMNMrSmubg==} + '@emnapi/core@1.11.1': resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} @@ -845,6 +863,168 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.35.2': + resolution: {integrity: sha512-eEieHsMksAW4IiO5NzauESRl2D2qz3J/kwUxUrSfV06A93eEaRfMpHXyUb1mAqrR7i8U9A0GRqE9pjn6u1Jjpg==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.35.2': + resolution: {integrity: sha512-BaktuGPCeHJMARpodR8jK4uKiZrPAy9WrfQW0sdI37clracq8Bp01AYS3SZgi5FS/y5twa9t4+LIuuxQjqRrWw==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [darwin] + + '@img/sharp-freebsd-wasm32@0.35.2': + resolution: {integrity: sha512-YoAxdnd8hPUkvLHd3bWY+YA8nw3xM/RyRopYucNsWHVSan8NLVM3X2volsfoRDcXdUJPg6tXahSd7HXPK7lRnw==} + engines: {node: '>=20.9.0'} + os: [freebsd] + + '@img/sharp-libvips-darwin-arm64@1.3.1': + resolution: {integrity: sha512-4V/M3roRMTYjiwZY9IOVQOE8OyeCxFAkYmyZDrZl51uOKjibm3oeEJ4WAmLxutAfzFbC9jqUiPs2gbnGflH+7g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.3.1': + resolution: {integrity: sha512-c0/DxItpJv2+dGhgycJBBgotdqruGYDvA79drdh0MD1dFpy7JzJ/PlXwi1H4rFf0eTy8tgbI91aHDnZIceY3jQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.3.1': + resolution: {integrity: sha512-JznefmcK9j1JKPz8AkQDh89kjojubyfOasWBPKfzMIhPwsgDy9evpE/naJTXXXmghS1iFwR8u/kTwh/I2/+GCw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.3.1': + resolution: {integrity: sha512-aGGy9aWzXgHBG7HNyQPWorZthlp7+x6fDRoPAQbGO3ThcttuTyKIx3NuSHb6zb4gBNq6/yNn9f1cy9nFKS/Vmg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.3.1': + resolution: {integrity: sha512-1EkwGNCZk6iWNCMWqrvdJ+r1j0PT1zIz60CNPhYnJlK/zyeWqlsPZIe+ocBVqPF8k/Ssee/NCk+tE9Ryrko6ng==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.3.1': + resolution: {integrity: sha512-Ilays+w2bXdnxzxtQdmXR62u8o8GYa3eL4+Gr+1KiE4xperMZUslRaVPJwwPkzlHEjGfXAfRVAa/7CYCtSqsBw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.3.1': + resolution: {integrity: sha512-VfBwVHQTbRoj4XlpA/KLZ7ltgMpz+4WSejFzQ+GnoImjo1PtEJ59QB2qR1xQEeRPYIkNrPIm2L4cICMvz4C2ew==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.3.1': + resolution: {integrity: sha512-+c8ukgwU62DS54nCAjw7keOfHUkmr0B5QHEdcOqRnodF/MNXJbVI8Eopoj4B/0H8Asr65I+A4Amrn7a85/md6A==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.3.1': + resolution: {integrity: sha512-qlKb/pwbkAi1WMsJrYHk7CuDrd12s27U2QnRhFYUoJNrRCmkosMTttuRFat/DDB3IlDm5qE1TJgZ4JDnHX8Ldw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.3.1': + resolution: {integrity: sha512-yO21HwoUVLN8Qa+/SBjQLMYwBWAVJjeGPNe+hc0OUeMeifEtJqu5a1c4HayE1nNpDih9y3/KkoltfkDodmKAlg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.35.2': + resolution: {integrity: sha512-af12Pnd0ZGu2HfP8NayB0kk6eC/lrfbQE6HlR4jD+34wdJ1Vw9TF6TMn6ZvffT+WgqVsl0hRbmNvz2u/23VmwA==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.35.2': + resolution: {integrity: sha512-SE4kzF2mepn6z+6E7L6lsV8FzuLL6IPQdyX8ZiwROAG/G8td+hP/m7FsFPwidtrF19gvajuC9l6TxAVcsA4S7A==} + engines: {node: '>=20.9.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.35.2': + resolution: {integrity: sha512-hYSBm7zcNtDCozCxQHYZJiu63b/bXsgRZuOxCIBZsStMM9Vap47iFHdbX4kCvQsblPB/k+clhELpdQJHQLSHvg==} + engines: {node: '>=20.9.0'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.35.2': + resolution: {integrity: sha512-qQt0Kc13+Hoan/Awq/qMSQw3L+RI1NCRPgD5cUJ/1WSSmIoysLOc72jlRM3E0OHN9Yr313jgeQ2T+zW+F03QFA==} + engines: {node: '>=20.9.0'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.35.2': + resolution: {integrity: sha512-E4fLLfRPzDLlEeDaTzI98OFLcv++WL5ChLLMwPoVd0CIoZQqupBSNbOisPL5am9XsbQ9T84+iiMpUvbFtkunbA==} + engines: {node: '>=20.9.0'} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.35.2': + resolution: {integrity: sha512-gi0zFJJRLswfCZmHtJdikXPOc5u7qamSOS3NHedLqLd4W8Q0NqjdBr6TTRIgsfFjqfTsHFgdfvJ9LwqSgcHiAA==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.35.2': + resolution: {integrity: sha512-siWbOW1u6HFnFLrp0waKyW7VEf7jYvcDWdrXEFa8AkdAQgEvuu5Fz8/Y70w9EeqAdwDtfU012BhEHHaDqvQNzg==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.35.2': + resolution: {integrity: sha512-YBqMMcjDi4QGYiSn4vNOYBhmlC4z5AXqkOUUqI2e0AFA4urNv4ESgOgwNl3K+4etQhha0twXlzeF20bbULm9Yg==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.35.2': + resolution: {integrity: sha512-Mrv4JQNYVQ94xH+jzZ9r+gowleN8mv2FTgKT+PI6bx5C0G8TdNYndu161pg2i7uoBwxy2ImPMHrJOM2LZef7Bw==} + engines: {node: '>=20.9.0'} + + '@img/sharp-webcontainers-wasm32@0.35.2': + resolution: {integrity: sha512-QNV27pxs9wpApEiCfvHM1RDoP1w1+2KrUWWDPEhEwg+latvOrfuhWrHWZKwdSFwU6jh3myjw/yOCRsUIuOft3g==} + engines: {node: '>=20.9.0'} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.35.2': + resolution: {integrity: sha512-BiVRYc/t6/Vl3e1hBx0hugG4oN9Pydf4fgMSpxTQJmwGUg/YoXTWHiFeRymHfCZzifxu4F4rpk/I67D0LQ20wQ==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.35.2': + resolution: {integrity: sha512-YYEhx9PImCC7T0tI8JDMi4DB9LwLCXCU5OWNYEXAxh5Q1ShKkyC6byxzoBJ3gEFDnH2lQckWuDe70G7mB2XJog==} + engines: {node: ^20.9.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.35.2': + resolution: {integrity: sha512-imoOyBcoM/iiUr4J6VPpCNjPnjvP/Gks95898yB8YqoGGYmHYbOyCuNv9FMhFgtaiHFGbHW8bxKqRV6VjtXThQ==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [win32] + '@isaacs/cliui@9.0.0': resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} engines: {node: '>=18'} @@ -877,6 +1057,52 @@ packages: '@livekit/protocol@1.48.0': resolution: {integrity: sha512-fYHYgltH6YavAsokl3qsHLkBdQeKCl4UORVTub5crS3t8JtKFZ0uinHDFQ+XXdNKS6Ub9gcOjV+UHcDiqnWXoQ==} + '@livekit/rtc-ffi-bindings-darwin-arm64@0.12.60': + resolution: {integrity: sha512-YHXqybkYfaTc3txJXXWoVogiSP3yKJdkaZlIlZ6IDMGnN9elUoHDYU+ZSn/rbdGu0pp4HUOzffXkbkItN735Bw==} + engines: {node: '>= 18'} + cpu: [arm64] + os: [darwin] + + '@livekit/rtc-ffi-bindings-darwin-x64@0.12.60': + resolution: {integrity: sha512-SkPPWE2/nb2BAXrCWP6+vaR2I4EeyG3Vv+csUaa1EvDVMbFqBHWqNVTQcx/ChgecbYB9dIFZHVYpfjbFkVd84g==} + engines: {node: '>= 18'} + cpu: [x64] + os: [darwin] + + '@livekit/rtc-ffi-bindings-linux-arm64-gnu@0.12.60': + resolution: {integrity: sha512-8umeMn9p/VZ41EGty1qX9zPV5mfGxCioYKeUnALpf8AKqT/yXDjnog1VkS5f8gFX/zY7HDaeE+s60nZXaOZJbw==} + engines: {node: '>= 18'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@livekit/rtc-ffi-bindings-linux-x64-gnu@0.12.60': + resolution: {integrity: sha512-ttWrR/e8Ghaa9I+LaStxK8lh+aA9QBz6Dge6eXyKwTrAMHwHEtL2Rnf1rHQTiwadeH7AoytpfWf5FZl/OelLaQ==} + engines: {node: '>= 18'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@livekit/rtc-ffi-bindings-win32-x64-msvc@0.12.60': + resolution: {integrity: sha512-HfOBEf3rmpsG7hU3/BM9x2jnkVwKFve2v3cyjxlk41d6OkCthYb+g/ULEPFBKPafYyepDwcd2c8MDo5fMM+4Zw==} + engines: {node: '>= 18'} + cpu: [x64] + os: [win32] + + '@livekit/rtc-ffi-bindings@0.12.60': + resolution: {integrity: sha512-ZJD2DNoHfR8PzKeyDMH6i1zKpk7S4LlrQDIZvisxj6HPaJnKofzSssNMF8fpGFvVCZ844kbcOFogRPgHFno82w==} + engines: {node: '>= 18'} + + '@livekit/rtc-node@0.13.29': + resolution: {integrity: sha512-3/mhTVW3zEa8u0l2UzLe74CyDxaz/1Fqrss+monBJARYHyMGMiDlnKAwDT2LiFkRy0xBjl2QY8Yldr0icfPUkw==} + engines: {node: '>= 18'} + + '@livekit/typed-emitter@3.0.0': + resolution: {integrity: sha512-9bl0k4MgBPZu3Qu3R3xy12rmbW17e3bE9yf4YY85gJIQ3ezLEj/uzpKHWBsLaDoL5Mozz8QCgggwIBudYQWeQg==} + + '@mongodb-js/saslprep@1.4.11': + resolution: {integrity: sha512-o9rAHc0IpIjuPSxRutWpE1F62x7n+4mVS4rCNHkzhIUMQcc18bb6xEq5wd2NdN0WjepIyXIppRshYI2kQDOZVA==} + '@napi-rs/wasm-runtime@1.1.6': resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} peerDependencies: @@ -993,6 +1219,9 @@ packages: '@oxc-project/types@0.137.0': resolution: {integrity: sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==} + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@protobufjs/aspromise@1.1.2': resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} @@ -1471,6 +1700,15 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/webidl-conversions@7.0.3': + resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} + + '@types/whatwg-url@13.0.0': + resolution: {integrity: sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==} + + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@typescript-eslint/eslint-plugin@8.62.0': resolution: {integrity: sha512-o+mpz7EYiMzXoySXiKmzlabIvTVqUuK5yLrAedRPRDA0IpPFMUV1IXt6OqljIxX/kumN6EjUYp41Hqelh6p/Dw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1586,6 +1824,10 @@ packages: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -1645,6 +1887,10 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + bson@7.3.1: + resolution: {integrity: sha512-h/C0qe6857pQhcSJHLfsR1uYGj98Ge3wKAD3Ed9KqH3wcVh+BM4Jq4xISD7vs9OPuT07n+q3QQVjslJ286j6ag==} + engines: {node: '>=20.19.0'} + buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} @@ -1678,6 +1924,9 @@ packages: caniuse-lite@1.0.30001799: resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -1742,6 +1991,9 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1800,6 +2052,9 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + enhanced-resolve@5.21.6: resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==} engines: {node: '>=10.13.0'} @@ -1912,6 +2167,9 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-copy@4.0.3: + resolution: {integrity: sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1921,6 +2179,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-uri@3.1.2: resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} @@ -2081,6 +2342,9 @@ packages: resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -2262,6 +2526,10 @@ packages: jose@6.2.3: resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2439,6 +2707,9 @@ packages: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} + memory-pager@1.5.0: + resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} + merge-descriptors@2.0.0: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} @@ -2459,10 +2730,44 @@ packages: resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} engines: {node: '>=10'} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} + mongodb-connection-string-url@7.0.1: + resolution: {integrity: sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ==} + engines: {node: '>=20.19.0'} + + mongodb@7.4.0: + resolution: {integrity: sha512-giySkkdYiwoBFo/oCc8nzov3xOYZ/sB8OpAYk5GINRLEjVw0LDsm8xgQL0XMTyU4extQlDZjhdUr1ZEwKFaazw==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@aws-sdk/credential-providers': ^3.806.0 + '@mongodb-js/zstd': ^7.0.0 + gcp-metadata: ^7.0.1 + kerberos: ^7.0.0 + mongodb-client-encryption: '>=7.0.0 <7.1.0' + snappy: ^7.3.2 + socks: ^2.8.6 + peerDependenciesMeta: + '@aws-sdk/credential-providers': + optional: true + '@mongodb-js/zstd': + optional: true + gcp-metadata: + optional: true + kerberos: + optional: true + mongodb-client-encryption: + optional: true + snappy: + optional: true + socks: + optional: true + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2511,6 +2816,10 @@ packages: resolution: {integrity: sha512-4+/OFSqOjoyULo7eN7EA97DE0Xydj/PW5aIckxqQIoFjFwqXKuFCvXUJObyJfBF9Khu4RL/jlDRI9FPaMGfPnw==} engines: {node: '>= 20'} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -2570,6 +2879,23 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} + pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-pretty@13.1.3: + resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} + hasBin: true + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@9.14.0: + resolution: {integrity: sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==} + hasBin: true + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -2595,6 +2921,9 @@ packages: resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} engines: {node: ^14.13.1 || >=16.0.0} + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + protobufjs@7.6.4: resolution: {integrity: sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==} engines: {node: '>=12.0.0'} @@ -2603,6 +2932,9 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2611,6 +2943,9 @@ packages: resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} engines: {node: '>=0.6'} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + quick-lru@6.1.2: resolution: {integrity: sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==} engines: {node: '>=12'} @@ -2632,6 +2967,10 @@ packages: resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} engines: {node: '>=0.10.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -2703,6 +3042,10 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -2716,6 +3059,9 @@ packages: sdp@3.2.2: resolution: {integrity: sha512-xZocWwfyp4hkbN4hLWxMjmv2Q8aNa9MhmOZ7L9aCZPT+dZsgRr6wZRrSYE3HTdyk/2pZKPSgqI7ns7Een1xMSA==} + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -2752,6 +3098,10 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + sharp@0.35.2: + resolution: {integrity: sha512-FVtFjtBCMiJS6yb5CX7Sop45WFMpeGw6oRKuJnXYgf/f1ms/D7LE/ZUSNxnW7rZ/dbslQWYkoqFHGPaDBtaK4w==} + engines: {node: '>=20.9.0'} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -2784,6 +3134,9 @@ packages: resolution: {integrity: sha512-RQsvleCbF8cVHEv+xuDGaA4pOizFqJ0GgjtMSRo6oP8pnN7WsigHgVGey6aILRBKv4W2YOMHLqbKdnB6hpB9fw==} engines: {node: '>=20.0.0'} + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2800,6 +3153,13 @@ packages: engines: {node: '>= 8'} deprecated: The work that was done in this beta branch won't be included in future versions + sparse-bitfield@3.0.3: + resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} @@ -2832,6 +3192,10 @@ packages: resolution: {integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==} engines: {node: '>=10'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -2856,6 +3220,9 @@ packages: engines: {node: '>=10'} hasBin: true + thread-stream@3.2.0: + resolution: {integrity: sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==} + tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} @@ -2871,6 +3238,10 @@ packages: tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} @@ -3052,10 +3423,18 @@ packages: webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + webrtc-adapter@9.0.5: resolution: {integrity: sha512-U9vjByy/sK2OMXu5mmfuZFKTMIUQe34c0JXRO+oDrxJTsntdYT2iIFwYMOV7HhMTuktcZLGf2W1N/OcSf9ssWg==} engines: {node: '>=6.0.0', npm: '>=3.10.0'} + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + whatwg-url@7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} @@ -3828,6 +4207,8 @@ snapshots: '@bufbuild/protobuf@1.10.1': {} + '@datastructures-js/deque@1.0.8': {} + '@emnapi/core@1.11.1': dependencies: '@emnapi/wasi-threads': 1.2.2 @@ -3983,6 +4364,112 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@img/colour@1.1.0': {} + + '@img/sharp-darwin-arm64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.3.1 + optional: true + + '@img/sharp-darwin-x64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.3.1 + optional: true + + '@img/sharp-freebsd-wasm32@0.35.2': + dependencies: + '@img/sharp-wasm32': 0.35.2 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.3.1': + optional: true + + '@img/sharp-libvips-darwin-x64@1.3.1': + optional: true + + '@img/sharp-libvips-linux-arm64@1.3.1': + optional: true + + '@img/sharp-libvips-linux-arm@1.3.1': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.3.1': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.3.1': + optional: true + + '@img/sharp-libvips-linux-s390x@1.3.1': + optional: true + + '@img/sharp-libvips-linux-x64@1.3.1': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.3.1': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.3.1': + optional: true + + '@img/sharp-linux-arm64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.3.1 + optional: true + + '@img/sharp-linux-arm@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.3.1 + optional: true + + '@img/sharp-linux-ppc64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.3.1 + optional: true + + '@img/sharp-linux-riscv64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.3.1 + optional: true + + '@img/sharp-linux-s390x@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.3.1 + optional: true + + '@img/sharp-linux-x64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.3.1 + optional: true + + '@img/sharp-linuxmusl-arm64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.3.1 + optional: true + + '@img/sharp-linuxmusl-x64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.3.1 + optional: true + + '@img/sharp-wasm32@0.35.2': + dependencies: + '@emnapi/runtime': 1.11.1 + optional: true + + '@img/sharp-webcontainers-wasm32@0.35.2': + dependencies: + '@img/sharp-wasm32': 0.35.2 + optional: true + + '@img/sharp-win32-arm64@0.35.2': + optional: true + + '@img/sharp-win32-ia32@0.35.2': + optional: true + + '@img/sharp-win32-x64@0.35.2': + optional: true + '@isaacs/cliui@9.0.0': {} '@jridgewell/gen-mapping@0.3.13': @@ -4019,6 +4506,46 @@ snapshots: dependencies: '@bufbuild/protobuf': 1.10.1 + '@livekit/rtc-ffi-bindings-darwin-arm64@0.12.60': + optional: true + + '@livekit/rtc-ffi-bindings-darwin-x64@0.12.60': + optional: true + + '@livekit/rtc-ffi-bindings-linux-arm64-gnu@0.12.60': + optional: true + + '@livekit/rtc-ffi-bindings-linux-x64-gnu@0.12.60': + optional: true + + '@livekit/rtc-ffi-bindings-win32-x64-msvc@0.12.60': + optional: true + + '@livekit/rtc-ffi-bindings@0.12.60': + dependencies: + '@bufbuild/protobuf': 1.10.1 + optionalDependencies: + '@livekit/rtc-ffi-bindings-darwin-arm64': 0.12.60 + '@livekit/rtc-ffi-bindings-darwin-x64': 0.12.60 + '@livekit/rtc-ffi-bindings-linux-arm64-gnu': 0.12.60 + '@livekit/rtc-ffi-bindings-linux-x64-gnu': 0.12.60 + '@livekit/rtc-ffi-bindings-win32-x64-msvc': 0.12.60 + + '@livekit/rtc-node@0.13.29': + dependencies: + '@datastructures-js/deque': 1.0.8 + '@livekit/mutex': 1.1.1 + '@livekit/rtc-ffi-bindings': 0.12.60 + '@livekit/typed-emitter': 3.0.0 + pino: 9.14.0 + pino-pretty: 13.1.3 + + '@livekit/typed-emitter@3.0.0': {} + + '@mongodb-js/saslprep@1.4.11': + dependencies: + sparse-bitfield: 3.0.3 + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': dependencies: '@emnapi/core': 1.11.1 @@ -4176,6 +4703,8 @@ snapshots: '@oxc-project/types@0.137.0': {} + '@pinojs/redact@0.4.0': {} + '@protobufjs/aspromise@1.1.2': {} '@protobufjs/base64@1.1.2': {} @@ -4514,6 +5043,16 @@ snapshots: '@types/trusted-types@2.0.7': {} + '@types/webidl-conversions@7.0.3': {} + + '@types/whatwg-url@13.0.0': + dependencies: + '@types/webidl-conversions': 7.0.3 + + '@types/ws@8.18.1': + dependencies: + '@types/node': 26.0.1 + '@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.62.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -4658,6 +5197,8 @@ snapshots: at-least-node@1.0.0: {} + atomic-sleep@1.0.0: {} + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 @@ -4730,6 +5271,8 @@ snapshots: node-releases: 2.0.50 update-browserslist-db: 1.2.3(browserslist@4.28.4) + bson@7.3.1: {} + buffer-equal-constant-time@1.0.1: {} buffer-from@1.1.2: {} @@ -4764,6 +5307,8 @@ snapshots: caniuse-lite@1.0.30001799: {} + colorette@2.0.20: {} + commander@2.20.3: {} common-tags@1.8.2: {} @@ -4819,6 +5364,8 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 + dateformat@4.6.3: {} + debug@4.4.3: dependencies: ms: 2.1.3 @@ -4865,6 +5412,10 @@ snapshots: encodeurl@2.0.0: {} + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + enhanced-resolve@5.21.6: dependencies: graceful-fs: 4.2.11 @@ -5102,12 +5653,16 @@ snapshots: extend@3.0.2: {} + fast-copy@4.0.3: {} + fast-deep-equal@3.1.3: {} fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} + fast-safe-stringify@2.1.1: {} + fast-uri@3.1.2: {} fdir@6.5.0(picomatch@4.0.4): @@ -5294,6 +5849,8 @@ snapshots: dependencies: function-bind: 1.1.2 + help-me@5.0.0: {} + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -5471,6 +6028,8 @@ snapshots: jose@6.2.3: {} + joycon@3.1.1: {} + js-tokens@4.0.0: {} jsesc@3.1.0: {} @@ -5618,6 +6177,8 @@ snapshots: media-typer@1.1.0: {} + memory-pager@1.5.0: {} + merge-descriptors@2.0.0: {} mime-db@1.54.0: {} @@ -5634,8 +6195,21 @@ snapshots: dependencies: brace-expansion: 2.1.1 + minimist@1.2.8: {} + minipass@7.1.3: {} + mongodb-connection-string-url@7.0.1: + dependencies: + '@types/whatwg-url': 13.0.0 + whatwg-url: 14.2.0 + + mongodb@7.4.0: + dependencies: + '@mongodb-js/saslprep': 1.4.11 + bson: 7.3.1 + mongodb-connection-string-url: 7.0.1 + ms@2.1.3: {} nanoid@3.3.15: {} @@ -5683,6 +6257,8 @@ snapshots: '@octokit/types': 16.0.0 '@octokit/webhooks': 14.2.0 + on-exit-leak-free@2.1.2: {} + on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -5740,6 +6316,46 @@ snapshots: picomatch@4.0.4: {} + pino-abstract-transport@2.0.0: + dependencies: + split2: 4.2.0 + + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-pretty@13.1.3: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 4.0.3 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pump: 3.0.4 + secure-json-parse: 4.1.0 + sonic-boom: 4.2.1 + strip-json-comments: 5.0.3 + + pino-std-serializers@7.1.0: {} + + pino@9.14.0: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 3.2.0 + possible-typed-array-names@1.1.0: {} postcss@8.5.15: @@ -5756,6 +6372,8 @@ snapshots: pretty-bytes@6.1.1: {} + process-warning@5.0.0: {} + protobufjs@7.6.4: dependencies: '@protobufjs/aspromise': 1.1.2 @@ -5775,6 +6393,11 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode@2.3.1: {} qs@6.15.3: @@ -5782,6 +6405,8 @@ snapshots: es-define-property: 1.0.1 side-channel: 1.1.1 + quick-format-unescaped@4.0.4: {} + quick-lru@6.1.2: {} range-parser@1.3.0: {} @@ -5800,6 +6425,8 @@ snapshots: react@19.2.7: {} + real-require@0.2.0: {} + reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.9 @@ -5940,6 +6567,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} scheduler@0.27.0: {} @@ -5948,6 +6577,8 @@ snapshots: sdp@3.2.2: {} + secure-json-parse@4.1.0: {} + semver@6.3.1: {} semver@7.8.5: {} @@ -6003,6 +6634,38 @@ snapshots: setprototypeof@1.2.0: {} + sharp@0.35.2: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.5 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.35.2 + '@img/sharp-darwin-x64': 0.35.2 + '@img/sharp-freebsd-wasm32': 0.35.2 + '@img/sharp-libvips-darwin-arm64': 1.3.1 + '@img/sharp-libvips-darwin-x64': 1.3.1 + '@img/sharp-libvips-linux-arm': 1.3.1 + '@img/sharp-libvips-linux-arm64': 1.3.1 + '@img/sharp-libvips-linux-ppc64': 1.3.1 + '@img/sharp-libvips-linux-riscv64': 1.3.1 + '@img/sharp-libvips-linux-s390x': 1.3.1 + '@img/sharp-libvips-linux-x64': 1.3.1 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.1 + '@img/sharp-libvips-linuxmusl-x64': 1.3.1 + '@img/sharp-linux-arm': 0.35.2 + '@img/sharp-linux-arm64': 0.35.2 + '@img/sharp-linux-ppc64': 0.35.2 + '@img/sharp-linux-riscv64': 0.35.2 + '@img/sharp-linux-s390x': 0.35.2 + '@img/sharp-linux-x64': 0.35.2 + '@img/sharp-linuxmusl-arm64': 0.35.2 + '@img/sharp-linuxmusl-x64': 0.35.2 + '@img/sharp-webcontainers-wasm32': 0.35.2 + '@img/sharp-win32-arm64': 0.35.2 + '@img/sharp-win32-ia32': 0.35.2 + '@img/sharp-win32-x64': 0.35.2 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -6041,6 +6704,10 @@ snapshots: smob@1.6.2: {} + sonic-boom@4.2.1: + dependencies: + atomic-sleep: 1.0.0 + source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -6054,6 +6721,12 @@ snapshots: dependencies: whatwg-url: 7.1.0 + sparse-bitfield@3.0.3: + dependencies: + memory-pager: 1.5.0 + + split2@4.2.0: {} + statuses@2.0.2: {} stop-iteration-iterator@1.1.0: @@ -6109,6 +6782,8 @@ snapshots: strip-comments@2.0.1: {} + strip-json-comments@5.0.3: {} + supports-preserve-symlinks-flag@1.0.0: {} tailwindcss@4.3.1: {} @@ -6131,6 +6806,10 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 + thread-stream@3.2.0: + dependencies: + real-require: 0.2.0 + tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.4) @@ -6144,6 +6823,10 @@ snapshots: dependencies: punycode: 2.3.1 + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + ts-api-utils@2.5.0(typescript@6.0.3): dependencies: typescript: 6.0.3 @@ -6296,10 +6979,17 @@ snapshots: webidl-conversions@4.0.2: {} + webidl-conversions@7.0.0: {} + webrtc-adapter@9.0.5: dependencies: sdp: 3.2.2 + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + whatwg-url@7.1.0: dependencies: lodash.sortby: 4.7.0 diff --git a/shared/src/index.ts b/shared/src/index.ts index dd414fb..e2aef1c 100644 --- a/shared/src/index.ts +++ b/shared/src/index.ts @@ -8,3 +8,4 @@ export type { SuggestedAction, SuggestedActionKind, } from './intervention.js'; +export * from './messages.js'; diff --git a/shared/src/messages.ts b/shared/src/messages.ts new file mode 100644 index 0000000..3c69c5b --- /dev/null +++ b/shared/src/messages.ts @@ -0,0 +1,45 @@ +import type { Collision } from './collision.js'; +import type { Intervention, InterventionStatus } from './intervention.js'; + +/** Topics multiplexed over the LiveKit data channel. */ +export const DATA_TOPIC = 'podman.intervention' as const; + +/** Wire messages exchanged between the PodMan agent and engineer PWAs. */ +export type DataMessage = + | { type: 'COLLISION'; collision: Collision; intervention: Intervention } + | { type: 'VOICE_CUE'; text: string } + | { type: 'ACK'; interventionId: string; status: InterventionStatus; note?: string } + | { type: 'GIT_REPORT'; report: LocalGitReport }; + +/** Outcome of an intervention — the supervision signal for policy learning. */ +export interface InterventionOutcome { + interventionId: string; + collisionId: string; + podId: string; + /** Did the predicted collision turn out real? (engineer-confirmed or inferred). */ + wasRealCollision: boolean; + /** Did the engineer accept the offered action (e.g. sync PR)? */ + accepted: boolean; + recordedAt: string; +} + +/** The continually-refined per-pod world model (Loop A). */ +export interface TeamModel { + podId: string; + /** filePath/dir -> engineerId most associated with it (de-facto owner). */ + ownership: Record; + /** Pairs of files that historically collide, with a co-occurrence weight. */ + hotspots: Array<{ files: [string, string]; weight: number }>; + updatedAt: string; +} + +/** OPTIONAL Tier-2 ground-truth from a per-laptop git sidecar. */ +export interface LocalGitReport { + engineerId: string; + branch: string; + /** Commits ahead of upstream (invisible to the GitHub API). */ + unpushedCount: number; + /** Working-tree files with uncommitted edits. */ + dirtyFiles: string[]; + reportedAt: string; +}