@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
@@ -63,9 +63,34 @@ var init_db_retry = __esm({
63
63
  }
64
64
  });
65
65
 
66
+ // src/lib/secure-files.ts
67
+ import { chmodSync, existsSync, mkdirSync } from "fs";
68
+ import { chmod, mkdir } from "fs/promises";
69
+ function ensurePrivateDirSync(dirPath) {
70
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
71
+ try {
72
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
73
+ } catch {
74
+ }
75
+ }
76
+ function enforcePrivateFileSync(filePath) {
77
+ try {
78
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
79
+ } catch {
80
+ }
81
+ }
82
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
83
+ var init_secure_files = __esm({
84
+ "src/lib/secure-files.ts"() {
85
+ "use strict";
86
+ PRIVATE_DIR_MODE = 448;
87
+ PRIVATE_FILE_MODE = 384;
88
+ }
89
+ });
90
+
66
91
  // src/lib/config.ts
67
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
68
- import { readFileSync, existsSync, renameSync } from "fs";
92
+ import { readFile, writeFile } from "fs/promises";
93
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
69
94
  import path from "path";
70
95
  import os from "os";
71
96
  function resolveDataDir() {
@@ -73,7 +98,7 @@ function resolveDataDir() {
73
98
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
74
99
  const newDir = path.join(os.homedir(), ".exe-os");
75
100
  const legacyDir = path.join(os.homedir(), ".exe-mem");
76
- if (!existsSync(newDir) && existsSync(legacyDir)) {
101
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
77
102
  try {
78
103
  renameSync(legacyDir, newDir);
79
104
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -88,6 +113,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
88
113
  var init_config = __esm({
89
114
  "src/lib/config.ts"() {
90
115
  "use strict";
116
+ init_secure_files();
91
117
  EXE_AI_DIR = resolveDataDir();
92
118
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
93
119
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -156,7 +182,7 @@ var init_config = __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,7 +199,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
173
199
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
174
200
  }
175
201
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
176
- if (!existsSync2(employeesPath)) {
202
+ if (!existsSync3(employeesPath)) {
177
203
  return [];
178
204
  }
179
205
  const raw = await readFile2(employeesPath, "utf-8");
@@ -188,7 +214,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
188
214
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
189
215
  }
190
216
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
191
- if (!existsSync2(employeesPath)) return [];
217
+ if (!existsSync3(employeesPath)) return [];
192
218
  try {
193
219
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
194
220
  } catch {
@@ -222,7 +248,7 @@ function registerBinSymlinks(name) {
222
248
  for (const suffix of ["", "-opencode"]) {
223
249
  const linkName = `${name}${suffix}`;
224
250
  const linkPath = path2.join(binDir, linkName);
225
- if (existsSync2(linkPath)) {
251
+ if (existsSync3(linkPath)) {
226
252
  skipped.push(linkName);
227
253
  continue;
228
254
  }
@@ -235,7 +261,7 @@ function registerBinSymlinks(name) {
235
261
  }
236
262
  return { created, skipped, errors };
237
263
  }
238
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
264
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
239
265
  var init_employees = __esm({
240
266
  "src/lib/employees.ts"() {
241
267
  "use strict";
@@ -243,16 +269,638 @@ var init_employees = __esm({
243
269
  EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
244
270
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
245
271
  COORDINATOR_ROLE = "COO";
272
+ IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
273
+ }
274
+ });
275
+
276
+ // src/lib/database-adapter.ts
277
+ import os3 from "os";
278
+ import path3 from "path";
279
+ import { createRequire } from "module";
280
+ import { pathToFileURL } from "url";
281
+ function quotedIdentifier(identifier) {
282
+ return `"${identifier.replace(/"/g, '""')}"`;
283
+ }
284
+ function unqualifiedTableName(name) {
285
+ const raw = name.trim().replace(/^"|"$/g, "");
286
+ const parts = raw.split(".");
287
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
288
+ }
289
+ function stripTrailingSemicolon(sql) {
290
+ return sql.trim().replace(/;+\s*$/u, "");
291
+ }
292
+ function appendClause(sql, clause) {
293
+ const trimmed = stripTrailingSemicolon(sql);
294
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
295
+ if (!returningMatch) {
296
+ return `${trimmed}${clause}`;
297
+ }
298
+ const idx = returningMatch.index;
299
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
300
+ }
301
+ function normalizeStatement(stmt) {
302
+ if (typeof stmt === "string") {
303
+ return { kind: "positional", sql: stmt, args: [] };
304
+ }
305
+ const sql = stmt.sql;
306
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
307
+ return { kind: "positional", sql, args: stmt.args ?? [] };
308
+ }
309
+ return { kind: "named", sql, args: stmt.args };
310
+ }
311
+ function rewriteBooleanLiterals(sql) {
312
+ let out = sql;
313
+ for (const column of BOOLEAN_COLUMN_NAMES) {
314
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
315
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
316
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
317
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
318
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
319
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
320
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
321
+ }
322
+ return out;
323
+ }
324
+ function rewriteInsertOrIgnore(sql) {
325
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
326
+ return sql;
327
+ }
328
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
329
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
330
+ }
331
+ function rewriteInsertOrReplace(sql) {
332
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
333
+ if (!match) {
334
+ return sql;
335
+ }
336
+ const rawTable = match[1];
337
+ const rawColumns = match[2];
338
+ const remainder = match[3];
339
+ const tableName = unqualifiedTableName(rawTable);
340
+ const conflictKeys = UPSERT_KEYS[tableName];
341
+ if (!conflictKeys?.length) {
342
+ return sql;
343
+ }
344
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
345
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
346
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
347
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
348
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
349
+ }
350
+ function rewriteSql(sql) {
351
+ let out = sql;
352
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
353
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
354
+ out = rewriteBooleanLiterals(out);
355
+ out = rewriteInsertOrReplace(out);
356
+ out = rewriteInsertOrIgnore(out);
357
+ return stripTrailingSemicolon(out);
358
+ }
359
+ function toBoolean(value) {
360
+ if (value === null || value === void 0) return value;
361
+ if (typeof value === "boolean") return value;
362
+ if (typeof value === "number") return value !== 0;
363
+ if (typeof value === "bigint") return value !== 0n;
364
+ if (typeof value === "string") {
365
+ const normalized = value.trim().toLowerCase();
366
+ if (normalized === "0" || normalized === "false") return false;
367
+ if (normalized === "1" || normalized === "true") return true;
368
+ }
369
+ return Boolean(value);
370
+ }
371
+ function countQuestionMarks(sql, end) {
372
+ let count = 0;
373
+ let inSingle = false;
374
+ let inDouble = false;
375
+ let inLineComment = false;
376
+ let inBlockComment = false;
377
+ for (let i = 0; i < end; i++) {
378
+ const ch = sql[i];
379
+ const next = sql[i + 1];
380
+ if (inLineComment) {
381
+ if (ch === "\n") inLineComment = false;
382
+ continue;
383
+ }
384
+ if (inBlockComment) {
385
+ if (ch === "*" && next === "/") {
386
+ inBlockComment = false;
387
+ i += 1;
388
+ }
389
+ continue;
390
+ }
391
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
392
+ inLineComment = true;
393
+ i += 1;
394
+ continue;
395
+ }
396
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
397
+ inBlockComment = true;
398
+ i += 1;
399
+ continue;
400
+ }
401
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
402
+ inSingle = !inSingle;
403
+ continue;
404
+ }
405
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
406
+ inDouble = !inDouble;
407
+ continue;
408
+ }
409
+ if (!inSingle && !inDouble && ch === "?") {
410
+ count += 1;
411
+ }
412
+ }
413
+ return count;
414
+ }
415
+ function findBooleanPlaceholderIndexes(sql) {
416
+ const indexes = /* @__PURE__ */ new Set();
417
+ for (const column of BOOLEAN_COLUMN_NAMES) {
418
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
419
+ for (const match of sql.matchAll(pattern)) {
420
+ const matchText = match[0];
421
+ const qIndex = match.index + matchText.lastIndexOf("?");
422
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
423
+ }
424
+ }
425
+ return indexes;
426
+ }
427
+ function coerceInsertBooleanArgs(sql, args) {
428
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
429
+ if (!match) return;
430
+ const rawTable = match[1];
431
+ const rawColumns = match[2];
432
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
433
+ if (!boolColumns?.size) return;
434
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
435
+ for (const [index, column] of columns.entries()) {
436
+ if (boolColumns.has(column) && index < args.length) {
437
+ args[index] = toBoolean(args[index]);
438
+ }
439
+ }
440
+ }
441
+ function coerceUpdateBooleanArgs(sql, args) {
442
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
443
+ if (!match) return;
444
+ const rawTable = match[1];
445
+ const setClause = match[2];
446
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
447
+ if (!boolColumns?.size) return;
448
+ const assignments = setClause.split(",");
449
+ let placeholderIndex = 0;
450
+ for (const assignment of assignments) {
451
+ if (!assignment.includes("?")) continue;
452
+ placeholderIndex += 1;
453
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
454
+ if (colMatch && boolColumns.has(colMatch[1])) {
455
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
456
+ }
457
+ }
458
+ }
459
+ function coerceBooleanArgs(sql, args) {
460
+ const nextArgs = [...args];
461
+ coerceInsertBooleanArgs(sql, nextArgs);
462
+ coerceUpdateBooleanArgs(sql, nextArgs);
463
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
464
+ for (const index of placeholderIndexes) {
465
+ if (index > 0 && index <= nextArgs.length) {
466
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
467
+ }
468
+ }
469
+ return nextArgs;
470
+ }
471
+ function convertQuestionMarksToDollarParams(sql) {
472
+ let out = "";
473
+ let placeholder = 0;
474
+ let inSingle = false;
475
+ let inDouble = false;
476
+ let inLineComment = false;
477
+ let inBlockComment = false;
478
+ for (let i = 0; i < sql.length; i++) {
479
+ const ch = sql[i];
480
+ const next = sql[i + 1];
481
+ if (inLineComment) {
482
+ out += ch;
483
+ if (ch === "\n") inLineComment = false;
484
+ continue;
485
+ }
486
+ if (inBlockComment) {
487
+ out += ch;
488
+ if (ch === "*" && next === "/") {
489
+ out += next;
490
+ inBlockComment = false;
491
+ i += 1;
492
+ }
493
+ continue;
494
+ }
495
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
496
+ out += ch + next;
497
+ inLineComment = true;
498
+ i += 1;
499
+ continue;
500
+ }
501
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
502
+ out += ch + next;
503
+ inBlockComment = true;
504
+ i += 1;
505
+ continue;
506
+ }
507
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
508
+ inSingle = !inSingle;
509
+ out += ch;
510
+ continue;
511
+ }
512
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
513
+ inDouble = !inDouble;
514
+ out += ch;
515
+ continue;
516
+ }
517
+ if (!inSingle && !inDouble && ch === "?") {
518
+ placeholder += 1;
519
+ out += `$${placeholder}`;
520
+ continue;
521
+ }
522
+ out += ch;
523
+ }
524
+ return out;
525
+ }
526
+ function translateStatementForPostgres(stmt) {
527
+ const normalized = normalizeStatement(stmt);
528
+ if (normalized.kind === "named") {
529
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
530
+ }
531
+ const rewrittenSql = rewriteSql(normalized.sql);
532
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
533
+ return {
534
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
535
+ args: coercedArgs
536
+ };
537
+ }
538
+ function shouldBypassPostgres(stmt) {
539
+ const normalized = normalizeStatement(stmt);
540
+ if (normalized.kind === "named") {
541
+ return true;
542
+ }
543
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
544
+ }
545
+ function shouldFallbackOnError(error) {
546
+ const message = error instanceof Error ? error.message : String(error);
547
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
548
+ }
549
+ function isReadQuery(sql) {
550
+ const trimmed = sql.trimStart();
551
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
552
+ }
553
+ function buildRow(row, columns) {
554
+ const values = columns.map((column) => row[column]);
555
+ return Object.assign(values, row);
556
+ }
557
+ function buildResultSet(rows, rowsAffected = 0) {
558
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
559
+ const resultRows = rows.map((row) => buildRow(row, columns));
560
+ return {
561
+ columns,
562
+ columnTypes: columns.map(() => ""),
563
+ rows: resultRows,
564
+ rowsAffected,
565
+ lastInsertRowid: void 0,
566
+ toJSON() {
567
+ return {
568
+ columns,
569
+ columnTypes: columns.map(() => ""),
570
+ rows,
571
+ rowsAffected,
572
+ lastInsertRowid: void 0
573
+ };
574
+ }
575
+ };
576
+ }
577
+ async function loadPrismaClient() {
578
+ if (!prismaClientPromise) {
579
+ prismaClientPromise = (async () => {
580
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
581
+ if (explicitPath) {
582
+ const module2 = await import(pathToFileURL(explicitPath).href);
583
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
584
+ if (!PrismaClient2) {
585
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
586
+ }
587
+ return new PrismaClient2();
588
+ }
589
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
590
+ const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
591
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
592
+ const module = await import(pathToFileURL(prismaEntry).href);
593
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
594
+ if (!PrismaClient) {
595
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
596
+ }
597
+ return new PrismaClient();
598
+ })();
599
+ }
600
+ return prismaClientPromise;
601
+ }
602
+ async function ensureCompatibilityViews(prisma) {
603
+ if (!compatibilityBootstrapPromise) {
604
+ compatibilityBootstrapPromise = (async () => {
605
+ for (const mapping of VIEW_MAPPINGS) {
606
+ const relation = mapping.source.replace(/"/g, "");
607
+ const rows = await prisma.$queryRawUnsafe(
608
+ "SELECT to_regclass($1) AS regclass",
609
+ relation
610
+ );
611
+ if (!rows[0]?.regclass) {
612
+ continue;
613
+ }
614
+ await prisma.$executeRawUnsafe(
615
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
616
+ );
617
+ }
618
+ })();
619
+ }
620
+ return compatibilityBootstrapPromise;
621
+ }
622
+ async function executeOnPrisma(executor, stmt) {
623
+ const translated = translateStatementForPostgres(stmt);
624
+ if (isReadQuery(translated.sql)) {
625
+ const rows = await executor.$queryRawUnsafe(
626
+ translated.sql,
627
+ ...translated.args
628
+ );
629
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
630
+ }
631
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
632
+ return buildResultSet([], rowsAffected);
633
+ }
634
+ function splitSqlStatements(sql) {
635
+ const parts = [];
636
+ let current = "";
637
+ let inSingle = false;
638
+ let inDouble = false;
639
+ let inLineComment = false;
640
+ let inBlockComment = false;
641
+ for (let i = 0; i < sql.length; i++) {
642
+ const ch = sql[i];
643
+ const next = sql[i + 1];
644
+ if (inLineComment) {
645
+ current += ch;
646
+ if (ch === "\n") inLineComment = false;
647
+ continue;
648
+ }
649
+ if (inBlockComment) {
650
+ current += ch;
651
+ if (ch === "*" && next === "/") {
652
+ current += next;
653
+ inBlockComment = false;
654
+ i += 1;
655
+ }
656
+ continue;
657
+ }
658
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
659
+ current += ch + next;
660
+ inLineComment = true;
661
+ i += 1;
662
+ continue;
663
+ }
664
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
665
+ current += ch + next;
666
+ inBlockComment = true;
667
+ i += 1;
668
+ continue;
669
+ }
670
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
671
+ inSingle = !inSingle;
672
+ current += ch;
673
+ continue;
674
+ }
675
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
676
+ inDouble = !inDouble;
677
+ current += ch;
678
+ continue;
679
+ }
680
+ if (!inSingle && !inDouble && ch === ";") {
681
+ if (current.trim()) {
682
+ parts.push(current.trim());
683
+ }
684
+ current = "";
685
+ continue;
686
+ }
687
+ current += ch;
688
+ }
689
+ if (current.trim()) {
690
+ parts.push(current.trim());
691
+ }
692
+ return parts;
693
+ }
694
+ async function createPrismaDbAdapter(fallbackClient) {
695
+ const prisma = await loadPrismaClient();
696
+ await ensureCompatibilityViews(prisma);
697
+ let closed = false;
698
+ let adapter;
699
+ const fallbackExecute = async (stmt, error) => {
700
+ if (!fallbackClient) {
701
+ if (error) throw error;
702
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
703
+ }
704
+ if (error) {
705
+ process.stderr.write(
706
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
707
+ `
708
+ );
709
+ }
710
+ return fallbackClient.execute(stmt);
711
+ };
712
+ adapter = {
713
+ async execute(stmt) {
714
+ if (shouldBypassPostgres(stmt)) {
715
+ return fallbackExecute(stmt);
716
+ }
717
+ try {
718
+ return await executeOnPrisma(prisma, stmt);
719
+ } catch (error) {
720
+ if (shouldFallbackOnError(error)) {
721
+ return fallbackExecute(stmt, error);
722
+ }
723
+ throw error;
724
+ }
725
+ },
726
+ async batch(stmts, mode) {
727
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
728
+ if (!fallbackClient) {
729
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
730
+ }
731
+ return fallbackClient.batch(stmts, mode);
732
+ }
733
+ try {
734
+ if (prisma.$transaction) {
735
+ return await prisma.$transaction(async (tx) => {
736
+ const results2 = [];
737
+ for (const stmt of stmts) {
738
+ results2.push(await executeOnPrisma(tx, stmt));
739
+ }
740
+ return results2;
741
+ });
742
+ }
743
+ const results = [];
744
+ for (const stmt of stmts) {
745
+ results.push(await executeOnPrisma(prisma, stmt));
746
+ }
747
+ return results;
748
+ } catch (error) {
749
+ if (fallbackClient && shouldFallbackOnError(error)) {
750
+ process.stderr.write(
751
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
752
+ `
753
+ );
754
+ return fallbackClient.batch(stmts, mode);
755
+ }
756
+ throw error;
757
+ }
758
+ },
759
+ async migrate(stmts) {
760
+ if (fallbackClient) {
761
+ return fallbackClient.migrate(stmts);
762
+ }
763
+ return adapter.batch(stmts, "deferred");
764
+ },
765
+ async transaction(mode) {
766
+ if (!fallbackClient) {
767
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
768
+ }
769
+ return fallbackClient.transaction(mode);
770
+ },
771
+ async executeMultiple(sql) {
772
+ if (fallbackClient && shouldBypassPostgres(sql)) {
773
+ return fallbackClient.executeMultiple(sql);
774
+ }
775
+ for (const statement of splitSqlStatements(sql)) {
776
+ await adapter.execute(statement);
777
+ }
778
+ },
779
+ async sync() {
780
+ if (fallbackClient) {
781
+ return fallbackClient.sync();
782
+ }
783
+ return { frame_no: 0, frames_synced: 0 };
784
+ },
785
+ close() {
786
+ closed = true;
787
+ prismaClientPromise = null;
788
+ compatibilityBootstrapPromise = null;
789
+ void prisma.$disconnect?.();
790
+ },
791
+ get closed() {
792
+ return closed;
793
+ },
794
+ get protocol() {
795
+ return "prisma-postgres";
796
+ }
797
+ };
798
+ return adapter;
799
+ }
800
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
801
+ var init_database_adapter = __esm({
802
+ "src/lib/database-adapter.ts"() {
803
+ "use strict";
804
+ VIEW_MAPPINGS = [
805
+ { view: "memories", source: "memory.memory_records" },
806
+ { view: "tasks", source: "memory.tasks" },
807
+ { view: "behaviors", source: "memory.behaviors" },
808
+ { view: "entities", source: "memory.entities" },
809
+ { view: "relationships", source: "memory.relationships" },
810
+ { view: "entity_memories", source: "memory.entity_memories" },
811
+ { view: "entity_aliases", source: "memory.entity_aliases" },
812
+ { view: "notifications", source: "memory.notifications" },
813
+ { view: "messages", source: "memory.messages" },
814
+ { view: "users", source: "wiki.users" },
815
+ { view: "workspaces", source: "wiki.workspaces" },
816
+ { view: "workspace_users", source: "wiki.workspace_users" },
817
+ { view: "documents", source: "wiki.workspace_documents" },
818
+ { view: "chats", source: "wiki.workspace_chats" }
819
+ ];
820
+ UPSERT_KEYS = {
821
+ memories: ["id"],
822
+ tasks: ["id"],
823
+ behaviors: ["id"],
824
+ entities: ["id"],
825
+ relationships: ["id"],
826
+ entity_aliases: ["alias"],
827
+ notifications: ["id"],
828
+ messages: ["id"],
829
+ users: ["id"],
830
+ workspaces: ["id"],
831
+ workspace_users: ["id"],
832
+ documents: ["id"],
833
+ chats: ["id"]
834
+ };
835
+ BOOLEAN_COLUMNS_BY_TABLE = {
836
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
837
+ behaviors: /* @__PURE__ */ new Set(["active"]),
838
+ notifications: /* @__PURE__ */ new Set(["read"]),
839
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
840
+ };
841
+ BOOLEAN_COLUMN_NAMES = new Set(
842
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
843
+ );
844
+ IMMEDIATE_FALLBACK_PATTERNS = [
845
+ /\bPRAGMA\b/i,
846
+ /\bsqlite_master\b/i,
847
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
848
+ /\bMATCH\b/i,
849
+ /\bvector_distance_cos\s*\(/i,
850
+ /\bjson_extract\s*\(/i,
851
+ /\bjulianday\s*\(/i,
852
+ /\bstrftime\s*\(/i,
853
+ /\blast_insert_rowid\s*\(/i
854
+ ];
855
+ prismaClientPromise = null;
856
+ compatibilityBootstrapPromise = null;
857
+ }
858
+ });
859
+
860
+ // src/lib/daemon-auth.ts
861
+ import crypto from "crypto";
862
+ import path4 from "path";
863
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
864
+ function normalizeToken(token) {
865
+ if (!token) return null;
866
+ const trimmed = token.trim();
867
+ return trimmed.length > 0 ? trimmed : null;
868
+ }
869
+ function readDaemonToken() {
870
+ try {
871
+ if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
872
+ return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
873
+ } catch {
874
+ return null;
875
+ }
876
+ }
877
+ function ensureDaemonToken(seed) {
878
+ const existing = readDaemonToken();
879
+ if (existing) return existing;
880
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
881
+ ensurePrivateDirSync(EXE_AI_DIR);
882
+ writeFileSync2(DAEMON_TOKEN_PATH, `${token}
883
+ `, "utf8");
884
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
885
+ return token;
886
+ }
887
+ var DAEMON_TOKEN_PATH;
888
+ var init_daemon_auth = __esm({
889
+ "src/lib/daemon-auth.ts"() {
890
+ "use strict";
891
+ init_config();
892
+ init_secure_files();
893
+ DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
246
894
  }
247
895
  });
248
896
 
249
897
  // src/lib/exe-daemon-client.ts
250
898
  import net from "net";
251
- import os3 from "os";
899
+ import os4 from "os";
252
900
  import { spawn } from "child_process";
253
901
  import { randomUUID } from "crypto";
254
- import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
255
- import path3 from "path";
902
+ import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
903
+ import path5 from "path";
256
904
  import { fileURLToPath } from "url";
257
905
  function handleData(chunk) {
258
906
  _buffer += chunk.toString();
@@ -280,9 +928,9 @@ function handleData(chunk) {
280
928
  }
281
929
  }
282
930
  function cleanupStaleFiles() {
283
- if (existsSync3(PID_PATH)) {
931
+ if (existsSync5(PID_PATH)) {
284
932
  try {
285
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
933
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
286
934
  if (pid > 0) {
287
935
  try {
288
936
  process.kill(pid, 0);
@@ -303,17 +951,17 @@ function cleanupStaleFiles() {
303
951
  }
304
952
  }
305
953
  function findPackageRoot() {
306
- let dir = path3.dirname(fileURLToPath(import.meta.url));
307
- const { root } = path3.parse(dir);
954
+ let dir = path5.dirname(fileURLToPath(import.meta.url));
955
+ const { root } = path5.parse(dir);
308
956
  while (dir !== root) {
309
- if (existsSync3(path3.join(dir, "package.json"))) return dir;
310
- dir = path3.dirname(dir);
957
+ if (existsSync5(path5.join(dir, "package.json"))) return dir;
958
+ dir = path5.dirname(dir);
311
959
  }
312
960
  return null;
313
961
  }
314
962
  function spawnDaemon() {
315
- const freeGB = os3.freemem() / (1024 * 1024 * 1024);
316
- const totalGB = os3.totalmem() / (1024 * 1024 * 1024);
963
+ const freeGB = os4.freemem() / (1024 * 1024 * 1024);
964
+ const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
317
965
  if (totalGB <= 8) {
318
966
  process.stderr.write(
319
967
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -333,16 +981,17 @@ function spawnDaemon() {
333
981
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
334
982
  return;
335
983
  }
336
- const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
337
- if (!existsSync3(daemonPath)) {
984
+ const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
985
+ if (!existsSync5(daemonPath)) {
338
986
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
339
987
  `);
340
988
  return;
341
989
  }
342
990
  const resolvedPath = daemonPath;
991
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
343
992
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
344
993
  `);
345
- const logPath = path3.join(path3.dirname(SOCKET_PATH), "exed.log");
994
+ const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
346
995
  let stderrFd = "ignore";
347
996
  try {
348
997
  stderrFd = openSync(logPath, "a");
@@ -360,7 +1009,8 @@ function spawnDaemon() {
360
1009
  TMUX_PANE: void 0,
361
1010
  // Prevents resolveExeSession() from scoping to one session
362
1011
  EXE_DAEMON_SOCK: SOCKET_PATH,
363
- EXE_DAEMON_PID: PID_PATH
1012
+ EXE_DAEMON_PID: PID_PATH,
1013
+ [DAEMON_TOKEN_ENV]: daemonToken
364
1014
  }
365
1015
  });
366
1016
  child.unref();
@@ -467,13 +1117,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
467
1117
  return;
468
1118
  }
469
1119
  const id = randomUUID();
1120
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
470
1121
  const timer = setTimeout(() => {
471
1122
  _pending.delete(id);
472
1123
  resolve({ error: "Request timeout" });
473
1124
  }, timeoutMs);
474
1125
  _pending.set(id, { resolve, timer });
475
1126
  try {
476
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1127
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
477
1128
  } catch {
478
1129
  clearTimeout(timer);
479
1130
  _pending.delete(id);
@@ -484,17 +1135,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
484
1135
  function isClientConnected() {
485
1136
  return _connected;
486
1137
  }
487
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1138
+ 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;
488
1139
  var init_exe_daemon_client = __esm({
489
1140
  "src/lib/exe-daemon-client.ts"() {
490
1141
  "use strict";
491
1142
  init_config();
492
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
493
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
494
- SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
1143
+ init_daemon_auth();
1144
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1145
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1146
+ SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
495
1147
  SPAWN_LOCK_STALE_MS = 3e4;
496
1148
  CONNECT_TIMEOUT_MS = 15e3;
497
1149
  REQUEST_TIMEOUT_MS = 3e4;
1150
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
498
1151
  _socket = null;
499
1152
  _connected = false;
500
1153
  _buffer = "";
@@ -573,7 +1226,7 @@ __export(db_daemon_client_exports, {
573
1226
  createDaemonDbClient: () => createDaemonDbClient,
574
1227
  initDaemonDbClient: () => initDaemonDbClient
575
1228
  });
576
- function normalizeStatement(stmt) {
1229
+ function normalizeStatement2(stmt) {
577
1230
  if (typeof stmt === "string") {
578
1231
  return { sql: stmt, args: [] };
579
1232
  }
@@ -597,7 +1250,7 @@ function createDaemonDbClient(fallbackClient) {
597
1250
  if (!_useDaemon || !isClientConnected()) {
598
1251
  return fallbackClient.execute(stmt);
599
1252
  }
600
- const { sql, args } = normalizeStatement(stmt);
1253
+ const { sql, args } = normalizeStatement2(stmt);
601
1254
  const response = await sendDaemonRequest({
602
1255
  type: "db-execute",
603
1256
  sql,
@@ -622,7 +1275,7 @@ function createDaemonDbClient(fallbackClient) {
622
1275
  if (!_useDaemon || !isClientConnected()) {
623
1276
  return fallbackClient.batch(stmts, mode);
624
1277
  }
625
- const statements = stmts.map(normalizeStatement);
1278
+ const statements = stmts.map(normalizeStatement2);
626
1279
  const response = await sendDaemonRequest({
627
1280
  type: "db-batch",
628
1281
  statements,
@@ -717,6 +1370,18 @@ __export(database_exports, {
717
1370
  });
718
1371
  import { createClient } from "@libsql/client";
719
1372
  async function initDatabase(config) {
1373
+ if (_walCheckpointTimer) {
1374
+ clearInterval(_walCheckpointTimer);
1375
+ _walCheckpointTimer = null;
1376
+ }
1377
+ if (_daemonClient) {
1378
+ _daemonClient.close();
1379
+ _daemonClient = null;
1380
+ }
1381
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1382
+ _adapterClient.close();
1383
+ }
1384
+ _adapterClient = null;
720
1385
  if (_client) {
721
1386
  _client.close();
722
1387
  _client = null;
@@ -730,6 +1395,7 @@ async function initDatabase(config) {
730
1395
  }
731
1396
  _client = createClient(opts);
732
1397
  _resilientClient = wrapWithRetry(_client);
1398
+ _adapterClient = _resilientClient;
733
1399
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
734
1400
  });
735
1401
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -740,14 +1406,20 @@ async function initDatabase(config) {
740
1406
  });
741
1407
  }, 3e4);
742
1408
  _walCheckpointTimer.unref();
1409
+ if (process.env.DATABASE_URL) {
1410
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1411
+ }
743
1412
  }
744
1413
  function isInitialized() {
745
- return _client !== null;
1414
+ return _adapterClient !== null || _client !== null;
746
1415
  }
747
1416
  function getClient() {
748
- if (!_resilientClient) {
1417
+ if (!_adapterClient) {
749
1418
  throw new Error("Database client not initialized. Call initDatabase() first.");
750
1419
  }
1420
+ if (process.env.DATABASE_URL) {
1421
+ return _adapterClient;
1422
+ }
751
1423
  if (process.env.EXE_IS_DAEMON === "1") {
752
1424
  return _resilientClient;
753
1425
  }
@@ -757,6 +1429,7 @@ function getClient() {
757
1429
  return _resilientClient;
758
1430
  }
759
1431
  async function initDaemonClient() {
1432
+ if (process.env.DATABASE_URL) return;
760
1433
  if (process.env.EXE_IS_DAEMON === "1") return;
761
1434
  if (!_resilientClient) return;
762
1435
  try {
@@ -1053,6 +1726,7 @@ async function ensureSchema() {
1053
1726
  project TEXT NOT NULL,
1054
1727
  summary TEXT NOT NULL,
1055
1728
  task_file TEXT,
1729
+ session_scope TEXT,
1056
1730
  read INTEGER NOT NULL DEFAULT 0,
1057
1731
  created_at TEXT NOT NULL
1058
1732
  );
@@ -1061,7 +1735,7 @@ async function ensureSchema() {
1061
1735
  ON notifications(read);
1062
1736
 
1063
1737
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1064
- ON notifications(agent_id);
1738
+ ON notifications(agent_id, session_scope);
1065
1739
 
1066
1740
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1067
1741
  ON notifications(task_file);
@@ -1099,6 +1773,7 @@ async function ensureSchema() {
1099
1773
  target_agent TEXT NOT NULL,
1100
1774
  target_project TEXT,
1101
1775
  target_device TEXT NOT NULL DEFAULT 'local',
1776
+ session_scope TEXT,
1102
1777
  content TEXT NOT NULL,
1103
1778
  priority TEXT DEFAULT 'normal',
1104
1779
  status TEXT DEFAULT 'pending',
@@ -1112,10 +1787,31 @@ async function ensureSchema() {
1112
1787
  );
1113
1788
 
1114
1789
  CREATE INDEX IF NOT EXISTS idx_messages_target
1115
- ON messages(target_agent, status);
1790
+ ON messages(target_agent, session_scope, status);
1116
1791
 
1117
1792
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1118
- ON messages(target_agent, from_agent, server_seq);
1793
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1794
+ `);
1795
+ try {
1796
+ await client.execute({
1797
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1798
+ args: []
1799
+ });
1800
+ } catch {
1801
+ }
1802
+ try {
1803
+ await client.execute({
1804
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1805
+ args: []
1806
+ });
1807
+ } catch {
1808
+ }
1809
+ await client.executeMultiple(`
1810
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1811
+ ON notifications(agent_id, session_scope, read, created_at);
1812
+
1813
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1814
+ ON messages(target_agent, session_scope, status, created_at);
1119
1815
  `);
1120
1816
  try {
1121
1817
  await client.execute({
@@ -1699,28 +2395,45 @@ async function ensureSchema() {
1699
2395
  } catch {
1700
2396
  }
1701
2397
  }
2398
+ try {
2399
+ await client.execute({
2400
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2401
+ args: []
2402
+ });
2403
+ } catch {
2404
+ }
1702
2405
  }
1703
2406
  async function disposeDatabase() {
2407
+ if (_walCheckpointTimer) {
2408
+ clearInterval(_walCheckpointTimer);
2409
+ _walCheckpointTimer = null;
2410
+ }
1704
2411
  if (_daemonClient) {
1705
2412
  _daemonClient.close();
1706
2413
  _daemonClient = null;
1707
2414
  }
2415
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2416
+ _adapterClient.close();
2417
+ }
2418
+ _adapterClient = null;
1708
2419
  if (_client) {
1709
2420
  _client.close();
1710
2421
  _client = null;
1711
2422
  _resilientClient = null;
1712
2423
  }
1713
2424
  }
1714
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
2425
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
1715
2426
  var init_database = __esm({
1716
2427
  "src/lib/database.ts"() {
1717
2428
  "use strict";
1718
2429
  init_db_retry();
1719
2430
  init_employees();
2431
+ init_database_adapter();
1720
2432
  _client = null;
1721
2433
  _resilientClient = null;
1722
2434
  _walCheckpointTimer = null;
1723
2435
  _daemonClient = null;
2436
+ _adapterClient = null;
1724
2437
  initTurso = initDatabase;
1725
2438
  disposeTurso = disposeDatabase;
1726
2439
  }
@@ -1745,8 +2458,8 @@ __export(crdt_sync_exports, {
1745
2458
  rebuildFromDb: () => rebuildFromDb
1746
2459
  });
1747
2460
  import * as Y from "yjs";
1748
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync2, unlinkSync as unlinkSync3 } from "fs";
1749
- import path5 from "path";
2461
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync7, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3 } from "fs";
2462
+ import path7 from "path";
1750
2463
  import { homedir } from "os";
1751
2464
  function getStatePath() {
1752
2465
  return _statePathOverride ?? DEFAULT_STATE_PATH;
@@ -1758,9 +2471,9 @@ function initCrdtDoc() {
1758
2471
  if (doc) return doc;
1759
2472
  doc = new Y.Doc();
1760
2473
  const sp = getStatePath();
1761
- if (existsSync5(sp)) {
2474
+ if (existsSync7(sp)) {
1762
2475
  try {
1763
- const state = readFileSync5(sp);
2476
+ const state = readFileSync6(sp);
1764
2477
  Y.applyUpdate(doc, new Uint8Array(state));
1765
2478
  } catch {
1766
2479
  console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
@@ -1902,10 +2615,10 @@ function persistState() {
1902
2615
  if (!doc) return;
1903
2616
  try {
1904
2617
  const sp = getStatePath();
1905
- const dir = path5.dirname(sp);
1906
- if (!existsSync5(dir)) mkdirSync2(dir, { recursive: true });
2618
+ const dir = path7.dirname(sp);
2619
+ if (!existsSync7(dir)) mkdirSync3(dir, { recursive: true });
1907
2620
  const state = Y.encodeStateAsUpdate(doc);
1908
- writeFileSync3(sp, Buffer.from(state));
2621
+ writeFileSync4(sp, Buffer.from(state));
1909
2622
  } catch {
1910
2623
  }
1911
2624
  }
@@ -1946,7 +2659,7 @@ var DEFAULT_STATE_PATH, _statePathOverride, doc;
1946
2659
  var init_crdt_sync = __esm({
1947
2660
  "src/lib/crdt-sync.ts"() {
1948
2661
  "use strict";
1949
- DEFAULT_STATE_PATH = path5.join(homedir(), ".exe-os", "crdt-state.bin");
2662
+ DEFAULT_STATE_PATH = path7.join(homedir(), ".exe-os", "crdt-state.bin");
1950
2663
  _statePathOverride = null;
1951
2664
  doc = null;
1952
2665
  }
@@ -1962,14 +2675,14 @@ __export(keychain_exports, {
1962
2675
  setMasterKey: () => setMasterKey
1963
2676
  });
1964
2677
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
1965
- import { existsSync as existsSync6 } from "fs";
1966
- import path6 from "path";
1967
- import os4 from "os";
2678
+ import { existsSync as existsSync8 } from "fs";
2679
+ import path8 from "path";
2680
+ import os6 from "os";
1968
2681
  function getKeyDir() {
1969
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os4.homedir(), ".exe-os");
2682
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path8.join(os6.homedir(), ".exe-os");
1970
2683
  }
1971
2684
  function getKeyPath() {
1972
- return path6.join(getKeyDir(), "master.key");
2685
+ return path8.join(getKeyDir(), "master.key");
1973
2686
  }
1974
2687
  async function tryKeytar() {
1975
2688
  try {
@@ -1990,9 +2703,9 @@ async function getMasterKey() {
1990
2703
  }
1991
2704
  }
1992
2705
  const keyPath = getKeyPath();
1993
- if (!existsSync6(keyPath)) {
2706
+ if (!existsSync8(keyPath)) {
1994
2707
  process.stderr.write(
1995
- `[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2708
+ `[keychain] Key not found at ${keyPath} (HOME=${os6.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
1996
2709
  `
1997
2710
  );
1998
2711
  return null;
@@ -2033,7 +2746,7 @@ async function deleteMasterKey() {
2033
2746
  }
2034
2747
  }
2035
2748
  const keyPath = getKeyPath();
2036
- if (existsSync6(keyPath)) {
2749
+ if (existsSync8(keyPath)) {
2037
2750
  await unlink(keyPath);
2038
2751
  }
2039
2752
  }
@@ -2077,13 +2790,13 @@ var init_keychain = __esm({
2077
2790
 
2078
2791
  // src/lib/cloud-sync.ts
2079
2792
  init_database();
2080
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync7, readdirSync, mkdirSync as mkdirSync3, appendFileSync, unlinkSync as unlinkSync4, openSync as openSync2, closeSync as closeSync2 } from "fs";
2081
- import crypto2 from "crypto";
2082
- import path7 from "path";
2793
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync9, readdirSync, mkdirSync as mkdirSync4, appendFileSync, unlinkSync as unlinkSync4, openSync as openSync2, closeSync as closeSync2 } from "fs";
2794
+ import crypto3 from "crypto";
2795
+ import path9 from "path";
2083
2796
  import { homedir as homedir2 } from "os";
2084
2797
 
2085
2798
  // src/lib/crypto.ts
2086
- import crypto from "crypto";
2799
+ import crypto2 from "crypto";
2087
2800
  var ALGORITHM = "aes-256-gcm";
2088
2801
  var IV_LENGTH = 12;
2089
2802
  var TAG_LENGTH = 16;
@@ -2094,7 +2807,7 @@ function initSyncCrypto(masterKey) {
2094
2807
  throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
2095
2808
  }
2096
2809
  _syncKey = Buffer.from(
2097
- crypto.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
2810
+ crypto2.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
2098
2811
  );
2099
2812
  }
2100
2813
  function isSyncCryptoInitialized() {
@@ -2108,8 +2821,8 @@ function requireSyncKey() {
2108
2821
  }
2109
2822
  function encryptSyncBlob(data) {
2110
2823
  const key = requireSyncKey();
2111
- const iv = crypto.randomBytes(IV_LENGTH);
2112
- const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
2824
+ const iv = crypto2.randomBytes(IV_LENGTH);
2825
+ const cipher = crypto2.createCipheriv(ALGORITHM, key, iv);
2113
2826
  const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
2114
2827
  const tag = cipher.getAuthTag();
2115
2828
  return Buffer.concat([iv, encrypted, tag]).toString("base64");
@@ -2123,7 +2836,7 @@ function decryptSyncBlob(ciphertext) {
2123
2836
  const iv = combined.subarray(0, IV_LENGTH);
2124
2837
  const tag = combined.subarray(combined.length - TAG_LENGTH);
2125
2838
  const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
2126
- const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
2839
+ const decipher = crypto2.createDecipheriv(ALGORITHM, key, iv);
2127
2840
  decipher.setAuthTag(tag);
2128
2841
  return Buffer.concat([decipher.update(encrypted), decipher.final()]);
2129
2842
  }
@@ -2145,32 +2858,35 @@ function decompress(input) {
2145
2858
 
2146
2859
  // src/lib/license.ts
2147
2860
  init_config();
2148
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync } from "fs";
2861
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
2149
2862
  import { randomUUID as randomUUID2 } from "crypto";
2150
- import path4 from "path";
2863
+ import { createRequire as createRequire2 } from "module";
2864
+ import { pathToFileURL as pathToFileURL2 } from "url";
2865
+ import os5 from "os";
2866
+ import path6 from "path";
2151
2867
  import { jwtVerify, importSPKI } from "jose";
2152
- var LICENSE_PATH = path4.join(EXE_AI_DIR, "license.key");
2153
- var CACHE_PATH = path4.join(EXE_AI_DIR, "license-cache.json");
2154
- var DEVICE_ID_PATH = path4.join(EXE_AI_DIR, "device-id");
2868
+ var LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
2869
+ var CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
2870
+ var DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
2155
2871
  function loadDeviceId() {
2156
- const deviceJsonPath = path4.join(EXE_AI_DIR, "device.json");
2872
+ const deviceJsonPath = path6.join(EXE_AI_DIR, "device.json");
2157
2873
  try {
2158
- if (existsSync4(deviceJsonPath)) {
2159
- const data = JSON.parse(readFileSync4(deviceJsonPath, "utf8"));
2874
+ if (existsSync6(deviceJsonPath)) {
2875
+ const data = JSON.parse(readFileSync5(deviceJsonPath, "utf8"));
2160
2876
  if (data.deviceId) return data.deviceId;
2161
2877
  }
2162
2878
  } catch {
2163
2879
  }
2164
2880
  try {
2165
- if (existsSync4(DEVICE_ID_PATH)) {
2166
- const id2 = readFileSync4(DEVICE_ID_PATH, "utf8").trim();
2881
+ if (existsSync6(DEVICE_ID_PATH)) {
2882
+ const id2 = readFileSync5(DEVICE_ID_PATH, "utf8").trim();
2167
2883
  if (id2) return id2;
2168
2884
  }
2169
2885
  } catch {
2170
2886
  }
2171
2887
  const id = randomUUID2();
2172
- mkdirSync(EXE_AI_DIR, { recursive: true });
2173
- writeFileSync2(DEVICE_ID_PATH, id, "utf8");
2888
+ mkdirSync2(EXE_AI_DIR, { recursive: true });
2889
+ writeFileSync3(DEVICE_ID_PATH, id, "utf8");
2174
2890
  return id;
2175
2891
  }
2176
2892
 
@@ -2178,12 +2894,13 @@ function loadDeviceId() {
2178
2894
  init_config();
2179
2895
  init_crdt_sync();
2180
2896
  init_employees();
2897
+ init_secure_files();
2181
2898
  function sqlSafe(v) {
2182
2899
  return v === void 0 ? null : v;
2183
2900
  }
2184
2901
  function logError(msg) {
2185
2902
  try {
2186
- const logPath = path7.join(homedir2(), ".exe-os", "workers.log");
2903
+ const logPath = path9.join(homedir2(), ".exe-os", "workers.log");
2187
2904
  appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
2188
2905
  `);
2189
2906
  } catch {
@@ -2192,24 +2909,93 @@ function logError(msg) {
2192
2909
  var LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
2193
2910
  var FETCH_TIMEOUT_MS = 3e4;
2194
2911
  var PUSH_BATCH_SIZE = 5e3;
2195
- var ROSTER_LOCK_PATH = path7.join(EXE_AI_DIR, "roster-merge.lock");
2912
+ var ROSTER_LOCK_PATH = path9.join(EXE_AI_DIR, "roster-merge.lock");
2196
2913
  var LOCK_STALE_MS = 3e4;
2914
+ var _pgPromise = null;
2915
+ var _pgFailed = false;
2916
+ function loadPgClient() {
2917
+ if (_pgFailed) return null;
2918
+ const postgresUrl = process.env.DATABASE_URL;
2919
+ const configPath = path9.join(EXE_AI_DIR, "config.json");
2920
+ let cloudPostgresUrl;
2921
+ try {
2922
+ if (existsSync9(configPath)) {
2923
+ const cfg = JSON.parse(readFileSync7(configPath, "utf8"));
2924
+ cloudPostgresUrl = cfg.cloud?.postgresUrl;
2925
+ if (cfg.cloud?.syncToPostgres === false) {
2926
+ _pgFailed = true;
2927
+ return null;
2928
+ }
2929
+ }
2930
+ } catch {
2931
+ }
2932
+ const url = postgresUrl || cloudPostgresUrl;
2933
+ if (!url) {
2934
+ _pgFailed = true;
2935
+ return null;
2936
+ }
2937
+ if (!_pgPromise) {
2938
+ _pgPromise = (async () => {
2939
+ const { createRequire: createRequire3 } = await import("module");
2940
+ const { pathToFileURL: pathToFileURL3 } = await import("url");
2941
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(homedir2(), "exe-db");
2942
+ const req = createRequire3(path9.join(exeDbRoot, "package.json"));
2943
+ const entry = req.resolve("@prisma/client");
2944
+ const mod = await import(pathToFileURL3(entry).href);
2945
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
2946
+ if (!Ctor) throw new Error("No PrismaClient");
2947
+ return new Ctor();
2948
+ })().catch(() => {
2949
+ _pgFailed = true;
2950
+ _pgPromise = null;
2951
+ throw new Error("pg_unavailable");
2952
+ });
2953
+ }
2954
+ return _pgPromise;
2955
+ }
2956
+ async function pushToPostgres(records) {
2957
+ const loader = loadPgClient();
2958
+ if (!loader) return 0;
2959
+ let prisma;
2960
+ try {
2961
+ prisma = await loader;
2962
+ } catch {
2963
+ return 0;
2964
+ }
2965
+ let inserted = 0;
2966
+ for (const rec of records) {
2967
+ try {
2968
+ await prisma.$executeRawUnsafe(
2969
+ `INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
2970
+ VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
2971
+ ON CONFLICT (source, source_id, event_type) DO NOTHING`,
2972
+ String(rec.id ?? ""),
2973
+ JSON.stringify(rec),
2974
+ JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
2975
+ rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
2976
+ );
2977
+ inserted++;
2978
+ } catch {
2979
+ }
2980
+ }
2981
+ return inserted;
2982
+ }
2197
2983
  async function withRosterLock(fn) {
2198
2984
  try {
2199
2985
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
2200
2986
  closeSync2(fd);
2201
- writeFileSync4(ROSTER_LOCK_PATH, String(Date.now()));
2987
+ writeFileSync5(ROSTER_LOCK_PATH, String(Date.now()));
2202
2988
  } catch (err) {
2203
2989
  if (err.code === "EEXIST") {
2204
2990
  try {
2205
- const ts = parseInt(readFileSync6(ROSTER_LOCK_PATH, "utf-8"), 10);
2991
+ const ts = parseInt(readFileSync7(ROSTER_LOCK_PATH, "utf-8"), 10);
2206
2992
  if (Date.now() - ts < LOCK_STALE_MS) {
2207
2993
  throw new Error("Roster merge already in progress \u2014 another sync is running");
2208
2994
  }
2209
2995
  unlinkSync4(ROSTER_LOCK_PATH);
2210
2996
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
2211
2997
  closeSync2(fd);
2212
- writeFileSync4(ROSTER_LOCK_PATH, String(Date.now()));
2998
+ writeFileSync5(ROSTER_LOCK_PATH, String(Date.now()));
2213
2999
  } catch (retryErr) {
2214
3000
  if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
2215
3001
  throw new Error("Roster merge already in progress \u2014 another sync is running");
@@ -2479,6 +3265,10 @@ async function cloudSync(config) {
2479
3265
  const maxVersion = Number(records[records.length - 1].version);
2480
3266
  const pushOk = await cloudPush(records, maxVersion, config);
2481
3267
  if (!pushOk) break;
3268
+ try {
3269
+ await pushToPostgres(records);
3270
+ } catch {
3271
+ }
2482
3272
  await client.execute({
2483
3273
  sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
2484
3274
  args: [String(maxVersion)]
@@ -2583,8 +3373,8 @@ async function cloudSync(config) {
2583
3373
  try {
2584
3374
  const employees = await loadEmployees();
2585
3375
  rosterResult.employees = employees.length;
2586
- const idDir = path7.join(EXE_AI_DIR, "identity");
2587
- if (existsSync7(idDir)) {
3376
+ const idDir = path9.join(EXE_AI_DIR, "identity");
3377
+ if (existsSync9(idDir)) {
2588
3378
  rosterResult.identities = readdirSync(idDir).filter((f) => f.endsWith(".md")).length;
2589
3379
  }
2590
3380
  } catch {
@@ -2602,66 +3392,66 @@ async function cloudSync(config) {
2602
3392
  roster: rosterResult
2603
3393
  };
2604
3394
  }
2605
- var ROSTER_DELETIONS_PATH = path7.join(EXE_AI_DIR, "roster-deletions.json");
3395
+ var ROSTER_DELETIONS_PATH = path9.join(EXE_AI_DIR, "roster-deletions.json");
2606
3396
  function recordRosterDeletion(name) {
2607
3397
  let deletions = [];
2608
3398
  try {
2609
- if (existsSync7(ROSTER_DELETIONS_PATH)) {
2610
- deletions = JSON.parse(readFileSync6(ROSTER_DELETIONS_PATH, "utf-8"));
3399
+ if (existsSync9(ROSTER_DELETIONS_PATH)) {
3400
+ deletions = JSON.parse(readFileSync7(ROSTER_DELETIONS_PATH, "utf-8"));
2611
3401
  }
2612
3402
  } catch {
2613
3403
  }
2614
3404
  if (!deletions.includes(name)) deletions.push(name);
2615
- writeFileSync4(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
3405
+ writeFileSync5(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
2616
3406
  }
2617
3407
  function consumeRosterDeletions() {
2618
3408
  try {
2619
- if (!existsSync7(ROSTER_DELETIONS_PATH)) return [];
2620
- const deletions = JSON.parse(readFileSync6(ROSTER_DELETIONS_PATH, "utf-8"));
2621
- writeFileSync4(ROSTER_DELETIONS_PATH, "[]");
3409
+ if (!existsSync9(ROSTER_DELETIONS_PATH)) return [];
3410
+ const deletions = JSON.parse(readFileSync7(ROSTER_DELETIONS_PATH, "utf-8"));
3411
+ writeFileSync5(ROSTER_DELETIONS_PATH, "[]");
2622
3412
  return deletions;
2623
3413
  } catch {
2624
3414
  return [];
2625
3415
  }
2626
3416
  }
2627
3417
  function buildRosterBlob(paths) {
2628
- const rosterPath = paths?.rosterPath ?? path7.join(EXE_AI_DIR, "exe-employees.json");
2629
- const identityDir = paths?.identityDir ?? path7.join(EXE_AI_DIR, "identity");
2630
- const configPath = paths?.configPath ?? path7.join(EXE_AI_DIR, "config.json");
3418
+ const rosterPath = paths?.rosterPath ?? path9.join(EXE_AI_DIR, "exe-employees.json");
3419
+ const identityDir = paths?.identityDir ?? path9.join(EXE_AI_DIR, "identity");
3420
+ const configPath = paths?.configPath ?? path9.join(EXE_AI_DIR, "config.json");
2631
3421
  let roster = [];
2632
- if (existsSync7(rosterPath)) {
3422
+ if (existsSync9(rosterPath)) {
2633
3423
  try {
2634
- roster = JSON.parse(readFileSync6(rosterPath, "utf-8"));
3424
+ roster = JSON.parse(readFileSync7(rosterPath, "utf-8"));
2635
3425
  } catch {
2636
3426
  }
2637
3427
  }
2638
3428
  const identities = {};
2639
- if (existsSync7(identityDir)) {
3429
+ if (existsSync9(identityDir)) {
2640
3430
  for (const file of readdirSync(identityDir).filter((f) => f.endsWith(".md"))) {
2641
3431
  try {
2642
- identities[file] = readFileSync6(path7.join(identityDir, file), "utf-8");
3432
+ identities[file] = readFileSync7(path9.join(identityDir, file), "utf-8");
2643
3433
  } catch {
2644
3434
  }
2645
3435
  }
2646
3436
  }
2647
3437
  let config;
2648
- if (existsSync7(configPath)) {
3438
+ if (existsSync9(configPath)) {
2649
3439
  try {
2650
- config = JSON.parse(readFileSync6(configPath, "utf-8"));
3440
+ config = JSON.parse(readFileSync7(configPath, "utf-8"));
2651
3441
  } catch {
2652
3442
  }
2653
3443
  }
2654
3444
  let agentConfig;
2655
- const agentConfigPath = path7.join(EXE_AI_DIR, "agent-config.json");
2656
- if (existsSync7(agentConfigPath)) {
3445
+ const agentConfigPath = path9.join(EXE_AI_DIR, "agent-config.json");
3446
+ if (existsSync9(agentConfigPath)) {
2657
3447
  try {
2658
- agentConfig = JSON.parse(readFileSync6(agentConfigPath, "utf-8"));
3448
+ agentConfig = JSON.parse(readFileSync7(agentConfigPath, "utf-8"));
2659
3449
  } catch {
2660
3450
  }
2661
3451
  }
2662
3452
  const deletedNames = consumeRosterDeletions();
2663
3453
  const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
2664
- const hash = crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
3454
+ const hash = crypto3.createHash("sha256").update(content).digest("hex").slice(0, 16);
2665
3455
  return { roster, identities, config, agentConfig, deletedNames, version: hash };
2666
3456
  }
2667
3457
  async function cloudPushRoster(config) {
@@ -2731,23 +3521,24 @@ async function cloudPullRoster(config) {
2731
3521
  }
2732
3522
  }
2733
3523
  function mergeConfig(remoteConfig, configPath) {
2734
- const cfgPath = configPath ?? path7.join(EXE_AI_DIR, "config.json");
3524
+ const cfgPath = configPath ?? path9.join(EXE_AI_DIR, "config.json");
2735
3525
  let local = {};
2736
- if (existsSync7(cfgPath)) {
3526
+ if (existsSync9(cfgPath)) {
2737
3527
  try {
2738
- local = JSON.parse(readFileSync6(cfgPath, "utf-8"));
3528
+ local = JSON.parse(readFileSync7(cfgPath, "utf-8"));
2739
3529
  } catch {
2740
3530
  }
2741
3531
  }
2742
3532
  const merged = { ...remoteConfig, ...local };
2743
- const dir = path7.dirname(cfgPath);
2744
- if (!existsSync7(dir)) mkdirSync3(dir, { recursive: true });
2745
- writeFileSync4(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
3533
+ const dir = path9.dirname(cfgPath);
3534
+ ensurePrivateDirSync(dir);
3535
+ writeFileSync5(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
3536
+ enforcePrivateFileSync(cfgPath);
2746
3537
  }
2747
3538
  async function mergeRosterFromRemote(remote, paths) {
2748
3539
  return withRosterLock(async () => {
2749
3540
  const rosterPath = paths?.rosterPath ?? void 0;
2750
- const identityDir = paths?.identityDir ?? path7.join(EXE_AI_DIR, "identity");
3541
+ const identityDir = paths?.identityDir ?? path9.join(EXE_AI_DIR, "identity");
2751
3542
  const localEmployees = await loadEmployees(rosterPath);
2752
3543
  const localNames = new Set(localEmployees.map((e) => e.name));
2753
3544
  let added = 0;
@@ -2768,15 +3559,15 @@ async function mergeRosterFromRemote(remote, paths) {
2768
3559
  ) ?? lookupKey;
2769
3560
  const remoteIdentity = remote.identities[matchedKey];
2770
3561
  if (remoteIdentity) {
2771
- if (!existsSync7(identityDir)) mkdirSync3(identityDir, { recursive: true });
2772
- const idPath = path7.join(identityDir, `${remoteEmp.name}.md`);
3562
+ if (!existsSync9(identityDir)) mkdirSync4(identityDir, { recursive: true });
3563
+ const idPath = path9.join(identityDir, `${remoteEmp.name}.md`);
2773
3564
  let localIdentity = null;
2774
3565
  try {
2775
- localIdentity = existsSync7(idPath) ? readFileSync6(idPath, "utf-8") : null;
3566
+ localIdentity = existsSync9(idPath) ? readFileSync7(idPath, "utf-8") : null;
2776
3567
  } catch {
2777
3568
  }
2778
3569
  if (localIdentity !== remoteIdentity) {
2779
- writeFileSync4(idPath, remoteIdentity, "utf-8");
3570
+ writeFileSync5(idPath, remoteIdentity, "utf-8");
2780
3571
  identitiesUpdated++;
2781
3572
  }
2782
3573
  }
@@ -2802,16 +3593,18 @@ async function mergeRosterFromRemote(remote, paths) {
2802
3593
  }
2803
3594
  if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
2804
3595
  try {
2805
- const agentConfigPath = path7.join(EXE_AI_DIR, "agent-config.json");
3596
+ const agentConfigPath = path9.join(EXE_AI_DIR, "agent-config.json");
2806
3597
  let local = {};
2807
- if (existsSync7(agentConfigPath)) {
3598
+ if (existsSync9(agentConfigPath)) {
2808
3599
  try {
2809
- local = JSON.parse(readFileSync6(agentConfigPath, "utf-8"));
3600
+ local = JSON.parse(readFileSync7(agentConfigPath, "utf-8"));
2810
3601
  } catch {
2811
3602
  }
2812
3603
  }
2813
3604
  const merged = { ...remote.agentConfig, ...local };
2814
- writeFileSync4(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
3605
+ ensurePrivateDirSync(path9.dirname(agentConfigPath));
3606
+ writeFileSync5(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
3607
+ enforcePrivateFileSync(agentConfigPath);
2815
3608
  } catch {
2816
3609
  }
2817
3610
  }
@@ -3259,5 +4052,6 @@ export {
3259
4052
  cloudSync,
3260
4053
  mergeConfig,
3261
4054
  mergeRosterFromRemote,
4055
+ pushToPostgres,
3262
4056
  recordRosterDeletion
3263
4057
  };