@askexenow/exe-os 0.9.7 → 0.9.9

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 (101) hide show
  1. package/dist/bin/backfill-conversations.js +953 -105
  2. package/dist/bin/backfill-responses.js +952 -104
  3. package/dist/bin/backfill-vectors.js +956 -108
  4. package/dist/bin/cleanup-stale-review-tasks.js +802 -58
  5. package/dist/bin/cli.js +2292 -1070
  6. package/dist/bin/exe-agent-config.js +157 -101
  7. package/dist/bin/exe-agent.js +55 -29
  8. package/dist/bin/exe-assign.js +940 -92
  9. package/dist/bin/exe-boot.js +1424 -442
  10. package/dist/bin/exe-call.js +240 -141
  11. package/dist/bin/exe-cloud.js +198 -70
  12. package/dist/bin/exe-dispatch.js +951 -192
  13. package/dist/bin/exe-doctor.js +791 -51
  14. package/dist/bin/exe-export-behaviors.js +790 -42
  15. package/dist/bin/exe-forget.js +771 -31
  16. package/dist/bin/exe-gateway.js +1592 -521
  17. package/dist/bin/exe-heartbeat.js +850 -109
  18. package/dist/bin/exe-kill.js +783 -35
  19. package/dist/bin/exe-launch-agent.js +1030 -107
  20. package/dist/bin/exe-link.js +916 -110
  21. package/dist/bin/exe-new-employee.js +526 -217
  22. package/dist/bin/exe-pending-messages.js +1046 -62
  23. package/dist/bin/exe-pending-notifications.js +1318 -111
  24. package/dist/bin/exe-pending-reviews.js +1040 -72
  25. package/dist/bin/exe-rename.js +772 -59
  26. package/dist/bin/exe-review.js +772 -32
  27. package/dist/bin/exe-search.js +982 -128
  28. package/dist/bin/exe-session-cleanup.js +1180 -306
  29. package/dist/bin/exe-settings.js +185 -105
  30. package/dist/bin/exe-start-codex.js +886 -132
  31. package/dist/bin/exe-start-opencode.js +873 -119
  32. package/dist/bin/exe-status.js +803 -59
  33. package/dist/bin/exe-team.js +772 -32
  34. package/dist/bin/git-sweep.js +1046 -223
  35. package/dist/bin/graph-backfill.js +779 -31
  36. package/dist/bin/graph-export.js +785 -37
  37. package/dist/bin/install.js +632 -200
  38. package/dist/bin/scan-tasks.js +1055 -232
  39. package/dist/bin/setup.js +1419 -320
  40. package/dist/bin/shard-migrate.js +783 -35
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +782 -34
  43. package/dist/gateway/index.js +1444 -449
  44. package/dist/hooks/bug-report-worker.js +1141 -269
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +1044 -221
  47. package/dist/hooks/error-recall.js +989 -135
  48. package/dist/hooks/exe-heartbeat-hook.js +99 -75
  49. package/dist/hooks/ingest-worker.js +4176 -3226
  50. package/dist/hooks/ingest.js +920 -168
  51. package/dist/hooks/instructions-loaded.js +874 -70
  52. package/dist/hooks/notification.js +860 -56
  53. package/dist/hooks/post-compact.js +881 -73
  54. package/dist/hooks/pre-compact.js +1050 -227
  55. package/dist/hooks/pre-tool-use.js +1084 -159
  56. package/dist/hooks/prompt-ingest-worker.js +1089 -164
  57. package/dist/hooks/prompt-submit.js +1469 -515
  58. package/dist/hooks/response-ingest-worker.js +1104 -179
  59. package/dist/hooks/session-end.js +1085 -251
  60. package/dist/hooks/session-start.js +1241 -231
  61. package/dist/hooks/stop.js +935 -109
  62. package/dist/hooks/subagent-stop.js +881 -73
  63. package/dist/hooks/summary-worker.js +1323 -307
  64. package/dist/index.js +1449 -452
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +909 -115
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +42 -9
  69. package/dist/lib/database.js +739 -33
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +2359 -0
  72. package/dist/lib/device-registry.js +760 -47
  73. package/dist/lib/embedder.js +201 -73
  74. package/dist/lib/employee-templates.js +30 -4
  75. package/dist/lib/employees.js +290 -86
  76. package/dist/lib/exe-daemon-client.js +187 -83
  77. package/dist/lib/exe-daemon.js +1696 -616
  78. package/dist/lib/hybrid-search.js +982 -128
  79. package/dist/lib/identity.js +43 -13
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +167 -80
  82. package/dist/lib/reminders.js +35 -5
  83. package/dist/lib/schedules.js +772 -32
  84. package/dist/lib/skill-learning.js +54 -7
  85. package/dist/lib/store.js +779 -31
  86. package/dist/lib/task-router.js +94 -73
  87. package/dist/lib/tasks.js +298 -225
  88. package/dist/lib/tmux-routing.js +246 -172
  89. package/dist/lib/token-spend.js +52 -14
  90. package/dist/mcp/server.js +2893 -850
  91. package/dist/mcp/tools/complete-reminder.js +35 -5
  92. package/dist/mcp/tools/create-reminder.js +35 -5
  93. package/dist/mcp/tools/create-task.js +507 -323
  94. package/dist/mcp/tools/deactivate-behavior.js +40 -10
  95. package/dist/mcp/tools/list-reminders.js +35 -5
  96. package/dist/mcp/tools/list-tasks.js +277 -104
  97. package/dist/mcp/tools/send-message.js +129 -56
  98. package/dist/mcp/tools/update-task.js +1864 -188
  99. package/dist/runtime/index.js +1083 -259
  100. package/dist/tui/App.js +1501 -434
  101. package/package.json +3 -2
@@ -8,9 +8,34 @@ var __export = (target, all) => {
8
8
  __defProp(target, name, { get: all[name], enumerable: true });
9
9
  };
10
10
 
11
+ // src/lib/secure-files.ts
12
+ import { chmodSync, existsSync, mkdirSync } from "fs";
13
+ import { chmod, mkdir } from "fs/promises";
14
+ function ensurePrivateDirSync(dirPath) {
15
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
16
+ try {
17
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
18
+ } catch {
19
+ }
20
+ }
21
+ function enforcePrivateFileSync(filePath) {
22
+ try {
23
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
24
+ } catch {
25
+ }
26
+ }
27
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
28
+ var init_secure_files = __esm({
29
+ "src/lib/secure-files.ts"() {
30
+ "use strict";
31
+ PRIVATE_DIR_MODE = 448;
32
+ PRIVATE_FILE_MODE = 384;
33
+ }
34
+ });
35
+
11
36
  // src/lib/config.ts
12
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
13
- import { readFileSync, existsSync, renameSync } from "fs";
37
+ import { readFile, writeFile } from "fs/promises";
38
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
14
39
  import path from "path";
15
40
  import os from "os";
16
41
  function resolveDataDir() {
@@ -18,7 +43,7 @@ function resolveDataDir() {
18
43
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
19
44
  const newDir = path.join(os.homedir(), ".exe-os");
20
45
  const legacyDir = path.join(os.homedir(), ".exe-mem");
21
- if (!existsSync(newDir) && existsSync(legacyDir)) {
46
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
22
47
  try {
23
48
  renameSync(legacyDir, newDir);
24
49
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -33,6 +58,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
33
58
  var init_config = __esm({
34
59
  "src/lib/config.ts"() {
35
60
  "use strict";
61
+ init_secure_files();
36
62
  EXE_AI_DIR = resolveDataDir();
37
63
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
38
64
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -156,7 +182,7 @@ var init_db_retry = __esm({
156
182
 
157
183
  // src/lib/employees.ts
158
184
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
159
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
185
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
160
186
  import { execSync } from "child_process";
161
187
  import path2 from "path";
162
188
  import os2 from "os";
@@ -173,14 +199,14 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
173
199
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
174
200
  }
175
201
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
176
- if (!existsSync2(employeesPath)) return [];
202
+ if (!existsSync3(employeesPath)) return [];
177
203
  try {
178
204
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
179
205
  } catch {
180
206
  return [];
181
207
  }
182
208
  }
183
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
209
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
184
210
  var init_employees = __esm({
185
211
  "src/lib/employees.ts"() {
186
212
  "use strict";
@@ -188,16 +214,638 @@ var init_employees = __esm({
188
214
  EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
189
215
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
190
216
  COORDINATOR_ROLE = "COO";
217
+ IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
218
+ }
219
+ });
220
+
221
+ // src/lib/database-adapter.ts
222
+ import os3 from "os";
223
+ import path3 from "path";
224
+ import { createRequire } from "module";
225
+ import { pathToFileURL } from "url";
226
+ function quotedIdentifier(identifier) {
227
+ return `"${identifier.replace(/"/g, '""')}"`;
228
+ }
229
+ function unqualifiedTableName(name) {
230
+ const raw = name.trim().replace(/^"|"$/g, "");
231
+ const parts = raw.split(".");
232
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
233
+ }
234
+ function stripTrailingSemicolon(sql) {
235
+ return sql.trim().replace(/;+\s*$/u, "");
236
+ }
237
+ function appendClause(sql, clause) {
238
+ const trimmed = stripTrailingSemicolon(sql);
239
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
240
+ if (!returningMatch) {
241
+ return `${trimmed}${clause}`;
242
+ }
243
+ const idx = returningMatch.index;
244
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
245
+ }
246
+ function normalizeStatement(stmt) {
247
+ if (typeof stmt === "string") {
248
+ return { kind: "positional", sql: stmt, args: [] };
249
+ }
250
+ const sql = stmt.sql;
251
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
252
+ return { kind: "positional", sql, args: stmt.args ?? [] };
253
+ }
254
+ return { kind: "named", sql, args: stmt.args };
255
+ }
256
+ function rewriteBooleanLiterals(sql) {
257
+ let out = sql;
258
+ for (const column of BOOLEAN_COLUMN_NAMES) {
259
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
260
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
261
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
262
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
263
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
264
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
265
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
266
+ }
267
+ return out;
268
+ }
269
+ function rewriteInsertOrIgnore(sql) {
270
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
271
+ return sql;
272
+ }
273
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
274
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
275
+ }
276
+ function rewriteInsertOrReplace(sql) {
277
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
278
+ if (!match) {
279
+ return sql;
280
+ }
281
+ const rawTable = match[1];
282
+ const rawColumns = match[2];
283
+ const remainder = match[3];
284
+ const tableName = unqualifiedTableName(rawTable);
285
+ const conflictKeys = UPSERT_KEYS[tableName];
286
+ if (!conflictKeys?.length) {
287
+ return sql;
288
+ }
289
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
290
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
291
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
292
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
293
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
294
+ }
295
+ function rewriteSql(sql) {
296
+ let out = sql;
297
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
298
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
299
+ out = rewriteBooleanLiterals(out);
300
+ out = rewriteInsertOrReplace(out);
301
+ out = rewriteInsertOrIgnore(out);
302
+ return stripTrailingSemicolon(out);
303
+ }
304
+ function toBoolean(value) {
305
+ if (value === null || value === void 0) return value;
306
+ if (typeof value === "boolean") return value;
307
+ if (typeof value === "number") return value !== 0;
308
+ if (typeof value === "bigint") return value !== 0n;
309
+ if (typeof value === "string") {
310
+ const normalized = value.trim().toLowerCase();
311
+ if (normalized === "0" || normalized === "false") return false;
312
+ if (normalized === "1" || normalized === "true") return true;
313
+ }
314
+ return Boolean(value);
315
+ }
316
+ function countQuestionMarks(sql, end) {
317
+ let count = 0;
318
+ let inSingle = false;
319
+ let inDouble = false;
320
+ let inLineComment = false;
321
+ let inBlockComment = false;
322
+ for (let i = 0; i < end; i++) {
323
+ const ch = sql[i];
324
+ const next = sql[i + 1];
325
+ if (inLineComment) {
326
+ if (ch === "\n") inLineComment = false;
327
+ continue;
328
+ }
329
+ if (inBlockComment) {
330
+ if (ch === "*" && next === "/") {
331
+ inBlockComment = false;
332
+ i += 1;
333
+ }
334
+ continue;
335
+ }
336
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
337
+ inLineComment = true;
338
+ i += 1;
339
+ continue;
340
+ }
341
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
342
+ inBlockComment = true;
343
+ i += 1;
344
+ continue;
345
+ }
346
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
347
+ inSingle = !inSingle;
348
+ continue;
349
+ }
350
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
351
+ inDouble = !inDouble;
352
+ continue;
353
+ }
354
+ if (!inSingle && !inDouble && ch === "?") {
355
+ count += 1;
356
+ }
357
+ }
358
+ return count;
359
+ }
360
+ function findBooleanPlaceholderIndexes(sql) {
361
+ const indexes = /* @__PURE__ */ new Set();
362
+ for (const column of BOOLEAN_COLUMN_NAMES) {
363
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
364
+ for (const match of sql.matchAll(pattern)) {
365
+ const matchText = match[0];
366
+ const qIndex = match.index + matchText.lastIndexOf("?");
367
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
368
+ }
369
+ }
370
+ return indexes;
371
+ }
372
+ function coerceInsertBooleanArgs(sql, args) {
373
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
374
+ if (!match) return;
375
+ const rawTable = match[1];
376
+ const rawColumns = match[2];
377
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
378
+ if (!boolColumns?.size) return;
379
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
380
+ for (const [index, column] of columns.entries()) {
381
+ if (boolColumns.has(column) && index < args.length) {
382
+ args[index] = toBoolean(args[index]);
383
+ }
384
+ }
385
+ }
386
+ function coerceUpdateBooleanArgs(sql, args) {
387
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
388
+ if (!match) return;
389
+ const rawTable = match[1];
390
+ const setClause = match[2];
391
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
392
+ if (!boolColumns?.size) return;
393
+ const assignments = setClause.split(",");
394
+ let placeholderIndex = 0;
395
+ for (const assignment of assignments) {
396
+ if (!assignment.includes("?")) continue;
397
+ placeholderIndex += 1;
398
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
399
+ if (colMatch && boolColumns.has(colMatch[1])) {
400
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
401
+ }
402
+ }
403
+ }
404
+ function coerceBooleanArgs(sql, args) {
405
+ const nextArgs = [...args];
406
+ coerceInsertBooleanArgs(sql, nextArgs);
407
+ coerceUpdateBooleanArgs(sql, nextArgs);
408
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
409
+ for (const index of placeholderIndexes) {
410
+ if (index > 0 && index <= nextArgs.length) {
411
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
412
+ }
413
+ }
414
+ return nextArgs;
415
+ }
416
+ function convertQuestionMarksToDollarParams(sql) {
417
+ let out = "";
418
+ let placeholder = 0;
419
+ let inSingle = false;
420
+ let inDouble = false;
421
+ let inLineComment = false;
422
+ let inBlockComment = false;
423
+ for (let i = 0; i < sql.length; i++) {
424
+ const ch = sql[i];
425
+ const next = sql[i + 1];
426
+ if (inLineComment) {
427
+ out += ch;
428
+ if (ch === "\n") inLineComment = false;
429
+ continue;
430
+ }
431
+ if (inBlockComment) {
432
+ out += ch;
433
+ if (ch === "*" && next === "/") {
434
+ out += next;
435
+ inBlockComment = false;
436
+ i += 1;
437
+ }
438
+ continue;
439
+ }
440
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
441
+ out += ch + next;
442
+ inLineComment = true;
443
+ i += 1;
444
+ continue;
445
+ }
446
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
447
+ out += ch + next;
448
+ inBlockComment = true;
449
+ i += 1;
450
+ continue;
451
+ }
452
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
453
+ inSingle = !inSingle;
454
+ out += ch;
455
+ continue;
456
+ }
457
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
458
+ inDouble = !inDouble;
459
+ out += ch;
460
+ continue;
461
+ }
462
+ if (!inSingle && !inDouble && ch === "?") {
463
+ placeholder += 1;
464
+ out += `$${placeholder}`;
465
+ continue;
466
+ }
467
+ out += ch;
468
+ }
469
+ return out;
470
+ }
471
+ function translateStatementForPostgres(stmt) {
472
+ const normalized = normalizeStatement(stmt);
473
+ if (normalized.kind === "named") {
474
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
475
+ }
476
+ const rewrittenSql = rewriteSql(normalized.sql);
477
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
478
+ return {
479
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
480
+ args: coercedArgs
481
+ };
482
+ }
483
+ function shouldBypassPostgres(stmt) {
484
+ const normalized = normalizeStatement(stmt);
485
+ if (normalized.kind === "named") {
486
+ return true;
487
+ }
488
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
489
+ }
490
+ function shouldFallbackOnError(error) {
491
+ const message = error instanceof Error ? error.message : String(error);
492
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
493
+ }
494
+ function isReadQuery(sql) {
495
+ const trimmed = sql.trimStart();
496
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
497
+ }
498
+ function buildRow(row, columns) {
499
+ const values = columns.map((column) => row[column]);
500
+ return Object.assign(values, row);
501
+ }
502
+ function buildResultSet(rows, rowsAffected = 0) {
503
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
504
+ const resultRows = rows.map((row) => buildRow(row, columns));
505
+ return {
506
+ columns,
507
+ columnTypes: columns.map(() => ""),
508
+ rows: resultRows,
509
+ rowsAffected,
510
+ lastInsertRowid: void 0,
511
+ toJSON() {
512
+ return {
513
+ columns,
514
+ columnTypes: columns.map(() => ""),
515
+ rows,
516
+ rowsAffected,
517
+ lastInsertRowid: void 0
518
+ };
519
+ }
520
+ };
521
+ }
522
+ async function loadPrismaClient() {
523
+ if (!prismaClientPromise) {
524
+ prismaClientPromise = (async () => {
525
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
526
+ if (explicitPath) {
527
+ const module2 = await import(pathToFileURL(explicitPath).href);
528
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
529
+ if (!PrismaClient2) {
530
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
531
+ }
532
+ return new PrismaClient2();
533
+ }
534
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
535
+ const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
536
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
537
+ const module = await import(pathToFileURL(prismaEntry).href);
538
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
539
+ if (!PrismaClient) {
540
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
541
+ }
542
+ return new PrismaClient();
543
+ })();
544
+ }
545
+ return prismaClientPromise;
546
+ }
547
+ async function ensureCompatibilityViews(prisma) {
548
+ if (!compatibilityBootstrapPromise) {
549
+ compatibilityBootstrapPromise = (async () => {
550
+ for (const mapping of VIEW_MAPPINGS) {
551
+ const relation = mapping.source.replace(/"/g, "");
552
+ const rows = await prisma.$queryRawUnsafe(
553
+ "SELECT to_regclass($1) AS regclass",
554
+ relation
555
+ );
556
+ if (!rows[0]?.regclass) {
557
+ continue;
558
+ }
559
+ await prisma.$executeRawUnsafe(
560
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
561
+ );
562
+ }
563
+ })();
564
+ }
565
+ return compatibilityBootstrapPromise;
566
+ }
567
+ async function executeOnPrisma(executor, stmt) {
568
+ const translated = translateStatementForPostgres(stmt);
569
+ if (isReadQuery(translated.sql)) {
570
+ const rows = await executor.$queryRawUnsafe(
571
+ translated.sql,
572
+ ...translated.args
573
+ );
574
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
575
+ }
576
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
577
+ return buildResultSet([], rowsAffected);
578
+ }
579
+ function splitSqlStatements(sql) {
580
+ const parts = [];
581
+ let current = "";
582
+ let inSingle = false;
583
+ let inDouble = false;
584
+ let inLineComment = false;
585
+ let inBlockComment = false;
586
+ for (let i = 0; i < sql.length; i++) {
587
+ const ch = sql[i];
588
+ const next = sql[i + 1];
589
+ if (inLineComment) {
590
+ current += ch;
591
+ if (ch === "\n") inLineComment = false;
592
+ continue;
593
+ }
594
+ if (inBlockComment) {
595
+ current += ch;
596
+ if (ch === "*" && next === "/") {
597
+ current += next;
598
+ inBlockComment = false;
599
+ i += 1;
600
+ }
601
+ continue;
602
+ }
603
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
604
+ current += ch + next;
605
+ inLineComment = true;
606
+ i += 1;
607
+ continue;
608
+ }
609
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
610
+ current += ch + next;
611
+ inBlockComment = true;
612
+ i += 1;
613
+ continue;
614
+ }
615
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
616
+ inSingle = !inSingle;
617
+ current += ch;
618
+ continue;
619
+ }
620
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
621
+ inDouble = !inDouble;
622
+ current += ch;
623
+ continue;
624
+ }
625
+ if (!inSingle && !inDouble && ch === ";") {
626
+ if (current.trim()) {
627
+ parts.push(current.trim());
628
+ }
629
+ current = "";
630
+ continue;
631
+ }
632
+ current += ch;
633
+ }
634
+ if (current.trim()) {
635
+ parts.push(current.trim());
636
+ }
637
+ return parts;
638
+ }
639
+ async function createPrismaDbAdapter(fallbackClient) {
640
+ const prisma = await loadPrismaClient();
641
+ await ensureCompatibilityViews(prisma);
642
+ let closed = false;
643
+ let adapter;
644
+ const fallbackExecute = async (stmt, error) => {
645
+ if (!fallbackClient) {
646
+ if (error) throw error;
647
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
648
+ }
649
+ if (error) {
650
+ process.stderr.write(
651
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
652
+ `
653
+ );
654
+ }
655
+ return fallbackClient.execute(stmt);
656
+ };
657
+ adapter = {
658
+ async execute(stmt) {
659
+ if (shouldBypassPostgres(stmt)) {
660
+ return fallbackExecute(stmt);
661
+ }
662
+ try {
663
+ return await executeOnPrisma(prisma, stmt);
664
+ } catch (error) {
665
+ if (shouldFallbackOnError(error)) {
666
+ return fallbackExecute(stmt, error);
667
+ }
668
+ throw error;
669
+ }
670
+ },
671
+ async batch(stmts, mode) {
672
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
673
+ if (!fallbackClient) {
674
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
675
+ }
676
+ return fallbackClient.batch(stmts, mode);
677
+ }
678
+ try {
679
+ if (prisma.$transaction) {
680
+ return await prisma.$transaction(async (tx) => {
681
+ const results2 = [];
682
+ for (const stmt of stmts) {
683
+ results2.push(await executeOnPrisma(tx, stmt));
684
+ }
685
+ return results2;
686
+ });
687
+ }
688
+ const results = [];
689
+ for (const stmt of stmts) {
690
+ results.push(await executeOnPrisma(prisma, stmt));
691
+ }
692
+ return results;
693
+ } catch (error) {
694
+ if (fallbackClient && shouldFallbackOnError(error)) {
695
+ process.stderr.write(
696
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
697
+ `
698
+ );
699
+ return fallbackClient.batch(stmts, mode);
700
+ }
701
+ throw error;
702
+ }
703
+ },
704
+ async migrate(stmts) {
705
+ if (fallbackClient) {
706
+ return fallbackClient.migrate(stmts);
707
+ }
708
+ return adapter.batch(stmts, "deferred");
709
+ },
710
+ async transaction(mode) {
711
+ if (!fallbackClient) {
712
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
713
+ }
714
+ return fallbackClient.transaction(mode);
715
+ },
716
+ async executeMultiple(sql) {
717
+ if (fallbackClient && shouldBypassPostgres(sql)) {
718
+ return fallbackClient.executeMultiple(sql);
719
+ }
720
+ for (const statement of splitSqlStatements(sql)) {
721
+ await adapter.execute(statement);
722
+ }
723
+ },
724
+ async sync() {
725
+ if (fallbackClient) {
726
+ return fallbackClient.sync();
727
+ }
728
+ return { frame_no: 0, frames_synced: 0 };
729
+ },
730
+ close() {
731
+ closed = true;
732
+ prismaClientPromise = null;
733
+ compatibilityBootstrapPromise = null;
734
+ void prisma.$disconnect?.();
735
+ },
736
+ get closed() {
737
+ return closed;
738
+ },
739
+ get protocol() {
740
+ return "prisma-postgres";
741
+ }
742
+ };
743
+ return adapter;
744
+ }
745
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
746
+ var init_database_adapter = __esm({
747
+ "src/lib/database-adapter.ts"() {
748
+ "use strict";
749
+ VIEW_MAPPINGS = [
750
+ { view: "memories", source: "memory.memory_records" },
751
+ { view: "tasks", source: "memory.tasks" },
752
+ { view: "behaviors", source: "memory.behaviors" },
753
+ { view: "entities", source: "memory.entities" },
754
+ { view: "relationships", source: "memory.relationships" },
755
+ { view: "entity_memories", source: "memory.entity_memories" },
756
+ { view: "entity_aliases", source: "memory.entity_aliases" },
757
+ { view: "notifications", source: "memory.notifications" },
758
+ { view: "messages", source: "memory.messages" },
759
+ { view: "users", source: "wiki.users" },
760
+ { view: "workspaces", source: "wiki.workspaces" },
761
+ { view: "workspace_users", source: "wiki.workspace_users" },
762
+ { view: "documents", source: "wiki.workspace_documents" },
763
+ { view: "chats", source: "wiki.workspace_chats" }
764
+ ];
765
+ UPSERT_KEYS = {
766
+ memories: ["id"],
767
+ tasks: ["id"],
768
+ behaviors: ["id"],
769
+ entities: ["id"],
770
+ relationships: ["id"],
771
+ entity_aliases: ["alias"],
772
+ notifications: ["id"],
773
+ messages: ["id"],
774
+ users: ["id"],
775
+ workspaces: ["id"],
776
+ workspace_users: ["id"],
777
+ documents: ["id"],
778
+ chats: ["id"]
779
+ };
780
+ BOOLEAN_COLUMNS_BY_TABLE = {
781
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
782
+ behaviors: /* @__PURE__ */ new Set(["active"]),
783
+ notifications: /* @__PURE__ */ new Set(["read"]),
784
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
785
+ };
786
+ BOOLEAN_COLUMN_NAMES = new Set(
787
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
788
+ );
789
+ IMMEDIATE_FALLBACK_PATTERNS = [
790
+ /\bPRAGMA\b/i,
791
+ /\bsqlite_master\b/i,
792
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
793
+ /\bMATCH\b/i,
794
+ /\bvector_distance_cos\s*\(/i,
795
+ /\bjson_extract\s*\(/i,
796
+ /\bjulianday\s*\(/i,
797
+ /\bstrftime\s*\(/i,
798
+ /\blast_insert_rowid\s*\(/i
799
+ ];
800
+ prismaClientPromise = null;
801
+ compatibilityBootstrapPromise = null;
802
+ }
803
+ });
804
+
805
+ // src/lib/daemon-auth.ts
806
+ import crypto from "crypto";
807
+ import path4 from "path";
808
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
809
+ function normalizeToken(token) {
810
+ if (!token) return null;
811
+ const trimmed = token.trim();
812
+ return trimmed.length > 0 ? trimmed : null;
813
+ }
814
+ function readDaemonToken() {
815
+ try {
816
+ if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
817
+ return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
818
+ } catch {
819
+ return null;
820
+ }
821
+ }
822
+ function ensureDaemonToken(seed) {
823
+ const existing = readDaemonToken();
824
+ if (existing) return existing;
825
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
826
+ ensurePrivateDirSync(EXE_AI_DIR);
827
+ writeFileSync2(DAEMON_TOKEN_PATH, `${token}
828
+ `, "utf8");
829
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
830
+ return token;
831
+ }
832
+ var DAEMON_TOKEN_PATH;
833
+ var init_daemon_auth = __esm({
834
+ "src/lib/daemon-auth.ts"() {
835
+ "use strict";
836
+ init_config();
837
+ init_secure_files();
838
+ DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
191
839
  }
192
840
  });
193
841
 
194
842
  // src/lib/exe-daemon-client.ts
195
843
  import net from "net";
196
- import os3 from "os";
844
+ import os4 from "os";
197
845
  import { spawn } from "child_process";
198
846
  import { randomUUID } from "crypto";
199
- import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
200
- import path3 from "path";
847
+ import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
848
+ import path5 from "path";
201
849
  import { fileURLToPath } from "url";
202
850
  function handleData(chunk) {
203
851
  _buffer += chunk.toString();
@@ -225,9 +873,9 @@ function handleData(chunk) {
225
873
  }
226
874
  }
227
875
  function cleanupStaleFiles() {
228
- if (existsSync3(PID_PATH)) {
876
+ if (existsSync5(PID_PATH)) {
229
877
  try {
230
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
878
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
231
879
  if (pid > 0) {
232
880
  try {
233
881
  process.kill(pid, 0);
@@ -248,17 +896,17 @@ function cleanupStaleFiles() {
248
896
  }
249
897
  }
250
898
  function findPackageRoot() {
251
- let dir = path3.dirname(fileURLToPath(import.meta.url));
252
- const { root } = path3.parse(dir);
899
+ let dir = path5.dirname(fileURLToPath(import.meta.url));
900
+ const { root } = path5.parse(dir);
253
901
  while (dir !== root) {
254
- if (existsSync3(path3.join(dir, "package.json"))) return dir;
255
- dir = path3.dirname(dir);
902
+ if (existsSync5(path5.join(dir, "package.json"))) return dir;
903
+ dir = path5.dirname(dir);
256
904
  }
257
905
  return null;
258
906
  }
259
907
  function spawnDaemon() {
260
- const freeGB = os3.freemem() / (1024 * 1024 * 1024);
261
- const totalGB = os3.totalmem() / (1024 * 1024 * 1024);
908
+ const freeGB = os4.freemem() / (1024 * 1024 * 1024);
909
+ const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
262
910
  if (totalGB <= 8) {
263
911
  process.stderr.write(
264
912
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -278,16 +926,17 @@ function spawnDaemon() {
278
926
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
279
927
  return;
280
928
  }
281
- const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
282
- if (!existsSync3(daemonPath)) {
929
+ const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
930
+ if (!existsSync5(daemonPath)) {
283
931
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
284
932
  `);
285
933
  return;
286
934
  }
287
935
  const resolvedPath = daemonPath;
936
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
288
937
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
289
938
  `);
290
- const logPath = path3.join(path3.dirname(SOCKET_PATH), "exed.log");
939
+ const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
291
940
  let stderrFd = "ignore";
292
941
  try {
293
942
  stderrFd = openSync(logPath, "a");
@@ -305,7 +954,8 @@ function spawnDaemon() {
305
954
  TMUX_PANE: void 0,
306
955
  // Prevents resolveExeSession() from scoping to one session
307
956
  EXE_DAEMON_SOCK: SOCKET_PATH,
308
- EXE_DAEMON_PID: PID_PATH
957
+ EXE_DAEMON_PID: PID_PATH,
958
+ [DAEMON_TOKEN_ENV]: daemonToken
309
959
  }
310
960
  });
311
961
  child.unref();
@@ -412,13 +1062,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
412
1062
  return;
413
1063
  }
414
1064
  const id = randomUUID();
1065
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
415
1066
  const timer = setTimeout(() => {
416
1067
  _pending.delete(id);
417
1068
  resolve({ error: "Request timeout" });
418
1069
  }, timeoutMs);
419
1070
  _pending.set(id, { resolve, timer });
420
1071
  try {
421
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1072
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
422
1073
  } catch {
423
1074
  clearTimeout(timer);
424
1075
  _pending.delete(id);
@@ -429,17 +1080,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
429
1080
  function isClientConnected() {
430
1081
  return _connected;
431
1082
  }
432
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1083
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _pending, MAX_BUFFER;
433
1084
  var init_exe_daemon_client = __esm({
434
1085
  "src/lib/exe-daemon-client.ts"() {
435
1086
  "use strict";
436
1087
  init_config();
437
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
438
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
439
- SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
1088
+ init_daemon_auth();
1089
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1090
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1091
+ SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
440
1092
  SPAWN_LOCK_STALE_MS = 3e4;
441
1093
  CONNECT_TIMEOUT_MS = 15e3;
442
1094
  REQUEST_TIMEOUT_MS = 3e4;
1095
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
443
1096
  _socket = null;
444
1097
  _connected = false;
445
1098
  _buffer = "";
@@ -518,7 +1171,7 @@ __export(db_daemon_client_exports, {
518
1171
  createDaemonDbClient: () => createDaemonDbClient,
519
1172
  initDaemonDbClient: () => initDaemonDbClient
520
1173
  });
521
- function normalizeStatement(stmt) {
1174
+ function normalizeStatement2(stmt) {
522
1175
  if (typeof stmt === "string") {
523
1176
  return { sql: stmt, args: [] };
524
1177
  }
@@ -542,7 +1195,7 @@ function createDaemonDbClient(fallbackClient) {
542
1195
  if (!_useDaemon || !isClientConnected()) {
543
1196
  return fallbackClient.execute(stmt);
544
1197
  }
545
- const { sql, args } = normalizeStatement(stmt);
1198
+ const { sql, args } = normalizeStatement2(stmt);
546
1199
  const response = await sendDaemonRequest({
547
1200
  type: "db-execute",
548
1201
  sql,
@@ -567,7 +1220,7 @@ function createDaemonDbClient(fallbackClient) {
567
1220
  if (!_useDaemon || !isClientConnected()) {
568
1221
  return fallbackClient.batch(stmts, mode);
569
1222
  }
570
- const statements = stmts.map(normalizeStatement);
1223
+ const statements = stmts.map(normalizeStatement2);
571
1224
  const response = await sendDaemonRequest({
572
1225
  type: "db-batch",
573
1226
  statements,
@@ -662,6 +1315,18 @@ __export(database_exports, {
662
1315
  });
663
1316
  import { createClient } from "@libsql/client";
664
1317
  async function initDatabase(config) {
1318
+ if (_walCheckpointTimer) {
1319
+ clearInterval(_walCheckpointTimer);
1320
+ _walCheckpointTimer = null;
1321
+ }
1322
+ if (_daemonClient) {
1323
+ _daemonClient.close();
1324
+ _daemonClient = null;
1325
+ }
1326
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1327
+ _adapterClient.close();
1328
+ }
1329
+ _adapterClient = null;
665
1330
  if (_client) {
666
1331
  _client.close();
667
1332
  _client = null;
@@ -675,6 +1340,7 @@ async function initDatabase(config) {
675
1340
  }
676
1341
  _client = createClient(opts);
677
1342
  _resilientClient = wrapWithRetry(_client);
1343
+ _adapterClient = _resilientClient;
678
1344
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
679
1345
  });
680
1346
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -685,14 +1351,20 @@ async function initDatabase(config) {
685
1351
  });
686
1352
  }, 3e4);
687
1353
  _walCheckpointTimer.unref();
1354
+ if (process.env.DATABASE_URL) {
1355
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1356
+ }
688
1357
  }
689
1358
  function isInitialized() {
690
- return _client !== null;
1359
+ return _adapterClient !== null || _client !== null;
691
1360
  }
692
1361
  function getClient() {
693
- if (!_resilientClient) {
1362
+ if (!_adapterClient) {
694
1363
  throw new Error("Database client not initialized. Call initDatabase() first.");
695
1364
  }
1365
+ if (process.env.DATABASE_URL) {
1366
+ return _adapterClient;
1367
+ }
696
1368
  if (process.env.EXE_IS_DAEMON === "1") {
697
1369
  return _resilientClient;
698
1370
  }
@@ -702,6 +1374,7 @@ function getClient() {
702
1374
  return _resilientClient;
703
1375
  }
704
1376
  async function initDaemonClient() {
1377
+ if (process.env.DATABASE_URL) return;
705
1378
  if (process.env.EXE_IS_DAEMON === "1") return;
706
1379
  if (!_resilientClient) return;
707
1380
  try {
@@ -998,6 +1671,7 @@ async function ensureSchema() {
998
1671
  project TEXT NOT NULL,
999
1672
  summary TEXT NOT NULL,
1000
1673
  task_file TEXT,
1674
+ session_scope TEXT,
1001
1675
  read INTEGER NOT NULL DEFAULT 0,
1002
1676
  created_at TEXT NOT NULL
1003
1677
  );
@@ -1006,7 +1680,7 @@ async function ensureSchema() {
1006
1680
  ON notifications(read);
1007
1681
 
1008
1682
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1009
- ON notifications(agent_id);
1683
+ ON notifications(agent_id, session_scope);
1010
1684
 
1011
1685
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1012
1686
  ON notifications(task_file);
@@ -1044,6 +1718,7 @@ async function ensureSchema() {
1044
1718
  target_agent TEXT NOT NULL,
1045
1719
  target_project TEXT,
1046
1720
  target_device TEXT NOT NULL DEFAULT 'local',
1721
+ session_scope TEXT,
1047
1722
  content TEXT NOT NULL,
1048
1723
  priority TEXT DEFAULT 'normal',
1049
1724
  status TEXT DEFAULT 'pending',
@@ -1057,10 +1732,31 @@ async function ensureSchema() {
1057
1732
  );
1058
1733
 
1059
1734
  CREATE INDEX IF NOT EXISTS idx_messages_target
1060
- ON messages(target_agent, status);
1735
+ ON messages(target_agent, session_scope, status);
1061
1736
 
1062
1737
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1063
- ON messages(target_agent, from_agent, server_seq);
1738
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1739
+ `);
1740
+ try {
1741
+ await client.execute({
1742
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1743
+ args: []
1744
+ });
1745
+ } catch {
1746
+ }
1747
+ try {
1748
+ await client.execute({
1749
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1750
+ args: []
1751
+ });
1752
+ } catch {
1753
+ }
1754
+ await client.executeMultiple(`
1755
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1756
+ ON notifications(agent_id, session_scope, read, created_at);
1757
+
1758
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1759
+ ON messages(target_agent, session_scope, status, created_at);
1064
1760
  `);
1065
1761
  try {
1066
1762
  await client.execute({
@@ -1644,28 +2340,45 @@ async function ensureSchema() {
1644
2340
  } catch {
1645
2341
  }
1646
2342
  }
2343
+ try {
2344
+ await client.execute({
2345
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2346
+ args: []
2347
+ });
2348
+ } catch {
2349
+ }
1647
2350
  }
1648
2351
  async function disposeDatabase() {
2352
+ if (_walCheckpointTimer) {
2353
+ clearInterval(_walCheckpointTimer);
2354
+ _walCheckpointTimer = null;
2355
+ }
1649
2356
  if (_daemonClient) {
1650
2357
  _daemonClient.close();
1651
2358
  _daemonClient = null;
1652
2359
  }
2360
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2361
+ _adapterClient.close();
2362
+ }
2363
+ _adapterClient = null;
1653
2364
  if (_client) {
1654
2365
  _client.close();
1655
2366
  _client = null;
1656
2367
  _resilientClient = null;
1657
2368
  }
1658
2369
  }
1659
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
2370
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
1660
2371
  var init_database = __esm({
1661
2372
  "src/lib/database.ts"() {
1662
2373
  "use strict";
1663
2374
  init_db_retry();
1664
2375
  init_employees();
2376
+ init_database_adapter();
1665
2377
  _client = null;
1666
2378
  _resilientClient = null;
1667
2379
  _walCheckpointTimer = null;
1668
2380
  _daemonClient = null;
2381
+ _adapterClient = null;
1669
2382
  initTurso = initDatabase;
1670
2383
  disposeTurso = disposeDatabase;
1671
2384
  }
@@ -1673,15 +2386,15 @@ var init_database = __esm({
1673
2386
 
1674
2387
  // src/lib/device-registry.ts
1675
2388
  init_config();
1676
- import crypto from "crypto";
1677
- import os4 from "os";
1678
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync4 } from "fs";
1679
- import path4 from "path";
1680
- var DEVICE_JSON_PATH = path4.join(EXE_AI_DIR, "device.json");
2389
+ import crypto2 from "crypto";
2390
+ import os5 from "os";
2391
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync6 } from "fs";
2392
+ import path6 from "path";
2393
+ var DEVICE_JSON_PATH = path6.join(EXE_AI_DIR, "device.json");
1681
2394
  function getDeviceInfo() {
1682
- if (existsSync4(DEVICE_JSON_PATH)) {
2395
+ if (existsSync6(DEVICE_JSON_PATH)) {
1683
2396
  try {
1684
- const raw = readFileSync4(DEVICE_JSON_PATH, "utf8");
2397
+ const raw = readFileSync5(DEVICE_JSON_PATH, "utf8");
1685
2398
  const data = JSON.parse(raw);
1686
2399
  if (data.deviceId && data.friendlyName && data.hostname) {
1687
2400
  return data;
@@ -1689,20 +2402,20 @@ function getDeviceInfo() {
1689
2402
  } catch {
1690
2403
  }
1691
2404
  }
1692
- const hostname = os4.hostname();
2405
+ const hostname = os5.hostname();
1693
2406
  const info = {
1694
- deviceId: crypto.randomUUID(),
2407
+ deviceId: crypto2.randomUUID(),
1695
2408
  friendlyName: hostname.replace(/\./g, "-").toLowerCase(),
1696
2409
  hostname
1697
2410
  };
1698
- mkdirSync(path4.dirname(DEVICE_JSON_PATH), { recursive: true });
1699
- writeFileSync2(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
2411
+ mkdirSync2(path6.dirname(DEVICE_JSON_PATH), { recursive: true });
2412
+ writeFileSync3(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
1700
2413
  return info;
1701
2414
  }
1702
2415
  function setFriendlyName(name) {
1703
2416
  const info = getDeviceInfo();
1704
2417
  info.friendlyName = name;
1705
- writeFileSync2(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
2418
+ writeFileSync3(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
1706
2419
  }
1707
2420
  async function resolveTargetDevice(targetAgent, targetProject) {
1708
2421
  const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));