@burdenoff/vibe-agent 2.1.1 → 2.2.0

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.
Files changed (53) hide show
  1. package/dist/app-6mmbmske.js +1166 -0
  2. package/dist/app-6mmbmske.js.map +19 -0
  3. package/dist/cli.js +152 -2036
  4. package/dist/cli.js.map +6 -28
  5. package/dist/index-05qfwz8r.js +122 -0
  6. package/dist/index-05qfwz8r.js.map +10 -0
  7. package/dist/index-30p492yv.js +294 -0
  8. package/dist/index-30p492yv.js.map +13 -0
  9. package/dist/index-3v78e2cn.js +373 -0
  10. package/dist/index-3v78e2cn.js.map +11 -0
  11. package/dist/index-41m1exz7.js +269 -0
  12. package/dist/index-41m1exz7.js.map +13 -0
  13. package/dist/index-88ym10cs.js +194 -0
  14. package/dist/index-88ym10cs.js.map +10 -0
  15. package/dist/index-9tgyd3ep.js +513 -0
  16. package/dist/index-9tgyd3ep.js.map +19 -0
  17. package/dist/index-a9g7hbj9.js +229 -0
  18. package/dist/index-a9g7hbj9.js.map +13 -0
  19. package/dist/index-atjhkm74.js +149 -0
  20. package/dist/index-atjhkm74.js.map +10 -0
  21. package/dist/index-c7zy3n33.js +167 -0
  22. package/dist/index-c7zy3n33.js.map +13 -0
  23. package/dist/index-hefqxwht.js +270 -0
  24. package/dist/index-hefqxwht.js.map +13 -0
  25. package/dist/index-k9hb0b93.js +280 -0
  26. package/dist/index-k9hb0b93.js.map +13 -0
  27. package/dist/index-npmvh1x9.js +385 -0
  28. package/dist/index-npmvh1x9.js.map +13 -0
  29. package/dist/index-q4ytrfx7.js +286 -0
  30. package/dist/index-q4ytrfx7.js.map +13 -0
  31. package/dist/index-qthbtg9n.js +302 -0
  32. package/dist/index-qthbtg9n.js.map +13 -0
  33. package/dist/index-rdm6e3rr.js +587 -0
  34. package/dist/index-rdm6e3rr.js.map +13 -0
  35. package/dist/index-wdtxbebz.js +339 -0
  36. package/dist/index-wdtxbebz.js.map +13 -0
  37. package/dist/{app-31chs2a1.js → index-wr0mkm57.js} +8 -3201
  38. package/dist/{app-31chs2a1.js.map → index-wr0mkm57.js.map} +4 -25
  39. package/dist/index-xmeskdnb.js +292 -0
  40. package/dist/index-xmeskdnb.js.map +11 -0
  41. package/dist/index-xn4tarcd.js +287 -0
  42. package/dist/index-xn4tarcd.js.map +13 -0
  43. package/dist/index.js +9 -6
  44. package/dist/index.js.map +2 -2
  45. package/dist/{package-hb6db316.js → package-ywexp6sg.js} +3 -3
  46. package/dist/{package-hb6db316.js.map → package-ywexp6sg.js.map} +1 -1
  47. package/dist/plugin-system-v7a7xnhk.js +475 -0
  48. package/dist/plugin-system-v7a7xnhk.js.map +10 -0
  49. package/package.json +1 -1
  50. package/dist/index-t06ktmx9.js +0 -216
  51. package/dist/index-t06ktmx9.js.map +0 -11
  52. package/dist/plugin-system-bg1pzjj9.js +0 -450
  53. package/dist/plugin-system-bg1pzjj9.js.map +0 -11
@@ -0,0 +1,1166 @@
1
+ // @bun
2
+ import {
3
+ ServiceRegistry
4
+ } from "./index-atjhkm74.js";
5
+ import {
6
+ gatewayClient
7
+ } from "./index-05qfwz8r.js";
8
+ import {
9
+ logger
10
+ } from "./index-88ym10cs.js";
11
+ import {
12
+ Elysia
13
+ } from "./index-wr0mkm57.js";
14
+ import {
15
+ __require,
16
+ __toESM
17
+ } from "./index-g8dczzvv.js";
18
+ import {
19
+ PluginManager
20
+ } from "./plugin-system-v7a7xnhk.js";
21
+
22
+ // node_modules/@elysiajs/cors/dist/index.mjs
23
+ var isBun = typeof new Headers()?.toJSON === "function";
24
+ var processHeaders = (headers) => {
25
+ if (isBun)
26
+ return Object.keys(headers.toJSON()).join(", ");
27
+ let keys = "";
28
+ let i = 0;
29
+ headers.forEach((_, key) => {
30
+ if (i)
31
+ keys = keys + ", " + key;
32
+ else
33
+ keys = key;
34
+ i++;
35
+ });
36
+ return keys;
37
+ };
38
+ var cors = (config) => {
39
+ let {
40
+ aot = true,
41
+ origin = true,
42
+ methods = true,
43
+ allowedHeaders = true,
44
+ exposeHeaders = true,
45
+ credentials = true,
46
+ maxAge = 5,
47
+ preflight = true
48
+ } = config ?? {};
49
+ if (Array.isArray(allowedHeaders))
50
+ allowedHeaders = allowedHeaders.join(", ");
51
+ if (Array.isArray(exposeHeaders))
52
+ exposeHeaders = exposeHeaders.join(", ");
53
+ const origins = typeof origin === "boolean" ? undefined : Array.isArray(origin) ? origin : [origin];
54
+ const app = new Elysia({
55
+ name: "@elysiajs/cors",
56
+ seed: config,
57
+ aot
58
+ });
59
+ const anyOrigin = origins?.some((o) => o === "*");
60
+ const originMap = {};
61
+ if (origins) {
62
+ for (const origin2 of origins)
63
+ if (typeof origin2 === "string")
64
+ originMap[origin2] = true;
65
+ }
66
+ const processOrigin = (origin2, request, from) => {
67
+ if (Array.isArray(origin2))
68
+ return origin2.some((o) => processOrigin(o, request, from));
69
+ switch (typeof origin2) {
70
+ case "string":
71
+ if (from in originMap)
72
+ return true;
73
+ const fromProtocol = from.indexOf("://");
74
+ if (fromProtocol !== -1)
75
+ from = from.slice(fromProtocol + 3);
76
+ return origin2 === from;
77
+ case "function":
78
+ return origin2(request) === true;
79
+ case "object":
80
+ if (origin2 instanceof RegExp)
81
+ return origin2.test(from);
82
+ }
83
+ return false;
84
+ };
85
+ const handleOrigin = (set, request) => {
86
+ if (origin === true) {
87
+ set.headers.vary = "*";
88
+ set.headers["access-control-allow-origin"] = request.headers.get("Origin") || "*";
89
+ return;
90
+ }
91
+ if (anyOrigin) {
92
+ set.headers.vary = "*";
93
+ set.headers["access-control-allow-origin"] = "*";
94
+ return;
95
+ }
96
+ if (!origins?.length)
97
+ return;
98
+ if (origins.length) {
99
+ const from = request.headers.get("Origin") ?? "";
100
+ for (let i = 0;i < origins.length; i++) {
101
+ const value = processOrigin(origins[i], request, from);
102
+ if (value === true) {
103
+ set.headers.vary = origin ? "Origin" : "*";
104
+ set.headers["access-control-allow-origin"] = from || "*";
105
+ return;
106
+ }
107
+ }
108
+ }
109
+ set.headers.vary = "Origin";
110
+ };
111
+ const handleMethod = (set, method) => {
112
+ if (!method)
113
+ return;
114
+ if (methods === true)
115
+ return set.headers["access-control-allow-methods"] = method ?? "*";
116
+ if (methods === false || !methods?.length)
117
+ return;
118
+ if (methods === "*")
119
+ return set.headers["access-control-allow-methods"] = "*";
120
+ if (!Array.isArray(methods))
121
+ return set.headers["access-control-allow-methods"] = methods;
122
+ set.headers["access-control-allow-methods"] = methods.join(", ");
123
+ };
124
+ const defaultHeaders = {};
125
+ if (typeof exposeHeaders === "string")
126
+ defaultHeaders["access-control-expose-headers"] = exposeHeaders;
127
+ if (typeof allowedHeaders === "string")
128
+ defaultHeaders["access-control-allow-headers"] = allowedHeaders;
129
+ if (credentials === true)
130
+ defaultHeaders["access-control-allow-credentials"] = "true";
131
+ app.headers(defaultHeaders);
132
+ function handleOption({ set, request, headers }) {
133
+ handleOrigin(set, request);
134
+ handleMethod(set, request.headers.get("access-control-request-method"));
135
+ if (allowedHeaders === true || exposeHeaders === true) {
136
+ if (allowedHeaders === true)
137
+ set.headers["access-control-allow-headers"] = headers["access-control-request-headers"];
138
+ if (exposeHeaders === true)
139
+ set.headers["access-control-expose-headers"] = Object.keys(headers).join(",");
140
+ }
141
+ if (maxAge)
142
+ set.headers["access-control-max-age"] = maxAge.toString();
143
+ return new Response(null, {
144
+ status: 204
145
+ });
146
+ }
147
+ if (preflight)
148
+ app.options("/", handleOption).options("/*", handleOption);
149
+ return app.onRequest(function processCors({ set, request }) {
150
+ handleOrigin(set, request);
151
+ if (preflight && request.method === "OPTIONS") {
152
+ return handleOption({
153
+ set,
154
+ request,
155
+ headers: isBun ? request.headers.toJSON() : Object.fromEntries(request.headers.entries())
156
+ });
157
+ }
158
+ handleMethod(set, request.method);
159
+ if (allowedHeaders === true || exposeHeaders === true) {
160
+ const headers = processHeaders(request.headers);
161
+ if (allowedHeaders === true)
162
+ set.headers["access-control-allow-headers"] = headers;
163
+ if (exposeHeaders === true)
164
+ set.headers["access-control-expose-headers"] = headers;
165
+ }
166
+ });
167
+ };
168
+
169
+ // src/db/database.ts
170
+ import { Database } from "bun:sqlite";
171
+ import { join } from "path";
172
+ import os from "os";
173
+ import { existsSync, mkdirSync } from "fs";
174
+ var VIBECONTROLS_DIR = join(os.homedir(), ".vibecontrols");
175
+ var DEFAULT_DB_PATH = join(VIBECONTROLS_DIR, "agent.db");
176
+
177
+ class AgentDatabase {
178
+ db;
179
+ constructor(dbPath = DEFAULT_DB_PATH) {
180
+ const dir = join(dbPath, "..");
181
+ if (!existsSync(dir)) {
182
+ mkdirSync(dir, { recursive: true });
183
+ }
184
+ this.db = new Database(dbPath);
185
+ this.db.exec("PRAGMA journal_mode = WAL");
186
+ this.db.exec("PRAGMA busy_timeout = 5000");
187
+ this.db.exec("PRAGMA synchronous = NORMAL");
188
+ this.db.exec("PRAGMA foreign_keys = ON");
189
+ this.initializeSchema();
190
+ }
191
+ initializeSchema() {
192
+ this.db.exec(`
193
+ CREATE TABLE IF NOT EXISTS tasks (
194
+ id TEXT PRIMARY KEY,
195
+ type TEXT CHECK(type IN ('command', 'script', 'file_operation')) NOT NULL,
196
+ status TEXT CHECK(status IN ('pending', 'running', 'completed', 'failed')) DEFAULT 'pending',
197
+ payload TEXT NOT NULL,
198
+ result TEXT,
199
+ error TEXT,
200
+ createdAt TEXT DEFAULT (datetime('now')),
201
+ updatedAt TEXT DEFAULT (datetime('now'))
202
+ );
203
+
204
+ CREATE TABLE IF NOT EXISTS agent_config (
205
+ id TEXT PRIMARY KEY,
206
+ key TEXT UNIQUE NOT NULL,
207
+ value TEXT NOT NULL,
208
+ updatedAt TEXT DEFAULT (datetime('now'))
209
+ );
210
+
211
+ CREATE TABLE IF NOT EXISTS git_repositories (
212
+ id TEXT PRIMARY KEY,
213
+ path TEXT UNIQUE NOT NULL,
214
+ name TEXT NOT NULL,
215
+ parentPath TEXT,
216
+ isSubmodule INTEGER DEFAULT 0,
217
+ projectType TEXT,
218
+ vitePort INTEGER,
219
+ lastScanned TEXT DEFAULT (datetime('now')),
220
+ createdAt TEXT DEFAULT (datetime('now'))
221
+ );
222
+
223
+ CREATE TABLE IF NOT EXISTS bookmarked_commands (
224
+ id TEXT PRIMARY KEY,
225
+ projectId TEXT,
226
+ command TEXT NOT NULL,
227
+ description TEXT,
228
+ category TEXT,
229
+ createdAt TEXT DEFAULT (datetime('now'))
230
+ );
231
+
232
+ CREATE TABLE IF NOT EXISTS notifications (
233
+ id TEXT PRIMARY KEY,
234
+ sessionName TEXT,
235
+ projectId TEXT,
236
+ type TEXT CHECK(type IN ('info', 'success', 'warning', 'error')) DEFAULT 'info',
237
+ title TEXT NOT NULL,
238
+ message TEXT NOT NULL,
239
+ status TEXT CHECK(status IN ('unread', 'read')) DEFAULT 'unread',
240
+ createdAt TEXT DEFAULT (datetime('now'))
241
+ );
242
+
243
+ CREATE TABLE IF NOT EXISTS plugin_state (
244
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
245
+ pluginName TEXT NOT NULL,
246
+ key TEXT NOT NULL,
247
+ value TEXT NOT NULL,
248
+ updatedAt TEXT DEFAULT (datetime('now')),
249
+ UNIQUE(pluginName, key)
250
+ );
251
+
252
+ -- Indexes
253
+ CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
254
+ CREATE INDEX IF NOT EXISTS idx_tasks_type ON tasks(type);
255
+ CREATE INDEX IF NOT EXISTS idx_git_repositories_path ON git_repositories(path);
256
+ CREATE INDEX IF NOT EXISTS idx_git_repositories_parentPath ON git_repositories(parentPath);
257
+ CREATE INDEX IF NOT EXISTS idx_bookmarked_commands_projectId ON bookmarked_commands(projectId);
258
+ CREATE INDEX IF NOT EXISTS idx_notifications_status ON notifications(status);
259
+ CREATE INDEX IF NOT EXISTS idx_notifications_projectId ON notifications(projectId);
260
+ CREATE INDEX IF NOT EXISTS idx_plugin_state_plugin ON plugin_state(pluginName);
261
+ `);
262
+ }
263
+ createTask(task) {
264
+ const stmt = this.db.prepare(`
265
+ INSERT INTO tasks (id, type, status, payload, result, error)
266
+ VALUES ($id, $type, $status, $payload, $result, $error)
267
+ `);
268
+ stmt.run({
269
+ $id: task.id,
270
+ $type: task.type,
271
+ $status: task.status,
272
+ $payload: task.payload,
273
+ $result: task.result ?? null,
274
+ $error: task.error ?? null
275
+ });
276
+ return this.getTask(task.id);
277
+ }
278
+ getTask(id) {
279
+ return this.db.prepare("SELECT * FROM tasks WHERE id = ?").get(id);
280
+ }
281
+ getAllTasks() {
282
+ return this.db.prepare("SELECT * FROM tasks ORDER BY createdAt DESC").all();
283
+ }
284
+ getPendingTasks() {
285
+ return this.db.prepare("SELECT * FROM tasks WHERE status = 'pending' ORDER BY createdAt").all();
286
+ }
287
+ updateTask(id, updates) {
288
+ const fields = Object.keys(updates).filter((k) => k !== "id" && k !== "createdAt").map((k) => `${k} = $${k}`).join(", ");
289
+ if (!fields)
290
+ return;
291
+ const stmt = this.db.prepare(`UPDATE tasks SET ${fields}, updatedAt = datetime('now') WHERE id = $id`);
292
+ const params = { $id: id };
293
+ for (const [k, v] of Object.entries(updates)) {
294
+ params[`$${k}`] = v ?? null;
295
+ }
296
+ stmt.run(params);
297
+ }
298
+ cancelTask(id) {
299
+ const result = this.db.prepare("UPDATE tasks SET status = 'failed', error = 'Cancelled', updatedAt = datetime('now') WHERE id = ? AND status IN ('pending', 'running')").run(id);
300
+ return result.changes > 0;
301
+ }
302
+ getConfig(key) {
303
+ const row = this.db.prepare("SELECT value FROM agent_config WHERE key = ?").get(key);
304
+ if (row?.value === "__DELETED__")
305
+ return;
306
+ return row?.value;
307
+ }
308
+ setConfig(key, value) {
309
+ this.db.prepare(`
310
+ INSERT INTO agent_config (id, key, value)
311
+ VALUES ($id, $key, $value)
312
+ ON CONFLICT(key) DO UPDATE SET value = $value, updatedAt = datetime('now')
313
+ `).run({ $id: key, $key: key, $value: value });
314
+ }
315
+ deleteConfig(key) {
316
+ const result = this.db.prepare("DELETE FROM agent_config WHERE key = ?").run(key);
317
+ return result.changes > 0;
318
+ }
319
+ getAllConfig() {
320
+ const rows = this.db.prepare("SELECT key, value FROM agent_config").all();
321
+ return rows.reduce((acc, row) => {
322
+ if (row.value !== "__DELETED__") {
323
+ acc[row.key] = row.value;
324
+ }
325
+ return acc;
326
+ }, {});
327
+ }
328
+ bulkSetConfig(entries) {
329
+ const stmt = this.db.prepare(`
330
+ INSERT INTO agent_config (id, key, value)
331
+ VALUES ($id, $key, $value)
332
+ ON CONFLICT(key) DO UPDATE SET value = $value, updatedAt = datetime('now')
333
+ `);
334
+ const transaction = this.db.transaction((entries2) => {
335
+ for (const [key, value] of entries2) {
336
+ stmt.run({ $id: key, $key: key, $value: value });
337
+ }
338
+ });
339
+ transaction(Object.entries(entries));
340
+ }
341
+ getConfigStatus() {
342
+ const count = this.db.prepare("SELECT COUNT(*) as count FROM agent_config").get();
343
+ const latest = this.db.prepare("SELECT MAX(updatedAt) as latest FROM agent_config").get();
344
+ return { totalKeys: count.count, lastUpdated: latest.latest };
345
+ }
346
+ createGitRepository(repo) {
347
+ this.db.prepare(`
348
+ INSERT INTO git_repositories (id, path, name, parentPath, isSubmodule, projectType, vitePort)
349
+ VALUES ($id, $path, $name, $parentPath, $isSubmodule, $projectType, $vitePort)
350
+ `).run({
351
+ $id: repo.id,
352
+ $path: repo.path,
353
+ $name: repo.name,
354
+ $parentPath: repo.parentPath ?? null,
355
+ $isSubmodule: repo.isSubmodule ? 1 : 0,
356
+ $projectType: repo.projectType ?? null,
357
+ $vitePort: repo.vitePort ?? null
358
+ });
359
+ return this.getGitRepository(repo.id);
360
+ }
361
+ getGitRepository(id) {
362
+ const row = this.db.prepare("SELECT * FROM git_repositories WHERE id = ?").get(id);
363
+ if (row)
364
+ row.isSubmodule = Boolean(row.isSubmodule);
365
+ return row ?? undefined;
366
+ }
367
+ getGitRepositoryByPath(path) {
368
+ const row = this.db.prepare("SELECT * FROM git_repositories WHERE path = ?").get(path);
369
+ if (row)
370
+ row.isSubmodule = Boolean(row.isSubmodule);
371
+ return row ?? undefined;
372
+ }
373
+ getAllGitRepositories() {
374
+ const rows = this.db.prepare("SELECT * FROM git_repositories ORDER BY path").all();
375
+ return rows.map((r) => ({ ...r, isSubmodule: Boolean(r.isSubmodule) }));
376
+ }
377
+ updateGitRepository(id, updates) {
378
+ const fields = Object.keys(updates).filter((k) => k !== "id" && k !== "createdAt").map((k) => `${k} = $${k}`).join(", ");
379
+ if (!fields)
380
+ return;
381
+ const params = { $id: id };
382
+ for (const [k, v] of Object.entries(updates)) {
383
+ if (k === "isSubmodule")
384
+ params[`$${k}`] = v ? 1 : 0;
385
+ else
386
+ params[`$${k}`] = v ?? null;
387
+ }
388
+ this.db.prepare(`UPDATE git_repositories SET ${fields}, lastScanned = datetime('now') WHERE id = $id`).run(params);
389
+ }
390
+ deleteGitRepository(id) {
391
+ const result = this.db.prepare("DELETE FROM git_repositories WHERE id = ?").run(id);
392
+ return result.changes > 0;
393
+ }
394
+ fixGitHierarchy() {
395
+ const repos = this.getAllGitRepositories();
396
+ let fixed = 0;
397
+ for (const repo of repos) {
398
+ if (repo.isSubmodule)
399
+ continue;
400
+ const parent = repos.find((r) => r.id !== repo.id && repo.path.startsWith(r.path + "/") && !r.isSubmodule);
401
+ if (parent && repo.parentPath !== parent.path) {
402
+ this.updateGitRepository(repo.id, { parentPath: parent.path });
403
+ fixed++;
404
+ }
405
+ }
406
+ return { fixed };
407
+ }
408
+ createBookmarkedCommand(cmd) {
409
+ this.db.prepare(`
410
+ INSERT INTO bookmarked_commands (id, projectId, command, description, category)
411
+ VALUES ($id, $projectId, $command, $description, $category)
412
+ `).run({
413
+ $id: cmd.id,
414
+ $projectId: cmd.projectId ?? null,
415
+ $command: cmd.command,
416
+ $description: cmd.description ?? null,
417
+ $category: cmd.category ?? null
418
+ });
419
+ return this.getBookmarkedCommand(cmd.id);
420
+ }
421
+ getBookmarkedCommand(id) {
422
+ return this.db.prepare("SELECT * FROM bookmarked_commands WHERE id = ?").get(id);
423
+ }
424
+ getAllBookmarkedCommands() {
425
+ return this.db.prepare("SELECT * FROM bookmarked_commands ORDER BY createdAt DESC").all();
426
+ }
427
+ getBookmarkedCommandsByProject(projectId) {
428
+ if (projectId === null) {
429
+ return this.db.prepare("SELECT * FROM bookmarked_commands WHERE projectId IS NULL ORDER BY createdAt DESC").all();
430
+ }
431
+ return this.db.prepare("SELECT * FROM bookmarked_commands WHERE projectId = ? ORDER BY createdAt DESC").all(projectId);
432
+ }
433
+ getBookmarkedCommandsByCategory(category) {
434
+ return this.db.prepare("SELECT * FROM bookmarked_commands WHERE category = ? ORDER BY createdAt DESC").all(category);
435
+ }
436
+ updateBookmarkedCommand(id, updates) {
437
+ const fields = Object.keys(updates).filter((k) => k !== "id" && k !== "createdAt").map((k) => `${k} = $${k}`).join(", ");
438
+ if (!fields)
439
+ return;
440
+ const params = { $id: id };
441
+ for (const [k, v] of Object.entries(updates)) {
442
+ params[`$${k}`] = v ?? null;
443
+ }
444
+ this.db.prepare(`UPDATE bookmarked_commands SET ${fields} WHERE id = $id`).run(params);
445
+ }
446
+ deleteBookmarkedCommand(id) {
447
+ const result = this.db.prepare("DELETE FROM bookmarked_commands WHERE id = ?").run(id);
448
+ return result.changes > 0;
449
+ }
450
+ executeBookmarkedCommand(id) {
451
+ return this.getBookmarkedCommand(id);
452
+ }
453
+ createNotification(notification) {
454
+ this.db.prepare(`
455
+ INSERT INTO notifications (id, sessionName, projectId, type, title, message, status)
456
+ VALUES ($id, $sessionName, $projectId, $type, $title, $message, $status)
457
+ `).run({
458
+ $id: notification.id,
459
+ $sessionName: notification.sessionName ?? null,
460
+ $projectId: notification.projectId ?? null,
461
+ $type: notification.type,
462
+ $title: notification.title,
463
+ $message: notification.message,
464
+ $status: notification.status
465
+ });
466
+ return this.getNotification(notification.id);
467
+ }
468
+ getNotification(id) {
469
+ return this.db.prepare("SELECT * FROM notifications WHERE id = ?").get(id);
470
+ }
471
+ getAllNotifications() {
472
+ return this.db.prepare("SELECT * FROM notifications ORDER BY createdAt DESC").all();
473
+ }
474
+ getNotificationsByProject(projectId) {
475
+ if (projectId === null) {
476
+ return this.db.prepare("SELECT * FROM notifications WHERE projectId IS NULL ORDER BY createdAt DESC").all();
477
+ }
478
+ return this.db.prepare("SELECT * FROM notifications WHERE projectId = ? ORDER BY createdAt DESC").all(projectId);
479
+ }
480
+ getGlobalNotifications() {
481
+ return this.db.prepare("SELECT * FROM notifications WHERE projectId IS NULL ORDER BY createdAt DESC").all();
482
+ }
483
+ getUnreadNotifications() {
484
+ return this.db.prepare("SELECT * FROM notifications WHERE status = 'unread' ORDER BY createdAt DESC").all();
485
+ }
486
+ updateNotificationStatus(id, status) {
487
+ this.db.prepare("UPDATE notifications SET status = ? WHERE id = ?").run(status, id);
488
+ }
489
+ markAllNotificationsRead() {
490
+ const result = this.db.prepare("UPDATE notifications SET status = 'read' WHERE status = 'unread'").run();
491
+ return result.changes;
492
+ }
493
+ deleteNotification(id) {
494
+ const result = this.db.prepare("DELETE FROM notifications WHERE id = ?").run(id);
495
+ return result.changes > 0;
496
+ }
497
+ clearOldNotifications(olderThanDays = 30) {
498
+ const result = this.db.prepare("DELETE FROM notifications WHERE createdAt < datetime('now', '-' || ? || ' days')").run(olderThanDays);
499
+ return result.changes;
500
+ }
501
+ getPluginState(pluginName, key) {
502
+ const row = this.db.prepare("SELECT value FROM plugin_state WHERE pluginName = ? AND key = ?").get(pluginName, key);
503
+ return row?.value;
504
+ }
505
+ setPluginState(pluginName, key, value) {
506
+ this.db.prepare(`
507
+ INSERT INTO plugin_state (pluginName, key, value)
508
+ VALUES ($pluginName, $key, $value)
509
+ ON CONFLICT(pluginName, key) DO UPDATE SET value = $value, updatedAt = datetime('now')
510
+ `).run({ $pluginName: pluginName, $key: key, $value: value });
511
+ }
512
+ deletePluginState(pluginName, key) {
513
+ const result = this.db.prepare("DELETE FROM plugin_state WHERE pluginName = ? AND key = ?").run(pluginName, key);
514
+ return result.changes > 0;
515
+ }
516
+ getAllPluginState(pluginName) {
517
+ return this.db.prepare("SELECT key, value, updatedAt FROM plugin_state WHERE pluginName = ? ORDER BY key").all(pluginName);
518
+ }
519
+ deleteAllPluginState(pluginName) {
520
+ const result = this.db.prepare("DELETE FROM plugin_state WHERE pluginName = ?").run(pluginName);
521
+ return result.changes;
522
+ }
523
+ close() {
524
+ this.db.close();
525
+ }
526
+ }
527
+
528
+ // src/core/plugin-router.ts
529
+ function createPluginRouter(pluginManager, deps) {
530
+ const router = new Elysia;
531
+ const publicPaths = [];
532
+ for (const plugin of pluginManager.getAllPlugins()) {
533
+ if (!plugin.createRoutes)
534
+ continue;
535
+ const prefix = plugin.apiPrefix || `/api/${plugin.name}`;
536
+ try {
537
+ const pluginRoutes = plugin.createRoutes(deps);
538
+ router.use(new Elysia({ prefix }).derive(() => ({
539
+ pluginContext: {
540
+ chain: [plugin.name],
541
+ parentPlugin: undefined,
542
+ activePlugin: plugin.name
543
+ }
544
+ })).use(pluginRoutes));
545
+ if (plugin.publicPaths) {
546
+ for (const p of plugin.publicPaths) {
547
+ publicPaths.push(`${prefix}${p}`);
548
+ }
549
+ }
550
+ } catch (err) {
551
+ console.error(`[plugin-router] Failed to mount routes for plugin '${plugin.name}':`, err);
552
+ }
553
+ }
554
+ return {
555
+ routes: router,
556
+ getPublicPaths: () => [...publicPaths]
557
+ };
558
+ }
559
+
560
+ // src/core/ui-server.ts
561
+ import { join as join2, extname } from "path";
562
+ var MIME_TYPES = {
563
+ ".html": "text/html",
564
+ ".js": "application/javascript",
565
+ ".mjs": "application/javascript",
566
+ ".css": "text/css",
567
+ ".json": "application/json",
568
+ ".png": "image/png",
569
+ ".jpg": "image/jpeg",
570
+ ".jpeg": "image/jpeg",
571
+ ".gif": "image/gif",
572
+ ".svg": "image/svg+xml",
573
+ ".ico": "image/x-icon",
574
+ ".woff": "font/woff",
575
+ ".woff2": "font/woff2",
576
+ ".ttf": "font/ttf",
577
+ ".map": "application/json",
578
+ ".wasm": "application/wasm"
579
+ };
580
+ function getMimeType(filePath) {
581
+ return MIME_TYPES[extname(filePath).toLowerCase()] || "application/octet-stream";
582
+ }
583
+ var AUTH_BRIDGE_SCRIPT = `<script>
584
+ window.__VIBE_AUTH__={apiKey:null};
585
+ (function(){
586
+ var p=new URLSearchParams(window.location.search);
587
+ if(p.get('apiKey'))window.__VIBE_AUTH__.apiKey=p.get('apiKey');
588
+ window.addEventListener('message',function(e){
589
+ if(e.data&&e.data.type==='vibe-auth'&&e.data.apiKey){
590
+ window.__VIBE_AUTH__.apiKey=e.data.apiKey;
591
+ window.dispatchEvent(new CustomEvent('vibe-auth-ready'));
592
+ }
593
+ });
594
+ })();
595
+ </script>`;
596
+ function injectAuthBridge(html) {
597
+ if (html.includes("</head>")) {
598
+ return html.replace("</head>", AUTH_BRIDGE_SCRIPT + "</head>");
599
+ }
600
+ if (html.includes("</body>")) {
601
+ return html.replace("</body>", AUTH_BRIDGE_SCRIPT + "</body>");
602
+ }
603
+ return html + AUTH_BRIDGE_SCRIPT;
604
+ }
605
+ function renderUIIndex(plugins) {
606
+ const items = plugins.map((p) => {
607
+ const tags = (p.tags || []).map((t) => `<span class="tag">${t}</span>`).join(" ");
608
+ return `<li><a href="/ui/${p.name}">${p.title || p.name}</a> ${tags}</li>`;
609
+ }).join(`
610
+ `);
611
+ return `<!DOCTYPE html>
612
+ <html lang="en">
613
+ <head>
614
+ <meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
615
+ <title>VibeControls - Plugin UIs</title>
616
+ <style>
617
+ body{font-family:system-ui,-apple-system,sans-serif;max-width:800px;margin:2rem auto;padding:0 1rem;background:#0a0a0a;color:#e0e0e0}
618
+ h1{color:#fff}a{color:#60a5fa;text-decoration:none}a:hover{text-decoration:underline}
619
+ ul{list-style:none;padding:0}li{padding:0.75rem 1rem;border:1px solid #333;border-radius:8px;margin-bottom:0.5rem;display:flex;align-items:center;gap:0.5rem}
620
+ .tag{font-size:0.75rem;padding:2px 8px;border-radius:4px;background:#1e3a5f;color:#93c5fd}
621
+ .empty{color:#888;font-style:italic}
622
+ </style>
623
+ </head>
624
+ <body>
625
+ <h1>VibeControls Plugin UIs</h1>
626
+ ${plugins.length > 0 ? `<ul>${items}</ul>` : '<p class="empty">No plugins with UIs installed.</p>'}
627
+ </body>
628
+ </html>`;
629
+ }
630
+ function createUIServer(pluginManager) {
631
+ return new Elysia({ prefix: "/ui" }).get("/", () => {
632
+ const plugins = pluginManager.getAllPlugins().filter((p) => p.ui).map((p) => ({
633
+ name: p.name,
634
+ title: p.ui?.title ?? p.name,
635
+ tags: p.tags
636
+ }));
637
+ return new Response(renderUIIndex(plugins), {
638
+ headers: { "Content-Type": "text/html; charset=utf-8" }
639
+ });
640
+ }).get("/*", async ({ params, set }) => {
641
+ const wildcard = params["*"] || "";
642
+ const parts = wildcard.split("/").filter(Boolean);
643
+ if (parts.length === 0) {
644
+ set.status = 404;
645
+ return { error: "Plugin not specified" };
646
+ }
647
+ const pluginKey = parts[0];
648
+ let plugin = pluginManager.getPluginByKey(pluginKey);
649
+ let subPath = parts.slice(1).join("/");
650
+ if (plugin && parts.length >= 2) {
651
+ const childPlugin = pluginManager.getPluginByKey(parts[1]);
652
+ if (childPlugin?.ui) {
653
+ plugin = childPlugin;
654
+ subPath = parts.slice(2).join("/");
655
+ }
656
+ }
657
+ if (!plugin?.ui) {
658
+ set.status = 404;
659
+ return { error: `No UI available for plugin '${pluginKey}'` };
660
+ }
661
+ if (plugin.ui.devUrl) {
662
+ try {
663
+ const targetUrl = new URL(subPath || "/", plugin.ui.devUrl).toString();
664
+ const resp = await fetch(targetUrl);
665
+ const body = await resp.arrayBuffer();
666
+ const headers = new Headers;
667
+ resp.headers.forEach((v, k) => headers.set(k, v));
668
+ return new Response(body, { status: resp.status, headers });
669
+ } catch {
670
+ set.status = 502;
671
+ return {
672
+ error: `Failed to proxy to dev server at ${plugin.ui.devUrl}`
673
+ };
674
+ }
675
+ }
676
+ if (plugin.ui.staticDir) {
677
+ const baseDir = plugin.ui.staticDir;
678
+ const requestedPath = subPath || "index.html";
679
+ const filePath = join2(baseDir, requestedPath);
680
+ if (!filePath.startsWith(baseDir)) {
681
+ set.status = 403;
682
+ return { error: "Access denied" };
683
+ }
684
+ const file = Bun.file(filePath);
685
+ if (await file.exists()) {
686
+ const contentType = getMimeType(filePath);
687
+ if (contentType === "text/html") {
688
+ const html = await file.text();
689
+ return new Response(injectAuthBridge(html), {
690
+ headers: { "Content-Type": "text/html; charset=utf-8" }
691
+ });
692
+ }
693
+ return new Response(file, {
694
+ headers: { "Content-Type": contentType }
695
+ });
696
+ }
697
+ const indexFile = Bun.file(join2(baseDir, "index.html"));
698
+ if (await indexFile.exists()) {
699
+ const html = await indexFile.text();
700
+ return new Response(injectAuthBridge(html), {
701
+ headers: { "Content-Type": "text/html; charset=utf-8" }
702
+ });
703
+ }
704
+ set.status = 404;
705
+ return { error: "File not found" };
706
+ }
707
+ set.status = 404;
708
+ return { error: "No UI configuration (neither staticDir nor devUrl)" };
709
+ });
710
+ }
711
+
712
+ // src/middleware/auth.ts
713
+ import crypto from "crypto";
714
+ import { join as join3 } from "path";
715
+ import { homedir } from "os";
716
+ import { existsSync as existsSync2, readFileSync } from "fs";
717
+ var STATIC_EXEMPT_PREFIXES = [
718
+ "/health",
719
+ "/api/agent/identity",
720
+ "/api/agent/api-key",
721
+ "/api/agent/tunnel",
722
+ "/terminal/",
723
+ "/ws/",
724
+ "/ui/"
725
+ ];
726
+ function isExempt(path, dynamicPaths) {
727
+ if (STATIC_EXEMPT_PREFIXES.some((prefix) => path.startsWith(prefix))) {
728
+ return true;
729
+ }
730
+ return dynamicPaths.some((prefix) => path.startsWith(prefix));
731
+ }
732
+ function generateApiKey() {
733
+ const bytes = crypto.randomBytes(32);
734
+ return `vcak_${bytes.toString("base64url")}`;
735
+ }
736
+ function loadStaticApiKey() {
737
+ const configFile = join3(homedir(), ".vibecontrols", "config.json");
738
+ if (existsSync2(configFile)) {
739
+ try {
740
+ const config = JSON.parse(readFileSync(configFile, "utf8"));
741
+ return config["static-api-key"] || undefined;
742
+ } catch {
743
+ return;
744
+ }
745
+ }
746
+ return;
747
+ }
748
+ function createAuthPlugin(getPluginPublicPaths) {
749
+ const envKey = process.env.AGENT_API_KEY;
750
+ const staticKey = loadStaticApiKey();
751
+ const apiKey = envKey || staticKey || generateApiKey();
752
+ logger.info("auth", `Agent API key: ${apiKey.substring(0, 12)}...`);
753
+ return new Elysia({ name: "plugin/auth" }).decorate("apiKey", apiKey).derive(({ headers, path }) => {
754
+ const dynamicPaths = getPluginPublicPaths?.() ?? [];
755
+ return {
756
+ isAuthenticated: (() => {
757
+ if (isExempt(path, dynamicPaths))
758
+ return true;
759
+ const providedKey = headers["x-agent-api-key"];
760
+ return providedKey === apiKey;
761
+ })()
762
+ };
763
+ }).onBeforeHandle(({ isAuthenticated, path, set }) => {
764
+ const dynamicPaths = getPluginPublicPaths?.() ?? [];
765
+ if (!isAuthenticated && !isExempt(path, dynamicPaths)) {
766
+ set.status = 401;
767
+ return { error: "Unauthorized", message: "Invalid or missing API key" };
768
+ }
769
+ });
770
+ }
771
+
772
+ // src/routes/health.routes.ts
773
+ function createHealthRoutes(_serviceRegistry) {
774
+ return new Elysia({ prefix: "/health" }).get("/", () => ({
775
+ status: "ok",
776
+ timestamp: new Date().toISOString(),
777
+ uptime: process.uptime()
778
+ })).get("/live", () => ({ status: "ok" })).get("/ready", () => ({ status: "ok" }));
779
+ }
780
+
781
+ // src/ws/events.ws.ts
782
+ import { EventEmitter } from "events";
783
+ var eventBus = new EventEmitter;
784
+ eventBus.setMaxListeners(100);
785
+ function createEventsWs() {
786
+ const clients = new Set;
787
+ eventBus.on("ws:event", (event) => {
788
+ const json = JSON.stringify(event);
789
+ for (const client of clients) {
790
+ try {
791
+ client.send(json);
792
+ } catch {}
793
+ }
794
+ });
795
+ return new Elysia().ws("/ws/events", {
796
+ open(ws) {
797
+ const client = {
798
+ ws,
799
+ channels: new Set,
800
+ send: (data) => ws.send(data)
801
+ };
802
+ clients.add(client);
803
+ ws._client = client;
804
+ logger.info("ws-events", "Client connected", {
805
+ id: String(ws.id || "unknown")
806
+ });
807
+ ws.send(JSON.stringify({
808
+ type: "connected",
809
+ timestamp: new Date().toISOString()
810
+ }));
811
+ },
812
+ message(ws, message) {
813
+ try {
814
+ const data = typeof message === "string" ? JSON.parse(message) : message;
815
+ switch (data.type) {
816
+ case "subscribe": {
817
+ const client = ws._client;
818
+ if (client)
819
+ client.channels.add(data.channel);
820
+ logger.debug("ws-events", `Subscribed to ${data.channel}`);
821
+ break;
822
+ }
823
+ case "unsubscribe": {
824
+ const client = ws._client;
825
+ if (client)
826
+ client.channels.delete(data.channel);
827
+ logger.debug("ws-events", `Unsubscribed from ${data.channel}`);
828
+ break;
829
+ }
830
+ case "ping":
831
+ ws.send(JSON.stringify({
832
+ type: "pong",
833
+ timestamp: new Date().toISOString()
834
+ }));
835
+ break;
836
+ default:
837
+ logger.debug("ws-events", `Unknown message type: ${data.type}`);
838
+ }
839
+ } catch {
840
+ logger.warn("ws-events", "Failed to parse incoming message");
841
+ }
842
+ },
843
+ close(ws) {
844
+ const client = ws._client;
845
+ if (client)
846
+ clients.delete(client);
847
+ logger.info("ws-events", "Client disconnected");
848
+ }
849
+ });
850
+ }
851
+ // src/ws/terminal.ws.ts
852
+ async function proxyToTtyd(port, upstreamPath, method, headers, body) {
853
+ const fetchHeaders = {};
854
+ for (const [k, v] of Object.entries(headers)) {
855
+ if (v && !["host", "connection"].includes(k.toLowerCase())) {
856
+ fetchHeaders[k] = v;
857
+ }
858
+ }
859
+ const init = { method, headers: fetchHeaders };
860
+ if (method !== "GET" && method !== "HEAD" && body) {
861
+ init.body = typeof body === "string" ? body : JSON.stringify(body);
862
+ }
863
+ return fetch(`http://127.0.0.1:${port}${upstreamPath}`, init);
864
+ }
865
+ async function resolveTtydPort(serviceRegistry, sessionId) {
866
+ const provider = serviceRegistry.getProvider("session");
867
+ if (!provider)
868
+ return null;
869
+ const terminal = await provider.getTerminalInfo(sessionId);
870
+ if (!terminal)
871
+ return null;
872
+ try {
873
+ process.kill(terminal.pid, 0);
874
+ return terminal.port;
875
+ } catch {
876
+ return null;
877
+ }
878
+ }
879
+ function createTerminalProxy(serviceRegistry) {
880
+ return new Elysia().all("/terminal/:sessionId", async ({ params, request, set }) => {
881
+ const port = await resolveTtydPort(serviceRegistry, params.sessionId);
882
+ if (!port) {
883
+ set.status = 502;
884
+ return { error: "Terminal not running for this session" };
885
+ }
886
+ const upstream = await proxyToTtyd(port, "/", request.method, Object.fromEntries(Object.entries(request.headers).map(([k, v]) => [k, v])));
887
+ set.status = upstream.status;
888
+ const buf = await upstream.arrayBuffer();
889
+ return new Response(buf, {
890
+ status: upstream.status,
891
+ headers: upstream.headers
892
+ });
893
+ }).all("/terminal/:sessionId/*", async ({ params, request, set }) => {
894
+ const sessionId = params.sessionId;
895
+ const wildcardPath = params["*"] || "";
896
+ const port = await resolveTtydPort(serviceRegistry, sessionId);
897
+ if (!port) {
898
+ set.status = 502;
899
+ return { error: "Terminal not running for this session" };
900
+ }
901
+ const upstream = await proxyToTtyd(port, `/${wildcardPath}`, request.method, Object.fromEntries(Object.entries(request.headers).map(([k, v]) => [k, v])));
902
+ set.status = upstream.status;
903
+ const buf = await upstream.arrayBuffer();
904
+ return new Response(buf, {
905
+ status: upstream.status,
906
+ headers: upstream.headers
907
+ });
908
+ }).ws("/terminal/:sessionId/ws", {
909
+ open(ws) {
910
+ const wsData = ws.data;
911
+ const sessionId = wsData.params?.sessionId;
912
+ if (!sessionId) {
913
+ ws.close(1008, "Missing sessionId");
914
+ return;
915
+ }
916
+ logger.info("terminal-ws", `Client connected for session ${sessionId}`);
917
+ resolveTtydPort(serviceRegistry, sessionId).then((port) => {
918
+ if (!port) {
919
+ logger.warn("terminal-ws", `No ttyd running for session ${sessionId}`);
920
+ ws.close(1011, "No terminal running");
921
+ return;
922
+ }
923
+ const upstreamUrl = `ws://127.0.0.1:${port}/ws`;
924
+ const upstreamWs = new WebSocket(upstreamUrl, ["tty"]);
925
+ const upstreamBuffer = [];
926
+ let upstreamReady = false;
927
+ upstreamWs.addEventListener("open", () => {
928
+ upstreamReady = true;
929
+ logger.info("terminal-ws", `Bridge established \u2192 port ${port}`, {
930
+ sessionId
931
+ });
932
+ for (const msg of upstreamBuffer) {
933
+ upstreamWs.send(msg);
934
+ }
935
+ upstreamBuffer.length = 0;
936
+ });
937
+ upstreamWs.addEventListener("message", (event) => {
938
+ try {
939
+ ws.send(event.data);
940
+ } catch {}
941
+ });
942
+ upstreamWs.addEventListener("close", (event) => {
943
+ logger.info("terminal-ws", `Upstream closed (code=${event.code})`, { sessionId });
944
+ try {
945
+ ws.close(1000, "Upstream closed");
946
+ } catch {}
947
+ });
948
+ upstreamWs.addEventListener("error", (event) => {
949
+ logger.error("terminal-ws", `Upstream error`, { sessionId });
950
+ try {
951
+ ws.close(1011, "Upstream error");
952
+ } catch {}
953
+ });
954
+ ws._upstream = upstreamWs;
955
+ ws._upstreamReady = () => upstreamReady;
956
+ ws._upstreamBuffer = upstreamBuffer;
957
+ });
958
+ },
959
+ message(ws, message) {
960
+ const upstreamWs = ws._upstream;
961
+ const isReady = ws._upstreamReady?.();
962
+ const buffer = ws._upstreamBuffer;
963
+ if (upstreamWs && isReady && upstreamWs.readyState === WebSocket.OPEN) {
964
+ upstreamWs.send(message);
965
+ } else if (buffer) {
966
+ buffer.push(message);
967
+ }
968
+ },
969
+ close(ws) {
970
+ const sessionId = ws.data.params?.sessionId;
971
+ logger.info("terminal-ws", `Client disconnected`, { sessionId });
972
+ const upstreamWs = ws._upstream;
973
+ if (upstreamWs && upstreamWs.readyState === WebSocket.OPEN) {
974
+ upstreamWs.close(1000, "Client disconnected");
975
+ }
976
+ }
977
+ });
978
+ }
979
+ // src/ws/logs.ws.ts
980
+ var LEVEL_PRIORITY = {
981
+ debug: 0,
982
+ info: 1,
983
+ warn: 2,
984
+ error: 3
985
+ };
986
+ function createLogWs() {
987
+ return new Elysia().ws("/ws/logs", {
988
+ open(ws) {
989
+ logger.info("ws-logs", "Log stream client connected");
990
+ const filters = {
991
+ minLevel: "info",
992
+ source: ""
993
+ };
994
+ ws._filters = filters;
995
+ const onLog = (entry) => {
996
+ const f = ws._filters;
997
+ if (LEVEL_PRIORITY[entry.level] < LEVEL_PRIORITY[f.minLevel])
998
+ return;
999
+ if (f.source && !entry.source.includes(f.source))
1000
+ return;
1001
+ try {
1002
+ ws.send(JSON.stringify(entry));
1003
+ } catch {}
1004
+ };
1005
+ logger.on("log", onLog);
1006
+ ws._logHandler = onLog;
1007
+ ws.send(JSON.stringify({
1008
+ type: "connected",
1009
+ timestamp: new Date().toISOString()
1010
+ }));
1011
+ },
1012
+ message(ws, message) {
1013
+ try {
1014
+ const data = typeof message === "string" ? JSON.parse(message) : message;
1015
+ if (data.type === "filter") {
1016
+ const filters = ws._filters;
1017
+ if (data.level)
1018
+ filters.minLevel = data.level;
1019
+ if (data.source !== undefined)
1020
+ filters.source = data.source;
1021
+ ws.send(JSON.stringify({ type: "filter-updated", filters }));
1022
+ }
1023
+ } catch {}
1024
+ },
1025
+ close(ws) {
1026
+ const handler = ws._logHandler;
1027
+ if (handler) {
1028
+ logger.removeListener("log", handler);
1029
+ }
1030
+ logger.info("ws-logs", "Log stream client disconnected");
1031
+ }
1032
+ });
1033
+ }
1034
+ // src/app.ts
1035
+ async function createApp(options) {
1036
+ const { port, host, dbPath, logLevel, corsOrigin } = options;
1037
+ if (logLevel) {
1038
+ logger.setLevel(logLevel);
1039
+ }
1040
+ const db = new AgentDatabase(dbPath);
1041
+ const serviceRegistry = new ServiceRegistry(db);
1042
+ const pluginManager = new PluginManager(db);
1043
+ await pluginManager.loadCorePlugins();
1044
+ try {
1045
+ await pluginManager.loadAll();
1046
+ } catch (err) {
1047
+ logger.warn("app", "Failed to load some external plugins", {
1048
+ error: String(err)
1049
+ });
1050
+ }
1051
+ logger.info("app", `Plugins loaded: ${pluginManager.getPluginDetails().length} total (${pluginManager.getPluginDetails().filter((p) => p.isCore).length} core)`);
1052
+ try {
1053
+ const gwGlobalUrl = db.getConfig("gateway-auth:globalGatewayUrl");
1054
+ const gwClientId = db.getConfig("gateway-auth:clientId");
1055
+ const gwClientSecret = db.getConfig("gateway-auth:clientSecret");
1056
+ if (gwGlobalUrl && gwClientId && gwClientSecret) {
1057
+ const gwWorkspaceUrl = db.getConfig("gateway-auth:workspaceGatewayUrl");
1058
+ gatewayClient.configure({
1059
+ globalGatewayUrl: gwGlobalUrl,
1060
+ workspaceGatewayUrl: gwWorkspaceUrl,
1061
+ clientId: gwClientId,
1062
+ clientSecret: gwClientSecret
1063
+ });
1064
+ logger.info("app", "Restored gateway client config from database");
1065
+ }
1066
+ } catch (err) {
1067
+ logger.warn("app", "Failed to restore gateway client config", {
1068
+ error: String(err)
1069
+ });
1070
+ }
1071
+ const storageProvider = {
1072
+ async get(key) {
1073
+ return db.getPluginState("_host", key) ?? null;
1074
+ },
1075
+ async set(key, value) {
1076
+ db.setPluginState("_host", key, value);
1077
+ },
1078
+ async delete(key) {
1079
+ return db.deletePluginState("_host", key);
1080
+ },
1081
+ async list() {
1082
+ return db.getAllPluginState("_host");
1083
+ },
1084
+ async deleteAll() {
1085
+ return db.deleteAllPluginState("_host");
1086
+ }
1087
+ };
1088
+ const packageVersion = await getPackageVersion();
1089
+ const hostServices = {
1090
+ storage: storageProvider,
1091
+ logger,
1092
+ serviceRegistry,
1093
+ getProvider: (type) => serviceRegistry.getProvider(type),
1094
+ getAgentBaseUrl: () => process.env.AGENT_URL || `http://localhost:${port}`,
1095
+ getAgentVersion: () => packageVersion
1096
+ };
1097
+ const { routes: pluginRoutes, getPublicPaths } = createPluginRouter(pluginManager, { db, serviceRegistry, pluginManager });
1098
+ const uiServer = createUIServer(pluginManager);
1099
+ const app = new Elysia().use(cors({
1100
+ origin: corsOrigin || true,
1101
+ credentials: true
1102
+ })).use(createAuthPlugin(getPublicPaths)).onAfterHandle(({ request, set }) => {
1103
+ const url = new URL(request.url).pathname;
1104
+ const isNoisy = url === "/health" || url.startsWith("/api/logs/stream") || url.startsWith("/ui/");
1105
+ const status = typeof set.status === "number" ? set.status : 200;
1106
+ const level = isNoisy ? "debug" : status >= 400 ? "warn" : "info";
1107
+ logger[level]("http", `${request.method} ${url} \u2192 ${status}`, {
1108
+ method: request.method,
1109
+ path: url,
1110
+ statusCode: status
1111
+ });
1112
+ }).use(createHealthRoutes(serviceRegistry)).use(pluginRoutes).use(uiServer).use(createEventsWs()).use(createLogWs()).use(createTerminalProxy(serviceRegistry));
1113
+ try {
1114
+ await pluginManager.dispatchServerStart(app, hostServices);
1115
+ } catch (err) {
1116
+ logger.warn("app", "Error during plugin onServerStart dispatch", {
1117
+ error: String(err)
1118
+ });
1119
+ }
1120
+ return {
1121
+ app,
1122
+ db,
1123
+ serviceRegistry,
1124
+ pluginManager,
1125
+ hostServices,
1126
+ async start() {
1127
+ app.listen({ port, hostname: host });
1128
+ logger.info("app", `Agent server listening on ${host}:${port}`);
1129
+ try {
1130
+ await pluginManager.dispatchServerReady(app, hostServices);
1131
+ } catch (err) {
1132
+ logger.warn("app", "Error during plugin onServerReady dispatch", {
1133
+ error: String(err)
1134
+ });
1135
+ }
1136
+ return app;
1137
+ },
1138
+ async stop() {
1139
+ try {
1140
+ await pluginManager.dispatchServerStop();
1141
+ } catch (err) {
1142
+ logger.warn("app", "Error stopping plugins", { error: String(err) });
1143
+ }
1144
+ db.close();
1145
+ logger.close();
1146
+ logger.info("app", "Agent server stopped");
1147
+ }
1148
+ };
1149
+ }
1150
+ async function getPackageVersion() {
1151
+ try {
1152
+ const { readFileSync: readFileSync2 } = await import("fs");
1153
+ const { join: join4, dirname } = await import("path");
1154
+ const { fileURLToPath } = await import("url");
1155
+ const __dirname2 = dirname(fileURLToPath(import.meta.url));
1156
+ const packageJson = JSON.parse(readFileSync2(join4(__dirname2, "..", "package.json"), "utf8"));
1157
+ return packageJson.version || "1.0.0";
1158
+ } catch {
1159
+ return "1.0.0";
1160
+ }
1161
+ }
1162
+
1163
+ export { createApp };
1164
+
1165
+ //# debugId=A25F451BEB8F8D4E64756E2164756E21
1166
+ //# sourceMappingURL=app-6mmbmske.js.map