@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
@@ -9,9 +9,34 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // src/lib/secure-files.ts
13
+ import { chmodSync, existsSync, mkdirSync } from "fs";
14
+ import { chmod, mkdir } from "fs/promises";
15
+ function ensurePrivateDirSync(dirPath) {
16
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
17
+ try {
18
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
19
+ } catch {
20
+ }
21
+ }
22
+ function enforcePrivateFileSync(filePath) {
23
+ try {
24
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
25
+ } catch {
26
+ }
27
+ }
28
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
29
+ var init_secure_files = __esm({
30
+ "src/lib/secure-files.ts"() {
31
+ "use strict";
32
+ PRIVATE_DIR_MODE = 448;
33
+ PRIVATE_FILE_MODE = 384;
34
+ }
35
+ });
36
+
12
37
  // src/lib/config.ts
13
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
14
- import { readFileSync, existsSync, renameSync } from "fs";
38
+ import { readFile, writeFile } from "fs/promises";
39
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
15
40
  import path from "path";
16
41
  import os from "os";
17
42
  function resolveDataDir() {
@@ -19,7 +44,7 @@ function resolveDataDir() {
19
44
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
20
45
  const newDir = path.join(os.homedir(), ".exe-os");
21
46
  const legacyDir = path.join(os.homedir(), ".exe-mem");
22
- if (!existsSync(newDir) && existsSync(legacyDir)) {
47
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
23
48
  try {
24
49
  renameSync(legacyDir, newDir);
25
50
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -34,6 +59,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
34
59
  var init_config = __esm({
35
60
  "src/lib/config.ts"() {
36
61
  "use strict";
62
+ init_secure_files();
37
63
  EXE_AI_DIR = resolveDataDir();
38
64
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
39
65
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -102,7 +128,7 @@ var init_config = __esm({
102
128
 
103
129
  // src/lib/employees.ts
104
130
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
105
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
131
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
106
132
  import { execSync } from "child_process";
107
133
  import path2 from "path";
108
134
  import os2 from "os";
@@ -134,7 +160,7 @@ function validateEmployeeName(name) {
134
160
  return { valid: true };
135
161
  }
136
162
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
137
- if (!existsSync2(employeesPath)) {
163
+ if (!existsSync3(employeesPath)) {
138
164
  return [];
139
165
  }
140
166
  const raw = await readFile2(employeesPath, "utf-8");
@@ -149,7 +175,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
149
175
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
150
176
  }
151
177
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
152
- if (!existsSync2(employeesPath)) return [];
178
+ if (!existsSync3(employeesPath)) return [];
153
179
  try {
154
180
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
155
181
  } catch {
@@ -183,7 +209,7 @@ function registerBinSymlinks(name) {
183
209
  for (const suffix of ["", "-opencode"]) {
184
210
  const linkName = `${name}${suffix}`;
185
211
  const linkPath = path2.join(binDir, linkName);
186
- if (existsSync2(linkPath)) {
212
+ if (existsSync3(linkPath)) {
187
213
  skipped.push(linkName);
188
214
  continue;
189
215
  }
@@ -196,7 +222,7 @@ function registerBinSymlinks(name) {
196
222
  }
197
223
  return { created, skipped, errors };
198
224
  }
199
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
225
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
200
226
  var init_employees = __esm({
201
227
  "src/lib/employees.ts"() {
202
228
  "use strict";
@@ -204,6 +230,7 @@ var init_employees = __esm({
204
230
  EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
205
231
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
206
232
  COORDINATOR_ROLE = "COO";
233
+ IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
207
234
  }
208
235
  });
209
236
 
@@ -262,13 +289,634 @@ var init_db_retry = __esm({
262
289
  }
263
290
  });
264
291
 
292
+ // src/lib/database-adapter.ts
293
+ import os3 from "os";
294
+ import path3 from "path";
295
+ import { createRequire } from "module";
296
+ import { pathToFileURL } from "url";
297
+ function quotedIdentifier(identifier) {
298
+ return `"${identifier.replace(/"/g, '""')}"`;
299
+ }
300
+ function unqualifiedTableName(name) {
301
+ const raw = name.trim().replace(/^"|"$/g, "");
302
+ const parts = raw.split(".");
303
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
304
+ }
305
+ function stripTrailingSemicolon(sql) {
306
+ return sql.trim().replace(/;+\s*$/u, "");
307
+ }
308
+ function appendClause(sql, clause) {
309
+ const trimmed = stripTrailingSemicolon(sql);
310
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
311
+ if (!returningMatch) {
312
+ return `${trimmed}${clause}`;
313
+ }
314
+ const idx = returningMatch.index;
315
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
316
+ }
317
+ function normalizeStatement(stmt) {
318
+ if (typeof stmt === "string") {
319
+ return { kind: "positional", sql: stmt, args: [] };
320
+ }
321
+ const sql = stmt.sql;
322
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
323
+ return { kind: "positional", sql, args: stmt.args ?? [] };
324
+ }
325
+ return { kind: "named", sql, args: stmt.args };
326
+ }
327
+ function rewriteBooleanLiterals(sql) {
328
+ let out = sql;
329
+ for (const column of BOOLEAN_COLUMN_NAMES) {
330
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
331
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
332
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
333
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
334
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
335
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
336
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
337
+ }
338
+ return out;
339
+ }
340
+ function rewriteInsertOrIgnore(sql) {
341
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
342
+ return sql;
343
+ }
344
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
345
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
346
+ }
347
+ function rewriteInsertOrReplace(sql) {
348
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
349
+ if (!match) {
350
+ return sql;
351
+ }
352
+ const rawTable = match[1];
353
+ const rawColumns = match[2];
354
+ const remainder = match[3];
355
+ const tableName = unqualifiedTableName(rawTable);
356
+ const conflictKeys = UPSERT_KEYS[tableName];
357
+ if (!conflictKeys?.length) {
358
+ return sql;
359
+ }
360
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
361
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
362
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
363
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
364
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
365
+ }
366
+ function rewriteSql(sql) {
367
+ let out = sql;
368
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
369
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
370
+ out = rewriteBooleanLiterals(out);
371
+ out = rewriteInsertOrReplace(out);
372
+ out = rewriteInsertOrIgnore(out);
373
+ return stripTrailingSemicolon(out);
374
+ }
375
+ function toBoolean(value) {
376
+ if (value === null || value === void 0) return value;
377
+ if (typeof value === "boolean") return value;
378
+ if (typeof value === "number") return value !== 0;
379
+ if (typeof value === "bigint") return value !== 0n;
380
+ if (typeof value === "string") {
381
+ const normalized = value.trim().toLowerCase();
382
+ if (normalized === "0" || normalized === "false") return false;
383
+ if (normalized === "1" || normalized === "true") return true;
384
+ }
385
+ return Boolean(value);
386
+ }
387
+ function countQuestionMarks(sql, end) {
388
+ let count = 0;
389
+ let inSingle = false;
390
+ let inDouble = false;
391
+ let inLineComment = false;
392
+ let inBlockComment = false;
393
+ for (let i = 0; i < end; i++) {
394
+ const ch = sql[i];
395
+ const next = sql[i + 1];
396
+ if (inLineComment) {
397
+ if (ch === "\n") inLineComment = false;
398
+ continue;
399
+ }
400
+ if (inBlockComment) {
401
+ if (ch === "*" && next === "/") {
402
+ inBlockComment = false;
403
+ i += 1;
404
+ }
405
+ continue;
406
+ }
407
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
408
+ inLineComment = true;
409
+ i += 1;
410
+ continue;
411
+ }
412
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
413
+ inBlockComment = true;
414
+ i += 1;
415
+ continue;
416
+ }
417
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
418
+ inSingle = !inSingle;
419
+ continue;
420
+ }
421
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
422
+ inDouble = !inDouble;
423
+ continue;
424
+ }
425
+ if (!inSingle && !inDouble && ch === "?") {
426
+ count += 1;
427
+ }
428
+ }
429
+ return count;
430
+ }
431
+ function findBooleanPlaceholderIndexes(sql) {
432
+ const indexes = /* @__PURE__ */ new Set();
433
+ for (const column of BOOLEAN_COLUMN_NAMES) {
434
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
435
+ for (const match of sql.matchAll(pattern)) {
436
+ const matchText = match[0];
437
+ const qIndex = match.index + matchText.lastIndexOf("?");
438
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
439
+ }
440
+ }
441
+ return indexes;
442
+ }
443
+ function coerceInsertBooleanArgs(sql, args) {
444
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
445
+ if (!match) return;
446
+ const rawTable = match[1];
447
+ const rawColumns = match[2];
448
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
449
+ if (!boolColumns?.size) return;
450
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
451
+ for (const [index, column] of columns.entries()) {
452
+ if (boolColumns.has(column) && index < args.length) {
453
+ args[index] = toBoolean(args[index]);
454
+ }
455
+ }
456
+ }
457
+ function coerceUpdateBooleanArgs(sql, args) {
458
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
459
+ if (!match) return;
460
+ const rawTable = match[1];
461
+ const setClause = match[2];
462
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
463
+ if (!boolColumns?.size) return;
464
+ const assignments = setClause.split(",");
465
+ let placeholderIndex = 0;
466
+ for (const assignment of assignments) {
467
+ if (!assignment.includes("?")) continue;
468
+ placeholderIndex += 1;
469
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
470
+ if (colMatch && boolColumns.has(colMatch[1])) {
471
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
472
+ }
473
+ }
474
+ }
475
+ function coerceBooleanArgs(sql, args) {
476
+ const nextArgs = [...args];
477
+ coerceInsertBooleanArgs(sql, nextArgs);
478
+ coerceUpdateBooleanArgs(sql, nextArgs);
479
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
480
+ for (const index of placeholderIndexes) {
481
+ if (index > 0 && index <= nextArgs.length) {
482
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
483
+ }
484
+ }
485
+ return nextArgs;
486
+ }
487
+ function convertQuestionMarksToDollarParams(sql) {
488
+ let out = "";
489
+ let placeholder = 0;
490
+ let inSingle = false;
491
+ let inDouble = false;
492
+ let inLineComment = false;
493
+ let inBlockComment = false;
494
+ for (let i = 0; i < sql.length; i++) {
495
+ const ch = sql[i];
496
+ const next = sql[i + 1];
497
+ if (inLineComment) {
498
+ out += ch;
499
+ if (ch === "\n") inLineComment = false;
500
+ continue;
501
+ }
502
+ if (inBlockComment) {
503
+ out += ch;
504
+ if (ch === "*" && next === "/") {
505
+ out += next;
506
+ inBlockComment = false;
507
+ i += 1;
508
+ }
509
+ continue;
510
+ }
511
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
512
+ out += ch + next;
513
+ inLineComment = true;
514
+ i += 1;
515
+ continue;
516
+ }
517
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
518
+ out += ch + next;
519
+ inBlockComment = true;
520
+ i += 1;
521
+ continue;
522
+ }
523
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
524
+ inSingle = !inSingle;
525
+ out += ch;
526
+ continue;
527
+ }
528
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
529
+ inDouble = !inDouble;
530
+ out += ch;
531
+ continue;
532
+ }
533
+ if (!inSingle && !inDouble && ch === "?") {
534
+ placeholder += 1;
535
+ out += `$${placeholder}`;
536
+ continue;
537
+ }
538
+ out += ch;
539
+ }
540
+ return out;
541
+ }
542
+ function translateStatementForPostgres(stmt) {
543
+ const normalized = normalizeStatement(stmt);
544
+ if (normalized.kind === "named") {
545
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
546
+ }
547
+ const rewrittenSql = rewriteSql(normalized.sql);
548
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
549
+ return {
550
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
551
+ args: coercedArgs
552
+ };
553
+ }
554
+ function shouldBypassPostgres(stmt) {
555
+ const normalized = normalizeStatement(stmt);
556
+ if (normalized.kind === "named") {
557
+ return true;
558
+ }
559
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
560
+ }
561
+ function shouldFallbackOnError(error) {
562
+ const message = error instanceof Error ? error.message : String(error);
563
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
564
+ }
565
+ function isReadQuery(sql) {
566
+ const trimmed = sql.trimStart();
567
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
568
+ }
569
+ function buildRow(row, columns) {
570
+ const values = columns.map((column) => row[column]);
571
+ return Object.assign(values, row);
572
+ }
573
+ function buildResultSet(rows, rowsAffected = 0) {
574
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
575
+ const resultRows = rows.map((row) => buildRow(row, columns));
576
+ return {
577
+ columns,
578
+ columnTypes: columns.map(() => ""),
579
+ rows: resultRows,
580
+ rowsAffected,
581
+ lastInsertRowid: void 0,
582
+ toJSON() {
583
+ return {
584
+ columns,
585
+ columnTypes: columns.map(() => ""),
586
+ rows,
587
+ rowsAffected,
588
+ lastInsertRowid: void 0
589
+ };
590
+ }
591
+ };
592
+ }
593
+ async function loadPrismaClient() {
594
+ if (!prismaClientPromise) {
595
+ prismaClientPromise = (async () => {
596
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
597
+ if (explicitPath) {
598
+ const module2 = await import(pathToFileURL(explicitPath).href);
599
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
600
+ if (!PrismaClient2) {
601
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
602
+ }
603
+ return new PrismaClient2();
604
+ }
605
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
606
+ const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
607
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
608
+ const module = await import(pathToFileURL(prismaEntry).href);
609
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
610
+ if (!PrismaClient) {
611
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
612
+ }
613
+ return new PrismaClient();
614
+ })();
615
+ }
616
+ return prismaClientPromise;
617
+ }
618
+ async function ensureCompatibilityViews(prisma) {
619
+ if (!compatibilityBootstrapPromise) {
620
+ compatibilityBootstrapPromise = (async () => {
621
+ for (const mapping of VIEW_MAPPINGS) {
622
+ const relation = mapping.source.replace(/"/g, "");
623
+ const rows = await prisma.$queryRawUnsafe(
624
+ "SELECT to_regclass($1) AS regclass",
625
+ relation
626
+ );
627
+ if (!rows[0]?.regclass) {
628
+ continue;
629
+ }
630
+ await prisma.$executeRawUnsafe(
631
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
632
+ );
633
+ }
634
+ })();
635
+ }
636
+ return compatibilityBootstrapPromise;
637
+ }
638
+ async function executeOnPrisma(executor, stmt) {
639
+ const translated = translateStatementForPostgres(stmt);
640
+ if (isReadQuery(translated.sql)) {
641
+ const rows = await executor.$queryRawUnsafe(
642
+ translated.sql,
643
+ ...translated.args
644
+ );
645
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
646
+ }
647
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
648
+ return buildResultSet([], rowsAffected);
649
+ }
650
+ function splitSqlStatements(sql) {
651
+ const parts = [];
652
+ let current = "";
653
+ let inSingle = false;
654
+ let inDouble = false;
655
+ let inLineComment = false;
656
+ let inBlockComment = false;
657
+ for (let i = 0; i < sql.length; i++) {
658
+ const ch = sql[i];
659
+ const next = sql[i + 1];
660
+ if (inLineComment) {
661
+ current += ch;
662
+ if (ch === "\n") inLineComment = false;
663
+ continue;
664
+ }
665
+ if (inBlockComment) {
666
+ current += ch;
667
+ if (ch === "*" && next === "/") {
668
+ current += next;
669
+ inBlockComment = false;
670
+ i += 1;
671
+ }
672
+ continue;
673
+ }
674
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
675
+ current += ch + next;
676
+ inLineComment = true;
677
+ i += 1;
678
+ continue;
679
+ }
680
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
681
+ current += ch + next;
682
+ inBlockComment = true;
683
+ i += 1;
684
+ continue;
685
+ }
686
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
687
+ inSingle = !inSingle;
688
+ current += ch;
689
+ continue;
690
+ }
691
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
692
+ inDouble = !inDouble;
693
+ current += ch;
694
+ continue;
695
+ }
696
+ if (!inSingle && !inDouble && ch === ";") {
697
+ if (current.trim()) {
698
+ parts.push(current.trim());
699
+ }
700
+ current = "";
701
+ continue;
702
+ }
703
+ current += ch;
704
+ }
705
+ if (current.trim()) {
706
+ parts.push(current.trim());
707
+ }
708
+ return parts;
709
+ }
710
+ async function createPrismaDbAdapter(fallbackClient) {
711
+ const prisma = await loadPrismaClient();
712
+ await ensureCompatibilityViews(prisma);
713
+ let closed = false;
714
+ let adapter;
715
+ const fallbackExecute = async (stmt, error) => {
716
+ if (!fallbackClient) {
717
+ if (error) throw error;
718
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
719
+ }
720
+ if (error) {
721
+ process.stderr.write(
722
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
723
+ `
724
+ );
725
+ }
726
+ return fallbackClient.execute(stmt);
727
+ };
728
+ adapter = {
729
+ async execute(stmt) {
730
+ if (shouldBypassPostgres(stmt)) {
731
+ return fallbackExecute(stmt);
732
+ }
733
+ try {
734
+ return await executeOnPrisma(prisma, stmt);
735
+ } catch (error) {
736
+ if (shouldFallbackOnError(error)) {
737
+ return fallbackExecute(stmt, error);
738
+ }
739
+ throw error;
740
+ }
741
+ },
742
+ async batch(stmts, mode) {
743
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
744
+ if (!fallbackClient) {
745
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
746
+ }
747
+ return fallbackClient.batch(stmts, mode);
748
+ }
749
+ try {
750
+ if (prisma.$transaction) {
751
+ return await prisma.$transaction(async (tx) => {
752
+ const results2 = [];
753
+ for (const stmt of stmts) {
754
+ results2.push(await executeOnPrisma(tx, stmt));
755
+ }
756
+ return results2;
757
+ });
758
+ }
759
+ const results = [];
760
+ for (const stmt of stmts) {
761
+ results.push(await executeOnPrisma(prisma, stmt));
762
+ }
763
+ return results;
764
+ } catch (error) {
765
+ if (fallbackClient && shouldFallbackOnError(error)) {
766
+ process.stderr.write(
767
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
768
+ `
769
+ );
770
+ return fallbackClient.batch(stmts, mode);
771
+ }
772
+ throw error;
773
+ }
774
+ },
775
+ async migrate(stmts) {
776
+ if (fallbackClient) {
777
+ return fallbackClient.migrate(stmts);
778
+ }
779
+ return adapter.batch(stmts, "deferred");
780
+ },
781
+ async transaction(mode) {
782
+ if (!fallbackClient) {
783
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
784
+ }
785
+ return fallbackClient.transaction(mode);
786
+ },
787
+ async executeMultiple(sql) {
788
+ if (fallbackClient && shouldBypassPostgres(sql)) {
789
+ return fallbackClient.executeMultiple(sql);
790
+ }
791
+ for (const statement of splitSqlStatements(sql)) {
792
+ await adapter.execute(statement);
793
+ }
794
+ },
795
+ async sync() {
796
+ if (fallbackClient) {
797
+ return fallbackClient.sync();
798
+ }
799
+ return { frame_no: 0, frames_synced: 0 };
800
+ },
801
+ close() {
802
+ closed = true;
803
+ prismaClientPromise = null;
804
+ compatibilityBootstrapPromise = null;
805
+ void prisma.$disconnect?.();
806
+ },
807
+ get closed() {
808
+ return closed;
809
+ },
810
+ get protocol() {
811
+ return "prisma-postgres";
812
+ }
813
+ };
814
+ return adapter;
815
+ }
816
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
817
+ var init_database_adapter = __esm({
818
+ "src/lib/database-adapter.ts"() {
819
+ "use strict";
820
+ VIEW_MAPPINGS = [
821
+ { view: "memories", source: "memory.memory_records" },
822
+ { view: "tasks", source: "memory.tasks" },
823
+ { view: "behaviors", source: "memory.behaviors" },
824
+ { view: "entities", source: "memory.entities" },
825
+ { view: "relationships", source: "memory.relationships" },
826
+ { view: "entity_memories", source: "memory.entity_memories" },
827
+ { view: "entity_aliases", source: "memory.entity_aliases" },
828
+ { view: "notifications", source: "memory.notifications" },
829
+ { view: "messages", source: "memory.messages" },
830
+ { view: "users", source: "wiki.users" },
831
+ { view: "workspaces", source: "wiki.workspaces" },
832
+ { view: "workspace_users", source: "wiki.workspace_users" },
833
+ { view: "documents", source: "wiki.workspace_documents" },
834
+ { view: "chats", source: "wiki.workspace_chats" }
835
+ ];
836
+ UPSERT_KEYS = {
837
+ memories: ["id"],
838
+ tasks: ["id"],
839
+ behaviors: ["id"],
840
+ entities: ["id"],
841
+ relationships: ["id"],
842
+ entity_aliases: ["alias"],
843
+ notifications: ["id"],
844
+ messages: ["id"],
845
+ users: ["id"],
846
+ workspaces: ["id"],
847
+ workspace_users: ["id"],
848
+ documents: ["id"],
849
+ chats: ["id"]
850
+ };
851
+ BOOLEAN_COLUMNS_BY_TABLE = {
852
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
853
+ behaviors: /* @__PURE__ */ new Set(["active"]),
854
+ notifications: /* @__PURE__ */ new Set(["read"]),
855
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
856
+ };
857
+ BOOLEAN_COLUMN_NAMES = new Set(
858
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
859
+ );
860
+ IMMEDIATE_FALLBACK_PATTERNS = [
861
+ /\bPRAGMA\b/i,
862
+ /\bsqlite_master\b/i,
863
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
864
+ /\bMATCH\b/i,
865
+ /\bvector_distance_cos\s*\(/i,
866
+ /\bjson_extract\s*\(/i,
867
+ /\bjulianday\s*\(/i,
868
+ /\bstrftime\s*\(/i,
869
+ /\blast_insert_rowid\s*\(/i
870
+ ];
871
+ prismaClientPromise = null;
872
+ compatibilityBootstrapPromise = null;
873
+ }
874
+ });
875
+
876
+ // src/lib/daemon-auth.ts
877
+ import crypto from "crypto";
878
+ import path4 from "path";
879
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
880
+ function normalizeToken(token) {
881
+ if (!token) return null;
882
+ const trimmed = token.trim();
883
+ return trimmed.length > 0 ? trimmed : null;
884
+ }
885
+ function readDaemonToken() {
886
+ try {
887
+ if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
888
+ return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
889
+ } catch {
890
+ return null;
891
+ }
892
+ }
893
+ function ensureDaemonToken(seed) {
894
+ const existing = readDaemonToken();
895
+ if (existing) return existing;
896
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
897
+ ensurePrivateDirSync(EXE_AI_DIR);
898
+ writeFileSync2(DAEMON_TOKEN_PATH, `${token}
899
+ `, "utf8");
900
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
901
+ return token;
902
+ }
903
+ var DAEMON_TOKEN_PATH;
904
+ var init_daemon_auth = __esm({
905
+ "src/lib/daemon-auth.ts"() {
906
+ "use strict";
907
+ init_config();
908
+ init_secure_files();
909
+ DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
910
+ }
911
+ });
912
+
265
913
  // src/lib/exe-daemon-client.ts
266
914
  import net from "net";
267
- import os3 from "os";
915
+ import os4 from "os";
268
916
  import { spawn } from "child_process";
269
917
  import { randomUUID } from "crypto";
270
- import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
271
- import path3 from "path";
918
+ import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
919
+ import path5 from "path";
272
920
  import { fileURLToPath } from "url";
273
921
  function handleData(chunk) {
274
922
  _buffer += chunk.toString();
@@ -296,9 +944,9 @@ function handleData(chunk) {
296
944
  }
297
945
  }
298
946
  function cleanupStaleFiles() {
299
- if (existsSync3(PID_PATH)) {
947
+ if (existsSync5(PID_PATH)) {
300
948
  try {
301
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
949
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
302
950
  if (pid > 0) {
303
951
  try {
304
952
  process.kill(pid, 0);
@@ -319,17 +967,17 @@ function cleanupStaleFiles() {
319
967
  }
320
968
  }
321
969
  function findPackageRoot() {
322
- let dir = path3.dirname(fileURLToPath(import.meta.url));
323
- const { root } = path3.parse(dir);
970
+ let dir = path5.dirname(fileURLToPath(import.meta.url));
971
+ const { root } = path5.parse(dir);
324
972
  while (dir !== root) {
325
- if (existsSync3(path3.join(dir, "package.json"))) return dir;
326
- dir = path3.dirname(dir);
973
+ if (existsSync5(path5.join(dir, "package.json"))) return dir;
974
+ dir = path5.dirname(dir);
327
975
  }
328
976
  return null;
329
977
  }
330
978
  function spawnDaemon() {
331
- const freeGB = os3.freemem() / (1024 * 1024 * 1024);
332
- const totalGB = os3.totalmem() / (1024 * 1024 * 1024);
979
+ const freeGB = os4.freemem() / (1024 * 1024 * 1024);
980
+ const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
333
981
  if (totalGB <= 8) {
334
982
  process.stderr.write(
335
983
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -349,16 +997,17 @@ function spawnDaemon() {
349
997
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
350
998
  return;
351
999
  }
352
- const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
353
- if (!existsSync3(daemonPath)) {
1000
+ const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1001
+ if (!existsSync5(daemonPath)) {
354
1002
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
355
1003
  `);
356
1004
  return;
357
1005
  }
358
1006
  const resolvedPath = daemonPath;
1007
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
359
1008
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
360
1009
  `);
361
- const logPath = path3.join(path3.dirname(SOCKET_PATH), "exed.log");
1010
+ const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
362
1011
  let stderrFd = "ignore";
363
1012
  try {
364
1013
  stderrFd = openSync(logPath, "a");
@@ -376,7 +1025,8 @@ function spawnDaemon() {
376
1025
  TMUX_PANE: void 0,
377
1026
  // Prevents resolveExeSession() from scoping to one session
378
1027
  EXE_DAEMON_SOCK: SOCKET_PATH,
379
- EXE_DAEMON_PID: PID_PATH
1028
+ EXE_DAEMON_PID: PID_PATH,
1029
+ [DAEMON_TOKEN_ENV]: daemonToken
380
1030
  }
381
1031
  });
382
1032
  child.unref();
@@ -483,13 +1133,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
483
1133
  return;
484
1134
  }
485
1135
  const id = randomUUID();
1136
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
486
1137
  const timer = setTimeout(() => {
487
1138
  _pending.delete(id);
488
1139
  resolve({ error: "Request timeout" });
489
1140
  }, timeoutMs);
490
1141
  _pending.set(id, { resolve, timer });
491
1142
  try {
492
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1143
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
493
1144
  } catch {
494
1145
  clearTimeout(timer);
495
1146
  _pending.delete(id);
@@ -500,17 +1151,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
500
1151
  function isClientConnected() {
501
1152
  return _connected;
502
1153
  }
503
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1154
+ 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;
504
1155
  var init_exe_daemon_client = __esm({
505
1156
  "src/lib/exe-daemon-client.ts"() {
506
1157
  "use strict";
507
1158
  init_config();
508
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
509
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
510
- SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
1159
+ init_daemon_auth();
1160
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1161
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1162
+ SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
511
1163
  SPAWN_LOCK_STALE_MS = 3e4;
512
1164
  CONNECT_TIMEOUT_MS = 15e3;
513
1165
  REQUEST_TIMEOUT_MS = 3e4;
1166
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
514
1167
  _socket = null;
515
1168
  _connected = false;
516
1169
  _buffer = "";
@@ -589,7 +1242,7 @@ __export(db_daemon_client_exports, {
589
1242
  createDaemonDbClient: () => createDaemonDbClient,
590
1243
  initDaemonDbClient: () => initDaemonDbClient
591
1244
  });
592
- function normalizeStatement(stmt) {
1245
+ function normalizeStatement2(stmt) {
593
1246
  if (typeof stmt === "string") {
594
1247
  return { sql: stmt, args: [] };
595
1248
  }
@@ -613,7 +1266,7 @@ function createDaemonDbClient(fallbackClient) {
613
1266
  if (!_useDaemon || !isClientConnected()) {
614
1267
  return fallbackClient.execute(stmt);
615
1268
  }
616
- const { sql, args } = normalizeStatement(stmt);
1269
+ const { sql, args } = normalizeStatement2(stmt);
617
1270
  const response = await sendDaemonRequest({
618
1271
  type: "db-execute",
619
1272
  sql,
@@ -638,7 +1291,7 @@ function createDaemonDbClient(fallbackClient) {
638
1291
  if (!_useDaemon || !isClientConnected()) {
639
1292
  return fallbackClient.batch(stmts, mode);
640
1293
  }
641
- const statements = stmts.map(normalizeStatement);
1294
+ const statements = stmts.map(normalizeStatement2);
642
1295
  const response = await sendDaemonRequest({
643
1296
  type: "db-batch",
644
1297
  statements,
@@ -733,6 +1386,18 @@ __export(database_exports, {
733
1386
  });
734
1387
  import { createClient } from "@libsql/client";
735
1388
  async function initDatabase(config) {
1389
+ if (_walCheckpointTimer) {
1390
+ clearInterval(_walCheckpointTimer);
1391
+ _walCheckpointTimer = null;
1392
+ }
1393
+ if (_daemonClient) {
1394
+ _daemonClient.close();
1395
+ _daemonClient = null;
1396
+ }
1397
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1398
+ _adapterClient.close();
1399
+ }
1400
+ _adapterClient = null;
736
1401
  if (_client) {
737
1402
  _client.close();
738
1403
  _client = null;
@@ -746,6 +1411,7 @@ async function initDatabase(config) {
746
1411
  }
747
1412
  _client = createClient(opts);
748
1413
  _resilientClient = wrapWithRetry(_client);
1414
+ _adapterClient = _resilientClient;
749
1415
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
750
1416
  });
751
1417
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -756,14 +1422,20 @@ async function initDatabase(config) {
756
1422
  });
757
1423
  }, 3e4);
758
1424
  _walCheckpointTimer.unref();
1425
+ if (process.env.DATABASE_URL) {
1426
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1427
+ }
759
1428
  }
760
1429
  function isInitialized() {
761
- return _client !== null;
1430
+ return _adapterClient !== null || _client !== null;
762
1431
  }
763
1432
  function getClient() {
764
- if (!_resilientClient) {
1433
+ if (!_adapterClient) {
765
1434
  throw new Error("Database client not initialized. Call initDatabase() first.");
766
1435
  }
1436
+ if (process.env.DATABASE_URL) {
1437
+ return _adapterClient;
1438
+ }
767
1439
  if (process.env.EXE_IS_DAEMON === "1") {
768
1440
  return _resilientClient;
769
1441
  }
@@ -773,6 +1445,7 @@ function getClient() {
773
1445
  return _resilientClient;
774
1446
  }
775
1447
  async function initDaemonClient() {
1448
+ if (process.env.DATABASE_URL) return;
776
1449
  if (process.env.EXE_IS_DAEMON === "1") return;
777
1450
  if (!_resilientClient) return;
778
1451
  try {
@@ -1069,6 +1742,7 @@ async function ensureSchema() {
1069
1742
  project TEXT NOT NULL,
1070
1743
  summary TEXT NOT NULL,
1071
1744
  task_file TEXT,
1745
+ session_scope TEXT,
1072
1746
  read INTEGER NOT NULL DEFAULT 0,
1073
1747
  created_at TEXT NOT NULL
1074
1748
  );
@@ -1077,7 +1751,7 @@ async function ensureSchema() {
1077
1751
  ON notifications(read);
1078
1752
 
1079
1753
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1080
- ON notifications(agent_id);
1754
+ ON notifications(agent_id, session_scope);
1081
1755
 
1082
1756
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1083
1757
  ON notifications(task_file);
@@ -1115,6 +1789,7 @@ async function ensureSchema() {
1115
1789
  target_agent TEXT NOT NULL,
1116
1790
  target_project TEXT,
1117
1791
  target_device TEXT NOT NULL DEFAULT 'local',
1792
+ session_scope TEXT,
1118
1793
  content TEXT NOT NULL,
1119
1794
  priority TEXT DEFAULT 'normal',
1120
1795
  status TEXT DEFAULT 'pending',
@@ -1128,10 +1803,31 @@ async function ensureSchema() {
1128
1803
  );
1129
1804
 
1130
1805
  CREATE INDEX IF NOT EXISTS idx_messages_target
1131
- ON messages(target_agent, status);
1806
+ ON messages(target_agent, session_scope, status);
1132
1807
 
1133
1808
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1134
- ON messages(target_agent, from_agent, server_seq);
1809
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1810
+ `);
1811
+ try {
1812
+ await client.execute({
1813
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1814
+ args: []
1815
+ });
1816
+ } catch {
1817
+ }
1818
+ try {
1819
+ await client.execute({
1820
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1821
+ args: []
1822
+ });
1823
+ } catch {
1824
+ }
1825
+ await client.executeMultiple(`
1826
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1827
+ ON notifications(agent_id, session_scope, read, created_at);
1828
+
1829
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1830
+ ON messages(target_agent, session_scope, status, created_at);
1135
1831
  `);
1136
1832
  try {
1137
1833
  await client.execute({
@@ -1715,28 +2411,45 @@ async function ensureSchema() {
1715
2411
  } catch {
1716
2412
  }
1717
2413
  }
2414
+ try {
2415
+ await client.execute({
2416
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2417
+ args: []
2418
+ });
2419
+ } catch {
2420
+ }
1718
2421
  }
1719
2422
  async function disposeDatabase() {
2423
+ if (_walCheckpointTimer) {
2424
+ clearInterval(_walCheckpointTimer);
2425
+ _walCheckpointTimer = null;
2426
+ }
1720
2427
  if (_daemonClient) {
1721
2428
  _daemonClient.close();
1722
2429
  _daemonClient = null;
1723
2430
  }
2431
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2432
+ _adapterClient.close();
2433
+ }
2434
+ _adapterClient = null;
1724
2435
  if (_client) {
1725
2436
  _client.close();
1726
2437
  _client = null;
1727
2438
  _resilientClient = null;
1728
2439
  }
1729
2440
  }
1730
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
2441
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
1731
2442
  var init_database = __esm({
1732
2443
  "src/lib/database.ts"() {
1733
2444
  "use strict";
1734
2445
  init_db_retry();
1735
2446
  init_employees();
2447
+ init_database_adapter();
1736
2448
  _client = null;
1737
2449
  _resilientClient = null;
1738
2450
  _walCheckpointTimer = null;
1739
2451
  _daemonClient = null;
2452
+ _adapterClient = null;
1740
2453
  initTurso = initDatabase;
1741
2454
  disposeTurso = disposeDatabase;
1742
2455
  }
@@ -1744,9 +2457,9 @@ var init_database = __esm({
1744
2457
 
1745
2458
  // src/bin/exe-rename.ts
1746
2459
  init_employees();
1747
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, renameSync as renameSync3, unlinkSync as unlinkSync3, existsSync as existsSync4 } from "fs";
2460
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync as renameSync3, unlinkSync as unlinkSync3, existsSync as existsSync6 } from "fs";
1748
2461
  import { execSync as execSync2 } from "child_process";
1749
- import path4 from "path";
2462
+ import path6 from "path";
1750
2463
  import { homedir } from "os";
1751
2464
 
1752
2465
  // src/lib/global-procedures.ts
@@ -1891,9 +2604,9 @@ function isMainModule(importMetaUrl) {
1891
2604
 
1892
2605
  // src/bin/exe-rename.ts
1893
2606
  async function renameEmployee(oldName, newName, opts = {}) {
1894
- const rosterPath = opts.rosterPath ?? path4.join(homedir(), ".exe-os", "exe-employees.json");
1895
- const identityDir = opts.identityDir ?? path4.join(homedir(), ".exe-os", "identity");
1896
- const agentsDir = opts.agentsDir ?? path4.join(homedir(), ".claude", "agents");
2607
+ const rosterPath = opts.rosterPath ?? path6.join(homedir(), ".exe-os", "exe-employees.json");
2608
+ const identityDir = opts.identityDir ?? path6.join(homedir(), ".exe-os", "identity");
2609
+ const agentsDir = opts.agentsDir ?? path6.join(homedir(), ".claude", "agents");
1897
2610
  const validation = validateEmployeeName(newName);
1898
2611
  if (!validation.valid) {
1899
2612
  return { success: false, error: validation.error };
@@ -1922,40 +2635,40 @@ async function renameEmployee(oldName, newName, opts = {}) {
1922
2635
  undo: () => {
1923
2636
  employee.name = originalName;
1924
2637
  employee.systemPrompt = originalPrompt;
1925
- writeFileSync2(rosterPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
2638
+ writeFileSync3(rosterPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
1926
2639
  }
1927
2640
  });
1928
- const oldIdentityPath = path4.join(identityDir, `${rosterOldName}.md`);
1929
- const newIdentityPath = path4.join(identityDir, `${newName}.md`);
1930
- if (existsSync4(oldIdentityPath)) {
1931
- const content = readFileSync4(oldIdentityPath, "utf-8");
2641
+ const oldIdentityPath = path6.join(identityDir, `${rosterOldName}.md`);
2642
+ const newIdentityPath = path6.join(identityDir, `${newName}.md`);
2643
+ if (existsSync6(oldIdentityPath)) {
2644
+ const content = readFileSync5(oldIdentityPath, "utf-8");
1932
2645
  const updatedContent = content.replace(
1933
2646
  /^(agent_id:\s*)\S+/m,
1934
2647
  `$1${newName}`
1935
2648
  );
1936
2649
  renameSync3(oldIdentityPath, newIdentityPath);
1937
- writeFileSync2(newIdentityPath, updatedContent, "utf-8");
2650
+ writeFileSync3(newIdentityPath, updatedContent, "utf-8");
1938
2651
  rollbackStack.push({
1939
2652
  description: "restore identity file",
1940
2653
  undo: () => {
1941
- if (existsSync4(newIdentityPath)) {
1942
- writeFileSync2(newIdentityPath, content, "utf-8");
2654
+ if (existsSync6(newIdentityPath)) {
2655
+ writeFileSync3(newIdentityPath, content, "utf-8");
1943
2656
  renameSync3(newIdentityPath, oldIdentityPath);
1944
2657
  }
1945
2658
  }
1946
2659
  });
1947
2660
  }
1948
- const oldAgentPath = path4.join(agentsDir, `${rosterOldName}.md`);
1949
- const newAgentPath = path4.join(agentsDir, `${newName}.md`);
1950
- if (existsSync4(oldAgentPath)) {
1951
- const agentContent = readFileSync4(oldAgentPath, "utf-8");
2661
+ const oldAgentPath = path6.join(agentsDir, `${rosterOldName}.md`);
2662
+ const newAgentPath = path6.join(agentsDir, `${newName}.md`);
2663
+ if (existsSync6(oldAgentPath)) {
2664
+ const agentContent = readFileSync5(oldAgentPath, "utf-8");
1952
2665
  renameSync3(oldAgentPath, newAgentPath);
1953
2666
  rollbackStack.push({
1954
2667
  description: "restore agent file",
1955
2668
  undo: () => {
1956
- if (existsSync4(newAgentPath)) {
2669
+ if (existsSync6(newAgentPath)) {
1957
2670
  renameSync3(newAgentPath, oldAgentPath);
1958
- writeFileSync2(oldAgentPath, agentContent, "utf-8");
2671
+ writeFileSync3(oldAgentPath, agentContent, "utf-8");
1959
2672
  }
1960
2673
  }
1961
2674
  });
@@ -2033,10 +2746,10 @@ function removeOldSymlinks(name) {
2033
2746
  try {
2034
2747
  const exeBinPath = findExeBin2();
2035
2748
  if (!exeBinPath) return;
2036
- const binDir = path4.dirname(exeBinPath);
2749
+ const binDir = path6.dirname(exeBinPath);
2037
2750
  for (const suffix of ["", "-opencode"]) {
2038
- const linkPath = path4.join(binDir, `${name}${suffix}`);
2039
- if (existsSync4(linkPath)) {
2751
+ const linkPath = path6.join(binDir, `${name}${suffix}`);
2752
+ if (existsSync6(linkPath)) {
2040
2753
  try {
2041
2754
  unlinkSync3(linkPath);
2042
2755
  } catch {