@bytespell/amux 0.0.1 → 0.0.2

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.
@@ -1,347 +1,15 @@
1
- var __defProp = Object.defineProperty;
2
- var __export = (target, all) => {
3
- for (var name in all)
4
- __defProp(target, name, { get: all[name], enumerable: true });
5
- };
6
-
7
- // src/agents/eventStore.ts
8
- import { randomUUID as randomUUID3 } from "crypto";
9
-
10
- // src/db/index.ts
11
- import { randomUUID as randomUUID2 } from "crypto";
12
- import Database from "better-sqlite3";
13
- import { drizzle } from "drizzle-orm/better-sqlite3";
14
-
15
- // src/lib/paths.ts
16
- import path from "path";
17
- import os from "os";
18
- import fs from "fs";
19
- var STARTUP_CWD = process.cwd();
20
- function getDataDir() {
21
- const home = os.homedir();
22
- let dataDir;
23
- switch (process.platform) {
24
- case "darwin":
25
- dataDir = path.join(home, "Library", "Application Support", "shella");
26
- break;
27
- case "win32":
28
- dataDir = path.join(process.env.APPDATA || path.join(home, "AppData", "Roaming"), "shella");
29
- break;
30
- default:
31
- dataDir = path.join(process.env.XDG_DATA_HOME || path.join(home, ".local", "share"), "shella");
32
- }
33
- return dataDir;
34
- }
35
- function isMockMode() {
36
- return process.env.SHELLA_MOCK_MODE === "true";
37
- }
38
- function getDbPath() {
39
- const filename = isMockMode() ? "shella.mock.db" : "shella.db";
40
- return path.join(getDataDir(), filename);
41
- }
42
- function ensureDir(dir) {
43
- if (!fs.existsSync(dir)) {
44
- fs.mkdirSync(dir, { recursive: true });
45
- }
46
- }
47
-
48
- // src/db/schema.ts
49
- var schema_exports = {};
50
- __export(schema_exports, {
51
- agentConfigs: () => agentConfigs,
52
- appState: () => appState,
53
- sessionEvents: () => sessionEvents,
54
- sessions: () => sessions
55
- });
56
- import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
57
- import { sql } from "drizzle-orm";
58
- var agentConfigs = sqliteTable("agent_configs", {
59
- id: text("id").primaryKey(),
60
- name: text("name").notNull(),
61
- command: text("command").notNull(),
62
- args: text("args", { mode: "json" }).$type().default([]),
63
- env: text("env", { mode: "json" }).$type().default({}),
64
- createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
65
- });
66
- var sessions = sqliteTable("sessions", {
67
- id: text("id").primaryKey(),
68
- directory: text("directory").notNull(),
69
- agentConfigId: text("agent_config_id").notNull().references(() => agentConfigs.id),
70
- // ACP protocol session ID for resuming (internal to agent protocol)
71
- acpSessionId: text("acp_session_id"),
72
- // Title from agent (via session_info_update)
73
- title: text("title"),
74
- model: text("model"),
75
- mode: text("mode"),
76
- createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
77
- });
78
- var appState = sqliteTable("app_state", {
79
- key: text("key").primaryKey(),
80
- value: text("value").notNull()
81
- });
82
- var sessionEvents = sqliteTable("session_events", {
83
- id: text("id").primaryKey(),
84
- sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
85
- turnId: text("turn_id").notNull(),
86
- sequenceNum: integer("sequence_num").notNull(),
87
- eventKind: text("event_kind").notNull(),
88
- payload: text("payload", { mode: "json" }).notNull(),
89
- createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull().default(sql`(unixepoch() * 1000)`)
90
- });
91
-
92
- // src/db/seed.ts
93
- import { randomUUID } from "crypto";
94
- import { execSync } from "child_process";
95
- var MOCK_AGENT_ID = "mock-agent";
96
- function commandExists(cmd) {
97
- try {
98
- execSync(`which ${cmd}`, { stdio: "ignore" });
99
- return true;
100
- } catch {
101
- return false;
102
- }
103
- }
104
- function detectAgents() {
105
- const agents = [];
106
- agents.push({
107
- id: randomUUID(),
108
- name: "Claude",
109
- command: "npx",
110
- args: ["@zed-industries/claude-code-acp"],
111
- env: {}
112
- });
113
- if (commandExists("opencode")) {
114
- agents.push({
115
- id: randomUUID(),
116
- name: "OpenCode",
117
- command: "opencode",
118
- args: ["acp"],
119
- env: {}
120
- });
121
- }
122
- return agents;
123
- }
124
- var MOCK_CONFIGS = [
125
- {
126
- id: MOCK_AGENT_ID,
127
- name: "Test Agent",
128
- command: "__stress__",
129
- args: [],
130
- env: {}
131
- }
132
- ];
133
- function seedAgentConfigs() {
134
- if (isMockMode()) {
135
- const existing2 = db.select().from(agentConfigs).all();
136
- if (existing2.length === 0) {
137
- for (const config of MOCK_CONFIGS) {
138
- db.insert(agentConfigs).values(config).run();
139
- }
140
- console.log("[db] Seeded mock agent configs");
141
- }
142
- seedMockSessions();
143
- return;
144
- }
145
- const detected = detectAgents();
146
- const existing = db.select().from(agentConfigs).all();
147
- let added = 0;
148
- for (const agent of detected) {
149
- const alreadyExists = existing.some(
150
- (e) => e.command === agent.command && JSON.stringify(e.args) === JSON.stringify(agent.args)
151
- );
152
- if (!alreadyExists) {
153
- db.insert(agentConfigs).values(agent).run();
154
- console.log(`[db] Detected new agent: ${agent.name}`);
155
- added++;
156
- }
157
- }
158
- if (added === 0 && detected.length > 0) {
159
- console.log(`[db] ${detected.length} known agent(s) already configured`);
160
- }
161
- }
162
- function seedMockSessions() {
163
- const existing = db.select().from(sessions).all();
164
- if (existing.length > 0) return;
165
- const count = getSessionCount(10);
166
- for (let i = 1; i <= count; i++) {
167
- db.insert(sessions).values({
168
- id: randomUUID(),
169
- directory: "~",
170
- agentConfigId: MOCK_AGENT_ID
171
- }).run();
172
- }
173
- console.log(`[db] Seeded ${count} test sessions`);
174
- }
175
- function getSessionCount(defaultCount) {
176
- const envCount = process.env.SHELLA_MOCK_SESSIONS ?? process.env.SHELLA_MOCK_WINDOWS;
177
- if (envCount) {
178
- const parsed = parseInt(envCount, 10);
179
- if (!isNaN(parsed) && parsed > 0) {
180
- return parsed;
181
- }
182
- }
183
- return defaultCount;
184
- }
185
-
186
- // src/db/index.ts
187
- ensureDir(getDataDir());
188
- var sqlite = new Database(getDbPath());
189
- sqlite.pragma("journal_mode = WAL");
190
- sqlite.pragma("foreign_keys = ON");
191
- var db = drizzle(sqlite, { schema: schema_exports });
192
- sqlite.exec(`
193
- CREATE TABLE IF NOT EXISTS agent_configs (
194
- id TEXT PRIMARY KEY,
195
- name TEXT NOT NULL,
196
- command TEXT NOT NULL,
197
- args TEXT DEFAULT '[]',
198
- env TEXT DEFAULT '{}',
199
- created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
200
- );
201
-
202
- CREATE TABLE IF NOT EXISTS sessions (
203
- id TEXT PRIMARY KEY,
204
- directory TEXT NOT NULL,
205
- agent_config_id TEXT NOT NULL REFERENCES agent_configs(id),
206
- acp_session_id TEXT,
207
- title TEXT,
208
- model TEXT,
209
- mode TEXT,
210
- created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
211
- );
212
-
213
- CREATE TABLE IF NOT EXISTS app_state (
214
- key TEXT PRIMARY KEY,
215
- value TEXT NOT NULL
216
- );
217
-
218
- CREATE TABLE IF NOT EXISTS session_events (
219
- id TEXT PRIMARY KEY,
220
- session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
221
- turn_id TEXT NOT NULL,
222
- sequence_num INTEGER NOT NULL,
223
- event_kind TEXT NOT NULL,
224
- payload TEXT NOT NULL,
225
- created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
226
- );
227
- `);
228
- try {
229
- const oldExists = sqlite.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='client_windows'`).get();
230
- const newCount = sqlite.prepare(`SELECT COUNT(*) as count FROM sessions`).get();
231
- if (oldExists && newCount.count === 0) {
232
- console.log("[db] Migrating from client_windows/window_events to sessions/session_events...");
233
- sqlite.exec(`
234
- INSERT INTO sessions (id, directory, agent_config_id, acp_session_id, model, mode, created_at)
235
- SELECT id, directory, agent_config_id, session_id, model, mode, created_at
236
- FROM client_windows;
237
-
238
- INSERT INTO session_events (id, session_id, turn_id, sequence_num, event_kind, payload, created_at)
239
- SELECT id, window_id, turn_id, sequence_num, event_kind, payload, created_at
240
- FROM window_events;
241
-
242
- -- Migrate custom titles to app_state
243
- INSERT OR IGNORE INTO app_state (key, value)
244
- SELECT 'window_title_' || id, title
245
- FROM client_windows
246
- WHERE has_custom_title = 1 AND title != '';
247
-
248
- -- Migrate active window
249
- INSERT OR REPLACE INTO app_state (key, value)
250
- SELECT 'active_window_id', value
251
- FROM app_state
252
- WHERE key = 'active_window_id';
253
- `);
254
- sqlite.exec(`
255
- DROP TABLE IF EXISTS window_events;
256
- DROP TABLE IF EXISTS client_windows;
257
- `);
258
- console.log("[db] Migration complete");
259
- }
260
- } catch (e) {
261
- }
262
- try {
263
- sqlite.exec(`ALTER TABLE sessions ADD COLUMN title TEXT`);
264
- } catch (e) {
265
- }
266
- var orphanedTurns = sqlite.prepare(`
267
- SELECT DISTINCT turn_id, session_id
268
- FROM session_events
269
- WHERE event_kind = 'turn_start'
270
- AND turn_id NOT IN (
271
- SELECT turn_id FROM session_events WHERE event_kind = 'turn_end'
272
- )
273
- `).all();
274
- if (orphanedTurns.length > 0) {
275
- const getMaxSeq = sqlite.prepare(`
276
- SELECT COALESCE(MAX(sequence_num), 0) + 1 as next_seq
277
- FROM session_events WHERE turn_id = ?
278
- `);
279
- const insert = sqlite.prepare(`
280
- INSERT INTO session_events (id, session_id, turn_id, sequence_num, event_kind, payload)
281
- VALUES (?, ?, ?, ?, 'turn_end', '{"amuxEvent":"turn_end"}')
282
- `);
283
- for (const turn of orphanedTurns) {
284
- const { next_seq } = getMaxSeq.get(turn.turn_id);
285
- insert.run(randomUUID2(), turn.session_id, turn.turn_id, next_seq);
286
- }
287
- console.log(`[db] Fixed ${orphanedTurns.length} orphaned turn(s) from previous session`);
288
- }
289
- seedAgentConfigs();
290
-
291
- // src/agents/eventStore.ts
292
- import { eq, asc } from "drizzle-orm";
293
-
294
- // src/types.ts
295
- function isSessionUpdate(update) {
296
- return "sessionUpdate" in update;
297
- }
298
- function isAmuxEvent(update) {
299
- return "amuxEvent" in update;
300
- }
301
-
302
- // src/agents/eventStore.ts
303
- var turnState = /* @__PURE__ */ new Map();
304
- function startTurn(sessionId) {
305
- const turnId = randomUUID3();
306
- turnState.set(sessionId, { turnId, seq: 0 });
307
- return turnId;
308
- }
309
- function storeEvent(sessionId, update) {
310
- let eventKind;
311
- if (isSessionUpdate(update)) {
312
- eventKind = update.sessionUpdate;
313
- } else if (isAmuxEvent(update) && (update.amuxEvent === "turn_start" || update.amuxEvent === "turn_end")) {
314
- eventKind = update.amuxEvent;
315
- } else {
316
- return;
317
- }
318
- let state = turnState.get(sessionId);
319
- if (!state) {
320
- state = { turnId: randomUUID3(), seq: 0 };
321
- turnState.set(sessionId, state);
322
- }
323
- const seq = state.seq++;
324
- db.insert(sessionEvents).values({
325
- id: randomUUID3(),
326
- sessionId,
327
- turnId: state.turnId,
328
- sequenceNum: seq,
329
- eventKind,
330
- payload: update
331
- }).run();
332
- }
333
- function endTurn(sessionId) {
334
- turnState.delete(sessionId);
335
- }
336
- function getEventsForSession(sessionId) {
337
- const rows = db.select().from(sessionEvents).where(eq(sessionEvents.sessionId, sessionId)).orderBy(asc(sessionEvents.createdAt), asc(sessionEvents.sequenceNum)).all();
338
- return rows.map((row) => row.payload);
339
- }
340
- function clearEventsForSession(sessionId) {
341
- db.delete(sessionEvents).where(eq(sessionEvents.sessionId, sessionId)).run();
342
- }
343
- var getEventsForWindow = getEventsForSession;
344
- var clearEventsForWindow = clearEventsForSession;
1
+ import {
2
+ clearEventsForSession,
3
+ clearEventsForWindow,
4
+ endTurn,
5
+ getEventsForSession,
6
+ getEventsForWindow,
7
+ startTurn,
8
+ storeEvent
9
+ } from "../../chunk-SX7NC3ZM.js";
10
+ import "../../chunk-226DBKL3.js";
11
+ import "../../chunk-OQ5K5ON2.js";
12
+ import "../../chunk-PZ5AY32C.js";
345
13
  export {
346
14
  clearEventsForSession,
347
15
  clearEventsForWindow,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/agents/eventStore.ts","../../../src/db/index.ts","../../../src/lib/paths.ts","../../../src/db/schema.ts","../../../src/db/seed.ts","../../../src/types.ts"],"sourcesContent":["import { randomUUID } from 'crypto';\nimport { db } from '../db/index.js';\nimport { sessionEvents } from '../db/schema.js';\nimport { eq, asc } from 'drizzle-orm';\nimport type { WindowUpdate } from '../types.js';\nimport { isSessionUpdate, isAmuxEvent } from '../types.js';\n\n// In-memory state for tracking current turn per session\nconst turnState = new Map<string, { turnId: string; seq: number }>();\n\n/**\n * Start a new prompt turn for a session\n */\nexport function startTurn(sessionId: string): string {\n const turnId = randomUUID();\n turnState.set(sessionId, { turnId, seq: 0 });\n return turnId;\n}\n\n/**\n * Store an event. Persists SessionUpdate events and turn_start/turn_end (for streaming state replay).\n * Other AmuxEvents (error, permission_request, permission_cleared) are transient.\n */\nexport function storeEvent(sessionId: string, update: WindowUpdate): void {\n // Determine event kind for storage\n let eventKind: string;\n if (isSessionUpdate(update)) {\n eventKind = update.sessionUpdate;\n } else if (isAmuxEvent(update) && (update.amuxEvent === 'turn_start' || update.amuxEvent === 'turn_end')) {\n eventKind = update.amuxEvent;\n } else {\n // Skip other amux events (error, permission_request, permission_cleared)\n return;\n }\n\n // Get or create turn state (handles events that arrive before startTurn)\n let state = turnState.get(sessionId);\n if (!state) {\n state = { turnId: randomUUID(), seq: 0 };\n turnState.set(sessionId, state);\n }\n\n const seq = state.seq++;\n\n db.insert(sessionEvents)\n .values({\n id: randomUUID(),\n sessionId,\n turnId: state.turnId,\n sequenceNum: seq,\n eventKind,\n payload: update as unknown as Record<string, unknown>,\n })\n .run();\n}\n\n/**\n * End the current turn for a session\n */\nexport function endTurn(sessionId: string): void {\n turnState.delete(sessionId);\n}\n\n/**\n * Get all stored events for a session, ordered for replay.\n * Returns both SessionUpdate events and turn_end events.\n */\nexport function getEventsForSession(sessionId: string): WindowUpdate[] {\n const rows = db\n .select()\n .from(sessionEvents)\n .where(eq(sessionEvents.sessionId, sessionId))\n .orderBy(asc(sessionEvents.createdAt), asc(sessionEvents.sequenceNum))\n .all();\n\n return rows.map((row) => row.payload as WindowUpdate);\n}\n\n/**\n * Clear all events for a session (e.g., when session is deleted)\n */\nexport function clearEventsForSession(sessionId: string): void {\n db.delete(sessionEvents).where(eq(sessionEvents.sessionId, sessionId)).run();\n}\n\n// Legacy aliases for compatibility during migration\n/** @deprecated Use getEventsForSession instead */\nexport const getEventsForWindow = getEventsForSession;\n/** @deprecated Use clearEventsForSession instead */\nexport const clearEventsForWindow = clearEventsForSession;\n","import { randomUUID } from 'node:crypto';\nimport Database from 'better-sqlite3';\nimport { drizzle } from 'drizzle-orm/better-sqlite3';\nimport { getDbPath, ensureDir, getDataDir } from '../lib/paths.js';\nimport * as schema from './schema.js';\nimport { seedAgentConfigs } from './seed.js';\n\nensureDir(getDataDir());\n\nconst sqlite = new Database(getDbPath());\nsqlite.pragma('journal_mode = WAL');\nsqlite.pragma('foreign_keys = ON');\n\nexport const db = drizzle(sqlite, { schema });\n\n// Create tables if they don't exist\nsqlite.exec(`\n CREATE TABLE IF NOT EXISTS agent_configs (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n command TEXT NOT NULL,\n args TEXT DEFAULT '[]',\n env TEXT DEFAULT '{}',\n created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)\n );\n\n CREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n directory TEXT NOT NULL,\n agent_config_id TEXT NOT NULL REFERENCES agent_configs(id),\n acp_session_id TEXT,\n title TEXT,\n model TEXT,\n mode TEXT,\n created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)\n );\n\n CREATE TABLE IF NOT EXISTS app_state (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS session_events (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,\n turn_id TEXT NOT NULL,\n sequence_num INTEGER NOT NULL,\n event_kind TEXT NOT NULL,\n payload TEXT NOT NULL,\n created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)\n );\n`);\n\n// Migration: rename old tables if they exist\ntry {\n // Check if old tables exist and new ones don't have data\n const oldExists = sqlite.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='client_windows'`).get();\n const newCount = sqlite.prepare(`SELECT COUNT(*) as count FROM sessions`).get() as { count: number };\n\n if (oldExists && newCount.count === 0) {\n console.log('[db] Migrating from client_windows/window_events to sessions/session_events...');\n\n // Migrate data from old tables to new tables\n sqlite.exec(`\n INSERT INTO sessions (id, directory, agent_config_id, acp_session_id, model, mode, created_at)\n SELECT id, directory, agent_config_id, session_id, model, mode, created_at\n FROM client_windows;\n\n INSERT INTO session_events (id, session_id, turn_id, sequence_num, event_kind, payload, created_at)\n SELECT id, window_id, turn_id, sequence_num, event_kind, payload, created_at\n FROM window_events;\n\n -- Migrate custom titles to app_state\n INSERT OR IGNORE INTO app_state (key, value)\n SELECT 'window_title_' || id, title\n FROM client_windows\n WHERE has_custom_title = 1 AND title != '';\n\n -- Migrate active window\n INSERT OR REPLACE INTO app_state (key, value)\n SELECT 'active_window_id', value\n FROM app_state\n WHERE key = 'active_window_id';\n `);\n\n // Drop old tables\n sqlite.exec(`\n DROP TABLE IF EXISTS window_events;\n DROP TABLE IF EXISTS client_windows;\n `);\n\n console.log('[db] Migration complete');\n }\n} catch (e) {\n // Migration not needed or already done\n}\n\n// Migration: add title column to sessions if missing\ntry {\n sqlite.exec(`ALTER TABLE sessions ADD COLUMN title TEXT`);\n} catch (e) {\n // Column already exists\n}\n\n// Fix orphaned turns from previous crash (turn_start without turn_end)\n// Using raw SQL to avoid circular dependency with eventStore\nconst orphanedTurns = sqlite.prepare(`\n SELECT DISTINCT turn_id, session_id\n FROM session_events\n WHERE event_kind = 'turn_start'\n AND turn_id NOT IN (\n SELECT turn_id FROM session_events WHERE event_kind = 'turn_end'\n )\n`).all() as { turn_id: string; session_id: string }[];\n\nif (orphanedTurns.length > 0) {\n const getMaxSeq = sqlite.prepare(`\n SELECT COALESCE(MAX(sequence_num), 0) + 1 as next_seq\n FROM session_events WHERE turn_id = ?\n `);\n const insert = sqlite.prepare(`\n INSERT INTO session_events (id, session_id, turn_id, sequence_num, event_kind, payload)\n VALUES (?, ?, ?, ?, 'turn_end', '{\"amuxEvent\":\"turn_end\"}')\n `);\n\n for (const turn of orphanedTurns) {\n const { next_seq } = getMaxSeq.get(turn.turn_id) as { next_seq: number };\n insert.run(randomUUID(), turn.session_id, turn.turn_id, next_seq);\n }\n\n console.log(`[db] Fixed ${orphanedTurns.length} orphaned turn(s) from previous session`);\n}\n\n// Seed defaults\nseedAgentConfigs();\n\nexport { schema };\n","import path from 'path';\nimport os from 'os';\nimport fs from 'fs';\n\n// Capture at module load time (before any chdir)\nconst STARTUP_CWD = process.cwd();\n\n/**\n * Get the working directory from when the server started\n */\nexport function getStartupCwd(): string {\n return STARTUP_CWD;\n}\n\n/**\n * Get the data directory following XDG Base Directory spec\n * - Linux: ~/.local/share/shella\n * - macOS: ~/Library/Application Support/shella\n * - Windows: %APPDATA%/shella\n */\nexport function getDataDir(): string {\n const home = os.homedir();\n\n let dataDir: string;\n\n switch (process.platform) {\n case 'darwin':\n dataDir = path.join(home, 'Library', 'Application Support', 'shella');\n break;\n case 'win32':\n dataDir = path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'shella');\n break;\n default:\n // Linux and others - follow XDG spec\n dataDir = path.join(process.env.XDG_DATA_HOME || path.join(home, '.local', 'share'), 'shella');\n }\n\n return dataDir;\n}\n\n/**\n * Get the config directory following XDG Base Directory spec\n * - Linux: ~/.config/shella\n * - macOS: ~/Library/Application Support/shella\n * - Windows: %APPDATA%/shella\n */\nexport function getConfigDir(): string {\n const home = os.homedir();\n\n let configDir: string;\n\n switch (process.platform) {\n case 'darwin':\n configDir = path.join(home, 'Library', 'Application Support', 'shella');\n break;\n case 'win32':\n configDir = path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'shella');\n break;\n default:\n // Linux and others - follow XDG spec\n configDir = path.join(process.env.XDG_CONFIG_HOME || path.join(home, '.config'), 'shella');\n }\n\n return configDir;\n}\n\n/**\n * Get the cache directory\n * - Linux: ~/.cache/shella\n * - macOS: ~/Library/Caches/shella\n * - Windows: %LOCALAPPDATA%/shella/cache\n */\nexport function getCacheDir(): string {\n const home = os.homedir();\n\n let cacheDir: string;\n\n switch (process.platform) {\n case 'darwin':\n cacheDir = path.join(home, 'Library', 'Caches', 'shella');\n break;\n case 'win32':\n cacheDir = path.join(process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local'), 'shella', 'cache');\n break;\n default:\n // Linux and others - follow XDG spec\n cacheDir = path.join(process.env.XDG_CACHE_HOME || path.join(home, '.cache'), 'shella');\n }\n\n return cacheDir;\n}\n\n/**\n * Check if running in mock mode\n */\nexport function isMockMode(): boolean {\n return process.env.SHELLA_MOCK_MODE === 'true';\n}\n\n/**\n * Get the path to the database file\n */\nexport function getDbPath(): string {\n const filename = isMockMode() ? 'shella.mock.db' : 'shella.db';\n return path.join(getDataDir(), filename);\n}\n\n/**\n * Ensure a directory exists, creating it if necessary\n */\nexport function ensureDir(dir: string): void {\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n}\n\n/**\n * Ensure all shella directories exist\n */\nexport function ensureAllDirs(): void {\n ensureDir(getDataDir());\n ensureDir(getConfigDir());\n ensureDir(getCacheDir());\n}\n","import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';\nimport { sql } from 'drizzle-orm';\n\n// User-configured agent types (e.g. claude-code-acp, opencode, etc.)\nexport const agentConfigs = sqliteTable('agent_configs', {\n id: text('id').primaryKey(),\n name: text('name').notNull(),\n command: text('command').notNull(),\n args: text('args', { mode: 'json' }).$type<string[]>().default([]),\n env: text('env', { mode: 'json' }).$type<Record<string, string>>().default({}),\n createdAt: integer('created_at', { mode: 'timestamp_ms' })\n .notNull()\n .default(sql`(unixepoch() * 1000)`),\n});\n\n// Agent sessions - each session is an independent agent instance.\nexport const sessions = sqliteTable('sessions', {\n id: text('id').primaryKey(),\n directory: text('directory').notNull(),\n agentConfigId: text('agent_config_id').notNull().references(() => agentConfigs.id),\n // ACP protocol session ID for resuming (internal to agent protocol)\n acpSessionId: text('acp_session_id'),\n // Title from agent (via session_info_update)\n title: text('title'),\n model: text('model'),\n mode: text('mode'),\n createdAt: integer('created_at', { mode: 'timestamp_ms' })\n .notNull()\n .default(sql`(unixepoch() * 1000)`),\n});\n\nexport const appState = sqliteTable('app_state', {\n key: text('key').primaryKey(),\n value: text('value').notNull(),\n});\n\n// Persisted session events for replay on reconnect\nexport const sessionEvents = sqliteTable('session_events', {\n id: text('id').primaryKey(),\n sessionId: text('session_id')\n .notNull()\n .references(() => sessions.id, { onDelete: 'cascade' }),\n turnId: text('turn_id').notNull(),\n sequenceNum: integer('sequence_num').notNull(),\n eventKind: text('event_kind').notNull(),\n payload: text('payload', { mode: 'json' }).notNull(),\n createdAt: integer('created_at', { mode: 'timestamp_ms' })\n .notNull()\n .default(sql`(unixepoch() * 1000)`),\n});\n\nexport type AgentConfig = typeof agentConfigs.$inferSelect;\nexport type NewAgentConfig = typeof agentConfigs.$inferInsert;\nexport type Session = typeof sessions.$inferSelect;\nexport type NewSession = typeof sessions.$inferInsert;\nexport type StoredSessionEvent = typeof sessionEvents.$inferSelect;\nexport type NewStoredSessionEvent = typeof sessionEvents.$inferInsert;\n\n// Legacy aliases for compatibility during migration\n/** @deprecated Use Session instead */\nexport type ClientWindow = Session;\n/** @deprecated Use NewSession instead */\nexport type NewClientWindow = NewSession;\n","import { db } from './index.js';\nimport { agentConfigs, sessions } from './schema.js';\nimport { randomUUID } from 'crypto';\nimport { execSync } from 'child_process';\nimport { isMockMode } from '../lib/paths.js';\n\nconst MOCK_AGENT_ID = 'mock-agent';\n\n// Check if a command exists on the system\nfunction commandExists(cmd: string): boolean {\n try {\n execSync(`which ${cmd}`, { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n}\n\n// Detect available ACP-compatible agents\nfunction detectAgents(): Array<{ id: string; name: string; command: string; args: string[]; env: Record<string, string> }> {\n const agents: Array<{ id: string; name: string; command: string; args: string[]; env: Record<string, string> }> = [];\n\n // Claude - always available via npx\n agents.push({\n id: randomUUID(),\n name: 'Claude',\n command: 'npx',\n args: ['@zed-industries/claude-code-acp'],\n env: {},\n });\n\n // OpenCode - check if installed\n if (commandExists('opencode')) {\n agents.push({\n id: randomUUID(),\n name: 'OpenCode',\n command: 'opencode',\n args: ['acp'],\n env: {},\n });\n }\n\n return agents;\n}\n\n// Mock mode now uses the stress backend for full testing capabilities\nconst MOCK_CONFIGS = [\n {\n id: MOCK_AGENT_ID,\n name: 'Test Agent',\n command: '__stress__',\n args: [],\n env: {},\n },\n];\n\nexport function seedAgentConfigs() {\n if (isMockMode()) {\n // Mock mode: seed once as before\n const existing = db.select().from(agentConfigs).all();\n if (existing.length === 0) {\n for (const config of MOCK_CONFIGS) {\n db.insert(agentConfigs).values(config).run();\n }\n console.log('[db] Seeded mock agent configs');\n }\n seedMockSessions();\n return;\n }\n\n // Production: detect and add new agents on every launch\n const detected = detectAgents();\n const existing = db.select().from(agentConfigs).all();\n\n let added = 0;\n for (const agent of detected) {\n // Check if this agent already exists (by command + args)\n const alreadyExists = existing.some(\n (e) => e.command === agent.command &&\n JSON.stringify(e.args) === JSON.stringify(agent.args)\n );\n if (!alreadyExists) {\n db.insert(agentConfigs).values(agent).run();\n console.log(`[db] Detected new agent: ${agent.name}`);\n added++;\n }\n }\n\n if (added === 0 && detected.length > 0) {\n console.log(`[db] ${detected.length} known agent(s) already configured`);\n }\n}\n\nfunction seedMockSessions() {\n const existing = db.select().from(sessions).all();\n if (existing.length > 0) return;\n\n const count = getSessionCount(10);\n for (let i = 1; i <= count; i++) {\n db.insert(sessions).values({\n id: randomUUID(),\n directory: '~',\n agentConfigId: MOCK_AGENT_ID,\n }).run();\n }\n console.log(`[db] Seeded ${count} test sessions`);\n}\n\nfunction getSessionCount(defaultCount: number): number {\n // Support both new and legacy env var names\n const envCount = process.env.SHELLA_MOCK_SESSIONS ?? process.env.SHELLA_MOCK_WINDOWS;\n if (envCount) {\n const parsed = parseInt(envCount, 10);\n if (!isNaN(parsed) && parsed > 0) {\n return parsed;\n }\n }\n return defaultCount;\n}\n","// Re-export ACP types from SDK\nexport type {\n SessionUpdate,\n ContentBlock,\n ContentChunk,\n TextContent,\n ImageContent,\n AudioContent,\n EmbeddedResource,\n ResourceLink,\n PlanEntry as AcpPlanEntry,\n PlanEntryPriority,\n PlanEntryStatus,\n Plan,\n AvailableCommand,\n AvailableCommandsUpdate,\n CurrentModeUpdate,\n ToolCall,\n ToolCallUpdate,\n ToolCallStatus,\n PermissionOption,\n RequestPermissionRequest,\n RequestPermissionResponse,\n} from '@agentclientprotocol/sdk';\n\nimport type { PlanEntry as AcpPlanEntry, PlanEntryPriority, PlanEntryStatus, SessionUpdate } from '@agentclientprotocol/sdk';\n\n/**\n * Normalized PlanEntry that handles agent-specific quirks.\n *\n * Standard ACP: content, priority, status\n * Claude-style: content, status, activeForm (priority always \"medium\")\n *\n * The `activeForm` field provides a present-tense description (e.g., \"Analyzing tests\")\n * vs the imperative `content` (e.g., \"Analyze tests\"). When `activeForm` is present\n * and the status is \"in_progress\", UI should prefer displaying `activeForm`.\n */\nexport interface NormalizedPlanEntry {\n content: string;\n status: PlanEntryStatus;\n priority: PlanEntryPriority;\n /** Present-tense form for in_progress display (Claude-style). */\n activeForm?: string;\n}\n\n/**\n * Normalize an incoming ACP plan entry.\n * Extracts `activeForm` from Claude's `_meta.claudeCode` extension if present.\n */\nexport function normalizePlanEntry(entry: AcpPlanEntry): NormalizedPlanEntry {\n // Try to extract activeForm from _meta.claudeCode (if claude-acp sends it there)\n const meta = entry._meta as Record<string, unknown> | undefined;\n const claudeCode = meta?.claudeCode as Record<string, unknown> | undefined;\n const activeForm = claudeCode?.activeForm as string | undefined;\n\n // Also check if activeForm is directly on the entry (non-standard but possible)\n const directActiveForm = (entry as Record<string, unknown>).activeForm as string | undefined;\n\n return {\n content: entry.content,\n status: entry.status,\n priority: entry.priority,\n activeForm: activeForm ?? directActiveForm,\n };\n}\n\n// Model and mode info returned from agent session\nexport interface ModelInfo {\n modelId: string;\n name: string;\n}\n\nexport interface ModeInfo {\n id: string;\n name: string;\n description?: string;\n}\n\n// Permission types for WebSocket push\nexport interface PendingPermission {\n requestId: string;\n toolCallId?: string;\n title: string;\n options: Array<{ optionId: string; name: string; kind: string }>;\n}\n\n// amux events pushed via WebSocket subscription.\n// ACP blocking RPCs (requestPermission, prompt) need WebSocket notifications\n// so the UI can track state independently of RPC callbacks (e.g. after page reload).\n// Pattern: RPC start → notification, RPC complete → notification\nexport type AmuxEvent =\n | { amuxEvent: 'error'; message: string }\n | { amuxEvent: 'permission_request'; permission: PendingPermission }\n | { amuxEvent: 'permission_cleared' }\n | { amuxEvent: 'turn_start' }\n | { amuxEvent: 'turn_end' }\n | { amuxEvent: 'turn_cancelled' };\n\n// Combined type for session subscriptions\nexport type WindowUpdate = SessionUpdate | AmuxEvent;\n\n// Event emitted by amux when a session update occurs\nexport interface SessionEvent {\n sessionId: string;\n update: WindowUpdate;\n}\n\n// Type guard helpers\nexport function isSessionUpdate(update: WindowUpdate): update is SessionUpdate {\n return 'sessionUpdate' in update;\n}\n\nexport function isAmuxEvent(update: WindowUpdate): update is AmuxEvent {\n return 'amuxEvent' in update;\n}\n"],"mappings":";;;;;;;AAAA,SAAS,cAAAA,mBAAkB;;;ACA3B,SAAS,cAAAC,mBAAkB;AAC3B,OAAO,cAAc;AACrB,SAAS,eAAe;;;ACFxB,OAAO,UAAU;AACjB,OAAO,QAAQ;AACf,OAAO,QAAQ;AAGf,IAAM,cAAc,QAAQ,IAAI;AAezB,SAAS,aAAqB;AACnC,QAAM,OAAO,GAAG,QAAQ;AAExB,MAAI;AAEJ,UAAQ,QAAQ,UAAU;AAAA,IACxB,KAAK;AACH,gBAAU,KAAK,KAAK,MAAM,WAAW,uBAAuB,QAAQ;AACpE;AAAA,IACF,KAAK;AACH,gBAAU,KAAK,KAAK,QAAQ,IAAI,WAAW,KAAK,KAAK,MAAM,WAAW,SAAS,GAAG,QAAQ;AAC1F;AAAA,IACF;AAEE,gBAAU,KAAK,KAAK,QAAQ,IAAI,iBAAiB,KAAK,KAAK,MAAM,UAAU,OAAO,GAAG,QAAQ;AAAA,EACjG;AAEA,SAAO;AACT;AAyDO,SAAS,aAAsB;AACpC,SAAO,QAAQ,IAAI,qBAAqB;AAC1C;AAKO,SAAS,YAAoB;AAClC,QAAM,WAAW,WAAW,IAAI,mBAAmB;AACnD,SAAO,KAAK,KAAK,WAAW,GAAG,QAAQ;AACzC;AAKO,SAAS,UAAU,KAAmB;AAC3C,MAAI,CAAC,GAAG,WAAW,GAAG,GAAG;AACvB,OAAG,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,EACvC;AACF;;;AClHA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAS,aAAa,MAAM,eAAe;AAC3C,SAAS,WAAW;AAGb,IAAM,eAAe,YAAY,iBAAiB;AAAA,EACvD,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,EAC1B,MAAM,KAAK,MAAM,EAAE,QAAQ;AAAA,EAC3B,SAAS,KAAK,SAAS,EAAE,QAAQ;AAAA,EACjC,MAAM,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAC,EAAE,MAAgB,EAAE,QAAQ,CAAC,CAAC;AAAA,EACjE,KAAK,KAAK,OAAO,EAAE,MAAM,OAAO,CAAC,EAAE,MAA8B,EAAE,QAAQ,CAAC,CAAC;AAAA,EAC7E,WAAW,QAAQ,cAAc,EAAE,MAAM,eAAe,CAAC,EACtD,QAAQ,EACR,QAAQ,yBAAyB;AACtC,CAAC;AAGM,IAAM,WAAW,YAAY,YAAY;AAAA,EAC9C,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,EAC1B,WAAW,KAAK,WAAW,EAAE,QAAQ;AAAA,EACrC,eAAe,KAAK,iBAAiB,EAAE,QAAQ,EAAE,WAAW,MAAM,aAAa,EAAE;AAAA;AAAA,EAEjF,cAAc,KAAK,gBAAgB;AAAA;AAAA,EAEnC,OAAO,KAAK,OAAO;AAAA,EACnB,OAAO,KAAK,OAAO;AAAA,EACnB,MAAM,KAAK,MAAM;AAAA,EACjB,WAAW,QAAQ,cAAc,EAAE,MAAM,eAAe,CAAC,EACtD,QAAQ,EACR,QAAQ,yBAAyB;AACtC,CAAC;AAEM,IAAM,WAAW,YAAY,aAAa;AAAA,EAC/C,KAAK,KAAK,KAAK,EAAE,WAAW;AAAA,EAC5B,OAAO,KAAK,OAAO,EAAE,QAAQ;AAC/B,CAAC;AAGM,IAAM,gBAAgB,YAAY,kBAAkB;AAAA,EACzD,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,EAC1B,WAAW,KAAK,YAAY,EACzB,QAAQ,EACR,WAAW,MAAM,SAAS,IAAI,EAAE,UAAU,UAAU,CAAC;AAAA,EACxD,QAAQ,KAAK,SAAS,EAAE,QAAQ;AAAA,EAChC,aAAa,QAAQ,cAAc,EAAE,QAAQ;AAAA,EAC7C,WAAW,KAAK,YAAY,EAAE,QAAQ;AAAA,EACtC,SAAS,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC,EAAE,QAAQ;AAAA,EACnD,WAAW,QAAQ,cAAc,EAAE,MAAM,eAAe,CAAC,EACtD,QAAQ,EACR,QAAQ,yBAAyB;AACtC,CAAC;;;AC/CD,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AAGzB,IAAM,gBAAgB;AAGtB,SAAS,cAAc,KAAsB;AAC3C,MAAI;AACF,aAAS,SAAS,GAAG,IAAI,EAAE,OAAO,SAAS,CAAC;AAC5C,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,eAAkH;AACzH,QAAM,SAA4G,CAAC;AAGnH,SAAO,KAAK;AAAA,IACV,IAAI,WAAW;AAAA,IACf,MAAM;AAAA,IACN,SAAS;AAAA,IACT,MAAM,CAAC,iCAAiC;AAAA,IACxC,KAAK,CAAC;AAAA,EACR,CAAC;AAGD,MAAI,cAAc,UAAU,GAAG;AAC7B,WAAO,KAAK;AAAA,MACV,IAAI,WAAW;AAAA,MACf,MAAM;AAAA,MACN,SAAS;AAAA,MACT,MAAM,CAAC,KAAK;AAAA,MACZ,KAAK,CAAC;AAAA,IACR,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAGA,IAAM,eAAe;AAAA,EACnB;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,MAAM,CAAC;AAAA,IACP,KAAK,CAAC;AAAA,EACR;AACF;AAEO,SAAS,mBAAmB;AACjC,MAAI,WAAW,GAAG;AAEhB,UAAMC,YAAW,GAAG,OAAO,EAAE,KAAK,YAAY,EAAE,IAAI;AACpD,QAAIA,UAAS,WAAW,GAAG;AACzB,iBAAW,UAAU,cAAc;AACjC,WAAG,OAAO,YAAY,EAAE,OAAO,MAAM,EAAE,IAAI;AAAA,MAC7C;AACA,cAAQ,IAAI,gCAAgC;AAAA,IAC9C;AACA,qBAAiB;AACjB;AAAA,EACF;AAGA,QAAM,WAAW,aAAa;AAC9B,QAAM,WAAW,GAAG,OAAO,EAAE,KAAK,YAAY,EAAE,IAAI;AAEpD,MAAI,QAAQ;AACZ,aAAW,SAAS,UAAU;AAE5B,UAAM,gBAAgB,SAAS;AAAA,MAC7B,CAAC,MAAM,EAAE,YAAY,MAAM,WACpB,KAAK,UAAU,EAAE,IAAI,MAAM,KAAK,UAAU,MAAM,IAAI;AAAA,IAC7D;AACA,QAAI,CAAC,eAAe;AAClB,SAAG,OAAO,YAAY,EAAE,OAAO,KAAK,EAAE,IAAI;AAC1C,cAAQ,IAAI,4BAA4B,MAAM,IAAI,EAAE;AACpD;AAAA,IACF;AAAA,EACF;AAEA,MAAI,UAAU,KAAK,SAAS,SAAS,GAAG;AACtC,YAAQ,IAAI,QAAQ,SAAS,MAAM,oCAAoC;AAAA,EACzE;AACF;AAEA,SAAS,mBAAmB;AAC1B,QAAM,WAAW,GAAG,OAAO,EAAE,KAAK,QAAQ,EAAE,IAAI;AAChD,MAAI,SAAS,SAAS,EAAG;AAEzB,QAAM,QAAQ,gBAAgB,EAAE;AAChC,WAAS,IAAI,GAAG,KAAK,OAAO,KAAK;AAC/B,OAAG,OAAO,QAAQ,EAAE,OAAO;AAAA,MACzB,IAAI,WAAW;AAAA,MACf,WAAW;AAAA,MACX,eAAe;AAAA,IACjB,CAAC,EAAE,IAAI;AAAA,EACT;AACA,UAAQ,IAAI,eAAe,KAAK,gBAAgB;AAClD;AAEA,SAAS,gBAAgB,cAA8B;AAErD,QAAM,WAAW,QAAQ,IAAI,wBAAwB,QAAQ,IAAI;AACjE,MAAI,UAAU;AACZ,UAAM,SAAS,SAAS,UAAU,EAAE;AACpC,QAAI,CAAC,MAAM,MAAM,KAAK,SAAS,GAAG;AAChC,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;;;AH/GA,UAAU,WAAW,CAAC;AAEtB,IAAM,SAAS,IAAI,SAAS,UAAU,CAAC;AACvC,OAAO,OAAO,oBAAoB;AAClC,OAAO,OAAO,mBAAmB;AAE1B,IAAM,KAAK,QAAQ,QAAQ,EAAE,uBAAO,CAAC;AAG5C,OAAO,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAmCX;AAGD,IAAI;AAEF,QAAM,YAAY,OAAO,QAAQ,6EAA6E,EAAE,IAAI;AACpH,QAAM,WAAW,OAAO,QAAQ,wCAAwC,EAAE,IAAI;AAE9E,MAAI,aAAa,SAAS,UAAU,GAAG;AACrC,YAAQ,IAAI,gFAAgF;AAG5F,WAAO,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAoBX;AAGD,WAAO,KAAK;AAAA;AAAA;AAAA,KAGX;AAED,YAAQ,IAAI,yBAAyB;AAAA,EACvC;AACF,SAAS,GAAG;AAEZ;AAGA,IAAI;AACF,SAAO,KAAK,4CAA4C;AAC1D,SAAS,GAAG;AAEZ;AAIA,IAAM,gBAAgB,OAAO,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAOpC,EAAE,IAAI;AAEP,IAAI,cAAc,SAAS,GAAG;AAC5B,QAAM,YAAY,OAAO,QAAQ;AAAA;AAAA;AAAA,GAGhC;AACD,QAAM,SAAS,OAAO,QAAQ;AAAA;AAAA;AAAA,GAG7B;AAED,aAAW,QAAQ,eAAe;AAChC,UAAM,EAAE,SAAS,IAAI,UAAU,IAAI,KAAK,OAAO;AAC/C,WAAO,IAAIC,YAAW,GAAG,KAAK,YAAY,KAAK,SAAS,QAAQ;AAAA,EAClE;AAEA,UAAQ,IAAI,cAAc,cAAc,MAAM,yCAAyC;AACzF;AAGA,iBAAiB;;;ADnIjB,SAAS,IAAI,WAAW;;;AKyGjB,SAAS,gBAAgB,QAA+C;AAC7E,SAAO,mBAAmB;AAC5B;AAEO,SAAS,YAAY,QAA2C;AACrE,SAAO,eAAe;AACxB;;;AL1GA,IAAM,YAAY,oBAAI,IAA6C;AAK5D,SAAS,UAAU,WAA2B;AACnD,QAAM,SAASC,YAAW;AAC1B,YAAU,IAAI,WAAW,EAAE,QAAQ,KAAK,EAAE,CAAC;AAC3C,SAAO;AACT;AAMO,SAAS,WAAW,WAAmB,QAA4B;AAExE,MAAI;AACJ,MAAI,gBAAgB,MAAM,GAAG;AAC3B,gBAAY,OAAO;AAAA,EACrB,WAAW,YAAY,MAAM,MAAM,OAAO,cAAc,gBAAgB,OAAO,cAAc,aAAa;AACxG,gBAAY,OAAO;AAAA,EACrB,OAAO;AAEL;AAAA,EACF;AAGA,MAAI,QAAQ,UAAU,IAAI,SAAS;AACnC,MAAI,CAAC,OAAO;AACV,YAAQ,EAAE,QAAQA,YAAW,GAAG,KAAK,EAAE;AACvC,cAAU,IAAI,WAAW,KAAK;AAAA,EAChC;AAEA,QAAM,MAAM,MAAM;AAElB,KAAG,OAAO,aAAa,EACpB,OAAO;AAAA,IACN,IAAIA,YAAW;AAAA,IACf;AAAA,IACA,QAAQ,MAAM;AAAA,IACd,aAAa;AAAA,IACb;AAAA,IACA,SAAS;AAAA,EACX,CAAC,EACA,IAAI;AACT;AAKO,SAAS,QAAQ,WAAyB;AAC/C,YAAU,OAAO,SAAS;AAC5B;AAMO,SAAS,oBAAoB,WAAmC;AACrE,QAAM,OAAO,GACV,OAAO,EACP,KAAK,aAAa,EAClB,MAAM,GAAG,cAAc,WAAW,SAAS,CAAC,EAC5C,QAAQ,IAAI,cAAc,SAAS,GAAG,IAAI,cAAc,WAAW,CAAC,EACpE,IAAI;AAEP,SAAO,KAAK,IAAI,CAAC,QAAQ,IAAI,OAAuB;AACtD;AAKO,SAAS,sBAAsB,WAAyB;AAC7D,KAAG,OAAO,aAAa,EAAE,MAAM,GAAG,cAAc,WAAW,SAAS,CAAC,EAAE,IAAI;AAC7E;AAIO,IAAM,qBAAqB;AAE3B,IAAM,uBAAuB;","names":["randomUUID","randomUUID","existing","randomUUID","randomUUID"]}
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}