@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
@@ -26,6 +26,44 @@ var __copyProps = (to, from, except, desc) => {
26
26
  };
27
27
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
28
 
29
+ // src/lib/secure-files.ts
30
+ import { chmodSync, existsSync, mkdirSync } from "fs";
31
+ import { chmod, mkdir } from "fs/promises";
32
+ async function ensurePrivateDir(dirPath) {
33
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
34
+ try {
35
+ await chmod(dirPath, PRIVATE_DIR_MODE);
36
+ } catch {
37
+ }
38
+ }
39
+ function ensurePrivateDirSync(dirPath) {
40
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
41
+ try {
42
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
43
+ } catch {
44
+ }
45
+ }
46
+ async function enforcePrivateFile(filePath) {
47
+ try {
48
+ await chmod(filePath, PRIVATE_FILE_MODE);
49
+ } catch {
50
+ }
51
+ }
52
+ function enforcePrivateFileSync(filePath) {
53
+ try {
54
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
55
+ } catch {
56
+ }
57
+ }
58
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
59
+ var init_secure_files = __esm({
60
+ "src/lib/secure-files.ts"() {
61
+ "use strict";
62
+ PRIVATE_DIR_MODE = 448;
63
+ PRIVATE_FILE_MODE = 384;
64
+ }
65
+ });
66
+
29
67
  // src/lib/config.ts
30
68
  var config_exports = {};
31
69
  __export(config_exports, {
@@ -42,8 +80,8 @@ __export(config_exports, {
42
80
  migrateConfig: () => migrateConfig,
43
81
  saveConfig: () => saveConfig
44
82
  });
45
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
46
- import { readFileSync, existsSync, renameSync } from "fs";
83
+ import { readFile, writeFile } from "fs/promises";
84
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
47
85
  import path from "path";
48
86
  import os from "os";
49
87
  function resolveDataDir() {
@@ -51,7 +89,7 @@ function resolveDataDir() {
51
89
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
52
90
  const newDir = path.join(os.homedir(), ".exe-os");
53
91
  const legacyDir = path.join(os.homedir(), ".exe-mem");
54
- if (!existsSync(newDir) && existsSync(legacyDir)) {
92
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
55
93
  try {
56
94
  renameSync(legacyDir, newDir);
57
95
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -114,9 +152,9 @@ function normalizeAutoUpdate(raw) {
114
152
  }
115
153
  async function loadConfig() {
116
154
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
117
- await mkdir(dir, { recursive: true });
155
+ await ensurePrivateDir(dir);
118
156
  const configPath = path.join(dir, "config.json");
119
- if (!existsSync(configPath)) {
157
+ if (!existsSync2(configPath)) {
120
158
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
121
159
  }
122
160
  const raw = await readFile(configPath, "utf-8");
@@ -129,6 +167,7 @@ async function loadConfig() {
129
167
  `);
130
168
  try {
131
169
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
170
+ await enforcePrivateFile(configPath);
132
171
  } catch {
133
172
  }
134
173
  }
@@ -147,7 +186,7 @@ async function loadConfig() {
147
186
  function loadConfigSync() {
148
187
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
149
188
  const configPath = path.join(dir, "config.json");
150
- if (!existsSync(configPath)) {
189
+ if (!existsSync2(configPath)) {
151
190
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
152
191
  }
153
192
  try {
@@ -165,12 +204,10 @@ function loadConfigSync() {
165
204
  }
166
205
  async function saveConfig(config) {
167
206
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
168
- await mkdir(dir, { recursive: true });
207
+ await ensurePrivateDir(dir);
169
208
  const configPath = path.join(dir, "config.json");
170
209
  await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
171
- if (config.cloud?.apiKey) {
172
- await chmod(configPath, 384);
173
- }
210
+ await enforcePrivateFile(configPath);
174
211
  }
175
212
  async function loadConfigFrom(configPath) {
176
213
  const raw = await readFile(configPath, "utf-8");
@@ -190,6 +227,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
190
227
  var init_config = __esm({
191
228
  "src/lib/config.ts"() {
192
229
  "use strict";
230
+ init_secure_files();
193
231
  EXE_AI_DIR = resolveDataDir();
194
232
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
195
233
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -268,7 +306,7 @@ var init_config = __esm({
268
306
 
269
307
  // src/lib/employees.ts
270
308
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
271
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
309
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
272
310
  import { execSync } from "child_process";
273
311
  import path2 from "path";
274
312
  import os2 from "os";
@@ -289,7 +327,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
289
327
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
290
328
  }
291
329
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
292
- if (!existsSync2(employeesPath)) {
330
+ if (!existsSync3(employeesPath)) {
293
331
  return [];
294
332
  }
295
333
  const raw = await readFile2(employeesPath, "utf-8");
@@ -304,7 +342,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
304
342
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
305
343
  }
306
344
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
307
- if (!existsSync2(employeesPath)) return [];
345
+ if (!existsSync3(employeesPath)) return [];
308
346
  try {
309
347
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
310
348
  } catch {
@@ -355,7 +393,7 @@ function registerBinSymlinks(name) {
355
393
  for (const suffix of ["", "-opencode"]) {
356
394
  const linkName = `${name}${suffix}`;
357
395
  const linkPath = path2.join(binDir, linkName);
358
- if (existsSync2(linkPath)) {
396
+ if (existsSync3(linkPath)) {
359
397
  skipped.push(linkName);
360
398
  continue;
361
399
  }
@@ -368,7 +406,7 @@ function registerBinSymlinks(name) {
368
406
  }
369
407
  return { created, skipped, errors };
370
408
  }
371
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
409
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
372
410
  var init_employees = __esm({
373
411
  "src/lib/employees.ts"() {
374
412
  "use strict";
@@ -377,6 +415,7 @@ var init_employees = __esm({
377
415
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
378
416
  COORDINATOR_ROLE = "COO";
379
417
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
418
+ IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
380
419
  }
381
420
  });
382
421
 
@@ -435,13 +474,634 @@ var init_db_retry = __esm({
435
474
  }
436
475
  });
437
476
 
477
+ // src/lib/database-adapter.ts
478
+ import os3 from "os";
479
+ import path3 from "path";
480
+ import { createRequire } from "module";
481
+ import { pathToFileURL } from "url";
482
+ function quotedIdentifier(identifier) {
483
+ return `"${identifier.replace(/"/g, '""')}"`;
484
+ }
485
+ function unqualifiedTableName(name) {
486
+ const raw = name.trim().replace(/^"|"$/g, "");
487
+ const parts = raw.split(".");
488
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
489
+ }
490
+ function stripTrailingSemicolon(sql) {
491
+ return sql.trim().replace(/;+\s*$/u, "");
492
+ }
493
+ function appendClause(sql, clause) {
494
+ const trimmed = stripTrailingSemicolon(sql);
495
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
496
+ if (!returningMatch) {
497
+ return `${trimmed}${clause}`;
498
+ }
499
+ const idx = returningMatch.index;
500
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
501
+ }
502
+ function normalizeStatement(stmt) {
503
+ if (typeof stmt === "string") {
504
+ return { kind: "positional", sql: stmt, args: [] };
505
+ }
506
+ const sql = stmt.sql;
507
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
508
+ return { kind: "positional", sql, args: stmt.args ?? [] };
509
+ }
510
+ return { kind: "named", sql, args: stmt.args };
511
+ }
512
+ function rewriteBooleanLiterals(sql) {
513
+ let out = sql;
514
+ for (const column of BOOLEAN_COLUMN_NAMES) {
515
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
516
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
517
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
518
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
519
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
520
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
521
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
522
+ }
523
+ return out;
524
+ }
525
+ function rewriteInsertOrIgnore(sql) {
526
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
527
+ return sql;
528
+ }
529
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
530
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
531
+ }
532
+ function rewriteInsertOrReplace(sql) {
533
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
534
+ if (!match) {
535
+ return sql;
536
+ }
537
+ const rawTable = match[1];
538
+ const rawColumns = match[2];
539
+ const remainder = match[3];
540
+ const tableName = unqualifiedTableName(rawTable);
541
+ const conflictKeys = UPSERT_KEYS[tableName];
542
+ if (!conflictKeys?.length) {
543
+ return sql;
544
+ }
545
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
546
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
547
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
548
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
549
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
550
+ }
551
+ function rewriteSql(sql) {
552
+ let out = sql;
553
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
554
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
555
+ out = rewriteBooleanLiterals(out);
556
+ out = rewriteInsertOrReplace(out);
557
+ out = rewriteInsertOrIgnore(out);
558
+ return stripTrailingSemicolon(out);
559
+ }
560
+ function toBoolean(value) {
561
+ if (value === null || value === void 0) return value;
562
+ if (typeof value === "boolean") return value;
563
+ if (typeof value === "number") return value !== 0;
564
+ if (typeof value === "bigint") return value !== 0n;
565
+ if (typeof value === "string") {
566
+ const normalized = value.trim().toLowerCase();
567
+ if (normalized === "0" || normalized === "false") return false;
568
+ if (normalized === "1" || normalized === "true") return true;
569
+ }
570
+ return Boolean(value);
571
+ }
572
+ function countQuestionMarks(sql, end) {
573
+ let count = 0;
574
+ let inSingle = false;
575
+ let inDouble = false;
576
+ let inLineComment = false;
577
+ let inBlockComment = false;
578
+ for (let i = 0; i < end; i++) {
579
+ const ch = sql[i];
580
+ const next = sql[i + 1];
581
+ if (inLineComment) {
582
+ if (ch === "\n") inLineComment = false;
583
+ continue;
584
+ }
585
+ if (inBlockComment) {
586
+ if (ch === "*" && next === "/") {
587
+ inBlockComment = false;
588
+ i += 1;
589
+ }
590
+ continue;
591
+ }
592
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
593
+ inLineComment = true;
594
+ i += 1;
595
+ continue;
596
+ }
597
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
598
+ inBlockComment = true;
599
+ i += 1;
600
+ continue;
601
+ }
602
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
603
+ inSingle = !inSingle;
604
+ continue;
605
+ }
606
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
607
+ inDouble = !inDouble;
608
+ continue;
609
+ }
610
+ if (!inSingle && !inDouble && ch === "?") {
611
+ count += 1;
612
+ }
613
+ }
614
+ return count;
615
+ }
616
+ function findBooleanPlaceholderIndexes(sql) {
617
+ const indexes = /* @__PURE__ */ new Set();
618
+ for (const column of BOOLEAN_COLUMN_NAMES) {
619
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
620
+ for (const match of sql.matchAll(pattern)) {
621
+ const matchText = match[0];
622
+ const qIndex = match.index + matchText.lastIndexOf("?");
623
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
624
+ }
625
+ }
626
+ return indexes;
627
+ }
628
+ function coerceInsertBooleanArgs(sql, args) {
629
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
630
+ if (!match) return;
631
+ const rawTable = match[1];
632
+ const rawColumns = match[2];
633
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
634
+ if (!boolColumns?.size) return;
635
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
636
+ for (const [index, column] of columns.entries()) {
637
+ if (boolColumns.has(column) && index < args.length) {
638
+ args[index] = toBoolean(args[index]);
639
+ }
640
+ }
641
+ }
642
+ function coerceUpdateBooleanArgs(sql, args) {
643
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
644
+ if (!match) return;
645
+ const rawTable = match[1];
646
+ const setClause = match[2];
647
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
648
+ if (!boolColumns?.size) return;
649
+ const assignments = setClause.split(",");
650
+ let placeholderIndex = 0;
651
+ for (const assignment of assignments) {
652
+ if (!assignment.includes("?")) continue;
653
+ placeholderIndex += 1;
654
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
655
+ if (colMatch && boolColumns.has(colMatch[1])) {
656
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
657
+ }
658
+ }
659
+ }
660
+ function coerceBooleanArgs(sql, args) {
661
+ const nextArgs = [...args];
662
+ coerceInsertBooleanArgs(sql, nextArgs);
663
+ coerceUpdateBooleanArgs(sql, nextArgs);
664
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
665
+ for (const index of placeholderIndexes) {
666
+ if (index > 0 && index <= nextArgs.length) {
667
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
668
+ }
669
+ }
670
+ return nextArgs;
671
+ }
672
+ function convertQuestionMarksToDollarParams(sql) {
673
+ let out = "";
674
+ let placeholder = 0;
675
+ let inSingle = false;
676
+ let inDouble = false;
677
+ let inLineComment = false;
678
+ let inBlockComment = false;
679
+ for (let i = 0; i < sql.length; i++) {
680
+ const ch = sql[i];
681
+ const next = sql[i + 1];
682
+ if (inLineComment) {
683
+ out += ch;
684
+ if (ch === "\n") inLineComment = false;
685
+ continue;
686
+ }
687
+ if (inBlockComment) {
688
+ out += ch;
689
+ if (ch === "*" && next === "/") {
690
+ out += next;
691
+ inBlockComment = false;
692
+ i += 1;
693
+ }
694
+ continue;
695
+ }
696
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
697
+ out += ch + next;
698
+ inLineComment = true;
699
+ i += 1;
700
+ continue;
701
+ }
702
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
703
+ out += ch + next;
704
+ inBlockComment = true;
705
+ i += 1;
706
+ continue;
707
+ }
708
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
709
+ inSingle = !inSingle;
710
+ out += ch;
711
+ continue;
712
+ }
713
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
714
+ inDouble = !inDouble;
715
+ out += ch;
716
+ continue;
717
+ }
718
+ if (!inSingle && !inDouble && ch === "?") {
719
+ placeholder += 1;
720
+ out += `$${placeholder}`;
721
+ continue;
722
+ }
723
+ out += ch;
724
+ }
725
+ return out;
726
+ }
727
+ function translateStatementForPostgres(stmt) {
728
+ const normalized = normalizeStatement(stmt);
729
+ if (normalized.kind === "named") {
730
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
731
+ }
732
+ const rewrittenSql = rewriteSql(normalized.sql);
733
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
734
+ return {
735
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
736
+ args: coercedArgs
737
+ };
738
+ }
739
+ function shouldBypassPostgres(stmt) {
740
+ const normalized = normalizeStatement(stmt);
741
+ if (normalized.kind === "named") {
742
+ return true;
743
+ }
744
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
745
+ }
746
+ function shouldFallbackOnError(error) {
747
+ const message = error instanceof Error ? error.message : String(error);
748
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
749
+ }
750
+ function isReadQuery(sql) {
751
+ const trimmed = sql.trimStart();
752
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
753
+ }
754
+ function buildRow(row, columns) {
755
+ const values = columns.map((column) => row[column]);
756
+ return Object.assign(values, row);
757
+ }
758
+ function buildResultSet(rows, rowsAffected = 0) {
759
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
760
+ const resultRows = rows.map((row) => buildRow(row, columns));
761
+ return {
762
+ columns,
763
+ columnTypes: columns.map(() => ""),
764
+ rows: resultRows,
765
+ rowsAffected,
766
+ lastInsertRowid: void 0,
767
+ toJSON() {
768
+ return {
769
+ columns,
770
+ columnTypes: columns.map(() => ""),
771
+ rows,
772
+ rowsAffected,
773
+ lastInsertRowid: void 0
774
+ };
775
+ }
776
+ };
777
+ }
778
+ async function loadPrismaClient() {
779
+ if (!prismaClientPromise) {
780
+ prismaClientPromise = (async () => {
781
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
782
+ if (explicitPath) {
783
+ const module2 = await import(pathToFileURL(explicitPath).href);
784
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
785
+ if (!PrismaClient2) {
786
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
787
+ }
788
+ return new PrismaClient2();
789
+ }
790
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
791
+ const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
792
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
793
+ const module = await import(pathToFileURL(prismaEntry).href);
794
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
795
+ if (!PrismaClient) {
796
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
797
+ }
798
+ return new PrismaClient();
799
+ })();
800
+ }
801
+ return prismaClientPromise;
802
+ }
803
+ async function ensureCompatibilityViews(prisma) {
804
+ if (!compatibilityBootstrapPromise) {
805
+ compatibilityBootstrapPromise = (async () => {
806
+ for (const mapping of VIEW_MAPPINGS) {
807
+ const relation = mapping.source.replace(/"/g, "");
808
+ const rows = await prisma.$queryRawUnsafe(
809
+ "SELECT to_regclass($1) AS regclass",
810
+ relation
811
+ );
812
+ if (!rows[0]?.regclass) {
813
+ continue;
814
+ }
815
+ await prisma.$executeRawUnsafe(
816
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
817
+ );
818
+ }
819
+ })();
820
+ }
821
+ return compatibilityBootstrapPromise;
822
+ }
823
+ async function executeOnPrisma(executor, stmt) {
824
+ const translated = translateStatementForPostgres(stmt);
825
+ if (isReadQuery(translated.sql)) {
826
+ const rows = await executor.$queryRawUnsafe(
827
+ translated.sql,
828
+ ...translated.args
829
+ );
830
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
831
+ }
832
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
833
+ return buildResultSet([], rowsAffected);
834
+ }
835
+ function splitSqlStatements(sql) {
836
+ const parts = [];
837
+ let current = "";
838
+ let inSingle = false;
839
+ let inDouble = false;
840
+ let inLineComment = false;
841
+ let inBlockComment = false;
842
+ for (let i = 0; i < sql.length; i++) {
843
+ const ch = sql[i];
844
+ const next = sql[i + 1];
845
+ if (inLineComment) {
846
+ current += ch;
847
+ if (ch === "\n") inLineComment = false;
848
+ continue;
849
+ }
850
+ if (inBlockComment) {
851
+ current += ch;
852
+ if (ch === "*" && next === "/") {
853
+ current += next;
854
+ inBlockComment = false;
855
+ i += 1;
856
+ }
857
+ continue;
858
+ }
859
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
860
+ current += ch + next;
861
+ inLineComment = true;
862
+ i += 1;
863
+ continue;
864
+ }
865
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
866
+ current += ch + next;
867
+ inBlockComment = true;
868
+ i += 1;
869
+ continue;
870
+ }
871
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
872
+ inSingle = !inSingle;
873
+ current += ch;
874
+ continue;
875
+ }
876
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
877
+ inDouble = !inDouble;
878
+ current += ch;
879
+ continue;
880
+ }
881
+ if (!inSingle && !inDouble && ch === ";") {
882
+ if (current.trim()) {
883
+ parts.push(current.trim());
884
+ }
885
+ current = "";
886
+ continue;
887
+ }
888
+ current += ch;
889
+ }
890
+ if (current.trim()) {
891
+ parts.push(current.trim());
892
+ }
893
+ return parts;
894
+ }
895
+ async function createPrismaDbAdapter(fallbackClient) {
896
+ const prisma = await loadPrismaClient();
897
+ await ensureCompatibilityViews(prisma);
898
+ let closed = false;
899
+ let adapter;
900
+ const fallbackExecute = async (stmt, error) => {
901
+ if (!fallbackClient) {
902
+ if (error) throw error;
903
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
904
+ }
905
+ if (error) {
906
+ process.stderr.write(
907
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
908
+ `
909
+ );
910
+ }
911
+ return fallbackClient.execute(stmt);
912
+ };
913
+ adapter = {
914
+ async execute(stmt) {
915
+ if (shouldBypassPostgres(stmt)) {
916
+ return fallbackExecute(stmt);
917
+ }
918
+ try {
919
+ return await executeOnPrisma(prisma, stmt);
920
+ } catch (error) {
921
+ if (shouldFallbackOnError(error)) {
922
+ return fallbackExecute(stmt, error);
923
+ }
924
+ throw error;
925
+ }
926
+ },
927
+ async batch(stmts, mode) {
928
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
929
+ if (!fallbackClient) {
930
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
931
+ }
932
+ return fallbackClient.batch(stmts, mode);
933
+ }
934
+ try {
935
+ if (prisma.$transaction) {
936
+ return await prisma.$transaction(async (tx) => {
937
+ const results2 = [];
938
+ for (const stmt of stmts) {
939
+ results2.push(await executeOnPrisma(tx, stmt));
940
+ }
941
+ return results2;
942
+ });
943
+ }
944
+ const results = [];
945
+ for (const stmt of stmts) {
946
+ results.push(await executeOnPrisma(prisma, stmt));
947
+ }
948
+ return results;
949
+ } catch (error) {
950
+ if (fallbackClient && shouldFallbackOnError(error)) {
951
+ process.stderr.write(
952
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
953
+ `
954
+ );
955
+ return fallbackClient.batch(stmts, mode);
956
+ }
957
+ throw error;
958
+ }
959
+ },
960
+ async migrate(stmts) {
961
+ if (fallbackClient) {
962
+ return fallbackClient.migrate(stmts);
963
+ }
964
+ return adapter.batch(stmts, "deferred");
965
+ },
966
+ async transaction(mode) {
967
+ if (!fallbackClient) {
968
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
969
+ }
970
+ return fallbackClient.transaction(mode);
971
+ },
972
+ async executeMultiple(sql) {
973
+ if (fallbackClient && shouldBypassPostgres(sql)) {
974
+ return fallbackClient.executeMultiple(sql);
975
+ }
976
+ for (const statement of splitSqlStatements(sql)) {
977
+ await adapter.execute(statement);
978
+ }
979
+ },
980
+ async sync() {
981
+ if (fallbackClient) {
982
+ return fallbackClient.sync();
983
+ }
984
+ return { frame_no: 0, frames_synced: 0 };
985
+ },
986
+ close() {
987
+ closed = true;
988
+ prismaClientPromise = null;
989
+ compatibilityBootstrapPromise = null;
990
+ void prisma.$disconnect?.();
991
+ },
992
+ get closed() {
993
+ return closed;
994
+ },
995
+ get protocol() {
996
+ return "prisma-postgres";
997
+ }
998
+ };
999
+ return adapter;
1000
+ }
1001
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
1002
+ var init_database_adapter = __esm({
1003
+ "src/lib/database-adapter.ts"() {
1004
+ "use strict";
1005
+ VIEW_MAPPINGS = [
1006
+ { view: "memories", source: "memory.memory_records" },
1007
+ { view: "tasks", source: "memory.tasks" },
1008
+ { view: "behaviors", source: "memory.behaviors" },
1009
+ { view: "entities", source: "memory.entities" },
1010
+ { view: "relationships", source: "memory.relationships" },
1011
+ { view: "entity_memories", source: "memory.entity_memories" },
1012
+ { view: "entity_aliases", source: "memory.entity_aliases" },
1013
+ { view: "notifications", source: "memory.notifications" },
1014
+ { view: "messages", source: "memory.messages" },
1015
+ { view: "users", source: "wiki.users" },
1016
+ { view: "workspaces", source: "wiki.workspaces" },
1017
+ { view: "workspace_users", source: "wiki.workspace_users" },
1018
+ { view: "documents", source: "wiki.workspace_documents" },
1019
+ { view: "chats", source: "wiki.workspace_chats" }
1020
+ ];
1021
+ UPSERT_KEYS = {
1022
+ memories: ["id"],
1023
+ tasks: ["id"],
1024
+ behaviors: ["id"],
1025
+ entities: ["id"],
1026
+ relationships: ["id"],
1027
+ entity_aliases: ["alias"],
1028
+ notifications: ["id"],
1029
+ messages: ["id"],
1030
+ users: ["id"],
1031
+ workspaces: ["id"],
1032
+ workspace_users: ["id"],
1033
+ documents: ["id"],
1034
+ chats: ["id"]
1035
+ };
1036
+ BOOLEAN_COLUMNS_BY_TABLE = {
1037
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
1038
+ behaviors: /* @__PURE__ */ new Set(["active"]),
1039
+ notifications: /* @__PURE__ */ new Set(["read"]),
1040
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
1041
+ };
1042
+ BOOLEAN_COLUMN_NAMES = new Set(
1043
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
1044
+ );
1045
+ IMMEDIATE_FALLBACK_PATTERNS = [
1046
+ /\bPRAGMA\b/i,
1047
+ /\bsqlite_master\b/i,
1048
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
1049
+ /\bMATCH\b/i,
1050
+ /\bvector_distance_cos\s*\(/i,
1051
+ /\bjson_extract\s*\(/i,
1052
+ /\bjulianday\s*\(/i,
1053
+ /\bstrftime\s*\(/i,
1054
+ /\blast_insert_rowid\s*\(/i
1055
+ ];
1056
+ prismaClientPromise = null;
1057
+ compatibilityBootstrapPromise = null;
1058
+ }
1059
+ });
1060
+
1061
+ // src/lib/daemon-auth.ts
1062
+ import crypto from "crypto";
1063
+ import path4 from "path";
1064
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1065
+ function normalizeToken(token) {
1066
+ if (!token) return null;
1067
+ const trimmed = token.trim();
1068
+ return trimmed.length > 0 ? trimmed : null;
1069
+ }
1070
+ function readDaemonToken() {
1071
+ try {
1072
+ if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
1073
+ return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
1074
+ } catch {
1075
+ return null;
1076
+ }
1077
+ }
1078
+ function ensureDaemonToken(seed) {
1079
+ const existing = readDaemonToken();
1080
+ if (existing) return existing;
1081
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1082
+ ensurePrivateDirSync(EXE_AI_DIR);
1083
+ writeFileSync2(DAEMON_TOKEN_PATH, `${token}
1084
+ `, "utf8");
1085
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1086
+ return token;
1087
+ }
1088
+ var DAEMON_TOKEN_PATH;
1089
+ var init_daemon_auth = __esm({
1090
+ "src/lib/daemon-auth.ts"() {
1091
+ "use strict";
1092
+ init_config();
1093
+ init_secure_files();
1094
+ DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
1095
+ }
1096
+ });
1097
+
438
1098
  // src/lib/exe-daemon-client.ts
439
1099
  import net from "net";
440
- import os3 from "os";
1100
+ import os4 from "os";
441
1101
  import { spawn } from "child_process";
442
1102
  import { randomUUID } from "crypto";
443
- import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
444
- import path3 from "path";
1103
+ import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
1104
+ import path5 from "path";
445
1105
  import { fileURLToPath } from "url";
446
1106
  function handleData(chunk) {
447
1107
  _buffer += chunk.toString();
@@ -469,9 +1129,9 @@ function handleData(chunk) {
469
1129
  }
470
1130
  }
471
1131
  function cleanupStaleFiles() {
472
- if (existsSync3(PID_PATH)) {
1132
+ if (existsSync5(PID_PATH)) {
473
1133
  try {
474
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
1134
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
475
1135
  if (pid > 0) {
476
1136
  try {
477
1137
  process.kill(pid, 0);
@@ -492,17 +1152,17 @@ function cleanupStaleFiles() {
492
1152
  }
493
1153
  }
494
1154
  function findPackageRoot() {
495
- let dir = path3.dirname(fileURLToPath(import.meta.url));
496
- const { root } = path3.parse(dir);
1155
+ let dir = path5.dirname(fileURLToPath(import.meta.url));
1156
+ const { root } = path5.parse(dir);
497
1157
  while (dir !== root) {
498
- if (existsSync3(path3.join(dir, "package.json"))) return dir;
499
- dir = path3.dirname(dir);
1158
+ if (existsSync5(path5.join(dir, "package.json"))) return dir;
1159
+ dir = path5.dirname(dir);
500
1160
  }
501
1161
  return null;
502
1162
  }
503
1163
  function spawnDaemon() {
504
- const freeGB = os3.freemem() / (1024 * 1024 * 1024);
505
- const totalGB = os3.totalmem() / (1024 * 1024 * 1024);
1164
+ const freeGB = os4.freemem() / (1024 * 1024 * 1024);
1165
+ const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
506
1166
  if (totalGB <= 8) {
507
1167
  process.stderr.write(
508
1168
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -522,16 +1182,17 @@ function spawnDaemon() {
522
1182
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
523
1183
  return;
524
1184
  }
525
- const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
526
- if (!existsSync3(daemonPath)) {
1185
+ const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1186
+ if (!existsSync5(daemonPath)) {
527
1187
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
528
1188
  `);
529
1189
  return;
530
1190
  }
531
1191
  const resolvedPath = daemonPath;
1192
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
532
1193
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
533
1194
  `);
534
- const logPath = path3.join(path3.dirname(SOCKET_PATH), "exed.log");
1195
+ const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
535
1196
  let stderrFd = "ignore";
536
1197
  try {
537
1198
  stderrFd = openSync(logPath, "a");
@@ -549,7 +1210,8 @@ function spawnDaemon() {
549
1210
  TMUX_PANE: void 0,
550
1211
  // Prevents resolveExeSession() from scoping to one session
551
1212
  EXE_DAEMON_SOCK: SOCKET_PATH,
552
- EXE_DAEMON_PID: PID_PATH
1213
+ EXE_DAEMON_PID: PID_PATH,
1214
+ [DAEMON_TOKEN_ENV]: daemonToken
553
1215
  }
554
1216
  });
555
1217
  child.unref();
@@ -656,13 +1318,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
656
1318
  return;
657
1319
  }
658
1320
  const id = randomUUID();
1321
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
659
1322
  const timer = setTimeout(() => {
660
1323
  _pending.delete(id);
661
1324
  resolve({ error: "Request timeout" });
662
1325
  }, timeoutMs);
663
1326
  _pending.set(id, { resolve, timer });
664
1327
  try {
665
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1328
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
666
1329
  } catch {
667
1330
  clearTimeout(timer);
668
1331
  _pending.delete(id);
@@ -673,17 +1336,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
673
1336
  function isClientConnected() {
674
1337
  return _connected;
675
1338
  }
676
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1339
+ 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;
677
1340
  var init_exe_daemon_client = __esm({
678
1341
  "src/lib/exe-daemon-client.ts"() {
679
1342
  "use strict";
680
1343
  init_config();
681
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
682
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
683
- SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
1344
+ init_daemon_auth();
1345
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1346
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1347
+ SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
684
1348
  SPAWN_LOCK_STALE_MS = 3e4;
685
1349
  CONNECT_TIMEOUT_MS = 15e3;
686
1350
  REQUEST_TIMEOUT_MS = 3e4;
1351
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
687
1352
  _socket = null;
688
1353
  _connected = false;
689
1354
  _buffer = "";
@@ -762,7 +1427,7 @@ __export(db_daemon_client_exports, {
762
1427
  createDaemonDbClient: () => createDaemonDbClient,
763
1428
  initDaemonDbClient: () => initDaemonDbClient
764
1429
  });
765
- function normalizeStatement(stmt) {
1430
+ function normalizeStatement2(stmt) {
766
1431
  if (typeof stmt === "string") {
767
1432
  return { sql: stmt, args: [] };
768
1433
  }
@@ -786,7 +1451,7 @@ function createDaemonDbClient(fallbackClient) {
786
1451
  if (!_useDaemon || !isClientConnected()) {
787
1452
  return fallbackClient.execute(stmt);
788
1453
  }
789
- const { sql, args } = normalizeStatement(stmt);
1454
+ const { sql, args } = normalizeStatement2(stmt);
790
1455
  const response = await sendDaemonRequest({
791
1456
  type: "db-execute",
792
1457
  sql,
@@ -811,7 +1476,7 @@ function createDaemonDbClient(fallbackClient) {
811
1476
  if (!_useDaemon || !isClientConnected()) {
812
1477
  return fallbackClient.batch(stmts, mode);
813
1478
  }
814
- const statements = stmts.map(normalizeStatement);
1479
+ const statements = stmts.map(normalizeStatement2);
815
1480
  const response = await sendDaemonRequest({
816
1481
  type: "db-batch",
817
1482
  statements,
@@ -906,6 +1571,18 @@ __export(database_exports, {
906
1571
  });
907
1572
  import { createClient } from "@libsql/client";
908
1573
  async function initDatabase(config) {
1574
+ if (_walCheckpointTimer) {
1575
+ clearInterval(_walCheckpointTimer);
1576
+ _walCheckpointTimer = null;
1577
+ }
1578
+ if (_daemonClient) {
1579
+ _daemonClient.close();
1580
+ _daemonClient = null;
1581
+ }
1582
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1583
+ _adapterClient.close();
1584
+ }
1585
+ _adapterClient = null;
909
1586
  if (_client) {
910
1587
  _client.close();
911
1588
  _client = null;
@@ -919,6 +1596,7 @@ async function initDatabase(config) {
919
1596
  }
920
1597
  _client = createClient(opts);
921
1598
  _resilientClient = wrapWithRetry(_client);
1599
+ _adapterClient = _resilientClient;
922
1600
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
923
1601
  });
924
1602
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -929,14 +1607,20 @@ async function initDatabase(config) {
929
1607
  });
930
1608
  }, 3e4);
931
1609
  _walCheckpointTimer.unref();
1610
+ if (process.env.DATABASE_URL) {
1611
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1612
+ }
932
1613
  }
933
1614
  function isInitialized() {
934
- return _client !== null;
1615
+ return _adapterClient !== null || _client !== null;
935
1616
  }
936
1617
  function getClient() {
937
- if (!_resilientClient) {
1618
+ if (!_adapterClient) {
938
1619
  throw new Error("Database client not initialized. Call initDatabase() first.");
939
1620
  }
1621
+ if (process.env.DATABASE_URL) {
1622
+ return _adapterClient;
1623
+ }
940
1624
  if (process.env.EXE_IS_DAEMON === "1") {
941
1625
  return _resilientClient;
942
1626
  }
@@ -946,6 +1630,7 @@ function getClient() {
946
1630
  return _resilientClient;
947
1631
  }
948
1632
  async function initDaemonClient() {
1633
+ if (process.env.DATABASE_URL) return;
949
1634
  if (process.env.EXE_IS_DAEMON === "1") return;
950
1635
  if (!_resilientClient) return;
951
1636
  try {
@@ -1242,6 +1927,7 @@ async function ensureSchema() {
1242
1927
  project TEXT NOT NULL,
1243
1928
  summary TEXT NOT NULL,
1244
1929
  task_file TEXT,
1930
+ session_scope TEXT,
1245
1931
  read INTEGER NOT NULL DEFAULT 0,
1246
1932
  created_at TEXT NOT NULL
1247
1933
  );
@@ -1250,7 +1936,7 @@ async function ensureSchema() {
1250
1936
  ON notifications(read);
1251
1937
 
1252
1938
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1253
- ON notifications(agent_id);
1939
+ ON notifications(agent_id, session_scope);
1254
1940
 
1255
1941
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1256
1942
  ON notifications(task_file);
@@ -1288,6 +1974,7 @@ async function ensureSchema() {
1288
1974
  target_agent TEXT NOT NULL,
1289
1975
  target_project TEXT,
1290
1976
  target_device TEXT NOT NULL DEFAULT 'local',
1977
+ session_scope TEXT,
1291
1978
  content TEXT NOT NULL,
1292
1979
  priority TEXT DEFAULT 'normal',
1293
1980
  status TEXT DEFAULT 'pending',
@@ -1301,10 +1988,31 @@ async function ensureSchema() {
1301
1988
  );
1302
1989
 
1303
1990
  CREATE INDEX IF NOT EXISTS idx_messages_target
1304
- ON messages(target_agent, status);
1991
+ ON messages(target_agent, session_scope, status);
1305
1992
 
1306
1993
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1307
- ON messages(target_agent, from_agent, server_seq);
1994
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1995
+ `);
1996
+ try {
1997
+ await client.execute({
1998
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1999
+ args: []
2000
+ });
2001
+ } catch {
2002
+ }
2003
+ try {
2004
+ await client.execute({
2005
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2006
+ args: []
2007
+ });
2008
+ } catch {
2009
+ }
2010
+ await client.executeMultiple(`
2011
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2012
+ ON notifications(agent_id, session_scope, read, created_at);
2013
+
2014
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2015
+ ON messages(target_agent, session_scope, status, created_at);
1308
2016
  `);
1309
2017
  try {
1310
2018
  await client.execute({
@@ -1888,28 +2596,45 @@ async function ensureSchema() {
1888
2596
  } catch {
1889
2597
  }
1890
2598
  }
2599
+ try {
2600
+ await client.execute({
2601
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2602
+ args: []
2603
+ });
2604
+ } catch {
2605
+ }
1891
2606
  }
1892
2607
  async function disposeDatabase() {
2608
+ if (_walCheckpointTimer) {
2609
+ clearInterval(_walCheckpointTimer);
2610
+ _walCheckpointTimer = null;
2611
+ }
1893
2612
  if (_daemonClient) {
1894
2613
  _daemonClient.close();
1895
2614
  _daemonClient = null;
1896
2615
  }
2616
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2617
+ _adapterClient.close();
2618
+ }
2619
+ _adapterClient = null;
1897
2620
  if (_client) {
1898
2621
  _client.close();
1899
2622
  _client = null;
1900
2623
  _resilientClient = null;
1901
2624
  }
1902
2625
  }
1903
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
2626
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
1904
2627
  var init_database = __esm({
1905
2628
  "src/lib/database.ts"() {
1906
2629
  "use strict";
1907
2630
  init_db_retry();
1908
2631
  init_employees();
2632
+ init_database_adapter();
1909
2633
  _client = null;
1910
2634
  _resilientClient = null;
1911
2635
  _walCheckpointTimer = null;
1912
2636
  _daemonClient = null;
2637
+ _adapterClient = null;
1913
2638
  initTurso = initDatabase;
1914
2639
  disposeTurso = disposeDatabase;
1915
2640
  }
@@ -2119,14 +2844,14 @@ __export(keychain_exports, {
2119
2844
  setMasterKey: () => setMasterKey
2120
2845
  });
2121
2846
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2122
- import { existsSync as existsSync4 } from "fs";
2123
- import path4 from "path";
2124
- import os4 from "os";
2847
+ import { existsSync as existsSync6 } from "fs";
2848
+ import path6 from "path";
2849
+ import os5 from "os";
2125
2850
  function getKeyDir() {
2126
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os4.homedir(), ".exe-os");
2851
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
2127
2852
  }
2128
2853
  function getKeyPath() {
2129
- return path4.join(getKeyDir(), "master.key");
2854
+ return path6.join(getKeyDir(), "master.key");
2130
2855
  }
2131
2856
  async function tryKeytar() {
2132
2857
  try {
@@ -2147,9 +2872,9 @@ async function getMasterKey() {
2147
2872
  }
2148
2873
  }
2149
2874
  const keyPath = getKeyPath();
2150
- if (!existsSync4(keyPath)) {
2875
+ if (!existsSync6(keyPath)) {
2151
2876
  process.stderr.write(
2152
- `[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2877
+ `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2153
2878
  `
2154
2879
  );
2155
2880
  return null;
@@ -2190,7 +2915,7 @@ async function deleteMasterKey() {
2190
2915
  }
2191
2916
  }
2192
2917
  const keyPath = getKeyPath();
2193
- if (existsSync4(keyPath)) {
2918
+ if (existsSync6(keyPath)) {
2194
2919
  await unlink(keyPath);
2195
2920
  }
2196
2921
  }
@@ -2292,6 +3017,7 @@ var shard_manager_exports = {};
2292
3017
  __export(shard_manager_exports, {
2293
3018
  disposeShards: () => disposeShards,
2294
3019
  ensureShardSchema: () => ensureShardSchema,
3020
+ getOpenShardCount: () => getOpenShardCount,
2295
3021
  getReadyShardClient: () => getReadyShardClient,
2296
3022
  getShardClient: () => getShardClient,
2297
3023
  getShardsDir: () => getShardsDir,
@@ -2300,15 +3026,18 @@ __export(shard_manager_exports, {
2300
3026
  listShards: () => listShards,
2301
3027
  shardExists: () => shardExists
2302
3028
  });
2303
- import path5 from "path";
2304
- import { existsSync as existsSync5, mkdirSync, readdirSync } from "fs";
3029
+ import path7 from "path";
3030
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync } from "fs";
2305
3031
  import { createClient as createClient2 } from "@libsql/client";
2306
3032
  function initShardManager(encryptionKey) {
2307
3033
  _encryptionKey = encryptionKey;
2308
- if (!existsSync5(SHARDS_DIR)) {
2309
- mkdirSync(SHARDS_DIR, { recursive: true });
3034
+ if (!existsSync7(SHARDS_DIR)) {
3035
+ mkdirSync2(SHARDS_DIR, { recursive: true });
2310
3036
  }
2311
3037
  _shardingEnabled = true;
3038
+ if (_evictionTimer) clearInterval(_evictionTimer);
3039
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
3040
+ _evictionTimer.unref();
2312
3041
  }
2313
3042
  function isShardingEnabled() {
2314
3043
  return _shardingEnabled;
@@ -2325,21 +3054,28 @@ function getShardClient(projectName) {
2325
3054
  throw new Error(`Invalid project name for shard: "${projectName}"`);
2326
3055
  }
2327
3056
  const cached = _shards.get(safeName);
2328
- if (cached) return cached;
2329
- const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
3057
+ if (cached) {
3058
+ _shardLastAccess.set(safeName, Date.now());
3059
+ return cached;
3060
+ }
3061
+ while (_shards.size >= MAX_OPEN_SHARDS) {
3062
+ evictLRU();
3063
+ }
3064
+ const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
2330
3065
  const client = createClient2({
2331
3066
  url: `file:${dbPath}`,
2332
3067
  encryptionKey: _encryptionKey
2333
3068
  });
2334
3069
  _shards.set(safeName, client);
3070
+ _shardLastAccess.set(safeName, Date.now());
2335
3071
  return client;
2336
3072
  }
2337
3073
  function shardExists(projectName) {
2338
3074
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2339
- return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
3075
+ return existsSync7(path7.join(SHARDS_DIR, `${safeName}.db`));
2340
3076
  }
2341
3077
  function listShards() {
2342
- if (!existsSync5(SHARDS_DIR)) return [];
3078
+ if (!existsSync7(SHARDS_DIR)) return [];
2343
3079
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2344
3080
  }
2345
3081
  async function ensureShardSchema(client) {
@@ -2391,6 +3127,8 @@ async function ensureShardSchema(client) {
2391
3127
  for (const col of [
2392
3128
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
2393
3129
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
3130
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
3131
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
2394
3132
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
2395
3133
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
2396
3134
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -2413,7 +3151,23 @@ async function ensureShardSchema(client) {
2413
3151
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
2414
3152
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
2415
3153
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
2416
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
3154
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
3155
+ // Metadata enrichment columns (must match database.ts)
3156
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
3157
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
3158
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
3159
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
3160
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
3161
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
3162
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
3163
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
3164
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
3165
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
3166
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
3167
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
3168
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
3169
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
3170
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2417
3171
  ]) {
2418
3172
  try {
2419
3173
  await client.execute(col);
@@ -2512,21 +3266,69 @@ async function getReadyShardClient(projectName) {
2512
3266
  await ensureShardSchema(client);
2513
3267
  return client;
2514
3268
  }
3269
+ function evictLRU() {
3270
+ let oldest = null;
3271
+ let oldestTime = Infinity;
3272
+ for (const [name, time] of _shardLastAccess) {
3273
+ if (time < oldestTime) {
3274
+ oldestTime = time;
3275
+ oldest = name;
3276
+ }
3277
+ }
3278
+ if (oldest) {
3279
+ const client = _shards.get(oldest);
3280
+ if (client) {
3281
+ client.close();
3282
+ }
3283
+ _shards.delete(oldest);
3284
+ _shardLastAccess.delete(oldest);
3285
+ }
3286
+ }
3287
+ function evictIdleShards() {
3288
+ const now = Date.now();
3289
+ const toEvict = [];
3290
+ for (const [name, lastAccess] of _shardLastAccess) {
3291
+ if (now - lastAccess > SHARD_IDLE_MS) {
3292
+ toEvict.push(name);
3293
+ }
3294
+ }
3295
+ for (const name of toEvict) {
3296
+ const client = _shards.get(name);
3297
+ if (client) {
3298
+ client.close();
3299
+ }
3300
+ _shards.delete(name);
3301
+ _shardLastAccess.delete(name);
3302
+ }
3303
+ }
3304
+ function getOpenShardCount() {
3305
+ return _shards.size;
3306
+ }
2515
3307
  function disposeShards() {
3308
+ if (_evictionTimer) {
3309
+ clearInterval(_evictionTimer);
3310
+ _evictionTimer = null;
3311
+ }
2516
3312
  for (const [, client] of _shards) {
2517
3313
  client.close();
2518
3314
  }
2519
3315
  _shards.clear();
3316
+ _shardLastAccess.clear();
2520
3317
  _shardingEnabled = false;
2521
3318
  _encryptionKey = null;
2522
3319
  }
2523
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
3320
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
2524
3321
  var init_shard_manager = __esm({
2525
3322
  "src/lib/shard-manager.ts"() {
2526
3323
  "use strict";
2527
3324
  init_config();
2528
- SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
3325
+ SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
3326
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
3327
+ MAX_OPEN_SHARDS = 10;
3328
+ EVICTION_INTERVAL_MS = 60 * 1e3;
2529
3329
  _shards = /* @__PURE__ */ new Map();
3330
+ _shardLastAccess = /* @__PURE__ */ new Map();
3331
+ _evictionTimer = null;
2530
3332
  _encryptionKey = null;
2531
3333
  _shardingEnabled = false;
2532
3334
  }
@@ -2631,14 +3433,14 @@ __export(session_registry_exports, {
2631
3433
  pruneStaleSessions: () => pruneStaleSessions,
2632
3434
  registerSession: () => registerSession
2633
3435
  });
2634
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync6 } from "fs";
3436
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync8 } from "fs";
2635
3437
  import { execSync as execSync2 } from "child_process";
2636
- import path6 from "path";
2637
- import os5 from "os";
3438
+ import path8 from "path";
3439
+ import os6 from "os";
2638
3440
  function registerSession(entry) {
2639
- const dir = path6.dirname(REGISTRY_PATH);
2640
- if (!existsSync6(dir)) {
2641
- mkdirSync2(dir, { recursive: true });
3441
+ const dir = path8.dirname(REGISTRY_PATH);
3442
+ if (!existsSync8(dir)) {
3443
+ mkdirSync3(dir, { recursive: true });
2642
3444
  }
2643
3445
  const sessions = listSessions();
2644
3446
  const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
@@ -2647,11 +3449,11 @@ function registerSession(entry) {
2647
3449
  } else {
2648
3450
  sessions.push(entry);
2649
3451
  }
2650
- writeFileSync2(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
3452
+ writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
2651
3453
  }
2652
3454
  function listSessions() {
2653
3455
  try {
2654
- const raw = readFileSync4(REGISTRY_PATH, "utf8");
3456
+ const raw = readFileSync5(REGISTRY_PATH, "utf8");
2655
3457
  return JSON.parse(raw);
2656
3458
  } catch {
2657
3459
  return [];
@@ -2672,7 +3474,7 @@ function pruneStaleSessions() {
2672
3474
  const alive = sessions.filter((s) => liveSet.has(s.windowName));
2673
3475
  const pruned = sessions.length - alive.length;
2674
3476
  if (pruned > 0) {
2675
- writeFileSync2(REGISTRY_PATH, JSON.stringify(alive, null, 2));
3477
+ writeFileSync3(REGISTRY_PATH, JSON.stringify(alive, null, 2));
2676
3478
  }
2677
3479
  return pruned;
2678
3480
  }
@@ -2680,7 +3482,7 @@ var REGISTRY_PATH;
2680
3482
  var init_session_registry = __esm({
2681
3483
  "src/lib/session-registry.ts"() {
2682
3484
  "use strict";
2683
- REGISTRY_PATH = path6.join(os5.homedir(), ".exe-os", "session-registry.json");
3485
+ REGISTRY_PATH = path8.join(os6.homedir(), ".exe-os", "session-registry.json");
2684
3486
  }
2685
3487
  });
2686
3488
 
@@ -2960,12 +3762,12 @@ var init_runtime_table = __esm({
2960
3762
  });
2961
3763
 
2962
3764
  // src/lib/agent-config.ts
2963
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
2964
- import path7 from "path";
3765
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync9 } from "fs";
3766
+ import path9 from "path";
2965
3767
  function loadAgentConfig() {
2966
- if (!existsSync7(AGENT_CONFIG_PATH)) return {};
3768
+ if (!existsSync9(AGENT_CONFIG_PATH)) return {};
2967
3769
  try {
2968
- return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
3770
+ return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
2969
3771
  } catch {
2970
3772
  return {};
2971
3773
  }
@@ -2984,7 +3786,8 @@ var init_agent_config = __esm({
2984
3786
  "use strict";
2985
3787
  init_config();
2986
3788
  init_runtime_table();
2987
- AGENT_CONFIG_PATH = path7.join(EXE_AI_DIR, "agent-config.json");
3789
+ init_secure_files();
3790
+ AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
2988
3791
  DEFAULT_MODELS = {
2989
3792
  claude: "claude-opus-4",
2990
3793
  codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
@@ -3002,17 +3805,17 @@ __export(intercom_queue_exports, {
3002
3805
  queueIntercom: () => queueIntercom,
3003
3806
  readQueue: () => readQueue
3004
3807
  });
3005
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
3006
- import path8 from "path";
3007
- import os6 from "os";
3808
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync10, mkdirSync as mkdirSync4 } from "fs";
3809
+ import path10 from "path";
3810
+ import os7 from "os";
3008
3811
  function ensureDir() {
3009
- const dir = path8.dirname(QUEUE_PATH);
3010
- if (!existsSync8(dir)) mkdirSync4(dir, { recursive: true });
3812
+ const dir = path10.dirname(QUEUE_PATH);
3813
+ if (!existsSync10(dir)) mkdirSync4(dir, { recursive: true });
3011
3814
  }
3012
3815
  function readQueue() {
3013
3816
  try {
3014
- if (!existsSync8(QUEUE_PATH)) return [];
3015
- return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
3817
+ if (!existsSync10(QUEUE_PATH)) return [];
3818
+ return JSON.parse(readFileSync7(QUEUE_PATH, "utf8"));
3016
3819
  } catch {
3017
3820
  return [];
3018
3821
  }
@@ -3020,7 +3823,7 @@ function readQueue() {
3020
3823
  function writeQueue(queue) {
3021
3824
  ensureDir();
3022
3825
  const tmp = `${QUEUE_PATH}.tmp`;
3023
- writeFileSync4(tmp, JSON.stringify(queue, null, 2));
3826
+ writeFileSync5(tmp, JSON.stringify(queue, null, 2));
3024
3827
  renameSync3(tmp, QUEUE_PATH);
3025
3828
  }
3026
3829
  function queueIntercom(targetSession, reason) {
@@ -3112,10 +3915,10 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
3112
3915
  var init_intercom_queue = __esm({
3113
3916
  "src/lib/intercom-queue.ts"() {
3114
3917
  "use strict";
3115
- QUEUE_PATH = path8.join(os6.homedir(), ".exe-os", "intercom-queue.json");
3918
+ QUEUE_PATH = path10.join(os7.homedir(), ".exe-os", "intercom-queue.json");
3116
3919
  MAX_RETRIES2 = 5;
3117
3920
  TTL_MS = 60 * 60 * 1e3;
3118
- INTERCOM_LOG = path8.join(os6.homedir(), ".exe-os", "intercom.log");
3921
+ INTERCOM_LOG = path10.join(os7.homedir(), ".exe-os", "intercom.log");
3119
3922
  }
3120
3923
  });
3121
3924
 
@@ -3136,9 +3939,12 @@ __export(license_exports, {
3136
3939
  stopLicenseRevalidation: () => stopLicenseRevalidation,
3137
3940
  validateLicense: () => validateLicense
3138
3941
  });
3139
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
3942
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync5 } from "fs";
3140
3943
  import { randomUUID as randomUUID3 } from "crypto";
3141
- import path9 from "path";
3944
+ import { createRequire as createRequire2 } from "module";
3945
+ import { pathToFileURL as pathToFileURL2 } from "url";
3946
+ import os8 from "os";
3947
+ import path11 from "path";
3142
3948
  import { jwtVerify, importSPKI } from "jose";
3143
3949
  async function fetchRetry(url, init) {
3144
3950
  try {
@@ -3149,37 +3955,37 @@ async function fetchRetry(url, init) {
3149
3955
  }
3150
3956
  }
3151
3957
  function loadDeviceId() {
3152
- const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
3958
+ const deviceJsonPath = path11.join(EXE_AI_DIR, "device.json");
3153
3959
  try {
3154
- if (existsSync9(deviceJsonPath)) {
3155
- const data = JSON.parse(readFileSync7(deviceJsonPath, "utf8"));
3960
+ if (existsSync11(deviceJsonPath)) {
3961
+ const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
3156
3962
  if (data.deviceId) return data.deviceId;
3157
3963
  }
3158
3964
  } catch {
3159
3965
  }
3160
3966
  try {
3161
- if (existsSync9(DEVICE_ID_PATH)) {
3162
- const id2 = readFileSync7(DEVICE_ID_PATH, "utf8").trim();
3967
+ if (existsSync11(DEVICE_ID_PATH)) {
3968
+ const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
3163
3969
  if (id2) return id2;
3164
3970
  }
3165
3971
  } catch {
3166
3972
  }
3167
3973
  const id = randomUUID3();
3168
3974
  mkdirSync5(EXE_AI_DIR, { recursive: true });
3169
- writeFileSync5(DEVICE_ID_PATH, id, "utf8");
3975
+ writeFileSync6(DEVICE_ID_PATH, id, "utf8");
3170
3976
  return id;
3171
3977
  }
3172
3978
  function loadLicense() {
3173
3979
  try {
3174
- if (!existsSync9(LICENSE_PATH)) return null;
3175
- return readFileSync7(LICENSE_PATH, "utf8").trim();
3980
+ if (!existsSync11(LICENSE_PATH)) return null;
3981
+ return readFileSync8(LICENSE_PATH, "utf8").trim();
3176
3982
  } catch {
3177
3983
  return null;
3178
3984
  }
3179
3985
  }
3180
3986
  function saveLicense(apiKey) {
3181
3987
  mkdirSync5(EXE_AI_DIR, { recursive: true });
3182
- writeFileSync5(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
3988
+ writeFileSync6(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
3183
3989
  }
3184
3990
  async function verifyLicenseJwt(token) {
3185
3991
  try {
@@ -3205,8 +4011,8 @@ async function verifyLicenseJwt(token) {
3205
4011
  }
3206
4012
  async function getCachedLicense() {
3207
4013
  try {
3208
- if (!existsSync9(CACHE_PATH)) return null;
3209
- const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
4014
+ if (!existsSync11(CACHE_PATH)) return null;
4015
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
3210
4016
  if (!raw.token || typeof raw.token !== "string") return null;
3211
4017
  return await verifyLicenseJwt(raw.token);
3212
4018
  } catch {
@@ -3215,8 +4021,8 @@ async function getCachedLicense() {
3215
4021
  }
3216
4022
  function readCachedToken() {
3217
4023
  try {
3218
- if (!existsSync9(CACHE_PATH)) return null;
3219
- const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
4024
+ if (!existsSync11(CACHE_PATH)) return null;
4025
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
3220
4026
  return typeof raw.token === "string" ? raw.token : null;
3221
4027
  } catch {
3222
4028
  return null;
@@ -3250,57 +4056,131 @@ function getRawCachedPlan() {
3250
4056
  }
3251
4057
  function cacheResponse(token) {
3252
4058
  try {
3253
- writeFileSync5(CACHE_PATH, JSON.stringify({ token }), "utf8");
4059
+ writeFileSync6(CACHE_PATH, JSON.stringify({ token }), "utf8");
3254
4060
  } catch {
3255
4061
  }
3256
4062
  }
3257
- async function validateLicense(apiKey, deviceId) {
3258
- const did = deviceId ?? loadDeviceId();
4063
+ function loadPrismaForLicense() {
4064
+ if (_prismaFailed) return null;
4065
+ const dbUrl = process.env.DATABASE_URL;
4066
+ if (!dbUrl) {
4067
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
4068
+ if (!existsSync11(path11.join(exeDbRoot, "package.json"))) {
4069
+ _prismaFailed = true;
4070
+ return null;
4071
+ }
4072
+ }
4073
+ if (!_prismaPromise) {
4074
+ _prismaPromise = (async () => {
4075
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
4076
+ if (explicitPath) {
4077
+ const mod2 = await import(pathToFileURL2(explicitPath).href);
4078
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
4079
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
4080
+ return new Ctor2();
4081
+ }
4082
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
4083
+ const req = createRequire2(path11.join(exeDbRoot, "package.json"));
4084
+ const entry = req.resolve("@prisma/client");
4085
+ const mod = await import(pathToFileURL2(entry).href);
4086
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
4087
+ if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
4088
+ return new Ctor();
4089
+ })().catch((err) => {
4090
+ _prismaFailed = true;
4091
+ _prismaPromise = null;
4092
+ throw err;
4093
+ });
4094
+ }
4095
+ return _prismaPromise;
4096
+ }
4097
+ async function validateViaPostgres(apiKey) {
4098
+ const loader = loadPrismaForLicense();
4099
+ if (!loader) return null;
4100
+ try {
4101
+ const prisma = await loader;
4102
+ const rows = await prisma.$queryRawUnsafe(
4103
+ `SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
4104
+ FROM billing.licenses WHERE key = $1 LIMIT 1`,
4105
+ apiKey
4106
+ );
4107
+ if (!rows || rows.length === 0) return null;
4108
+ const row = rows[0];
4109
+ if (row.status !== "active") return null;
4110
+ if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
4111
+ const plan = row.plan;
4112
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
4113
+ return {
4114
+ valid: true,
4115
+ plan,
4116
+ email: row.email,
4117
+ expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
4118
+ deviceLimit: row.device_limit ?? limits.devices,
4119
+ employeeLimit: row.employee_limit ?? limits.employees,
4120
+ memoryLimit: row.memory_limit ?? limits.memories
4121
+ };
4122
+ } catch {
4123
+ return null;
4124
+ }
4125
+ }
4126
+ async function validateViaCFWorker(apiKey, deviceId) {
3259
4127
  try {
3260
4128
  const res = await fetchRetry(`${API_BASE}/auth/activate`, {
3261
4129
  method: "POST",
3262
4130
  headers: { "Content-Type": "application/json" },
3263
- body: JSON.stringify({ apiKey, deviceId: did }),
4131
+ body: JSON.stringify({ apiKey, deviceId }),
3264
4132
  signal: AbortSignal.timeout(1e4)
3265
4133
  });
3266
- if (res.ok) {
3267
- const data = await res.json();
3268
- if (data.error === "device_limit_exceeded") {
3269
- const cached2 = await getCachedLicense();
3270
- if (cached2) return cached2;
3271
- const raw2 = getRawCachedPlan();
3272
- if (raw2) return { ...raw2, valid: false };
3273
- return { ...FREE_LICENSE, valid: false, plan: "free" };
3274
- }
3275
- if (data.token) {
3276
- cacheResponse(data.token);
3277
- const verified = await verifyLicenseJwt(data.token);
3278
- if (verified) return verified;
3279
- }
3280
- const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
3281
- return {
3282
- valid: data.valid,
3283
- plan: data.plan,
3284
- email: data.email,
3285
- expiresAt: data.expiresAt,
3286
- deviceLimit: limits.devices,
3287
- employeeLimit: limits.employees,
3288
- memoryLimit: limits.memories
3289
- };
4134
+ if (!res.ok) return null;
4135
+ const data = await res.json();
4136
+ if (data.error === "device_limit_exceeded") return null;
4137
+ if (!data.valid) return null;
4138
+ if (data.token) {
4139
+ cacheResponse(data.token);
4140
+ const verified = await verifyLicenseJwt(data.token);
4141
+ if (verified) return verified;
3290
4142
  }
3291
- const cached = await getCachedLicense();
3292
- if (cached) return cached;
3293
- const raw = getRawCachedPlan();
3294
- if (raw) return raw;
3295
- return { ...FREE_LICENSE, valid: false, plan: "free" };
4143
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
4144
+ return {
4145
+ valid: data.valid,
4146
+ plan: data.plan,
4147
+ email: data.email,
4148
+ expiresAt: data.expiresAt,
4149
+ deviceLimit: limits.devices,
4150
+ employeeLimit: limits.employees,
4151
+ memoryLimit: limits.memories
4152
+ };
3296
4153
  } catch {
3297
- const cached = await getCachedLicense();
3298
- if (cached) return cached;
3299
- const rawFallback = getRawCachedPlan();
3300
- if (rawFallback) return rawFallback;
3301
- return { ...FREE_LICENSE, valid: false, error: "offline" };
4154
+ return null;
3302
4155
  }
3303
4156
  }
4157
+ async function validateLicense(apiKey, deviceId) {
4158
+ const did = deviceId ?? loadDeviceId();
4159
+ const pgResult = await validateViaPostgres(apiKey);
4160
+ if (pgResult) {
4161
+ try {
4162
+ writeFileSync6(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
4163
+ } catch {
4164
+ }
4165
+ return pgResult;
4166
+ }
4167
+ const cfResult = await validateViaCFWorker(apiKey, did);
4168
+ if (cfResult) return cfResult;
4169
+ const cached = await getCachedLicense();
4170
+ if (cached) return cached;
4171
+ try {
4172
+ if (existsSync11(CACHE_PATH)) {
4173
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
4174
+ if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
4175
+ return raw.pgLicense;
4176
+ }
4177
+ }
4178
+ } catch {
4179
+ }
4180
+ const rawFallback = getRawCachedPlan();
4181
+ if (rawFallback) return rawFallback;
4182
+ return { ...FREE_LICENSE, valid: false };
4183
+ }
3304
4184
  function getCacheAgeMs() {
3305
4185
  try {
3306
4186
  const { statSync: statSync2 } = __require("fs");
@@ -3314,9 +4194,9 @@ async function checkLicense() {
3314
4194
  let key = loadLicense();
3315
4195
  if (!key) {
3316
4196
  try {
3317
- const configPath = path9.join(EXE_AI_DIR, "config.json");
3318
- if (existsSync9(configPath)) {
3319
- const raw = JSON.parse(readFileSync7(configPath, "utf8"));
4197
+ const configPath = path11.join(EXE_AI_DIR, "config.json");
4198
+ if (existsSync11(configPath)) {
4199
+ const raw = JSON.parse(readFileSync8(configPath, "utf8"));
3320
4200
  const cloud = raw.cloud;
3321
4201
  if (cloud?.apiKey) {
3322
4202
  key = cloud.apiKey;
@@ -3470,14 +4350,14 @@ function stopLicenseRevalidation() {
3470
4350
  _revalTimer = null;
3471
4351
  }
3472
4352
  }
3473
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
4353
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS, _revalTimer;
3474
4354
  var init_license = __esm({
3475
4355
  "src/lib/license.ts"() {
3476
4356
  "use strict";
3477
4357
  init_config();
3478
- LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
3479
- CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
3480
- DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
4358
+ LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
4359
+ CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
4360
+ DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
3481
4361
  API_BASE = "https://askexe.com/cloud";
3482
4362
  RETRY_DELAY_MS = 500;
3483
4363
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
@@ -3501,18 +4381,20 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
3501
4381
  employeeLimit: 1,
3502
4382
  memoryLimit: 5e3
3503
4383
  };
4384
+ _prismaPromise = null;
4385
+ _prismaFailed = false;
3504
4386
  CACHE_MAX_AGE_MS = 36e5;
3505
4387
  _revalTimer = null;
3506
4388
  }
3507
4389
  });
3508
4390
 
3509
4391
  // src/lib/plan-limits.ts
3510
- import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
3511
- import path10 from "path";
4392
+ import { readFileSync as readFileSync9, existsSync as existsSync12 } from "fs";
4393
+ import path12 from "path";
3512
4394
  function getLicenseSync() {
3513
4395
  try {
3514
- if (!existsSync10(CACHE_PATH2)) return freeLicense();
3515
- const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
4396
+ if (!existsSync12(CACHE_PATH2)) return freeLicense();
4397
+ const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
3516
4398
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
3517
4399
  const parts = raw.token.split(".");
3518
4400
  if (parts.length !== 3) return freeLicense();
@@ -3550,8 +4432,8 @@ function assertEmployeeLimitSync(rosterPath) {
3550
4432
  const filePath = rosterPath ?? EMPLOYEES_PATH;
3551
4433
  let count = 0;
3552
4434
  try {
3553
- if (existsSync10(filePath)) {
3554
- const raw = readFileSync8(filePath, "utf8");
4435
+ if (existsSync12(filePath)) {
4436
+ const raw = readFileSync9(filePath, "utf8");
3555
4437
  const employees = JSON.parse(raw);
3556
4438
  count = Array.isArray(employees) ? employees.length : 0;
3557
4439
  }
@@ -3580,29 +4462,30 @@ var init_plan_limits = __esm({
3580
4462
  this.name = "PlanLimitError";
3581
4463
  }
3582
4464
  };
3583
- CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
4465
+ CACHE_PATH2 = path12.join(EXE_AI_DIR, "license-cache.json");
3584
4466
  }
3585
4467
  });
3586
4468
 
3587
4469
  // src/lib/notifications.ts
3588
- import crypto from "crypto";
3589
- import path11 from "path";
3590
- import os7 from "os";
4470
+ import crypto2 from "crypto";
4471
+ import path13 from "path";
4472
+ import os9 from "os";
3591
4473
  import {
3592
- readFileSync as readFileSync9,
4474
+ readFileSync as readFileSync10,
3593
4475
  readdirSync as readdirSync2,
3594
4476
  unlinkSync as unlinkSync3,
3595
- existsSync as existsSync11,
4477
+ existsSync as existsSync13,
3596
4478
  rmdirSync
3597
4479
  } from "fs";
3598
4480
  async function writeNotification(notification) {
3599
4481
  try {
3600
4482
  const client = getClient();
3601
- const id = crypto.randomUUID();
4483
+ const id = crypto2.randomUUID();
3602
4484
  const now = (/* @__PURE__ */ new Date()).toISOString();
4485
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
3603
4486
  await client.execute({
3604
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
3605
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
4487
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
4488
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3606
4489
  args: [
3607
4490
  id,
3608
4491
  notification.agentId,
@@ -3611,6 +4494,7 @@ async function writeNotification(notification) {
3611
4494
  notification.project,
3612
4495
  notification.summary,
3613
4496
  notification.taskFile ?? null,
4497
+ sessionScope,
3614
4498
  now
3615
4499
  ]
3616
4500
  });
@@ -3619,21 +4503,22 @@ async function writeNotification(notification) {
3619
4503
  `);
3620
4504
  }
3621
4505
  }
3622
- async function readUnreadNotifications(agentFilter) {
4506
+ async function readUnreadNotifications(agentFilter, sessionScope) {
3623
4507
  try {
3624
4508
  const client = getClient();
3625
4509
  const conditions = ["read = 0"];
3626
4510
  const args = [];
4511
+ const scope = strictSessionScopeFilter(sessionScope);
3627
4512
  if (agentFilter) {
3628
4513
  conditions.push("agent_id = ?");
3629
4514
  args.push(agentFilter);
3630
4515
  }
3631
4516
  const result = await client.execute({
3632
- sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, created_at
4517
+ sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
3633
4518
  FROM notifications
3634
- WHERE ${conditions.join(" AND ")}
4519
+ WHERE ${conditions.join(" AND ")}${scope.sql}
3635
4520
  ORDER BY created_at ASC`,
3636
- args
4521
+ args: [...args, ...scope.args]
3637
4522
  });
3638
4523
  return result.rows.map((r) => ({
3639
4524
  id: String(r.id),
@@ -3643,6 +4528,7 @@ async function readUnreadNotifications(agentFilter) {
3643
4528
  project: String(r.project),
3644
4529
  summary: String(r.summary),
3645
4530
  taskFile: r.task_file ? String(r.task_file) : void 0,
4531
+ sessionScope: r.session_scope == null ? null : String(r.session_scope),
3646
4532
  timestamp: String(r.created_at),
3647
4533
  read: false
3648
4534
  }));
@@ -3650,54 +4536,60 @@ async function readUnreadNotifications(agentFilter) {
3650
4536
  return [];
3651
4537
  }
3652
4538
  }
3653
- async function markAsRead(ids) {
4539
+ async function markAsRead(ids, sessionScope) {
3654
4540
  if (ids.length === 0) return;
3655
4541
  try {
3656
4542
  const client = getClient();
3657
4543
  const placeholders = ids.map(() => "?").join(", ");
4544
+ const scope = strictSessionScopeFilter(sessionScope);
3658
4545
  await client.execute({
3659
- sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})`,
3660
- args: ids
4546
+ sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
4547
+ args: [...ids, ...scope.args]
3661
4548
  });
3662
4549
  } catch {
3663
4550
  }
3664
4551
  }
3665
- async function markAsReadByTaskFile(taskFile) {
4552
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
3666
4553
  try {
3667
4554
  const client = getClient();
4555
+ const scope = strictSessionScopeFilter(sessionScope);
3668
4556
  await client.execute({
3669
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
3670
- args: [taskFile]
4557
+ sql: `UPDATE notifications SET read = 1
4558
+ WHERE task_file = ? AND read = 0${scope.sql}`,
4559
+ args: [taskFile, ...scope.args]
3671
4560
  });
3672
4561
  } catch {
3673
4562
  }
3674
4563
  }
3675
- async function cleanupOldNotifications(daysOld = CLEANUP_DAYS) {
4564
+ async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
3676
4565
  try {
3677
4566
  const client = getClient();
3678
4567
  const cutoff = new Date(
3679
4568
  Date.now() - daysOld * 24 * 60 * 60 * 1e3
3680
4569
  ).toISOString();
4570
+ const scope = strictSessionScopeFilter(sessionScope);
3681
4571
  const result = await client.execute({
3682
- sql: "DELETE FROM notifications WHERE created_at < ?",
3683
- args: [cutoff]
4572
+ sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
4573
+ args: [cutoff, ...scope.args]
3684
4574
  });
3685
4575
  return result.rowsAffected;
3686
4576
  } catch {
3687
4577
  return 0;
3688
4578
  }
3689
4579
  }
3690
- async function markDoneTaskNotificationsAsRead() {
4580
+ async function markDoneTaskNotificationsAsRead(sessionScope) {
3691
4581
  try {
3692
4582
  const client = getClient();
4583
+ const scope = strictSessionScopeFilter(sessionScope);
3693
4584
  const result = await client.execute({
3694
4585
  sql: `UPDATE notifications SET read = 1
3695
4586
  WHERE read = 0
3696
4587
  AND task_file IS NOT NULL
4588
+ ${scope.sql}
3697
4589
  AND task_file IN (
3698
- SELECT task_file FROM tasks WHERE status = 'done'
4590
+ SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
3699
4591
  )`,
3700
- args: []
4592
+ args: [...scope.args, ...scope.args]
3701
4593
  });
3702
4594
  return result.rowsAffected;
3703
4595
  } catch {
@@ -3705,9 +4597,9 @@ async function markDoneTaskNotificationsAsRead() {
3705
4597
  }
3706
4598
  }
3707
4599
  async function migrateJsonNotifications() {
3708
- const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path11.join(os7.homedir(), ".exe-os");
3709
- const notifDir = path11.join(base, "notifications");
3710
- if (!existsSync11(notifDir)) return 0;
4600
+ const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path13.join(os9.homedir(), ".exe-os");
4601
+ const notifDir = path13.join(base, "notifications");
4602
+ if (!existsSync13(notifDir)) return 0;
3711
4603
  let migrated = 0;
3712
4604
  try {
3713
4605
  const files = readdirSync2(notifDir).filter((f) => f.endsWith(".json"));
@@ -3715,19 +4607,20 @@ async function migrateJsonNotifications() {
3715
4607
  const client = getClient();
3716
4608
  for (const file of files) {
3717
4609
  try {
3718
- const filePath = path11.join(notifDir, file);
3719
- const data = JSON.parse(readFileSync9(filePath, "utf8"));
4610
+ const filePath = path13.join(notifDir, file);
4611
+ const data = JSON.parse(readFileSync10(filePath, "utf8"));
3720
4612
  await client.execute({
3721
- sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
3722
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4613
+ sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
4614
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3723
4615
  args: [
3724
- crypto.randomUUID(),
4616
+ crypto2.randomUUID(),
3725
4617
  data.agentId ?? "unknown",
3726
4618
  data.agentRole ?? "unknown",
3727
4619
  data.event ?? "session_summary",
3728
4620
  data.project ?? "unknown",
3729
4621
  data.summary ?? "",
3730
4622
  data.taskFile ?? null,
4623
+ null,
3731
4624
  data.read ? 1 : 0,
3732
4625
  data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
3733
4626
  ]
@@ -3753,6 +4646,7 @@ var init_notifications = __esm({
3753
4646
  "src/lib/notifications.ts"() {
3754
4647
  "use strict";
3755
4648
  init_database();
4649
+ init_task_scope();
3756
4650
  CLEANUP_DAYS = 7;
3757
4651
  }
3758
4652
  });
@@ -3770,7 +4664,7 @@ __export(session_kill_telemetry_exports, {
3770
4664
  recordSessionKill: () => recordSessionKill,
3771
4665
  sumTokensSavedSince: () => sumTokensSavedSince
3772
4666
  });
3773
- import crypto2 from "crypto";
4667
+ import crypto3 from "crypto";
3774
4668
  async function recordSessionKill(input) {
3775
4669
  try {
3776
4670
  const client = getClient();
@@ -3780,7 +4674,7 @@ async function recordSessionKill(input) {
3780
4674
  ticks_idle, estimated_tokens_saved)
3781
4675
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
3782
4676
  args: [
3783
- crypto2.randomUUID(),
4677
+ crypto3.randomUUID(),
3784
4678
  input.sessionName,
3785
4679
  input.agentId,
3786
4680
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -3862,12 +4756,12 @@ var init_session_kill_telemetry = __esm({
3862
4756
  });
3863
4757
 
3864
4758
  // src/lib/tasks-crud.ts
3865
- import crypto3 from "crypto";
3866
- import path12 from "path";
3867
- import os8 from "os";
4759
+ import crypto4 from "crypto";
4760
+ import path14 from "path";
4761
+ import os10 from "os";
3868
4762
  import { execSync as execSync5 } from "child_process";
3869
4763
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
3870
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
4764
+ import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
3871
4765
  async function writeCheckpoint(input) {
3872
4766
  const client = getClient();
3873
4767
  const row = await resolveTask(client, input.taskId);
@@ -3983,7 +4877,7 @@ async function resolveTask(client, identifier, scopeSession) {
3983
4877
  }
3984
4878
  async function createTaskCore(input) {
3985
4879
  const client = getClient();
3986
- const id = crypto3.randomUUID();
4880
+ const id = crypto4.randomUUID();
3987
4881
  const now = (/* @__PURE__ */ new Date()).toISOString();
3988
4882
  const slug = slugify(input.title);
3989
4883
  let earlySessionScope = null;
@@ -4042,8 +4936,8 @@ ${laneWarning}` : laneWarning;
4042
4936
  }
4043
4937
  if (input.baseDir) {
4044
4938
  try {
4045
- await mkdir4(path12.join(input.baseDir, "exe", "output"), { recursive: true });
4046
- await mkdir4(path12.join(input.baseDir, "exe", "research"), { recursive: true });
4939
+ await mkdir4(path14.join(input.baseDir, "exe", "output"), { recursive: true });
4940
+ await mkdir4(path14.join(input.baseDir, "exe", "research"), { recursive: true });
4047
4941
  await ensureArchitectureDoc(input.baseDir, input.projectName);
4048
4942
  await ensureGitignoreExe(input.baseDir);
4049
4943
  } catch {
@@ -4079,13 +4973,19 @@ ${laneWarning}` : laneWarning;
4079
4973
  });
4080
4974
  if (input.baseDir) {
4081
4975
  try {
4082
- const EXE_OS_DIR = path12.join(os8.homedir(), ".exe-os");
4083
- const mdPath = path12.join(EXE_OS_DIR, taskFile);
4084
- const mdDir = path12.dirname(mdPath);
4085
- if (!existsSync12(mdDir)) await mkdir4(mdDir, { recursive: true });
4976
+ const EXE_OS_DIR = path14.join(os10.homedir(), ".exe-os");
4977
+ const mdPath = path14.join(EXE_OS_DIR, taskFile);
4978
+ const mdDir = path14.dirname(mdPath);
4979
+ if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
4086
4980
  const reviewer = input.reviewer ?? input.assignedBy;
4087
4981
  const mdContent = `# ${input.title}
4088
4982
 
4983
+ ## MANDATORY: When done
4984
+
4985
+ You MUST call update_task with status "done" and a result summary when finished.
4986
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
4987
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
4988
+
4089
4989
  **ID:** ${id}
4090
4990
  **Status:** ${initialStatus}
4091
4991
  **Priority:** ${input.priority}
@@ -4099,12 +4999,6 @@ ${laneWarning}` : laneWarning;
4099
4999
  ## Context
4100
5000
 
4101
5001
  ${input.context}
4102
-
4103
- ## MANDATORY: When done
4104
-
4105
- You MUST call update_task with status "done" and a result summary when finished.
4106
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
4107
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
4108
5002
  `;
4109
5003
  await writeFile4(mdPath, mdContent, "utf-8");
4110
5004
  } catch (err) {
@@ -4353,7 +5247,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
4353
5247
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
4354
5248
  } catch {
4355
5249
  }
4356
- if (input.status === "done" || input.status === "cancelled") {
5250
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
4357
5251
  try {
4358
5252
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
4359
5253
  clearQueueForAgent2(String(row.assigned_to));
@@ -4382,9 +5276,9 @@ async function deleteTaskCore(taskId, _baseDir) {
4382
5276
  return { taskFile, assignedTo, assignedBy, taskSlug };
4383
5277
  }
4384
5278
  async function ensureArchitectureDoc(baseDir, projectName) {
4385
- const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
5279
+ const archPath = path14.join(baseDir, "exe", "ARCHITECTURE.md");
4386
5280
  try {
4387
- if (existsSync12(archPath)) return;
5281
+ if (existsSync14(archPath)) return;
4388
5282
  const template = [
4389
5283
  `# ${projectName} \u2014 System Architecture`,
4390
5284
  "",
@@ -4417,10 +5311,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
4417
5311
  }
4418
5312
  }
4419
5313
  async function ensureGitignoreExe(baseDir) {
4420
- const gitignorePath = path12.join(baseDir, ".gitignore");
5314
+ const gitignorePath = path14.join(baseDir, ".gitignore");
4421
5315
  try {
4422
- if (existsSync12(gitignorePath)) {
4423
- const content = readFileSync10(gitignorePath, "utf-8");
5316
+ if (existsSync14(gitignorePath)) {
5317
+ const content = readFileSync11(gitignorePath, "utf-8");
4424
5318
  if (/^\/?exe\/?$/m.test(content)) return;
4425
5319
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
4426
5320
  } else {
@@ -4451,58 +5345,42 @@ var init_tasks_crud = __esm({
4451
5345
  });
4452
5346
 
4453
5347
  // src/lib/tasks-review.ts
4454
- import path13 from "path";
4455
- import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
5348
+ import path15 from "path";
5349
+ import { existsSync as existsSync15, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
4456
5350
  async function countPendingReviews(sessionScope) {
4457
5351
  const client = getClient();
4458
- if (sessionScope) {
4459
- const result2 = await client.execute({
4460
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
4461
- args: [sessionScope]
4462
- });
4463
- return Number(result2.rows[0]?.cnt) || 0;
4464
- }
5352
+ const scope = strictSessionScopeFilter(
5353
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
5354
+ );
4465
5355
  const result = await client.execute({
4466
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
4467
- args: []
5356
+ sql: `SELECT COUNT(*) as cnt FROM tasks
5357
+ WHERE status = 'needs_review'${scope.sql}`,
5358
+ args: [...scope.args]
4468
5359
  });
4469
5360
  return Number(result.rows[0]?.cnt) || 0;
4470
5361
  }
4471
5362
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
4472
5363
  const client = getClient();
4473
- if (sessionScope) {
4474
- const result2 = await client.execute({
4475
- sql: `SELECT COUNT(*) as cnt FROM tasks
4476
- WHERE status = 'needs_review' AND updated_at > ?
4477
- AND session_scope = ?`,
4478
- args: [sinceIso, sessionScope]
4479
- });
4480
- return Number(result2.rows[0]?.cnt) || 0;
4481
- }
5364
+ const scope = strictSessionScopeFilter(
5365
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
5366
+ );
4482
5367
  const result = await client.execute({
4483
5368
  sql: `SELECT COUNT(*) as cnt FROM tasks
4484
- WHERE status = 'needs_review' AND updated_at > ?`,
4485
- args: [sinceIso]
5369
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
5370
+ args: [sinceIso, ...scope.args]
4486
5371
  });
4487
5372
  return Number(result.rows[0]?.cnt) || 0;
4488
5373
  }
4489
5374
  async function listPendingReviews(limit, sessionScope) {
4490
5375
  const client = getClient();
4491
- if (sessionScope) {
4492
- const result2 = await client.execute({
4493
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
4494
- WHERE status = 'needs_review'
4495
- AND session_scope = ?
4496
- ORDER BY updated_at ASC LIMIT ?`,
4497
- args: [sessionScope, limit]
4498
- });
4499
- return result2.rows;
4500
- }
5376
+ const scope = strictSessionScopeFilter(
5377
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
5378
+ );
4501
5379
  const result = await client.execute({
4502
5380
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
4503
- WHERE status = 'needs_review'
5381
+ WHERE status = 'needs_review'${scope.sql}
4504
5382
  ORDER BY updated_at ASC LIMIT ?`,
4505
- args: [limit]
5383
+ args: [...scope.args, limit]
4506
5384
  });
4507
5385
  return result.rows;
4508
5386
  }
@@ -4514,7 +5392,7 @@ async function cleanupOrphanedReviews() {
4514
5392
  WHERE status IN ('open', 'needs_review', 'in_progress')
4515
5393
  AND assigned_by = 'system'
4516
5394
  AND title LIKE 'Review:%'
4517
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
5395
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
4518
5396
  args: [now]
4519
5397
  });
4520
5398
  const r1b = await client.execute({
@@ -4633,11 +5511,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
4633
5511
  );
4634
5512
  }
4635
5513
  try {
4636
- const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
4637
- if (existsSync13(cacheDir)) {
5514
+ const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
5515
+ if (existsSync15(cacheDir)) {
4638
5516
  for (const f of readdirSync3(cacheDir)) {
4639
5517
  if (f.startsWith("review-notified-")) {
4640
- unlinkSync4(path13.join(cacheDir, f));
5518
+ unlinkSync4(path15.join(cacheDir, f));
4641
5519
  }
4642
5520
  }
4643
5521
  }
@@ -4654,11 +5532,12 @@ var init_tasks_review = __esm({
4654
5532
  init_tmux_routing();
4655
5533
  init_session_key();
4656
5534
  init_state_bus();
5535
+ init_task_scope();
4657
5536
  }
4658
5537
  });
4659
5538
 
4660
5539
  // src/lib/tasks-chain.ts
4661
- import path14 from "path";
5540
+ import path16 from "path";
4662
5541
  import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
4663
5542
  async function cascadeUnblock(taskId, baseDir, now) {
4664
5543
  const client = getClient();
@@ -4675,7 +5554,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
4675
5554
  });
4676
5555
  for (const ur of unblockedRows.rows) {
4677
5556
  try {
4678
- const ubFile = path14.join(baseDir, String(ur.task_file));
5557
+ const ubFile = path16.join(baseDir, String(ur.task_file));
4679
5558
  let ubContent = await readFile4(ubFile, "utf-8");
4680
5559
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
4681
5560
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -4710,7 +5589,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
4710
5589
  const scScope = sessionScopeFilter();
4711
5590
  const remaining = await client.execute({
4712
5591
  sql: `SELECT COUNT(*) as cnt FROM tasks
4713
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
5592
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
4714
5593
  args: [parentTaskId, ...scScope.args]
4715
5594
  });
4716
5595
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -4749,7 +5628,7 @@ __export(project_name_exports, {
4749
5628
  getProjectName: () => getProjectName
4750
5629
  });
4751
5630
  import { execSync as execSync6 } from "child_process";
4752
- import path15 from "path";
5631
+ import path17 from "path";
4753
5632
  function getProjectName(cwd) {
4754
5633
  const dir = cwd ?? process.cwd();
4755
5634
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -4762,7 +5641,7 @@ function getProjectName(cwd) {
4762
5641
  timeout: 2e3,
4763
5642
  stdio: ["pipe", "pipe", "pipe"]
4764
5643
  }).trim();
4765
- repoRoot = path15.dirname(gitCommonDir);
5644
+ repoRoot = path17.dirname(gitCommonDir);
4766
5645
  } catch {
4767
5646
  repoRoot = execSync6("git rev-parse --show-toplevel", {
4768
5647
  cwd: dir,
@@ -4771,11 +5650,11 @@ function getProjectName(cwd) {
4771
5650
  stdio: ["pipe", "pipe", "pipe"]
4772
5651
  }).trim();
4773
5652
  }
4774
- _cached2 = path15.basename(repoRoot);
5653
+ _cached2 = path17.basename(repoRoot);
4775
5654
  _cachedCwd = dir;
4776
5655
  return _cached2;
4777
5656
  } catch {
4778
- _cached2 = path15.basename(dir);
5657
+ _cached2 = path17.basename(dir);
4779
5658
  _cachedCwd = dir;
4780
5659
  return _cached2;
4781
5660
  }
@@ -4922,10 +5801,10 @@ var init_tasks_notify = __esm({
4922
5801
  });
4923
5802
 
4924
5803
  // src/lib/behaviors.ts
4925
- import crypto4 from "crypto";
5804
+ import crypto5 from "crypto";
4926
5805
  async function storeBehavior(opts) {
4927
5806
  const client = getClient();
4928
- const id = crypto4.randomUUID();
5807
+ const id = crypto5.randomUUID();
4929
5808
  const now = (/* @__PURE__ */ new Date()).toISOString();
4930
5809
  await client.execute({
4931
5810
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -4954,7 +5833,7 @@ __export(skill_learning_exports, {
4954
5833
  storeTrajectory: () => storeTrajectory,
4955
5834
  sweepTrajectories: () => sweepTrajectories
4956
5835
  });
4957
- import crypto5 from "crypto";
5836
+ import crypto6 from "crypto";
4958
5837
  async function extractTrajectory(taskId, agentId) {
4959
5838
  const client = getClient();
4960
5839
  const result = await client.execute({
@@ -4983,11 +5862,11 @@ async function extractTrajectory(taskId, agentId) {
4983
5862
  return signature;
4984
5863
  }
4985
5864
  function hashSignature(signature) {
4986
- return crypto5.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
5865
+ return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4987
5866
  }
4988
5867
  async function storeTrajectory(opts) {
4989
5868
  const client = getClient();
4990
- const id = crypto5.randomUUID();
5869
+ const id = crypto6.randomUUID();
4991
5870
  const now = (/* @__PURE__ */ new Date()).toISOString();
4992
5871
  const signatureHash = hashSignature(opts.signature);
4993
5872
  await client.execute({
@@ -5252,8 +6131,8 @@ __export(tasks_exports, {
5252
6131
  updateTaskStatus: () => updateTaskStatus,
5253
6132
  writeCheckpoint: () => writeCheckpoint
5254
6133
  });
5255
- import path16 from "path";
5256
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
6134
+ import path18 from "path";
6135
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
5257
6136
  async function createTask(input) {
5258
6137
  const result = await createTaskCore(input);
5259
6138
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -5272,12 +6151,12 @@ async function updateTask(input) {
5272
6151
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
5273
6152
  try {
5274
6153
  const agent = String(row.assigned_to);
5275
- const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
5276
- const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
6154
+ const cacheDir = path18.join(EXE_AI_DIR, "session-cache");
6155
+ const cachePath = path18.join(cacheDir, `current-task-${agent}.json`);
5277
6156
  if (input.status === "in_progress") {
5278
6157
  mkdirSync6(cacheDir, { recursive: true });
5279
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
5280
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
6158
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
6159
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
5281
6160
  try {
5282
6161
  unlinkSync5(cachePath);
5283
6162
  } catch {
@@ -5285,10 +6164,10 @@ async function updateTask(input) {
5285
6164
  }
5286
6165
  } catch {
5287
6166
  }
5288
- if (input.status === "done") {
6167
+ if (input.status === "done" || input.status === "closed") {
5289
6168
  await cleanupReviewFile(row, taskFile, input.baseDir);
5290
6169
  }
5291
- if (input.status === "done" || input.status === "cancelled") {
6170
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
5292
6171
  try {
5293
6172
  const client = getClient();
5294
6173
  const taskTitle = String(row.title);
@@ -5304,7 +6183,7 @@ async function updateTask(input) {
5304
6183
  if (!isCoordinatorName(assignedAgent)) {
5305
6184
  try {
5306
6185
  const draftClient = getClient();
5307
- if (input.status === "done") {
6186
+ if (input.status === "done" || input.status === "closed") {
5308
6187
  await draftClient.execute({
5309
6188
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
5310
6189
  args: [assignedAgent]
@@ -5321,7 +6200,7 @@ async function updateTask(input) {
5321
6200
  try {
5322
6201
  const client = getClient();
5323
6202
  const cascaded = await client.execute({
5324
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
6203
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
5325
6204
  WHERE parent_task_id = ? AND status = 'needs_review'`,
5326
6205
  args: [now, taskId]
5327
6206
  });
@@ -5334,14 +6213,14 @@ async function updateTask(input) {
5334
6213
  } catch {
5335
6214
  }
5336
6215
  }
5337
- const isTerminal = input.status === "done" || input.status === "needs_review";
6216
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
5338
6217
  if (isTerminal) {
5339
6218
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
5340
6219
  if (!isCoordinator) {
5341
6220
  notifyTaskDone();
5342
6221
  }
5343
6222
  await markTaskNotificationsRead(taskFile);
5344
- if (input.status === "done") {
6223
+ if (input.status === "done" || input.status === "closed") {
5345
6224
  try {
5346
6225
  await cascadeUnblock(taskId, input.baseDir, now);
5347
6226
  } catch {
@@ -5361,7 +6240,7 @@ async function updateTask(input) {
5361
6240
  }
5362
6241
  }
5363
6242
  }
5364
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
6243
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
5365
6244
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
5366
6245
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
5367
6246
  taskId,
@@ -5733,6 +6612,7 @@ __export(tmux_routing_exports, {
5733
6612
  isEmployeeAlive: () => isEmployeeAlive,
5734
6613
  isExeSession: () => isExeSession,
5735
6614
  isSessionBusy: () => isSessionBusy,
6615
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
5736
6616
  notifyParentExe: () => notifyParentExe,
5737
6617
  parseParentExe: () => parseParentExe,
5738
6618
  registerParentExe: () => registerParentExe,
@@ -5743,13 +6623,13 @@ __export(tmux_routing_exports, {
5743
6623
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
5744
6624
  });
5745
6625
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
5746
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync4 } from "fs";
5747
- import path17 from "path";
5748
- import os9 from "os";
6626
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync4 } from "fs";
6627
+ import path19 from "path";
6628
+ import os11 from "os";
5749
6629
  import { fileURLToPath as fileURLToPath2 } from "url";
5750
6630
  import { unlinkSync as unlinkSync6 } from "fs";
5751
6631
  function spawnLockPath(sessionName) {
5752
- return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
6632
+ return path19.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5753
6633
  }
5754
6634
  function isProcessAlive(pid) {
5755
6635
  try {
@@ -5760,13 +6640,13 @@ function isProcessAlive(pid) {
5760
6640
  }
5761
6641
  }
5762
6642
  function acquireSpawnLock2(sessionName) {
5763
- if (!existsSync14(SPAWN_LOCK_DIR)) {
6643
+ if (!existsSync16(SPAWN_LOCK_DIR)) {
5764
6644
  mkdirSync7(SPAWN_LOCK_DIR, { recursive: true });
5765
6645
  }
5766
6646
  const lockFile = spawnLockPath(sessionName);
5767
- if (existsSync14(lockFile)) {
6647
+ if (existsSync16(lockFile)) {
5768
6648
  try {
5769
- const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
6649
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
5770
6650
  const age = Date.now() - lock.timestamp;
5771
6651
  if (isProcessAlive(lock.pid) && age < 6e4) {
5772
6652
  return false;
@@ -5774,7 +6654,7 @@ function acquireSpawnLock2(sessionName) {
5774
6654
  } catch {
5775
6655
  }
5776
6656
  }
5777
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
6657
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5778
6658
  return true;
5779
6659
  }
5780
6660
  function releaseSpawnLock2(sessionName) {
@@ -5786,13 +6666,13 @@ function releaseSpawnLock2(sessionName) {
5786
6666
  function resolveBehaviorsExporterScript() {
5787
6667
  try {
5788
6668
  const thisFile = fileURLToPath2(import.meta.url);
5789
- const scriptPath = path17.join(
5790
- path17.dirname(thisFile),
6669
+ const scriptPath = path19.join(
6670
+ path19.dirname(thisFile),
5791
6671
  "..",
5792
6672
  "bin",
5793
6673
  "exe-export-behaviors.js"
5794
6674
  );
5795
- return existsSync14(scriptPath) ? scriptPath : null;
6675
+ return existsSync16(scriptPath) ? scriptPath : null;
5796
6676
  } catch {
5797
6677
  return null;
5798
6678
  }
@@ -5858,12 +6738,12 @@ function extractRootExe(name) {
5858
6738
  return parts.length > 0 ? parts[parts.length - 1] : null;
5859
6739
  }
5860
6740
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5861
- if (!existsSync14(SESSION_CACHE)) {
6741
+ if (!existsSync16(SESSION_CACHE)) {
5862
6742
  mkdirSync7(SESSION_CACHE, { recursive: true });
5863
6743
  }
5864
6744
  const rootExe = extractRootExe(parentExe) ?? parentExe;
5865
- const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5866
- writeFileSync7(filePath, JSON.stringify({
6745
+ const filePath = path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
6746
+ writeFileSync8(filePath, JSON.stringify({
5867
6747
  parentExe: rootExe,
5868
6748
  dispatchedBy: dispatchedBy || rootExe,
5869
6749
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5871,7 +6751,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5871
6751
  }
5872
6752
  function getParentExe(sessionKey) {
5873
6753
  try {
5874
- const data = JSON.parse(readFileSync11(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
6754
+ const data = JSON.parse(readFileSync12(path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5875
6755
  return data.parentExe || null;
5876
6756
  } catch {
5877
6757
  return null;
@@ -5879,8 +6759,8 @@ function getParentExe(sessionKey) {
5879
6759
  }
5880
6760
  function getDispatchedBy(sessionKey) {
5881
6761
  try {
5882
- const data = JSON.parse(readFileSync11(
5883
- path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
6762
+ const data = JSON.parse(readFileSync12(
6763
+ path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5884
6764
  "utf8"
5885
6765
  ));
5886
6766
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -5950,8 +6830,8 @@ async function verifyPaneAtCapacity(sessionName) {
5950
6830
  }
5951
6831
  function readDebounceState() {
5952
6832
  try {
5953
- if (!existsSync14(DEBOUNCE_FILE)) return {};
5954
- const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
6833
+ if (!existsSync16(DEBOUNCE_FILE)) return {};
6834
+ const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
5955
6835
  const state = {};
5956
6836
  for (const [key, val] of Object.entries(raw)) {
5957
6837
  if (typeof val === "number") {
@@ -5967,8 +6847,8 @@ function readDebounceState() {
5967
6847
  }
5968
6848
  function writeDebounceState(state) {
5969
6849
  try {
5970
- if (!existsSync14(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
5971
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
6850
+ if (!existsSync16(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
6851
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
5972
6852
  } catch {
5973
6853
  }
5974
6854
  }
@@ -6066,8 +6946,8 @@ function sendIntercom(targetSession) {
6066
6946
  try {
6067
6947
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
6068
6948
  const agent = baseAgentName(rawAgent);
6069
- const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
6070
- if (existsSync14(markerPath)) {
6949
+ const markerPath = path19.join(SESSION_CACHE, `current-task-${agent}.json`);
6950
+ if (existsSync16(markerPath)) {
6071
6951
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
6072
6952
  return "debounced";
6073
6953
  }
@@ -6076,8 +6956,8 @@ function sendIntercom(targetSession) {
6076
6956
  try {
6077
6957
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
6078
6958
  const agent = baseAgentName(rawAgent);
6079
- const taskDir = path17.join(process.cwd(), "exe", agent);
6080
- if (existsSync14(taskDir)) {
6959
+ const taskDir = path19.join(process.cwd(), "exe", agent);
6960
+ if (existsSync16(taskDir)) {
6081
6961
  const files = readdirSync4(taskDir).filter(
6082
6962
  (f) => f.endsWith(".md") && f !== "DONE.txt"
6083
6963
  );
@@ -6137,6 +7017,21 @@ function notifyParentExe(sessionKey) {
6137
7017
  }
6138
7018
  return true;
6139
7019
  }
7020
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
7021
+ const transport = getTransport();
7022
+ try {
7023
+ const sessions = transport.listSessions();
7024
+ if (!sessions.includes(coordinatorSession)) return false;
7025
+ execSync7(
7026
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
7027
+ { timeout: 3e3 }
7028
+ );
7029
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
7030
+ return true;
7031
+ } catch {
7032
+ return false;
7033
+ }
7034
+ }
6140
7035
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
6141
7036
  if (isCoordinatorName(employeeName)) {
6142
7037
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -6210,26 +7105,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6210
7105
  const transport = getTransport();
6211
7106
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
6212
7107
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
6213
- const logDir = path17.join(os9.homedir(), ".exe-os", "session-logs");
6214
- const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
6215
- if (!existsSync14(logDir)) {
7108
+ const logDir = path19.join(os11.homedir(), ".exe-os", "session-logs");
7109
+ const logFile = path19.join(logDir, `${instanceLabel}-${Date.now()}.log`);
7110
+ if (!existsSync16(logDir)) {
6216
7111
  mkdirSync7(logDir, { recursive: true });
6217
7112
  }
6218
7113
  transport.kill(sessionName);
6219
7114
  let cleanupSuffix = "";
6220
7115
  try {
6221
7116
  const thisFile = fileURLToPath2(import.meta.url);
6222
- const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
6223
- if (existsSync14(cleanupScript)) {
7117
+ const cleanupScript = path19.join(path19.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
7118
+ if (existsSync16(cleanupScript)) {
6224
7119
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
6225
7120
  }
6226
7121
  } catch {
6227
7122
  }
6228
7123
  try {
6229
- const claudeJsonPath = path17.join(os9.homedir(), ".claude.json");
7124
+ const claudeJsonPath = path19.join(os11.homedir(), ".claude.json");
6230
7125
  let claudeJson = {};
6231
7126
  try {
6232
- claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
7127
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
6233
7128
  } catch {
6234
7129
  }
6235
7130
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -6237,17 +7132,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6237
7132
  const trustDir = opts?.cwd ?? projectDir;
6238
7133
  if (!projects[trustDir]) projects[trustDir] = {};
6239
7134
  projects[trustDir].hasTrustDialogAccepted = true;
6240
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
7135
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6241
7136
  } catch {
6242
7137
  }
6243
7138
  try {
6244
- const settingsDir = path17.join(os9.homedir(), ".claude", "projects");
7139
+ const settingsDir = path19.join(os11.homedir(), ".claude", "projects");
6245
7140
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
6246
- const projSettingsDir = path17.join(settingsDir, normalizedKey);
6247
- const settingsPath = path17.join(projSettingsDir, "settings.json");
7141
+ const projSettingsDir = path19.join(settingsDir, normalizedKey);
7142
+ const settingsPath = path19.join(projSettingsDir, "settings.json");
6248
7143
  let settings = {};
6249
7144
  try {
6250
- settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
7145
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
6251
7146
  } catch {
6252
7147
  }
6253
7148
  const perms = settings.permissions ?? {};
@@ -6276,7 +7171,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6276
7171
  perms.allow = allow;
6277
7172
  settings.permissions = perms;
6278
7173
  mkdirSync7(projSettingsDir, { recursive: true });
6279
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
7174
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6280
7175
  }
6281
7176
  } catch {
6282
7177
  }
@@ -6291,8 +7186,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6291
7186
  let behaviorsFlag = "";
6292
7187
  let legacyFallbackWarned = false;
6293
7188
  if (!useExeAgent && !useBinSymlink) {
6294
- const identityPath = path17.join(
6295
- os9.homedir(),
7189
+ const identityPath = path19.join(
7190
+ os11.homedir(),
6296
7191
  ".exe-os",
6297
7192
  "identity",
6298
7193
  `${employeeName}.md`
@@ -6301,13 +7196,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6301
7196
  const hasAgentFlag = claudeSupportsAgentFlag();
6302
7197
  if (hasAgentFlag) {
6303
7198
  identityFlag = ` --agent ${employeeName}`;
6304
- } else if (existsSync14(identityPath)) {
7199
+ } else if (existsSync16(identityPath)) {
6305
7200
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
6306
7201
  legacyFallbackWarned = true;
6307
7202
  }
6308
7203
  const behaviorsFile = exportBehaviorsSync(
6309
7204
  employeeName,
6310
- path17.basename(spawnCwd),
7205
+ path19.basename(spawnCwd),
6311
7206
  sessionName
6312
7207
  );
6313
7208
  if (behaviorsFile) {
@@ -6322,16 +7217,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6322
7217
  }
6323
7218
  let sessionContextFlag = "";
6324
7219
  try {
6325
- const ctxDir = path17.join(os9.homedir(), ".exe-os", "session-cache");
7220
+ const ctxDir = path19.join(os11.homedir(), ".exe-os", "session-cache");
6326
7221
  mkdirSync7(ctxDir, { recursive: true });
6327
- const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
7222
+ const ctxFile = path19.join(ctxDir, `session-context-${sessionName}.md`);
6328
7223
  const ctxContent = [
6329
7224
  `## Session Context`,
6330
7225
  `You are running in tmux session: ${sessionName}.`,
6331
7226
  `Your parent coordinator session is ${exeSession}.`,
6332
7227
  `Your employees (if any) use the -${exeSession} suffix.`
6333
7228
  ].join("\n");
6334
- writeFileSync7(ctxFile, ctxContent);
7229
+ writeFileSync8(ctxFile, ctxContent);
6335
7230
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
6336
7231
  } catch {
6337
7232
  }
@@ -6408,8 +7303,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6408
7303
  transport.pipeLog(sessionName, logFile);
6409
7304
  try {
6410
7305
  const mySession = getMySession();
6411
- const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6412
- writeFileSync7(dispatchInfo, JSON.stringify({
7306
+ const dispatchInfo = path19.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
7307
+ writeFileSync8(dispatchInfo, JSON.stringify({
6413
7308
  dispatchedBy: mySession,
6414
7309
  rootExe: exeSession,
6415
7310
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -6483,15 +7378,15 @@ var init_tmux_routing = __esm({
6483
7378
  init_intercom_queue();
6484
7379
  init_plan_limits();
6485
7380
  init_employees();
6486
- SPAWN_LOCK_DIR = path17.join(os9.homedir(), ".exe-os", "spawn-locks");
6487
- SESSION_CACHE = path17.join(os9.homedir(), ".exe-os", "session-cache");
7381
+ SPAWN_LOCK_DIR = path19.join(os11.homedir(), ".exe-os", "spawn-locks");
7382
+ SESSION_CACHE = path19.join(os11.homedir(), ".exe-os", "session-cache");
6488
7383
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
6489
7384
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
6490
7385
  VERIFY_PANE_LINES = 200;
6491
7386
  INTERCOM_DEBOUNCE_MS = 3e4;
6492
7387
  CODEX_DEBOUNCE_MS = 12e4;
6493
- INTERCOM_LOG2 = path17.join(os9.homedir(), ".exe-os", "intercom.log");
6494
- DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
7388
+ INTERCOM_LOG2 = path19.join(os11.homedir(), ".exe-os", "intercom.log");
7389
+ DEBOUNCE_FILE = path19.join(SESSION_CACHE, "intercom-debounce.json");
6495
7390
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
6496
7391
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
6497
7392
  }
@@ -6514,6 +7409,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
6514
7409
  args: [scope]
6515
7410
  };
6516
7411
  }
7412
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
7413
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
7414
+ if (!scope) return { sql: "", args: [] };
7415
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
7416
+ return {
7417
+ sql: ` AND ${col} = ?`,
7418
+ args: [scope]
7419
+ };
7420
+ }
6517
7421
  var init_task_scope = __esm({
6518
7422
  "src/lib/task-scope.ts"() {
6519
7423
  "use strict";
@@ -6548,14 +7452,14 @@ __export(worker_gate_exports, {
6548
7452
  tryAcquireBackfillLock: () => tryAcquireBackfillLock,
6549
7453
  tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
6550
7454
  });
6551
- import { readdirSync as readdirSync6, writeFileSync as writeFileSync9, unlinkSync as unlinkSync8, mkdirSync as mkdirSync9, existsSync as existsSync15 } from "fs";
6552
- import path19 from "path";
7455
+ import { readdirSync as readdirSync6, writeFileSync as writeFileSync10, unlinkSync as unlinkSync8, mkdirSync as mkdirSync9, existsSync as existsSync17 } from "fs";
7456
+ import path21 from "path";
6553
7457
  function tryAcquireWorkerSlot() {
6554
7458
  try {
6555
7459
  mkdirSync9(WORKER_PID_DIR, { recursive: true });
6556
7460
  const reservationId = `res-${process.pid}-${Date.now()}`;
6557
- const reservationPath = path19.join(WORKER_PID_DIR, `${reservationId}.pid`);
6558
- writeFileSync9(reservationPath, String(process.pid));
7461
+ const reservationPath = path21.join(WORKER_PID_DIR, `${reservationId}.pid`);
7462
+ writeFileSync10(reservationPath, String(process.pid));
6559
7463
  const files = readdirSync6(WORKER_PID_DIR);
6560
7464
  let alive = 0;
6561
7465
  for (const f of files) {
@@ -6572,7 +7476,7 @@ function tryAcquireWorkerSlot() {
6572
7476
  alive++;
6573
7477
  } catch {
6574
7478
  try {
6575
- unlinkSync8(path19.join(WORKER_PID_DIR, f));
7479
+ unlinkSync8(path21.join(WORKER_PID_DIR, f));
6576
7480
  } catch {
6577
7481
  }
6578
7482
  }
@@ -6596,20 +7500,20 @@ function tryAcquireWorkerSlot() {
6596
7500
  function registerWorkerPid(pid) {
6597
7501
  try {
6598
7502
  mkdirSync9(WORKER_PID_DIR, { recursive: true });
6599
- writeFileSync9(path19.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
7503
+ writeFileSync10(path21.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
6600
7504
  } catch {
6601
7505
  }
6602
7506
  }
6603
7507
  function cleanupWorkerPid() {
6604
7508
  try {
6605
- unlinkSync8(path19.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
7509
+ unlinkSync8(path21.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
6606
7510
  } catch {
6607
7511
  }
6608
7512
  }
6609
7513
  function tryAcquireBackfillLock() {
6610
7514
  try {
6611
7515
  mkdirSync9(WORKER_PID_DIR, { recursive: true });
6612
- if (existsSync15(BACKFILL_LOCK)) {
7516
+ if (existsSync17(BACKFILL_LOCK)) {
6613
7517
  try {
6614
7518
  const pid = parseInt(
6615
7519
  __require("fs").readFileSync(BACKFILL_LOCK, "utf8").trim(),
@@ -6625,7 +7529,7 @@ function tryAcquireBackfillLock() {
6625
7529
  } catch {
6626
7530
  }
6627
7531
  }
6628
- writeFileSync9(BACKFILL_LOCK, String(process.pid));
7532
+ writeFileSync10(BACKFILL_LOCK, String(process.pid));
6629
7533
  return true;
6630
7534
  } catch {
6631
7535
  return true;
@@ -6642,9 +7546,9 @@ var init_worker_gate = __esm({
6642
7546
  "src/lib/worker-gate.ts"() {
6643
7547
  "use strict";
6644
7548
  init_config();
6645
- WORKER_PID_DIR = path19.join(EXE_AI_DIR, "worker-pids");
7549
+ WORKER_PID_DIR = path21.join(EXE_AI_DIR, "worker-pids");
6646
7550
  MAX_CONCURRENT_WORKERS = 3;
6647
- BACKFILL_LOCK = path19.join(WORKER_PID_DIR, "backfill.lock");
7551
+ BACKFILL_LOCK = path21.join(WORKER_PID_DIR, "backfill.lock");
6648
7552
  }
6649
7553
  });
6650
7554
 
@@ -6656,13 +7560,13 @@ __export(crypto_exports, {
6656
7560
  initSyncCrypto: () => initSyncCrypto,
6657
7561
  isSyncCryptoInitialized: () => isSyncCryptoInitialized
6658
7562
  });
6659
- import crypto6 from "crypto";
7563
+ import crypto7 from "crypto";
6660
7564
  function initSyncCrypto(masterKey) {
6661
7565
  if (masterKey.length !== 32) {
6662
7566
  throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
6663
7567
  }
6664
7568
  _syncKey = Buffer.from(
6665
- crypto6.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
7569
+ crypto7.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
6666
7570
  );
6667
7571
  }
6668
7572
  function isSyncCryptoInitialized() {
@@ -6676,8 +7580,8 @@ function requireSyncKey() {
6676
7580
  }
6677
7581
  function encryptSyncBlob(data) {
6678
7582
  const key = requireSyncKey();
6679
- const iv = crypto6.randomBytes(IV_LENGTH);
6680
- const cipher = crypto6.createCipheriv(ALGORITHM, key, iv);
7583
+ const iv = crypto7.randomBytes(IV_LENGTH);
7584
+ const cipher = crypto7.createCipheriv(ALGORITHM, key, iv);
6681
7585
  const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
6682
7586
  const tag = cipher.getAuthTag();
6683
7587
  return Buffer.concat([iv, encrypted, tag]).toString("base64");
@@ -6691,7 +7595,7 @@ function decryptSyncBlob(ciphertext) {
6691
7595
  const iv = combined.subarray(0, IV_LENGTH);
6692
7596
  const tag = combined.subarray(combined.length - TAG_LENGTH);
6693
7597
  const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
6694
- const decipher = crypto6.createDecipheriv(ALGORITHM, key, iv);
7598
+ const decipher = crypto7.createDecipheriv(ALGORITHM, key, iv);
6695
7599
  decipher.setAuthTag(tag);
6696
7600
  return Buffer.concat([decipher.update(encrypted), decipher.final()]);
6697
7601
  }
@@ -6746,8 +7650,8 @@ __export(crdt_sync_exports, {
6746
7650
  rebuildFromDb: () => rebuildFromDb
6747
7651
  });
6748
7652
  import * as Y from "yjs";
6749
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync16, mkdirSync as mkdirSync10, unlinkSync as unlinkSync9 } from "fs";
6750
- import path20 from "path";
7653
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync11, existsSync as existsSync18, mkdirSync as mkdirSync10, unlinkSync as unlinkSync9 } from "fs";
7654
+ import path22 from "path";
6751
7655
  import { homedir } from "os";
6752
7656
  function getStatePath() {
6753
7657
  return _statePathOverride ?? DEFAULT_STATE_PATH;
@@ -6759,9 +7663,9 @@ function initCrdtDoc() {
6759
7663
  if (doc) return doc;
6760
7664
  doc = new Y.Doc();
6761
7665
  const sp = getStatePath();
6762
- if (existsSync16(sp)) {
7666
+ if (existsSync18(sp)) {
6763
7667
  try {
6764
- const state = readFileSync13(sp);
7668
+ const state = readFileSync14(sp);
6765
7669
  Y.applyUpdate(doc, new Uint8Array(state));
6766
7670
  } catch {
6767
7671
  console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
@@ -6903,10 +7807,10 @@ function persistState() {
6903
7807
  if (!doc) return;
6904
7808
  try {
6905
7809
  const sp = getStatePath();
6906
- const dir = path20.dirname(sp);
6907
- if (!existsSync16(dir)) mkdirSync10(dir, { recursive: true });
7810
+ const dir = path22.dirname(sp);
7811
+ if (!existsSync18(dir)) mkdirSync10(dir, { recursive: true });
6908
7812
  const state = Y.encodeStateAsUpdate(doc);
6909
- writeFileSync10(sp, Buffer.from(state));
7813
+ writeFileSync11(sp, Buffer.from(state));
6910
7814
  } catch {
6911
7815
  }
6912
7816
  }
@@ -6947,7 +7851,7 @@ var DEFAULT_STATE_PATH, _statePathOverride, doc;
6947
7851
  var init_crdt_sync = __esm({
6948
7852
  "src/lib/crdt-sync.ts"() {
6949
7853
  "use strict";
6950
- DEFAULT_STATE_PATH = path20.join(homedir(), ".exe-os", "crdt-state.bin");
7854
+ DEFAULT_STATE_PATH = path22.join(homedir(), ".exe-os", "crdt-state.bin");
6951
7855
  _statePathOverride = null;
6952
7856
  doc = null;
6953
7857
  }
@@ -6979,39 +7883,107 @@ __export(cloud_sync_exports, {
6979
7883
  cloudSync: () => cloudSync,
6980
7884
  mergeConfig: () => mergeConfig,
6981
7885
  mergeRosterFromRemote: () => mergeRosterFromRemote,
7886
+ pushToPostgres: () => pushToPostgres,
6982
7887
  recordRosterDeletion: () => recordRosterDeletion
6983
7888
  });
6984
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync11, existsSync as existsSync17, readdirSync as readdirSync7, mkdirSync as mkdirSync11, appendFileSync as appendFileSync2, unlinkSync as unlinkSync10, openSync as openSync2, closeSync as closeSync2 } from "fs";
6985
- import crypto7 from "crypto";
6986
- import path21 from "path";
7889
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync12, existsSync as existsSync19, readdirSync as readdirSync7, mkdirSync as mkdirSync11, appendFileSync as appendFileSync2, unlinkSync as unlinkSync10, openSync as openSync2, closeSync as closeSync2 } from "fs";
7890
+ import crypto8 from "crypto";
7891
+ import path23 from "path";
6987
7892
  import { homedir as homedir2 } from "os";
6988
7893
  function sqlSafe(v) {
6989
7894
  return v === void 0 ? null : v;
6990
7895
  }
6991
7896
  function logError(msg) {
6992
7897
  try {
6993
- const logPath = path21.join(homedir2(), ".exe-os", "workers.log");
7898
+ const logPath = path23.join(homedir2(), ".exe-os", "workers.log");
6994
7899
  appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
6995
7900
  `);
6996
7901
  } catch {
6997
7902
  }
6998
7903
  }
7904
+ function loadPgClient() {
7905
+ if (_pgFailed) return null;
7906
+ const postgresUrl = process.env.DATABASE_URL;
7907
+ const configPath = path23.join(EXE_AI_DIR, "config.json");
7908
+ let cloudPostgresUrl;
7909
+ try {
7910
+ if (existsSync19(configPath)) {
7911
+ const cfg = JSON.parse(readFileSync15(configPath, "utf8"));
7912
+ cloudPostgresUrl = cfg.cloud?.postgresUrl;
7913
+ if (cfg.cloud?.syncToPostgres === false) {
7914
+ _pgFailed = true;
7915
+ return null;
7916
+ }
7917
+ }
7918
+ } catch {
7919
+ }
7920
+ const url = postgresUrl || cloudPostgresUrl;
7921
+ if (!url) {
7922
+ _pgFailed = true;
7923
+ return null;
7924
+ }
7925
+ if (!_pgPromise) {
7926
+ _pgPromise = (async () => {
7927
+ const { createRequire: createRequire3 } = await import("module");
7928
+ const { pathToFileURL: pathToFileURL3 } = await import("url");
7929
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path23.join(homedir2(), "exe-db");
7930
+ const req = createRequire3(path23.join(exeDbRoot, "package.json"));
7931
+ const entry = req.resolve("@prisma/client");
7932
+ const mod = await import(pathToFileURL3(entry).href);
7933
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
7934
+ if (!Ctor) throw new Error("No PrismaClient");
7935
+ return new Ctor();
7936
+ })().catch(() => {
7937
+ _pgFailed = true;
7938
+ _pgPromise = null;
7939
+ throw new Error("pg_unavailable");
7940
+ });
7941
+ }
7942
+ return _pgPromise;
7943
+ }
7944
+ async function pushToPostgres(records) {
7945
+ const loader = loadPgClient();
7946
+ if (!loader) return 0;
7947
+ let prisma;
7948
+ try {
7949
+ prisma = await loader;
7950
+ } catch {
7951
+ return 0;
7952
+ }
7953
+ let inserted = 0;
7954
+ for (const rec of records) {
7955
+ try {
7956
+ await prisma.$executeRawUnsafe(
7957
+ `INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
7958
+ VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
7959
+ ON CONFLICT (source, source_id, event_type) DO NOTHING`,
7960
+ String(rec.id ?? ""),
7961
+ JSON.stringify(rec),
7962
+ JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
7963
+ rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
7964
+ );
7965
+ inserted++;
7966
+ } catch {
7967
+ }
7968
+ }
7969
+ return inserted;
7970
+ }
6999
7971
  async function withRosterLock(fn) {
7000
7972
  try {
7001
7973
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
7002
7974
  closeSync2(fd);
7003
- writeFileSync11(ROSTER_LOCK_PATH, String(Date.now()));
7975
+ writeFileSync12(ROSTER_LOCK_PATH, String(Date.now()));
7004
7976
  } catch (err) {
7005
7977
  if (err.code === "EEXIST") {
7006
7978
  try {
7007
- const ts = parseInt(readFileSync14(ROSTER_LOCK_PATH, "utf-8"), 10);
7979
+ const ts = parseInt(readFileSync15(ROSTER_LOCK_PATH, "utf-8"), 10);
7008
7980
  if (Date.now() - ts < LOCK_STALE_MS) {
7009
7981
  throw new Error("Roster merge already in progress \u2014 another sync is running");
7010
7982
  }
7011
7983
  unlinkSync10(ROSTER_LOCK_PATH);
7012
7984
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
7013
7985
  closeSync2(fd);
7014
- writeFileSync11(ROSTER_LOCK_PATH, String(Date.now()));
7986
+ writeFileSync12(ROSTER_LOCK_PATH, String(Date.now()));
7015
7987
  } catch (retryErr) {
7016
7988
  if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
7017
7989
  throw new Error("Roster merge already in progress \u2014 another sync is running");
@@ -7281,6 +8253,10 @@ async function cloudSync(config) {
7281
8253
  const maxVersion = Number(records[records.length - 1].version);
7282
8254
  const pushOk = await cloudPush(records, maxVersion, config);
7283
8255
  if (!pushOk) break;
8256
+ try {
8257
+ await pushToPostgres(records);
8258
+ } catch {
8259
+ }
7284
8260
  await client.execute({
7285
8261
  sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
7286
8262
  args: [String(maxVersion)]
@@ -7385,8 +8361,8 @@ async function cloudSync(config) {
7385
8361
  try {
7386
8362
  const employees = await loadEmployees();
7387
8363
  rosterResult.employees = employees.length;
7388
- const idDir = path21.join(EXE_AI_DIR, "identity");
7389
- if (existsSync17(idDir)) {
8364
+ const idDir = path23.join(EXE_AI_DIR, "identity");
8365
+ if (existsSync19(idDir)) {
7390
8366
  rosterResult.identities = readdirSync7(idDir).filter((f) => f.endsWith(".md")).length;
7391
8367
  }
7392
8368
  } catch {
@@ -7407,62 +8383,62 @@ async function cloudSync(config) {
7407
8383
  function recordRosterDeletion(name) {
7408
8384
  let deletions = [];
7409
8385
  try {
7410
- if (existsSync17(ROSTER_DELETIONS_PATH)) {
7411
- deletions = JSON.parse(readFileSync14(ROSTER_DELETIONS_PATH, "utf-8"));
8386
+ if (existsSync19(ROSTER_DELETIONS_PATH)) {
8387
+ deletions = JSON.parse(readFileSync15(ROSTER_DELETIONS_PATH, "utf-8"));
7412
8388
  }
7413
8389
  } catch {
7414
8390
  }
7415
8391
  if (!deletions.includes(name)) deletions.push(name);
7416
- writeFileSync11(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
8392
+ writeFileSync12(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
7417
8393
  }
7418
8394
  function consumeRosterDeletions() {
7419
8395
  try {
7420
- if (!existsSync17(ROSTER_DELETIONS_PATH)) return [];
7421
- const deletions = JSON.parse(readFileSync14(ROSTER_DELETIONS_PATH, "utf-8"));
7422
- writeFileSync11(ROSTER_DELETIONS_PATH, "[]");
8396
+ if (!existsSync19(ROSTER_DELETIONS_PATH)) return [];
8397
+ const deletions = JSON.parse(readFileSync15(ROSTER_DELETIONS_PATH, "utf-8"));
8398
+ writeFileSync12(ROSTER_DELETIONS_PATH, "[]");
7423
8399
  return deletions;
7424
8400
  } catch {
7425
8401
  return [];
7426
8402
  }
7427
8403
  }
7428
8404
  function buildRosterBlob(paths) {
7429
- const rosterPath = paths?.rosterPath ?? path21.join(EXE_AI_DIR, "exe-employees.json");
7430
- const identityDir = paths?.identityDir ?? path21.join(EXE_AI_DIR, "identity");
7431
- const configPath = paths?.configPath ?? path21.join(EXE_AI_DIR, "config.json");
8405
+ const rosterPath = paths?.rosterPath ?? path23.join(EXE_AI_DIR, "exe-employees.json");
8406
+ const identityDir = paths?.identityDir ?? path23.join(EXE_AI_DIR, "identity");
8407
+ const configPath = paths?.configPath ?? path23.join(EXE_AI_DIR, "config.json");
7432
8408
  let roster = [];
7433
- if (existsSync17(rosterPath)) {
8409
+ if (existsSync19(rosterPath)) {
7434
8410
  try {
7435
- roster = JSON.parse(readFileSync14(rosterPath, "utf-8"));
8411
+ roster = JSON.parse(readFileSync15(rosterPath, "utf-8"));
7436
8412
  } catch {
7437
8413
  }
7438
8414
  }
7439
8415
  const identities = {};
7440
- if (existsSync17(identityDir)) {
8416
+ if (existsSync19(identityDir)) {
7441
8417
  for (const file of readdirSync7(identityDir).filter((f) => f.endsWith(".md"))) {
7442
8418
  try {
7443
- identities[file] = readFileSync14(path21.join(identityDir, file), "utf-8");
8419
+ identities[file] = readFileSync15(path23.join(identityDir, file), "utf-8");
7444
8420
  } catch {
7445
8421
  }
7446
8422
  }
7447
8423
  }
7448
8424
  let config;
7449
- if (existsSync17(configPath)) {
8425
+ if (existsSync19(configPath)) {
7450
8426
  try {
7451
- config = JSON.parse(readFileSync14(configPath, "utf-8"));
8427
+ config = JSON.parse(readFileSync15(configPath, "utf-8"));
7452
8428
  } catch {
7453
8429
  }
7454
8430
  }
7455
8431
  let agentConfig;
7456
- const agentConfigPath = path21.join(EXE_AI_DIR, "agent-config.json");
7457
- if (existsSync17(agentConfigPath)) {
8432
+ const agentConfigPath = path23.join(EXE_AI_DIR, "agent-config.json");
8433
+ if (existsSync19(agentConfigPath)) {
7458
8434
  try {
7459
- agentConfig = JSON.parse(readFileSync14(agentConfigPath, "utf-8"));
8435
+ agentConfig = JSON.parse(readFileSync15(agentConfigPath, "utf-8"));
7460
8436
  } catch {
7461
8437
  }
7462
8438
  }
7463
8439
  const deletedNames = consumeRosterDeletions();
7464
8440
  const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
7465
- const hash = crypto7.createHash("sha256").update(content).digest("hex").slice(0, 16);
8441
+ const hash = crypto8.createHash("sha256").update(content).digest("hex").slice(0, 16);
7466
8442
  return { roster, identities, config, agentConfig, deletedNames, version: hash };
7467
8443
  }
7468
8444
  async function cloudPushRoster(config) {
@@ -7532,23 +8508,24 @@ async function cloudPullRoster(config) {
7532
8508
  }
7533
8509
  }
7534
8510
  function mergeConfig(remoteConfig, configPath) {
7535
- const cfgPath = configPath ?? path21.join(EXE_AI_DIR, "config.json");
8511
+ const cfgPath = configPath ?? path23.join(EXE_AI_DIR, "config.json");
7536
8512
  let local = {};
7537
- if (existsSync17(cfgPath)) {
8513
+ if (existsSync19(cfgPath)) {
7538
8514
  try {
7539
- local = JSON.parse(readFileSync14(cfgPath, "utf-8"));
8515
+ local = JSON.parse(readFileSync15(cfgPath, "utf-8"));
7540
8516
  } catch {
7541
8517
  }
7542
8518
  }
7543
8519
  const merged = { ...remoteConfig, ...local };
7544
- const dir = path21.dirname(cfgPath);
7545
- if (!existsSync17(dir)) mkdirSync11(dir, { recursive: true });
7546
- writeFileSync11(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
8520
+ const dir = path23.dirname(cfgPath);
8521
+ ensurePrivateDirSync(dir);
8522
+ writeFileSync12(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
8523
+ enforcePrivateFileSync(cfgPath);
7547
8524
  }
7548
8525
  async function mergeRosterFromRemote(remote, paths) {
7549
8526
  return withRosterLock(async () => {
7550
8527
  const rosterPath = paths?.rosterPath ?? void 0;
7551
- const identityDir = paths?.identityDir ?? path21.join(EXE_AI_DIR, "identity");
8528
+ const identityDir = paths?.identityDir ?? path23.join(EXE_AI_DIR, "identity");
7552
8529
  const localEmployees = await loadEmployees(rosterPath);
7553
8530
  const localNames = new Set(localEmployees.map((e) => e.name));
7554
8531
  let added = 0;
@@ -7569,15 +8546,15 @@ async function mergeRosterFromRemote(remote, paths) {
7569
8546
  ) ?? lookupKey;
7570
8547
  const remoteIdentity = remote.identities[matchedKey];
7571
8548
  if (remoteIdentity) {
7572
- if (!existsSync17(identityDir)) mkdirSync11(identityDir, { recursive: true });
7573
- const idPath = path21.join(identityDir, `${remoteEmp.name}.md`);
8549
+ if (!existsSync19(identityDir)) mkdirSync11(identityDir, { recursive: true });
8550
+ const idPath = path23.join(identityDir, `${remoteEmp.name}.md`);
7574
8551
  let localIdentity = null;
7575
8552
  try {
7576
- localIdentity = existsSync17(idPath) ? readFileSync14(idPath, "utf-8") : null;
8553
+ localIdentity = existsSync19(idPath) ? readFileSync15(idPath, "utf-8") : null;
7577
8554
  } catch {
7578
8555
  }
7579
8556
  if (localIdentity !== remoteIdentity) {
7580
- writeFileSync11(idPath, remoteIdentity, "utf-8");
8557
+ writeFileSync12(idPath, remoteIdentity, "utf-8");
7581
8558
  identitiesUpdated++;
7582
8559
  }
7583
8560
  }
@@ -7603,16 +8580,18 @@ async function mergeRosterFromRemote(remote, paths) {
7603
8580
  }
7604
8581
  if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
7605
8582
  try {
7606
- const agentConfigPath = path21.join(EXE_AI_DIR, "agent-config.json");
8583
+ const agentConfigPath = path23.join(EXE_AI_DIR, "agent-config.json");
7607
8584
  let local = {};
7608
- if (existsSync17(agentConfigPath)) {
8585
+ if (existsSync19(agentConfigPath)) {
7609
8586
  try {
7610
- local = JSON.parse(readFileSync14(agentConfigPath, "utf-8"));
8587
+ local = JSON.parse(readFileSync15(agentConfigPath, "utf-8"));
7611
8588
  } catch {
7612
8589
  }
7613
8590
  }
7614
8591
  const merged = { ...remote.agentConfig, ...local };
7615
- writeFileSync11(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
8592
+ ensurePrivateDirSync(path23.dirname(agentConfigPath));
8593
+ writeFileSync12(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
8594
+ enforcePrivateFileSync(agentConfigPath);
7616
8595
  } catch {
7617
8596
  }
7618
8597
  }
@@ -8036,7 +9015,7 @@ async function cloudPullDocuments(config) {
8036
9015
  }
8037
9016
  return { pulled };
8038
9017
  }
8039
- var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, ROSTER_DELETIONS_PATH;
9018
+ var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, ROSTER_DELETIONS_PATH;
8040
9019
  var init_cloud_sync = __esm({
8041
9020
  "src/lib/cloud-sync.ts"() {
8042
9021
  "use strict";
@@ -8047,12 +9026,15 @@ var init_cloud_sync = __esm({
8047
9026
  init_config();
8048
9027
  init_crdt_sync();
8049
9028
  init_employees();
9029
+ init_secure_files();
8050
9030
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
8051
9031
  FETCH_TIMEOUT_MS = 3e4;
8052
9032
  PUSH_BATCH_SIZE = 5e3;
8053
- ROSTER_LOCK_PATH = path21.join(EXE_AI_DIR, "roster-merge.lock");
9033
+ ROSTER_LOCK_PATH = path23.join(EXE_AI_DIR, "roster-merge.lock");
8054
9034
  LOCK_STALE_MS = 3e4;
8055
- ROSTER_DELETIONS_PATH = path21.join(EXE_AI_DIR, "roster-deletions.json");
9035
+ _pgPromise = null;
9036
+ _pgFailed = false;
9037
+ ROSTER_DELETIONS_PATH = path23.join(EXE_AI_DIR, "roster-deletions.json");
8056
9038
  }
8057
9039
  });
8058
9040
 
@@ -8064,7 +9046,7 @@ __export(schedules_exports, {
8064
9046
  listSchedules: () => listSchedules,
8065
9047
  parseHumanCron: () => parseHumanCron
8066
9048
  });
8067
- import crypto8 from "crypto";
9049
+ import crypto9 from "crypto";
8068
9050
  import { execSync as execSync9 } from "child_process";
8069
9051
  async function ensureDb() {
8070
9052
  if (!isInitialized()) {
@@ -8133,7 +9115,7 @@ function parseHumanCron(input) {
8133
9115
  async function createSchedule(input) {
8134
9116
  await ensureDb();
8135
9117
  const client = getClient();
8136
- const id = crypto8.randomUUID().slice(0, 8);
9118
+ const id = crypto9.randomUUID().slice(0, 8);
8137
9119
  const now = (/* @__PURE__ */ new Date()).toISOString();
8138
9120
  const prompt = input.prompt ?? input.description;
8139
9121
  await client.execute({
@@ -8234,10 +9216,10 @@ var init_schedules = __esm({
8234
9216
 
8235
9217
  // src/bin/exe-boot.ts
8236
9218
  init_employees();
8237
- import path22 from "path";
9219
+ import path24 from "path";
8238
9220
  import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
8239
- import { existsSync as existsSync18, readFileSync as readFileSync15, readdirSync as readdirSync8, unlinkSync as unlinkSync11 } from "fs";
8240
- import os10 from "os";
9221
+ import { existsSync as existsSync20, readFileSync as readFileSync16, readdirSync as readdirSync8, unlinkSync as unlinkSync11 } from "fs";
9222
+ import os12 from "os";
8241
9223
 
8242
9224
  // src/lib/employee-templates.ts
8243
9225
  init_global_procedures();
@@ -8739,18 +9721,18 @@ init_notifications();
8739
9721
  init_config();
8740
9722
  init_session_key();
8741
9723
  init_employees();
8742
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, unlinkSync as unlinkSync7, readdirSync as readdirSync5 } from "fs";
9724
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync8, unlinkSync as unlinkSync7, readdirSync as readdirSync5 } from "fs";
8743
9725
  import { execSync as execSync8 } from "child_process";
8744
- import path18 from "path";
8745
- var CACHE_DIR = path18.join(EXE_AI_DIR, "session-cache");
9726
+ import path20 from "path";
9727
+ var CACHE_DIR = path20.join(EXE_AI_DIR, "session-cache");
8746
9728
  var STALE_MS = 24 * 60 * 60 * 1e3;
8747
9729
  function getMarkerPath() {
8748
- return path18.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
9730
+ return path20.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
8749
9731
  }
8750
9732
  function writeActiveAgent(agentId, agentRole) {
8751
9733
  try {
8752
9734
  mkdirSync8(CACHE_DIR, { recursive: true });
8753
- writeFileSync8(
9735
+ writeFileSync9(
8754
9736
  getMarkerPath(),
8755
9737
  JSON.stringify({ agentId, agentRole, startedAt: (/* @__PURE__ */ new Date()).toISOString() })
8756
9738
  );
@@ -8760,11 +9742,11 @@ function writeActiveAgent(agentId, agentRole) {
8760
9742
  function cleanupSessionMarkers() {
8761
9743
  const key = getSessionKey();
8762
9744
  try {
8763
- unlinkSync7(path18.join(CACHE_DIR, `active-agent-${key}.json`));
9745
+ unlinkSync7(path20.join(CACHE_DIR, `active-agent-${key}.json`));
8764
9746
  } catch {
8765
9747
  }
8766
9748
  try {
8767
- unlinkSync7(path18.join(CACHE_DIR, "active-agent-undefined.json"));
9749
+ unlinkSync7(path20.join(CACHE_DIR, "active-agent-undefined.json"));
8768
9750
  } catch {
8769
9751
  }
8770
9752
  }
@@ -8854,7 +9836,7 @@ async function boot(options) {
8854
9836
  const employeeDirs = entries.filter((e) => e.isDirectory() && !["output", "research"].includes(e.name));
8855
9837
  for (const dir of employeeDirs) {
8856
9838
  const employee = dir.name;
8857
- const taskDir = path22.join(exeDir, employee);
9839
+ const taskDir = path24.join(exeDir, employee);
8858
9840
  let files;
8859
9841
  try {
8860
9842
  files = readdirSync9(taskDir).filter((f) => f.endsWith(".md"));
@@ -8865,7 +9847,7 @@ async function boot(options) {
8865
9847
  const taskFilePath = `exe/${employee}/${file}`;
8866
9848
  let content;
8867
9849
  try {
8868
- content = readFs(path22.join(taskDir, file), "utf8");
9850
+ content = readFs(path24.join(taskDir, file), "utf8");
8869
9851
  } catch {
8870
9852
  continue;
8871
9853
  }
@@ -8951,12 +9933,12 @@ async function boot(options) {
8951
9933
  }
8952
9934
  try {
8953
9935
  for (const reviewDirName of /* @__PURE__ */ new Set(["exe", coordinatorName])) {
8954
- const reviewDir = path22.join(process.cwd(), "exe", reviewDirName);
8955
- if (existsSync18(reviewDir)) {
9936
+ const reviewDir = path24.join(process.cwd(), "exe", reviewDirName);
9937
+ if (existsSync20(reviewDir)) {
8956
9938
  for (const f of readdirSync8(reviewDir)) {
8957
9939
  if (f.startsWith("review-") && f.endsWith(".md")) {
8958
9940
  try {
8959
- unlinkSync11(path22.join(reviewDir, f));
9941
+ unlinkSync11(path24.join(reviewDir, f));
8960
9942
  } catch {
8961
9943
  }
8962
9944
  }
@@ -9002,12 +9984,12 @@ async function boot(options) {
9002
9984
  });
9003
9985
  const taskFile = String(r.task_file);
9004
9986
  try {
9005
- const filePath = path22.join(process.cwd(), taskFile);
9006
- if (existsSync18(filePath)) {
9007
- let content = readFileSync15(filePath, "utf8");
9987
+ const filePath = path24.join(process.cwd(), taskFile);
9988
+ if (existsSync20(filePath)) {
9989
+ let content = readFileSync16(filePath, "utf8");
9008
9990
  content = content.replace(/\*\*Status:\*\* needs_review/, "**Status:** done");
9009
- const { writeFileSync: writeFileSync12 } = await import("fs");
9010
- writeFileSync12(filePath, content);
9991
+ const { writeFileSync: writeFileSync13 } = await import("fs");
9992
+ writeFileSync13(filePath, content);
9011
9993
  }
9012
9994
  } catch {
9013
9995
  }
@@ -9501,19 +10483,19 @@ async function boot(options) {
9501
10483
  })()
9502
10484
  ]);
9503
10485
  try {
9504
- const configPath = path22.join(
9505
- process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path22.join(os10.homedir(), ".exe-os"),
10486
+ const configPath = path24.join(
10487
+ process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path24.join(os12.homedir(), ".exe-os"),
9506
10488
  "config.json"
9507
10489
  );
9508
- if (existsSync18(configPath)) {
9509
- const raw = JSON.parse(readFileSync15(configPath, "utf8"));
10490
+ if (existsSync20(configPath)) {
10491
+ const raw = JSON.parse(readFileSync16(configPath, "utf8"));
9510
10492
  briefData.cloudConnected = !!(raw.cloud || raw.turso);
9511
10493
  }
9512
10494
  } catch {
9513
10495
  }
9514
10496
  try {
9515
- const backfillFlagPath = path22.join(EXE_AI_DIR, "session-cache", "needs-backfill");
9516
- const isBackfillNeeded = () => existsSync18(backfillFlagPath);
10497
+ const backfillFlagPath = path24.join(EXE_AI_DIR, "session-cache", "needs-backfill");
10498
+ const isBackfillNeeded = () => existsSync20(backfillFlagPath);
9517
10499
  const coverageResult = await client.execute({
9518
10500
  sql: `SELECT COUNT(*) as total,
9519
10501
  SUM(CASE WHEN vector IS NOT NULL THEN 1 ELSE 0 END) as with_vectors
@@ -9535,8 +10517,8 @@ async function boot(options) {
9535
10517
  let daemonRunning = false;
9536
10518
  let daemonUptime;
9537
10519
  let daemonRequestsServed;
9538
- const socketPath = path22.join(EXE_AI_DIR, "exed.sock");
9539
- if (existsSync18(socketPath)) {
10520
+ const socketPath = path24.join(EXE_AI_DIR, "exed.sock");
10521
+ if (existsSync20(socketPath)) {
9540
10522
  try {
9541
10523
  const net2 = await import("net");
9542
10524
  const health = await new Promise((resolve) => {
@@ -9578,10 +10560,10 @@ async function boot(options) {
9578
10560
  }
9579
10561
  }
9580
10562
  if (!daemonRunning) {
9581
- const pidPath = path22.join(EXE_AI_DIR, "exed.pid");
9582
- if (existsSync18(pidPath)) {
10563
+ const pidPath = path24.join(EXE_AI_DIR, "exed.pid");
10564
+ if (existsSync20(pidPath)) {
9583
10565
  try {
9584
- const pid = parseInt(readFileSync15(pidPath, "utf8").trim(), 10);
10566
+ const pid = parseInt(readFileSync16(pidPath, "utf8").trim(), 10);
9585
10567
  if (pid > 0) {
9586
10568
  process.kill(pid, 0);
9587
10569
  daemonRunning = true;
@@ -9592,8 +10574,8 @@ async function boot(options) {
9592
10574
  }
9593
10575
  if (nullCount === 0) {
9594
10576
  try {
9595
- const flagPath = path22.join(EXE_AI_DIR, "session-cache", "needs-backfill");
9596
- if (existsSync18(flagPath)) {
10577
+ const flagPath = path24.join(EXE_AI_DIR, "session-cache", "needs-backfill");
10578
+ if (existsSync20(flagPath)) {
9597
10579
  const { unlinkSync: unlinkSync12 } = await import("fs");
9598
10580
  unlinkSync12(flagPath);
9599
10581
  }
@@ -9619,10 +10601,10 @@ async function boot(options) {
9619
10601
  const { spawn: spawn2 } = await import("child_process");
9620
10602
  const { fileURLToPath: fileURLToPath4 } = await import("url");
9621
10603
  const thisFile = fileURLToPath4(import.meta.url);
9622
- const backfillPath = path22.resolve(path22.dirname(thisFile), "backfill-vectors.js");
9623
- if (existsSync18(backfillPath)) {
10604
+ const backfillPath = path24.resolve(path24.dirname(thisFile), "backfill-vectors.js");
10605
+ if (existsSync20(backfillPath)) {
9624
10606
  const { openSync: openSync3, closeSync: closeSync3 } = await import("fs");
9625
- const workerLogPath = path22.join(EXE_AI_DIR, "workers.log");
10607
+ const workerLogPath = path24.join(EXE_AI_DIR, "workers.log");
9626
10608
  let stderrFd = "ignore";
9627
10609
  try {
9628
10610
  stderrFd = openSync3(workerLogPath, "a");
@@ -9652,8 +10634,8 @@ async function boot(options) {
9652
10634
  const criticalBinaries = ["backfill-vectors.js", "scan-tasks.js"];
9653
10635
  const missing = [];
9654
10636
  for (const bin of criticalBinaries) {
9655
- const binPath = path22.resolve(path22.dirname(thisFile), bin);
9656
- if (!existsSync18(binPath)) {
10637
+ const binPath = path24.resolve(path24.dirname(thisFile), bin);
10638
+ if (!existsSync20(binPath)) {
9657
10639
  missing.push(`dist/bin/${bin}`);
9658
10640
  }
9659
10641
  }
@@ -9682,7 +10664,7 @@ async function boot(options) {
9682
10664
  console.log(brief);
9683
10665
  return;
9684
10666
  }
9685
- const sessionDir = path22.join(EXE_AI_DIR, "sessions", coordinatorName);
10667
+ const sessionDir = path24.join(EXE_AI_DIR, "sessions", coordinatorName);
9686
10668
  await mkdir5(sessionDir, { recursive: true });
9687
10669
  const claudeMdContent = `${getSessionPrompt(coordinatorEmployee.systemPrompt)}
9688
10670
 
@@ -9691,7 +10673,7 @@ async function boot(options) {
9691
10673
  # Status Brief
9692
10674
 
9693
10675
  ${brief}`;
9694
- await writeFile6(path22.join(sessionDir, "CLAUDE.md"), claudeMdContent, "utf-8");
10676
+ await writeFile6(path24.join(sessionDir, "CLAUDE.md"), claudeMdContent, "utf-8");
9695
10677
  const unread = await readUnreadNotifications();
9696
10678
  if (unread.length > 0) {
9697
10679
  console.log(`\u{1F4EC} ${unread.length} unread notification${unread.length === 1 ? "" : "s"}`);
@@ -9700,12 +10682,12 @@ ${brief}`;
9700
10682
  await cleanupOldNotifications();
9701
10683
  console.log(brief);
9702
10684
  try {
9703
- const configPath2 = path22.join(
9704
- process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path22.join(os10.homedir(), ".exe-os"),
10685
+ const configPath2 = path24.join(
10686
+ process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path24.join(os12.homedir(), ".exe-os"),
9705
10687
  "config.json"
9706
10688
  );
9707
- if (existsSync18(configPath2)) {
9708
- const rawCfg = JSON.parse(readFileSync15(configPath2, "utf8"));
10689
+ if (existsSync20(configPath2)) {
10690
+ const rawCfg = JSON.parse(readFileSync16(configPath2, "utf8"));
9709
10691
  const cloudCfg = rawCfg.cloud;
9710
10692
  if (cloudCfg?.apiKey) {
9711
10693
  const { initSyncCrypto: initSyncCrypto2, isSyncCryptoInitialized: isSyncCryptoInitialized2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));