@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");
@@ -99,13 +125,50 @@ var init_config = __esm({
99
125
  }
100
126
  });
101
127
 
128
+ // src/lib/daemon-auth.ts
129
+ import crypto from "crypto";
130
+ import path4 from "path";
131
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
132
+ function normalizeToken(token) {
133
+ if (!token) return null;
134
+ const trimmed = token.trim();
135
+ return trimmed.length > 0 ? trimmed : null;
136
+ }
137
+ function readDaemonToken() {
138
+ try {
139
+ if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
140
+ return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
141
+ } catch {
142
+ return null;
143
+ }
144
+ }
145
+ function ensureDaemonToken(seed) {
146
+ const existing = readDaemonToken();
147
+ if (existing) return existing;
148
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
149
+ ensurePrivateDirSync(EXE_AI_DIR);
150
+ writeFileSync2(DAEMON_TOKEN_PATH, `${token}
151
+ `, "utf8");
152
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
153
+ return token;
154
+ }
155
+ var DAEMON_TOKEN_PATH;
156
+ var init_daemon_auth = __esm({
157
+ "src/lib/daemon-auth.ts"() {
158
+ "use strict";
159
+ init_config();
160
+ init_secure_files();
161
+ DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
162
+ }
163
+ });
164
+
102
165
  // src/lib/exe-daemon-client.ts
103
166
  import net from "net";
104
- import os3 from "os";
167
+ import os4 from "os";
105
168
  import { spawn } from "child_process";
106
169
  import { randomUUID } from "crypto";
107
- import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
108
- import path3 from "path";
170
+ import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
171
+ import path5 from "path";
109
172
  import { fileURLToPath } from "url";
110
173
  function handleData(chunk) {
111
174
  _buffer += chunk.toString();
@@ -133,9 +196,9 @@ function handleData(chunk) {
133
196
  }
134
197
  }
135
198
  function cleanupStaleFiles() {
136
- if (existsSync3(PID_PATH)) {
199
+ if (existsSync5(PID_PATH)) {
137
200
  try {
138
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
201
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
139
202
  if (pid > 0) {
140
203
  try {
141
204
  process.kill(pid, 0);
@@ -156,17 +219,17 @@ function cleanupStaleFiles() {
156
219
  }
157
220
  }
158
221
  function findPackageRoot() {
159
- let dir = path3.dirname(fileURLToPath(import.meta.url));
160
- const { root } = path3.parse(dir);
222
+ let dir = path5.dirname(fileURLToPath(import.meta.url));
223
+ const { root } = path5.parse(dir);
161
224
  while (dir !== root) {
162
- if (existsSync3(path3.join(dir, "package.json"))) return dir;
163
- dir = path3.dirname(dir);
225
+ if (existsSync5(path5.join(dir, "package.json"))) return dir;
226
+ dir = path5.dirname(dir);
164
227
  }
165
228
  return null;
166
229
  }
167
230
  function spawnDaemon() {
168
- const freeGB = os3.freemem() / (1024 * 1024 * 1024);
169
- const totalGB = os3.totalmem() / (1024 * 1024 * 1024);
231
+ const freeGB = os4.freemem() / (1024 * 1024 * 1024);
232
+ const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
170
233
  if (totalGB <= 8) {
171
234
  process.stderr.write(
172
235
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -186,16 +249,17 @@ function spawnDaemon() {
186
249
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
187
250
  return;
188
251
  }
189
- const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
190
- if (!existsSync3(daemonPath)) {
252
+ const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
253
+ if (!existsSync5(daemonPath)) {
191
254
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
192
255
  `);
193
256
  return;
194
257
  }
195
258
  const resolvedPath = daemonPath;
259
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
196
260
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
197
261
  `);
198
- const logPath = path3.join(path3.dirname(SOCKET_PATH), "exed.log");
262
+ const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
199
263
  let stderrFd = "ignore";
200
264
  try {
201
265
  stderrFd = openSync(logPath, "a");
@@ -213,7 +277,8 @@ function spawnDaemon() {
213
277
  TMUX_PANE: void 0,
214
278
  // Prevents resolveExeSession() from scoping to one session
215
279
  EXE_DAEMON_SOCK: SOCKET_PATH,
216
- EXE_DAEMON_PID: PID_PATH
280
+ EXE_DAEMON_PID: PID_PATH,
281
+ [DAEMON_TOKEN_ENV]: daemonToken
217
282
  }
218
283
  });
219
284
  child.unref();
@@ -320,13 +385,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
320
385
  return;
321
386
  }
322
387
  const id = randomUUID();
388
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
323
389
  const timer = setTimeout(() => {
324
390
  _pending.delete(id);
325
391
  resolve({ error: "Request timeout" });
326
392
  }, timeoutMs);
327
393
  _pending.set(id, { resolve, timer });
328
394
  try {
329
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
395
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
330
396
  } catch {
331
397
  clearTimeout(timer);
332
398
  _pending.delete(id);
@@ -337,17 +403,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
337
403
  function isClientConnected() {
338
404
  return _connected;
339
405
  }
340
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
406
+ 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;
341
407
  var init_exe_daemon_client = __esm({
342
408
  "src/lib/exe-daemon-client.ts"() {
343
409
  "use strict";
344
410
  init_config();
345
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
346
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
347
- SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
411
+ init_daemon_auth();
412
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
413
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
414
+ SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
348
415
  SPAWN_LOCK_STALE_MS = 3e4;
349
416
  CONNECT_TIMEOUT_MS = 15e3;
350
417
  REQUEST_TIMEOUT_MS = 3e4;
418
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
351
419
  _socket = null;
352
420
  _connected = false;
353
421
  _buffer = "";
@@ -426,7 +494,7 @@ __export(db_daemon_client_exports, {
426
494
  createDaemonDbClient: () => createDaemonDbClient,
427
495
  initDaemonDbClient: () => initDaemonDbClient
428
496
  });
429
- function normalizeStatement(stmt) {
497
+ function normalizeStatement2(stmt) {
430
498
  if (typeof stmt === "string") {
431
499
  return { sql: stmt, args: [] };
432
500
  }
@@ -450,7 +518,7 @@ function createDaemonDbClient(fallbackClient) {
450
518
  if (!_useDaemon || !isClientConnected()) {
451
519
  return fallbackClient.execute(stmt);
452
520
  }
453
- const { sql, args } = normalizeStatement(stmt);
521
+ const { sql, args } = normalizeStatement2(stmt);
454
522
  const response = await sendDaemonRequest({
455
523
  type: "db-execute",
456
524
  sql,
@@ -475,7 +543,7 @@ function createDaemonDbClient(fallbackClient) {
475
543
  if (!_useDaemon || !isClientConnected()) {
476
544
  return fallbackClient.batch(stmts, mode);
477
545
  }
478
- const statements = stmts.map(normalizeStatement);
546
+ const statements = stmts.map(normalizeStatement2);
479
547
  const response = await sendDaemonRequest({
480
548
  type: "db-batch",
481
549
  statements,
@@ -610,7 +678,7 @@ function wrapWithRetry(client) {
610
678
  // src/lib/employees.ts
611
679
  init_config();
612
680
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
613
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
681
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
614
682
  import { execSync } from "child_process";
615
683
  import path2 from "path";
616
684
  import os2 from "os";
@@ -630,21 +698,613 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
630
698
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
631
699
  }
632
700
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
633
- if (!existsSync2(employeesPath)) return [];
701
+ if (!existsSync3(employeesPath)) return [];
634
702
  try {
635
703
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
636
704
  } catch {
637
705
  return [];
638
706
  }
639
707
  }
708
+ var IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
709
+
710
+ // src/lib/database-adapter.ts
711
+ import os3 from "os";
712
+ import path3 from "path";
713
+ import { createRequire } from "module";
714
+ import { pathToFileURL } from "url";
715
+ var VIEW_MAPPINGS = [
716
+ { view: "memories", source: "memory.memory_records" },
717
+ { view: "tasks", source: "memory.tasks" },
718
+ { view: "behaviors", source: "memory.behaviors" },
719
+ { view: "entities", source: "memory.entities" },
720
+ { view: "relationships", source: "memory.relationships" },
721
+ { view: "entity_memories", source: "memory.entity_memories" },
722
+ { view: "entity_aliases", source: "memory.entity_aliases" },
723
+ { view: "notifications", source: "memory.notifications" },
724
+ { view: "messages", source: "memory.messages" },
725
+ { view: "users", source: "wiki.users" },
726
+ { view: "workspaces", source: "wiki.workspaces" },
727
+ { view: "workspace_users", source: "wiki.workspace_users" },
728
+ { view: "documents", source: "wiki.workspace_documents" },
729
+ { view: "chats", source: "wiki.workspace_chats" }
730
+ ];
731
+ var UPSERT_KEYS = {
732
+ memories: ["id"],
733
+ tasks: ["id"],
734
+ behaviors: ["id"],
735
+ entities: ["id"],
736
+ relationships: ["id"],
737
+ entity_aliases: ["alias"],
738
+ notifications: ["id"],
739
+ messages: ["id"],
740
+ users: ["id"],
741
+ workspaces: ["id"],
742
+ workspace_users: ["id"],
743
+ documents: ["id"],
744
+ chats: ["id"]
745
+ };
746
+ var BOOLEAN_COLUMNS_BY_TABLE = {
747
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
748
+ behaviors: /* @__PURE__ */ new Set(["active"]),
749
+ notifications: /* @__PURE__ */ new Set(["read"]),
750
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
751
+ };
752
+ var BOOLEAN_COLUMN_NAMES = new Set(
753
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
754
+ );
755
+ var IMMEDIATE_FALLBACK_PATTERNS = [
756
+ /\bPRAGMA\b/i,
757
+ /\bsqlite_master\b/i,
758
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
759
+ /\bMATCH\b/i,
760
+ /\bvector_distance_cos\s*\(/i,
761
+ /\bjson_extract\s*\(/i,
762
+ /\bjulianday\s*\(/i,
763
+ /\bstrftime\s*\(/i,
764
+ /\blast_insert_rowid\s*\(/i
765
+ ];
766
+ var prismaClientPromise = null;
767
+ var compatibilityBootstrapPromise = null;
768
+ function quotedIdentifier(identifier) {
769
+ return `"${identifier.replace(/"/g, '""')}"`;
770
+ }
771
+ function unqualifiedTableName(name) {
772
+ const raw = name.trim().replace(/^"|"$/g, "");
773
+ const parts = raw.split(".");
774
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
775
+ }
776
+ function stripTrailingSemicolon(sql) {
777
+ return sql.trim().replace(/;+\s*$/u, "");
778
+ }
779
+ function appendClause(sql, clause) {
780
+ const trimmed = stripTrailingSemicolon(sql);
781
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
782
+ if (!returningMatch) {
783
+ return `${trimmed}${clause}`;
784
+ }
785
+ const idx = returningMatch.index;
786
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
787
+ }
788
+ function normalizeStatement(stmt) {
789
+ if (typeof stmt === "string") {
790
+ return { kind: "positional", sql: stmt, args: [] };
791
+ }
792
+ const sql = stmt.sql;
793
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
794
+ return { kind: "positional", sql, args: stmt.args ?? [] };
795
+ }
796
+ return { kind: "named", sql, args: stmt.args };
797
+ }
798
+ function rewriteBooleanLiterals(sql) {
799
+ let out = sql;
800
+ for (const column of BOOLEAN_COLUMN_NAMES) {
801
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
802
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
803
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
804
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
805
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
806
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
807
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
808
+ }
809
+ return out;
810
+ }
811
+ function rewriteInsertOrIgnore(sql) {
812
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
813
+ return sql;
814
+ }
815
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
816
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
817
+ }
818
+ function rewriteInsertOrReplace(sql) {
819
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
820
+ if (!match) {
821
+ return sql;
822
+ }
823
+ const rawTable = match[1];
824
+ const rawColumns = match[2];
825
+ const remainder = match[3];
826
+ const tableName = unqualifiedTableName(rawTable);
827
+ const conflictKeys = UPSERT_KEYS[tableName];
828
+ if (!conflictKeys?.length) {
829
+ return sql;
830
+ }
831
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
832
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
833
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
834
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
835
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
836
+ }
837
+ function rewriteSql(sql) {
838
+ let out = sql;
839
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
840
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
841
+ out = rewriteBooleanLiterals(out);
842
+ out = rewriteInsertOrReplace(out);
843
+ out = rewriteInsertOrIgnore(out);
844
+ return stripTrailingSemicolon(out);
845
+ }
846
+ function toBoolean(value) {
847
+ if (value === null || value === void 0) return value;
848
+ if (typeof value === "boolean") return value;
849
+ if (typeof value === "number") return value !== 0;
850
+ if (typeof value === "bigint") return value !== 0n;
851
+ if (typeof value === "string") {
852
+ const normalized = value.trim().toLowerCase();
853
+ if (normalized === "0" || normalized === "false") return false;
854
+ if (normalized === "1" || normalized === "true") return true;
855
+ }
856
+ return Boolean(value);
857
+ }
858
+ function countQuestionMarks(sql, end) {
859
+ let count = 0;
860
+ let inSingle = false;
861
+ let inDouble = false;
862
+ let inLineComment = false;
863
+ let inBlockComment = false;
864
+ for (let i = 0; i < end; i++) {
865
+ const ch = sql[i];
866
+ const next = sql[i + 1];
867
+ if (inLineComment) {
868
+ if (ch === "\n") inLineComment = false;
869
+ continue;
870
+ }
871
+ if (inBlockComment) {
872
+ if (ch === "*" && next === "/") {
873
+ inBlockComment = false;
874
+ i += 1;
875
+ }
876
+ continue;
877
+ }
878
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
879
+ inLineComment = true;
880
+ i += 1;
881
+ continue;
882
+ }
883
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
884
+ inBlockComment = true;
885
+ i += 1;
886
+ continue;
887
+ }
888
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
889
+ inSingle = !inSingle;
890
+ continue;
891
+ }
892
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
893
+ inDouble = !inDouble;
894
+ continue;
895
+ }
896
+ if (!inSingle && !inDouble && ch === "?") {
897
+ count += 1;
898
+ }
899
+ }
900
+ return count;
901
+ }
902
+ function findBooleanPlaceholderIndexes(sql) {
903
+ const indexes = /* @__PURE__ */ new Set();
904
+ for (const column of BOOLEAN_COLUMN_NAMES) {
905
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
906
+ for (const match of sql.matchAll(pattern)) {
907
+ const matchText = match[0];
908
+ const qIndex = match.index + matchText.lastIndexOf("?");
909
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
910
+ }
911
+ }
912
+ return indexes;
913
+ }
914
+ function coerceInsertBooleanArgs(sql, args) {
915
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
916
+ if (!match) return;
917
+ const rawTable = match[1];
918
+ const rawColumns = match[2];
919
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
920
+ if (!boolColumns?.size) return;
921
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
922
+ for (const [index, column] of columns.entries()) {
923
+ if (boolColumns.has(column) && index < args.length) {
924
+ args[index] = toBoolean(args[index]);
925
+ }
926
+ }
927
+ }
928
+ function coerceUpdateBooleanArgs(sql, args) {
929
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
930
+ if (!match) return;
931
+ const rawTable = match[1];
932
+ const setClause = match[2];
933
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
934
+ if (!boolColumns?.size) return;
935
+ const assignments = setClause.split(",");
936
+ let placeholderIndex = 0;
937
+ for (const assignment of assignments) {
938
+ if (!assignment.includes("?")) continue;
939
+ placeholderIndex += 1;
940
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
941
+ if (colMatch && boolColumns.has(colMatch[1])) {
942
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
943
+ }
944
+ }
945
+ }
946
+ function coerceBooleanArgs(sql, args) {
947
+ const nextArgs = [...args];
948
+ coerceInsertBooleanArgs(sql, nextArgs);
949
+ coerceUpdateBooleanArgs(sql, nextArgs);
950
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
951
+ for (const index of placeholderIndexes) {
952
+ if (index > 0 && index <= nextArgs.length) {
953
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
954
+ }
955
+ }
956
+ return nextArgs;
957
+ }
958
+ function convertQuestionMarksToDollarParams(sql) {
959
+ let out = "";
960
+ let placeholder = 0;
961
+ let inSingle = false;
962
+ let inDouble = false;
963
+ let inLineComment = false;
964
+ let inBlockComment = false;
965
+ for (let i = 0; i < sql.length; i++) {
966
+ const ch = sql[i];
967
+ const next = sql[i + 1];
968
+ if (inLineComment) {
969
+ out += ch;
970
+ if (ch === "\n") inLineComment = false;
971
+ continue;
972
+ }
973
+ if (inBlockComment) {
974
+ out += ch;
975
+ if (ch === "*" && next === "/") {
976
+ out += next;
977
+ inBlockComment = false;
978
+ i += 1;
979
+ }
980
+ continue;
981
+ }
982
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
983
+ out += ch + next;
984
+ inLineComment = true;
985
+ i += 1;
986
+ continue;
987
+ }
988
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
989
+ out += ch + next;
990
+ inBlockComment = true;
991
+ i += 1;
992
+ continue;
993
+ }
994
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
995
+ inSingle = !inSingle;
996
+ out += ch;
997
+ continue;
998
+ }
999
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1000
+ inDouble = !inDouble;
1001
+ out += ch;
1002
+ continue;
1003
+ }
1004
+ if (!inSingle && !inDouble && ch === "?") {
1005
+ placeholder += 1;
1006
+ out += `$${placeholder}`;
1007
+ continue;
1008
+ }
1009
+ out += ch;
1010
+ }
1011
+ return out;
1012
+ }
1013
+ function translateStatementForPostgres(stmt) {
1014
+ const normalized = normalizeStatement(stmt);
1015
+ if (normalized.kind === "named") {
1016
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
1017
+ }
1018
+ const rewrittenSql = rewriteSql(normalized.sql);
1019
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
1020
+ return {
1021
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
1022
+ args: coercedArgs
1023
+ };
1024
+ }
1025
+ function shouldBypassPostgres(stmt) {
1026
+ const normalized = normalizeStatement(stmt);
1027
+ if (normalized.kind === "named") {
1028
+ return true;
1029
+ }
1030
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
1031
+ }
1032
+ function shouldFallbackOnError(error) {
1033
+ const message = error instanceof Error ? error.message : String(error);
1034
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
1035
+ }
1036
+ function isReadQuery(sql) {
1037
+ const trimmed = sql.trimStart();
1038
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
1039
+ }
1040
+ function buildRow(row, columns) {
1041
+ const values = columns.map((column) => row[column]);
1042
+ return Object.assign(values, row);
1043
+ }
1044
+ function buildResultSet(rows, rowsAffected = 0) {
1045
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
1046
+ const resultRows = rows.map((row) => buildRow(row, columns));
1047
+ return {
1048
+ columns,
1049
+ columnTypes: columns.map(() => ""),
1050
+ rows: resultRows,
1051
+ rowsAffected,
1052
+ lastInsertRowid: void 0,
1053
+ toJSON() {
1054
+ return {
1055
+ columns,
1056
+ columnTypes: columns.map(() => ""),
1057
+ rows,
1058
+ rowsAffected,
1059
+ lastInsertRowid: void 0
1060
+ };
1061
+ }
1062
+ };
1063
+ }
1064
+ async function loadPrismaClient() {
1065
+ if (!prismaClientPromise) {
1066
+ prismaClientPromise = (async () => {
1067
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
1068
+ if (explicitPath) {
1069
+ const module2 = await import(pathToFileURL(explicitPath).href);
1070
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
1071
+ if (!PrismaClient2) {
1072
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
1073
+ }
1074
+ return new PrismaClient2();
1075
+ }
1076
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
1077
+ const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
1078
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1079
+ const module = await import(pathToFileURL(prismaEntry).href);
1080
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
1081
+ if (!PrismaClient) {
1082
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
1083
+ }
1084
+ return new PrismaClient();
1085
+ })();
1086
+ }
1087
+ return prismaClientPromise;
1088
+ }
1089
+ async function ensureCompatibilityViews(prisma) {
1090
+ if (!compatibilityBootstrapPromise) {
1091
+ compatibilityBootstrapPromise = (async () => {
1092
+ for (const mapping of VIEW_MAPPINGS) {
1093
+ const relation = mapping.source.replace(/"/g, "");
1094
+ const rows = await prisma.$queryRawUnsafe(
1095
+ "SELECT to_regclass($1) AS regclass",
1096
+ relation
1097
+ );
1098
+ if (!rows[0]?.regclass) {
1099
+ continue;
1100
+ }
1101
+ await prisma.$executeRawUnsafe(
1102
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
1103
+ );
1104
+ }
1105
+ })();
1106
+ }
1107
+ return compatibilityBootstrapPromise;
1108
+ }
1109
+ async function executeOnPrisma(executor, stmt) {
1110
+ const translated = translateStatementForPostgres(stmt);
1111
+ if (isReadQuery(translated.sql)) {
1112
+ const rows = await executor.$queryRawUnsafe(
1113
+ translated.sql,
1114
+ ...translated.args
1115
+ );
1116
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
1117
+ }
1118
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
1119
+ return buildResultSet([], rowsAffected);
1120
+ }
1121
+ function splitSqlStatements(sql) {
1122
+ const parts = [];
1123
+ let current = "";
1124
+ let inSingle = false;
1125
+ let inDouble = false;
1126
+ let inLineComment = false;
1127
+ let inBlockComment = false;
1128
+ for (let i = 0; i < sql.length; i++) {
1129
+ const ch = sql[i];
1130
+ const next = sql[i + 1];
1131
+ if (inLineComment) {
1132
+ current += ch;
1133
+ if (ch === "\n") inLineComment = false;
1134
+ continue;
1135
+ }
1136
+ if (inBlockComment) {
1137
+ current += ch;
1138
+ if (ch === "*" && next === "/") {
1139
+ current += next;
1140
+ inBlockComment = false;
1141
+ i += 1;
1142
+ }
1143
+ continue;
1144
+ }
1145
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1146
+ current += ch + next;
1147
+ inLineComment = true;
1148
+ i += 1;
1149
+ continue;
1150
+ }
1151
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1152
+ current += ch + next;
1153
+ inBlockComment = true;
1154
+ i += 1;
1155
+ continue;
1156
+ }
1157
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1158
+ inSingle = !inSingle;
1159
+ current += ch;
1160
+ continue;
1161
+ }
1162
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1163
+ inDouble = !inDouble;
1164
+ current += ch;
1165
+ continue;
1166
+ }
1167
+ if (!inSingle && !inDouble && ch === ";") {
1168
+ if (current.trim()) {
1169
+ parts.push(current.trim());
1170
+ }
1171
+ current = "";
1172
+ continue;
1173
+ }
1174
+ current += ch;
1175
+ }
1176
+ if (current.trim()) {
1177
+ parts.push(current.trim());
1178
+ }
1179
+ return parts;
1180
+ }
1181
+ async function createPrismaDbAdapter(fallbackClient) {
1182
+ const prisma = await loadPrismaClient();
1183
+ await ensureCompatibilityViews(prisma);
1184
+ let closed = false;
1185
+ let adapter;
1186
+ const fallbackExecute = async (stmt, error) => {
1187
+ if (!fallbackClient) {
1188
+ if (error) throw error;
1189
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
1190
+ }
1191
+ if (error) {
1192
+ process.stderr.write(
1193
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
1194
+ `
1195
+ );
1196
+ }
1197
+ return fallbackClient.execute(stmt);
1198
+ };
1199
+ adapter = {
1200
+ async execute(stmt) {
1201
+ if (shouldBypassPostgres(stmt)) {
1202
+ return fallbackExecute(stmt);
1203
+ }
1204
+ try {
1205
+ return await executeOnPrisma(prisma, stmt);
1206
+ } catch (error) {
1207
+ if (shouldFallbackOnError(error)) {
1208
+ return fallbackExecute(stmt, error);
1209
+ }
1210
+ throw error;
1211
+ }
1212
+ },
1213
+ async batch(stmts, mode) {
1214
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
1215
+ if (!fallbackClient) {
1216
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
1217
+ }
1218
+ return fallbackClient.batch(stmts, mode);
1219
+ }
1220
+ try {
1221
+ if (prisma.$transaction) {
1222
+ return await prisma.$transaction(async (tx) => {
1223
+ const results2 = [];
1224
+ for (const stmt of stmts) {
1225
+ results2.push(await executeOnPrisma(tx, stmt));
1226
+ }
1227
+ return results2;
1228
+ });
1229
+ }
1230
+ const results = [];
1231
+ for (const stmt of stmts) {
1232
+ results.push(await executeOnPrisma(prisma, stmt));
1233
+ }
1234
+ return results;
1235
+ } catch (error) {
1236
+ if (fallbackClient && shouldFallbackOnError(error)) {
1237
+ process.stderr.write(
1238
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
1239
+ `
1240
+ );
1241
+ return fallbackClient.batch(stmts, mode);
1242
+ }
1243
+ throw error;
1244
+ }
1245
+ },
1246
+ async migrate(stmts) {
1247
+ if (fallbackClient) {
1248
+ return fallbackClient.migrate(stmts);
1249
+ }
1250
+ return adapter.batch(stmts, "deferred");
1251
+ },
1252
+ async transaction(mode) {
1253
+ if (!fallbackClient) {
1254
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
1255
+ }
1256
+ return fallbackClient.transaction(mode);
1257
+ },
1258
+ async executeMultiple(sql) {
1259
+ if (fallbackClient && shouldBypassPostgres(sql)) {
1260
+ return fallbackClient.executeMultiple(sql);
1261
+ }
1262
+ for (const statement of splitSqlStatements(sql)) {
1263
+ await adapter.execute(statement);
1264
+ }
1265
+ },
1266
+ async sync() {
1267
+ if (fallbackClient) {
1268
+ return fallbackClient.sync();
1269
+ }
1270
+ return { frame_no: 0, frames_synced: 0 };
1271
+ },
1272
+ close() {
1273
+ closed = true;
1274
+ prismaClientPromise = null;
1275
+ compatibilityBootstrapPromise = null;
1276
+ void prisma.$disconnect?.();
1277
+ },
1278
+ get closed() {
1279
+ return closed;
1280
+ },
1281
+ get protocol() {
1282
+ return "prisma-postgres";
1283
+ }
1284
+ };
1285
+ return adapter;
1286
+ }
640
1287
 
641
1288
  // src/lib/database.ts
642
1289
  var _client = null;
643
1290
  var _resilientClient = null;
644
1291
  var _walCheckpointTimer = null;
645
1292
  var _daemonClient = null;
1293
+ var _adapterClient = null;
646
1294
  var initTurso = initDatabase;
647
1295
  async function initDatabase(config) {
1296
+ if (_walCheckpointTimer) {
1297
+ clearInterval(_walCheckpointTimer);
1298
+ _walCheckpointTimer = null;
1299
+ }
1300
+ if (_daemonClient) {
1301
+ _daemonClient.close();
1302
+ _daemonClient = null;
1303
+ }
1304
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1305
+ _adapterClient.close();
1306
+ }
1307
+ _adapterClient = null;
648
1308
  if (_client) {
649
1309
  _client.close();
650
1310
  _client = null;
@@ -658,6 +1318,7 @@ async function initDatabase(config) {
658
1318
  }
659
1319
  _client = createClient(opts);
660
1320
  _resilientClient = wrapWithRetry(_client);
1321
+ _adapterClient = _resilientClient;
661
1322
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
662
1323
  });
663
1324
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -668,14 +1329,20 @@ async function initDatabase(config) {
668
1329
  });
669
1330
  }, 3e4);
670
1331
  _walCheckpointTimer.unref();
1332
+ if (process.env.DATABASE_URL) {
1333
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1334
+ }
671
1335
  }
672
1336
  function isInitialized() {
673
- return _client !== null;
1337
+ return _adapterClient !== null || _client !== null;
674
1338
  }
675
1339
  function getClient() {
676
- if (!_resilientClient) {
1340
+ if (!_adapterClient) {
677
1341
  throw new Error("Database client not initialized. Call initDatabase() first.");
678
1342
  }
1343
+ if (process.env.DATABASE_URL) {
1344
+ return _adapterClient;
1345
+ }
679
1346
  if (process.env.EXE_IS_DAEMON === "1") {
680
1347
  return _resilientClient;
681
1348
  }
@@ -685,6 +1352,7 @@ function getClient() {
685
1352
  return _resilientClient;
686
1353
  }
687
1354
  async function initDaemonClient() {
1355
+ if (process.env.DATABASE_URL) return;
688
1356
  if (process.env.EXE_IS_DAEMON === "1") return;
689
1357
  if (!_resilientClient) return;
690
1358
  try {
@@ -981,6 +1649,7 @@ async function ensureSchema() {
981
1649
  project TEXT NOT NULL,
982
1650
  summary TEXT NOT NULL,
983
1651
  task_file TEXT,
1652
+ session_scope TEXT,
984
1653
  read INTEGER NOT NULL DEFAULT 0,
985
1654
  created_at TEXT NOT NULL
986
1655
  );
@@ -989,7 +1658,7 @@ async function ensureSchema() {
989
1658
  ON notifications(read);
990
1659
 
991
1660
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
992
- ON notifications(agent_id);
1661
+ ON notifications(agent_id, session_scope);
993
1662
 
994
1663
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
995
1664
  ON notifications(task_file);
@@ -1027,6 +1696,7 @@ async function ensureSchema() {
1027
1696
  target_agent TEXT NOT NULL,
1028
1697
  target_project TEXT,
1029
1698
  target_device TEXT NOT NULL DEFAULT 'local',
1699
+ session_scope TEXT,
1030
1700
  content TEXT NOT NULL,
1031
1701
  priority TEXT DEFAULT 'normal',
1032
1702
  status TEXT DEFAULT 'pending',
@@ -1040,10 +1710,31 @@ async function ensureSchema() {
1040
1710
  );
1041
1711
 
1042
1712
  CREATE INDEX IF NOT EXISTS idx_messages_target
1043
- ON messages(target_agent, status);
1713
+ ON messages(target_agent, session_scope, status);
1044
1714
 
1045
1715
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1046
- ON messages(target_agent, from_agent, server_seq);
1716
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1717
+ `);
1718
+ try {
1719
+ await client.execute({
1720
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1721
+ args: []
1722
+ });
1723
+ } catch {
1724
+ }
1725
+ try {
1726
+ await client.execute({
1727
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1728
+ args: []
1729
+ });
1730
+ } catch {
1731
+ }
1732
+ await client.executeMultiple(`
1733
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1734
+ ON notifications(agent_id, session_scope, read, created_at);
1735
+
1736
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1737
+ ON messages(target_agent, session_scope, status, created_at);
1047
1738
  `);
1048
1739
  try {
1049
1740
  await client.execute({
@@ -1627,13 +2318,28 @@ async function ensureSchema() {
1627
2318
  } catch {
1628
2319
  }
1629
2320
  }
2321
+ try {
2322
+ await client.execute({
2323
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2324
+ args: []
2325
+ });
2326
+ } catch {
2327
+ }
1630
2328
  }
1631
2329
  var disposeTurso = disposeDatabase;
1632
2330
  async function disposeDatabase() {
2331
+ if (_walCheckpointTimer) {
2332
+ clearInterval(_walCheckpointTimer);
2333
+ _walCheckpointTimer = null;
2334
+ }
1633
2335
  if (_daemonClient) {
1634
2336
  _daemonClient.close();
1635
2337
  _daemonClient = null;
1636
2338
  }
2339
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2340
+ _adapterClient.close();
2341
+ }
2342
+ _adapterClient = null;
1637
2343
  if (_client) {
1638
2344
  _client.close();
1639
2345
  _client = null;