@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.
- package/dist/bin/cli.js +10 -2171
- package/dist/bin/cli.js.map +1 -1
- package/dist/chunk-226DBKL3.js +26 -0
- package/dist/chunk-226DBKL3.js.map +1 -0
- package/dist/chunk-5IPYOXBE.js +32 -0
- package/dist/chunk-5IPYOXBE.js.map +1 -0
- package/dist/chunk-KUFDA4M3.js +333 -0
- package/dist/chunk-KUFDA4M3.js.map +1 -0
- package/dist/chunk-L4DBPVMA.js +122 -0
- package/dist/chunk-L4DBPVMA.js.map +1 -0
- package/dist/chunk-OQ5K5ON2.js +319 -0
- package/dist/chunk-OQ5K5ON2.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-RSLSN7F2.js +1591 -0
- package/dist/chunk-RSLSN7F2.js.map +1 -0
- package/dist/chunk-SX7NC3ZM.js +65 -0
- package/dist/chunk-SX7NC3ZM.js.map +1 -0
- package/dist/src/agents/eventStore.js +12 -344
- package/dist/src/agents/eventStore.js.map +1 -1
- package/dist/src/agents/manager.js +7 -1740
- package/dist/src/agents/manager.js.map +1 -1
- package/dist/src/db/index.js +5 -286
- package/dist/src/db/index.js.map +1 -1
- package/dist/src/lib/logger.js +18 -0
- package/dist/src/lib/logger.js.map +1 -0
- package/dist/src/server.js +10 -2168
- package/dist/src/server.js.map +1 -1
- package/dist/src/trpc/files.js +6 -396
- package/dist/src/trpc/files.js.map +1 -1
- package/dist/src/types.js +6 -19
- package/dist/src/types.js.map +1 -1
- package/package.json +8 -2
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__export
|
|
3
|
+
} from "./chunk-PZ5AY32C.js";
|
|
4
|
+
|
|
5
|
+
// src/db/index.ts
|
|
6
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
7
|
+
import Database from "better-sqlite3";
|
|
8
|
+
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
9
|
+
|
|
10
|
+
// src/lib/paths.ts
|
|
11
|
+
import path from "path";
|
|
12
|
+
import os from "os";
|
|
13
|
+
import fs from "fs";
|
|
14
|
+
var STARTUP_CWD = process.cwd();
|
|
15
|
+
function getStartupCwd() {
|
|
16
|
+
return STARTUP_CWD;
|
|
17
|
+
}
|
|
18
|
+
function getDataDir() {
|
|
19
|
+
const home = os.homedir();
|
|
20
|
+
let dataDir;
|
|
21
|
+
switch (process.platform) {
|
|
22
|
+
case "darwin":
|
|
23
|
+
dataDir = path.join(home, "Library", "Application Support", "amux");
|
|
24
|
+
break;
|
|
25
|
+
case "win32":
|
|
26
|
+
dataDir = path.join(process.env.APPDATA || path.join(home, "AppData", "Roaming"), "amux");
|
|
27
|
+
break;
|
|
28
|
+
default:
|
|
29
|
+
dataDir = path.join(process.env.XDG_DATA_HOME || path.join(home, ".local", "share"), "amux");
|
|
30
|
+
}
|
|
31
|
+
return dataDir;
|
|
32
|
+
}
|
|
33
|
+
function isMockMode() {
|
|
34
|
+
return process.env.SHELLA_MOCK_MODE === "true";
|
|
35
|
+
}
|
|
36
|
+
function getDbPath() {
|
|
37
|
+
const filename = isMockMode() ? "amux.mock.db" : "amux.db";
|
|
38
|
+
return path.join(getDataDir(), filename);
|
|
39
|
+
}
|
|
40
|
+
function ensureDir(dir) {
|
|
41
|
+
if (!fs.existsSync(dir)) {
|
|
42
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/db/schema.ts
|
|
47
|
+
var schema_exports = {};
|
|
48
|
+
__export(schema_exports, {
|
|
49
|
+
agentConfigs: () => agentConfigs,
|
|
50
|
+
appState: () => appState,
|
|
51
|
+
sessionEvents: () => sessionEvents,
|
|
52
|
+
sessions: () => sessions
|
|
53
|
+
});
|
|
54
|
+
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
|
|
55
|
+
import { sql } from "drizzle-orm";
|
|
56
|
+
var agentConfigs = sqliteTable("agent_configs", {
|
|
57
|
+
id: text("id").primaryKey(),
|
|
58
|
+
name: text("name").notNull(),
|
|
59
|
+
command: text("command").notNull(),
|
|
60
|
+
args: text("args", { mode: "json" }).$type().default([]),
|
|
61
|
+
env: text("env", { mode: "json" }).$type().default({}),
|
|
62
|
+
// Stream type: 'acp' for AI agents (message-based), 'pty' for terminals (byte stream)
|
|
63
|
+
streamType: text("stream_type").$type().notNull().default("acp"),
|
|
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
|
+
var seeded = false;
|
|
97
|
+
function commandExists(cmd) {
|
|
98
|
+
try {
|
|
99
|
+
execSync(`which ${cmd}`, { stdio: "ignore" });
|
|
100
|
+
return true;
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function detectAgents() {
|
|
106
|
+
const agents = [];
|
|
107
|
+
agents.push({
|
|
108
|
+
id: randomUUID(),
|
|
109
|
+
name: "Claude",
|
|
110
|
+
command: "npx",
|
|
111
|
+
args: ["@zed-industries/claude-code-acp"],
|
|
112
|
+
env: {}
|
|
113
|
+
});
|
|
114
|
+
if (commandExists("opencode")) {
|
|
115
|
+
agents.push({
|
|
116
|
+
id: randomUUID(),
|
|
117
|
+
name: "OpenCode",
|
|
118
|
+
command: "opencode",
|
|
119
|
+
args: ["acp"],
|
|
120
|
+
env: {}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
agents.push({
|
|
124
|
+
id: "shell",
|
|
125
|
+
// Fixed ID for easy reference
|
|
126
|
+
name: "shell",
|
|
127
|
+
command: "__shell__",
|
|
128
|
+
args: [],
|
|
129
|
+
env: {},
|
|
130
|
+
streamType: "pty"
|
|
131
|
+
});
|
|
132
|
+
return agents;
|
|
133
|
+
}
|
|
134
|
+
var MOCK_CONFIGS = [
|
|
135
|
+
{
|
|
136
|
+
id: MOCK_AGENT_ID,
|
|
137
|
+
name: "Test Agent",
|
|
138
|
+
command: "__stress__",
|
|
139
|
+
args: [],
|
|
140
|
+
env: {}
|
|
141
|
+
}
|
|
142
|
+
];
|
|
143
|
+
function seedAgentConfigs() {
|
|
144
|
+
if (seeded) return;
|
|
145
|
+
seeded = true;
|
|
146
|
+
if (isMockMode()) {
|
|
147
|
+
const existing2 = db.select().from(agentConfigs).all();
|
|
148
|
+
if (existing2.length === 0) {
|
|
149
|
+
for (const config of MOCK_CONFIGS) {
|
|
150
|
+
db.insert(agentConfigs).values(config).run();
|
|
151
|
+
}
|
|
152
|
+
console.log("[db] Seeded mock agent configs");
|
|
153
|
+
}
|
|
154
|
+
seedMockSessions();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const detected = detectAgents();
|
|
158
|
+
const existing = db.select().from(agentConfigs).all();
|
|
159
|
+
let added = 0;
|
|
160
|
+
for (const agent of detected) {
|
|
161
|
+
const alreadyExists = existing.some(
|
|
162
|
+
(e) => e.command === agent.command && JSON.stringify(e.args) === JSON.stringify(agent.args)
|
|
163
|
+
);
|
|
164
|
+
if (!alreadyExists) {
|
|
165
|
+
db.insert(agentConfigs).values(agent).run();
|
|
166
|
+
console.log(`[db] Detected new agent: ${agent.name}`);
|
|
167
|
+
added++;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (added > 0) {
|
|
171
|
+
console.log(`[db] ${added} new agent(s) configured, ${detected.length} total`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function seedMockSessions() {
|
|
175
|
+
const existing = db.select().from(sessions).all();
|
|
176
|
+
if (existing.length > 0) return;
|
|
177
|
+
const count = getSessionCount(10);
|
|
178
|
+
for (let i = 1; i <= count; i++) {
|
|
179
|
+
db.insert(sessions).values({
|
|
180
|
+
id: randomUUID(),
|
|
181
|
+
directory: "~",
|
|
182
|
+
agentConfigId: MOCK_AGENT_ID
|
|
183
|
+
}).run();
|
|
184
|
+
}
|
|
185
|
+
console.log(`[db] Seeded ${count} test sessions`);
|
|
186
|
+
}
|
|
187
|
+
function getSessionCount(defaultCount) {
|
|
188
|
+
const envCount = process.env.SHELLA_MOCK_SESSIONS ?? process.env.SHELLA_MOCK_WINDOWS;
|
|
189
|
+
if (envCount) {
|
|
190
|
+
const parsed = parseInt(envCount, 10);
|
|
191
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
192
|
+
return parsed;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return defaultCount;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/db/index.ts
|
|
199
|
+
ensureDir(getDataDir());
|
|
200
|
+
var sqlite = new Database(getDbPath());
|
|
201
|
+
sqlite.pragma("journal_mode = WAL");
|
|
202
|
+
sqlite.pragma("foreign_keys = ON");
|
|
203
|
+
var db = drizzle(sqlite, { schema: schema_exports });
|
|
204
|
+
sqlite.exec(`
|
|
205
|
+
CREATE TABLE IF NOT EXISTS agent_configs (
|
|
206
|
+
id TEXT PRIMARY KEY,
|
|
207
|
+
name TEXT NOT NULL,
|
|
208
|
+
command TEXT NOT NULL,
|
|
209
|
+
args TEXT DEFAULT '[]',
|
|
210
|
+
env TEXT DEFAULT '{}',
|
|
211
|
+
stream_type TEXT NOT NULL DEFAULT 'acp',
|
|
212
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
216
|
+
id TEXT PRIMARY KEY,
|
|
217
|
+
directory TEXT NOT NULL,
|
|
218
|
+
agent_config_id TEXT NOT NULL REFERENCES agent_configs(id),
|
|
219
|
+
acp_session_id TEXT,
|
|
220
|
+
title TEXT,
|
|
221
|
+
model TEXT,
|
|
222
|
+
mode TEXT,
|
|
223
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
CREATE TABLE IF NOT EXISTS app_state (
|
|
227
|
+
key TEXT PRIMARY KEY,
|
|
228
|
+
value TEXT NOT NULL
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
CREATE TABLE IF NOT EXISTS session_events (
|
|
232
|
+
id TEXT PRIMARY KEY,
|
|
233
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
234
|
+
turn_id TEXT NOT NULL,
|
|
235
|
+
sequence_num INTEGER NOT NULL,
|
|
236
|
+
event_kind TEXT NOT NULL,
|
|
237
|
+
payload TEXT NOT NULL,
|
|
238
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
|
|
239
|
+
);
|
|
240
|
+
`);
|
|
241
|
+
try {
|
|
242
|
+
const oldExists = sqlite.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='client_windows'`).get();
|
|
243
|
+
const newCount = sqlite.prepare(`SELECT COUNT(*) as count FROM sessions`).get();
|
|
244
|
+
if (oldExists && newCount.count === 0) {
|
|
245
|
+
console.log("[db] Migrating from client_windows/window_events to sessions/session_events...");
|
|
246
|
+
sqlite.exec(`
|
|
247
|
+
INSERT INTO sessions (id, directory, agent_config_id, acp_session_id, model, mode, created_at)
|
|
248
|
+
SELECT id, directory, agent_config_id, session_id, model, mode, created_at
|
|
249
|
+
FROM client_windows;
|
|
250
|
+
|
|
251
|
+
INSERT INTO session_events (id, session_id, turn_id, sequence_num, event_kind, payload, created_at)
|
|
252
|
+
SELECT id, window_id, turn_id, sequence_num, event_kind, payload, created_at
|
|
253
|
+
FROM window_events;
|
|
254
|
+
|
|
255
|
+
-- Migrate custom titles to app_state
|
|
256
|
+
INSERT OR IGNORE INTO app_state (key, value)
|
|
257
|
+
SELECT 'window_title_' || id, title
|
|
258
|
+
FROM client_windows
|
|
259
|
+
WHERE has_custom_title = 1 AND title != '';
|
|
260
|
+
|
|
261
|
+
-- Migrate active window
|
|
262
|
+
INSERT OR REPLACE INTO app_state (key, value)
|
|
263
|
+
SELECT 'active_window_id', value
|
|
264
|
+
FROM app_state
|
|
265
|
+
WHERE key = 'active_window_id';
|
|
266
|
+
`);
|
|
267
|
+
sqlite.exec(`
|
|
268
|
+
DROP TABLE IF EXISTS window_events;
|
|
269
|
+
DROP TABLE IF EXISTS client_windows;
|
|
270
|
+
`);
|
|
271
|
+
console.log("[db] Migration complete");
|
|
272
|
+
}
|
|
273
|
+
} catch (e) {
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
sqlite.exec(`ALTER TABLE sessions ADD COLUMN title TEXT`);
|
|
277
|
+
} catch (e) {
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
sqlite.exec(`ALTER TABLE agent_configs ADD COLUMN stream_type TEXT NOT NULL DEFAULT 'acp'`);
|
|
281
|
+
sqlite.exec(`UPDATE agent_configs SET stream_type = 'pty' WHERE command = '__shell__'`);
|
|
282
|
+
} catch (e) {
|
|
283
|
+
}
|
|
284
|
+
var orphanedTurns = sqlite.prepare(`
|
|
285
|
+
SELECT DISTINCT turn_id, session_id
|
|
286
|
+
FROM session_events
|
|
287
|
+
WHERE event_kind = 'turn_start'
|
|
288
|
+
AND turn_id NOT IN (
|
|
289
|
+
SELECT turn_id FROM session_events WHERE event_kind = 'turn_end'
|
|
290
|
+
)
|
|
291
|
+
`).all();
|
|
292
|
+
if (orphanedTurns.length > 0) {
|
|
293
|
+
const getMaxSeq = sqlite.prepare(`
|
|
294
|
+
SELECT COALESCE(MAX(sequence_num), 0) + 1 as next_seq
|
|
295
|
+
FROM session_events WHERE turn_id = ?
|
|
296
|
+
`);
|
|
297
|
+
const insert = sqlite.prepare(`
|
|
298
|
+
INSERT INTO session_events (id, session_id, turn_id, sequence_num, event_kind, payload)
|
|
299
|
+
VALUES (?, ?, ?, ?, 'turn_end', '{"amuxEvent":"turn_end"}')
|
|
300
|
+
`);
|
|
301
|
+
for (const turn of orphanedTurns) {
|
|
302
|
+
const { next_seq } = getMaxSeq.get(turn.turn_id);
|
|
303
|
+
insert.run(randomUUID2(), turn.session_id, turn.turn_id, next_seq);
|
|
304
|
+
}
|
|
305
|
+
console.log(`[db] Fixed ${orphanedTurns.length} orphaned turn(s) from previous session`);
|
|
306
|
+
}
|
|
307
|
+
seedAgentConfigs();
|
|
308
|
+
|
|
309
|
+
export {
|
|
310
|
+
getStartupCwd,
|
|
311
|
+
getDbPath,
|
|
312
|
+
agentConfigs,
|
|
313
|
+
sessions,
|
|
314
|
+
appState,
|
|
315
|
+
sessionEvents,
|
|
316
|
+
schema_exports,
|
|
317
|
+
db
|
|
318
|
+
};
|
|
319
|
+
//# sourceMappingURL=chunk-OQ5K5ON2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/db/index.ts","../src/lib/paths.ts","../src/db/schema.ts","../src/db/seed.ts"],"sourcesContent":["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 stream_type TEXT NOT NULL DEFAULT 'acp',\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// Migration: add stream_type column to agent_configs if missing\ntry {\n sqlite.exec(`ALTER TABLE agent_configs ADD COLUMN stream_type TEXT NOT NULL DEFAULT 'acp'`);\n // Set shell agent to pty stream type\n sqlite.exec(`UPDATE agent_configs SET stream_type = 'pty' WHERE command = '__shell__'`);\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/amux\n * - macOS: ~/Library/Application Support/amux\n * - Windows: %APPDATA%/amux\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', 'amux');\n break;\n case 'win32':\n dataDir = path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'amux');\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'), 'amux');\n }\n\n return dataDir;\n}\n\n/**\n * Get the config directory following XDG Base Directory spec\n * - Linux: ~/.config/amux\n * - macOS: ~/Library/Application Support/amux\n * - Windows: %APPDATA%/amux\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', 'amux');\n break;\n case 'win32':\n configDir = path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'amux');\n break;\n default:\n // Linux and others - follow XDG spec\n configDir = path.join(process.env.XDG_CONFIG_HOME || path.join(home, '.config'), 'amux');\n }\n\n return configDir;\n}\n\n/**\n * Get the cache directory\n * - Linux: ~/.cache/amux\n * - macOS: ~/Library/Caches/amux\n * - Windows: %LOCALAPPDATA%/amux/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', 'amux');\n break;\n case 'win32':\n cacheDir = path.join(process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local'), 'amux', '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'), 'amux');\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() ? 'amux.mock.db' : 'amux.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 amux 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 // Stream type: 'acp' for AI agents (message-based), 'pty' for terminals (byte stream)\n streamType: text('stream_type').$type<'acp' | 'pty'>().notNull().default('acp'),\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';\nlet seeded = false;\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 // Shell - interactive terminal (always available)\n agents.push({\n id: 'shell', // Fixed ID for easy reference\n name: 'shell',\n command: '__shell__',\n args: [],\n env: {},\n streamType: 'pty',\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 (seeded) return;\n seeded = true;\n\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 // Only log if something changed\n if (added > 0) {\n console.log(`[db] ${added} new agent(s) configured, ${detected.length} total`);\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"],"mappings":"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","names":["randomUUID","existing","randomUUID"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|