+
Omnia Play
+
+ {!snapshot && (
+
+
+
Start New Simulation
+
+
+
+
+
Resume Simulation
+ {savedSessions.length === 0 ? (
+
No saved sessions found. Start a new one!
+ ) : (
+
+ {savedSessions.map((s) => (
+
+
+ {s.scenarioName}
+
+ Turn {s.turn} · {s.entities.length} entities · {s.status}
+
+
+ Session ID: {s.id}
+
+
+
+ handleResume(s.id)} disabled={loading}>
+ Resume
+
+ handleDelete(s.id, e)}
+ disabled={loading}
+ className="delete-btn"
+ title="Delete Session"
+ >
+ Delete
+
+
+
+ ))}
+
+ )}
+
+
+ )}
+
+ {snapshot && (
+ <>
+
+
+
{snapshot.scenarioName}
+ {snapshot.status !== "done" && snapshot.status !== "error" && (
+ {
+ setSnapshot(null);
+ setError("");
+ }}
+ >
+ Stop
+
+ )}
+
+
{snapshot.scenarioDescription}
+
+ {loading && "⏳ "}
+ {statusMessage()}
+
+
+
+
+ {snapshot.log.map((entry, i) => (
+
+ ))}
+ {loading && (
+
+
+ {statusText || "Processing..."}
+
+ )}
+
+
+
+ {snapshot.status === "waiting_player" && snapshot.waitingEntity && (
+
+
+
+
+ Your context as {snapshot.waitingEntity.name}
+
+
+
+ {snapshot.waitingEntity.userContext}
+
+
+
+
+
+ )}
+
+ {(snapshot.status === "done" || snapshot.status === "error") && (
+
{
+ setSnapshot(null);
+ setError("");
+ }}
+ className="new-sim-btn"
+ >
+ {snapshot.status === "error" ? "Try Again" : "New Simulation"}
+
+ )}
+
+ {error && !loading && (
+
+ {error}
+
+ )}
+
+ {selectedEntryForModal && (
+
setSelectedEntryForModal(null)}
+ />
+ )}
+ >
+ )}
+
+
+
+ );
+}
diff --git a/apps/gui/src/lib/simulation-types.ts b/apps/gui/src/lib/simulation-types.ts
new file mode 100644
index 0000000..288958d
--- /dev/null
+++ b/apps/gui/src/lib/simulation-types.ts
@@ -0,0 +1,62 @@
+export interface IntentInfo {
+ type: string;
+ description: string;
+ targetIds: string[];
+ isValid?: boolean;
+ reason?: string;
+ minutesToAdvance?: number;
+}
+
+export interface LogEntry {
+ turn: number;
+ entityId: string;
+ entityName: string;
+ narrativeProse: string;
+ intents: IntentInfo[];
+ timestamp: string;
+ rawPrompt?: {
+ systemPrompt: string;
+ userContext: string;
+ };
+ usage?: {
+ inputTokens: number;
+ outputTokens: number;
+ totalTokens: number;
+ };
+ decoderPrompt?: {
+ systemPrompt: string;
+ userContext: string;
+ };
+ decoderUsage?: {
+ inputTokens: number;
+ outputTokens: number;
+ totalTokens: number;
+ };
+}
+
+export interface EntityInfo {
+ id: string;
+ name: string;
+ isPlayer: boolean;
+}
+
+export interface WaitingContext {
+ entityId: string;
+ name: string;
+ systemPrompt: string;
+ userContext: string;
+}
+
+export interface SimSnapshot {
+ id: string;
+ status: "running" | "waiting_player" | "done" | "error";
+ turn: number;
+ maxTurns: number;
+ scenarioName: string;
+ scenarioDescription: string;
+ entities: EntityInfo[];
+ log: LogEntry[];
+ entityIndex: number;
+ waitingEntity?: WaitingContext;
+ error?: string;
+}
diff --git a/apps/gui/src/lib/simulation.ts b/apps/gui/src/lib/simulation.ts
new file mode 100644
index 0000000..0218dd8
--- /dev/null
+++ b/apps/gui/src/lib/simulation.ts
@@ -0,0 +1,751 @@
+import dotenv from "dotenv";
+import Database from "better-sqlite3";
+import path from "path";
+import fs from "fs";
+import { SQLiteRepository } from "@omnia/core";
+
+// Load .env from monorepo root or apps/gui/
+const cwd = process.cwd();
+const envCandidates = [
+ path.resolve(cwd, ".env"),
+ path.resolve(cwd, "../../.env"),
+];
+for (const c of envCandidates) {
+ if (fs.existsSync(c) && fs.statSync(c).isFile()) {
+ dotenv.config({ path: c });
+ break;
+ }
+}
+
+import { BufferRepository } from "@omnia/memory";
+import { Architect, AliasDeltaGenerator } from "@omnia/architect";
+import {
+ ActorAgent,
+ ActorPromptBuilder,
+ IActorProseGenerator,
+ buildBufferEntryForIntent,
+} from "@omnia/actor";
+import { GeminiProvider } from "@omnia/llm";
+import { ScenarioLoader } from "@omnia/scenario";
+
+import type {
+ IntentInfo,
+ LogEntry,
+ EntityInfo,
+ WaitingContext,
+ SimSnapshot,
+} from "./simulation-types.js";
+
+export type { SimSnapshot, EntityInfo, LogEntry, IntentInfo, WaitingContext };
+
+class FixedProseGenerator implements IActorProseGenerator {
+ constructor(private prose: string) {}
+
+ async generate(
+ entityId: string,
+ systemPrompt: string,
+ userContext: string,
+ ): Promise