@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
@@ -90,6 +90,44 @@ var init_db_retry = __esm({
90
90
  }
91
91
  });
92
92
 
93
+ // src/lib/secure-files.ts
94
+ import { chmodSync, existsSync, mkdirSync } from "fs";
95
+ import { chmod, mkdir } from "fs/promises";
96
+ async function ensurePrivateDir(dirPath) {
97
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
98
+ try {
99
+ await chmod(dirPath, PRIVATE_DIR_MODE);
100
+ } catch {
101
+ }
102
+ }
103
+ function ensurePrivateDirSync(dirPath) {
104
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
105
+ try {
106
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
107
+ } catch {
108
+ }
109
+ }
110
+ async function enforcePrivateFile(filePath) {
111
+ try {
112
+ await chmod(filePath, PRIVATE_FILE_MODE);
113
+ } catch {
114
+ }
115
+ }
116
+ function enforcePrivateFileSync(filePath) {
117
+ try {
118
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
119
+ } catch {
120
+ }
121
+ }
122
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
123
+ var init_secure_files = __esm({
124
+ "src/lib/secure-files.ts"() {
125
+ "use strict";
126
+ PRIVATE_DIR_MODE = 448;
127
+ PRIVATE_FILE_MODE = 384;
128
+ }
129
+ });
130
+
93
131
  // src/lib/config.ts
94
132
  var config_exports = {};
95
133
  __export(config_exports, {
@@ -106,8 +144,8 @@ __export(config_exports, {
106
144
  migrateConfig: () => migrateConfig,
107
145
  saveConfig: () => saveConfig
108
146
  });
109
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
110
- import { readFileSync, existsSync, renameSync } from "fs";
147
+ import { readFile, writeFile } from "fs/promises";
148
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
111
149
  import path from "path";
112
150
  import os from "os";
113
151
  function resolveDataDir() {
@@ -115,7 +153,7 @@ function resolveDataDir() {
115
153
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
116
154
  const newDir = path.join(os.homedir(), ".exe-os");
117
155
  const legacyDir = path.join(os.homedir(), ".exe-mem");
118
- if (!existsSync(newDir) && existsSync(legacyDir)) {
156
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
119
157
  try {
120
158
  renameSync(legacyDir, newDir);
121
159
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -178,9 +216,9 @@ function normalizeAutoUpdate(raw) {
178
216
  }
179
217
  async function loadConfig() {
180
218
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
181
- await mkdir(dir, { recursive: true });
219
+ await ensurePrivateDir(dir);
182
220
  const configPath = path.join(dir, "config.json");
183
- if (!existsSync(configPath)) {
221
+ if (!existsSync2(configPath)) {
184
222
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
185
223
  }
186
224
  const raw = await readFile(configPath, "utf-8");
@@ -193,6 +231,7 @@ async function loadConfig() {
193
231
  `);
194
232
  try {
195
233
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
234
+ await enforcePrivateFile(configPath);
196
235
  } catch {
197
236
  }
198
237
  }
@@ -211,7 +250,7 @@ async function loadConfig() {
211
250
  function loadConfigSync() {
212
251
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
213
252
  const configPath = path.join(dir, "config.json");
214
- if (!existsSync(configPath)) {
253
+ if (!existsSync2(configPath)) {
215
254
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
216
255
  }
217
256
  try {
@@ -229,12 +268,10 @@ function loadConfigSync() {
229
268
  }
230
269
  async function saveConfig(config) {
231
270
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
232
- await mkdir(dir, { recursive: true });
271
+ await ensurePrivateDir(dir);
233
272
  const configPath = path.join(dir, "config.json");
234
273
  await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
235
- if (config.cloud?.apiKey) {
236
- await chmod(configPath, 384);
237
- }
274
+ await enforcePrivateFile(configPath);
238
275
  }
239
276
  async function loadConfigFrom(configPath) {
240
277
  const raw = await readFile(configPath, "utf-8");
@@ -254,6 +291,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
254
291
  var init_config = __esm({
255
292
  "src/lib/config.ts"() {
256
293
  "use strict";
294
+ init_secure_files();
257
295
  EXE_AI_DIR = resolveDataDir();
258
296
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
259
297
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -332,7 +370,7 @@ var init_config = __esm({
332
370
 
333
371
  // src/lib/employees.ts
334
372
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
335
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
373
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
336
374
  import { execSync } from "child_process";
337
375
  import path2 from "path";
338
376
  import os2 from "os";
@@ -353,7 +391,7 @@ function isCoordinatorName(agentName2, employees = loadEmployeesSync()) {
353
391
  return agentName2.toLowerCase() === getCoordinatorName(employees).toLowerCase();
354
392
  }
355
393
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
356
- if (!existsSync2(employeesPath)) {
394
+ if (!existsSync3(employeesPath)) {
357
395
  return [];
358
396
  }
359
397
  const raw = await readFile2(employeesPath, "utf-8");
@@ -364,7 +402,7 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
364
402
  }
365
403
  }
366
404
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
367
- if (!existsSync2(employeesPath)) return [];
405
+ if (!existsSync3(employeesPath)) return [];
368
406
  try {
369
407
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
370
408
  } catch {
@@ -388,7 +426,7 @@ function isMultiInstance(agentName2, employees) {
388
426
  if (!emp) return false;
389
427
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
390
428
  }
391
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
429
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
392
430
  var init_employees = __esm({
393
431
  "src/lib/employees.ts"() {
394
432
  "use strict";
@@ -397,16 +435,638 @@ var init_employees = __esm({
397
435
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
398
436
  COORDINATOR_ROLE = "COO";
399
437
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
438
+ IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
439
+ }
440
+ });
441
+
442
+ // src/lib/database-adapter.ts
443
+ import os3 from "os";
444
+ import path3 from "path";
445
+ import { createRequire } from "module";
446
+ import { pathToFileURL } from "url";
447
+ function quotedIdentifier(identifier) {
448
+ return `"${identifier.replace(/"/g, '""')}"`;
449
+ }
450
+ function unqualifiedTableName(name) {
451
+ const raw = name.trim().replace(/^"|"$/g, "");
452
+ const parts = raw.split(".");
453
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
454
+ }
455
+ function stripTrailingSemicolon(sql) {
456
+ return sql.trim().replace(/;+\s*$/u, "");
457
+ }
458
+ function appendClause(sql, clause) {
459
+ const trimmed = stripTrailingSemicolon(sql);
460
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
461
+ if (!returningMatch) {
462
+ return `${trimmed}${clause}`;
463
+ }
464
+ const idx = returningMatch.index;
465
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
466
+ }
467
+ function normalizeStatement(stmt) {
468
+ if (typeof stmt === "string") {
469
+ return { kind: "positional", sql: stmt, args: [] };
470
+ }
471
+ const sql = stmt.sql;
472
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
473
+ return { kind: "positional", sql, args: stmt.args ?? [] };
474
+ }
475
+ return { kind: "named", sql, args: stmt.args };
476
+ }
477
+ function rewriteBooleanLiterals(sql) {
478
+ let out = sql;
479
+ for (const column of BOOLEAN_COLUMN_NAMES) {
480
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
481
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
482
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
483
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
484
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
485
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
486
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
487
+ }
488
+ return out;
489
+ }
490
+ function rewriteInsertOrIgnore(sql) {
491
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
492
+ return sql;
493
+ }
494
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
495
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
496
+ }
497
+ function rewriteInsertOrReplace(sql) {
498
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
499
+ if (!match) {
500
+ return sql;
501
+ }
502
+ const rawTable = match[1];
503
+ const rawColumns = match[2];
504
+ const remainder = match[3];
505
+ const tableName = unqualifiedTableName(rawTable);
506
+ const conflictKeys = UPSERT_KEYS[tableName];
507
+ if (!conflictKeys?.length) {
508
+ return sql;
509
+ }
510
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
511
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
512
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
513
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
514
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
515
+ }
516
+ function rewriteSql(sql) {
517
+ let out = sql;
518
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
519
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
520
+ out = rewriteBooleanLiterals(out);
521
+ out = rewriteInsertOrReplace(out);
522
+ out = rewriteInsertOrIgnore(out);
523
+ return stripTrailingSemicolon(out);
524
+ }
525
+ function toBoolean(value) {
526
+ if (value === null || value === void 0) return value;
527
+ if (typeof value === "boolean") return value;
528
+ if (typeof value === "number") return value !== 0;
529
+ if (typeof value === "bigint") return value !== 0n;
530
+ if (typeof value === "string") {
531
+ const normalized = value.trim().toLowerCase();
532
+ if (normalized === "0" || normalized === "false") return false;
533
+ if (normalized === "1" || normalized === "true") return true;
534
+ }
535
+ return Boolean(value);
536
+ }
537
+ function countQuestionMarks(sql, end) {
538
+ let count = 0;
539
+ let inSingle = false;
540
+ let inDouble = false;
541
+ let inLineComment = false;
542
+ let inBlockComment = false;
543
+ for (let i = 0; i < end; i++) {
544
+ const ch = sql[i];
545
+ const next = sql[i + 1];
546
+ if (inLineComment) {
547
+ if (ch === "\n") inLineComment = false;
548
+ continue;
549
+ }
550
+ if (inBlockComment) {
551
+ if (ch === "*" && next === "/") {
552
+ inBlockComment = false;
553
+ i += 1;
554
+ }
555
+ continue;
556
+ }
557
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
558
+ inLineComment = true;
559
+ i += 1;
560
+ continue;
561
+ }
562
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
563
+ inBlockComment = true;
564
+ i += 1;
565
+ continue;
566
+ }
567
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
568
+ inSingle = !inSingle;
569
+ continue;
570
+ }
571
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
572
+ inDouble = !inDouble;
573
+ continue;
574
+ }
575
+ if (!inSingle && !inDouble && ch === "?") {
576
+ count += 1;
577
+ }
578
+ }
579
+ return count;
580
+ }
581
+ function findBooleanPlaceholderIndexes(sql) {
582
+ const indexes = /* @__PURE__ */ new Set();
583
+ for (const column of BOOLEAN_COLUMN_NAMES) {
584
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
585
+ for (const match of sql.matchAll(pattern)) {
586
+ const matchText = match[0];
587
+ const qIndex = match.index + matchText.lastIndexOf("?");
588
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
589
+ }
590
+ }
591
+ return indexes;
592
+ }
593
+ function coerceInsertBooleanArgs(sql, args) {
594
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
595
+ if (!match) return;
596
+ const rawTable = match[1];
597
+ const rawColumns = match[2];
598
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
599
+ if (!boolColumns?.size) return;
600
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
601
+ for (const [index, column] of columns.entries()) {
602
+ if (boolColumns.has(column) && index < args.length) {
603
+ args[index] = toBoolean(args[index]);
604
+ }
605
+ }
606
+ }
607
+ function coerceUpdateBooleanArgs(sql, args) {
608
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
609
+ if (!match) return;
610
+ const rawTable = match[1];
611
+ const setClause = match[2];
612
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
613
+ if (!boolColumns?.size) return;
614
+ const assignments = setClause.split(",");
615
+ let placeholderIndex = 0;
616
+ for (const assignment of assignments) {
617
+ if (!assignment.includes("?")) continue;
618
+ placeholderIndex += 1;
619
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
620
+ if (colMatch && boolColumns.has(colMatch[1])) {
621
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
622
+ }
623
+ }
624
+ }
625
+ function coerceBooleanArgs(sql, args) {
626
+ const nextArgs = [...args];
627
+ coerceInsertBooleanArgs(sql, nextArgs);
628
+ coerceUpdateBooleanArgs(sql, nextArgs);
629
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
630
+ for (const index of placeholderIndexes) {
631
+ if (index > 0 && index <= nextArgs.length) {
632
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
633
+ }
634
+ }
635
+ return nextArgs;
636
+ }
637
+ function convertQuestionMarksToDollarParams(sql) {
638
+ let out = "";
639
+ let placeholder = 0;
640
+ let inSingle = false;
641
+ let inDouble = false;
642
+ let inLineComment = false;
643
+ let inBlockComment = false;
644
+ for (let i = 0; i < sql.length; i++) {
645
+ const ch = sql[i];
646
+ const next = sql[i + 1];
647
+ if (inLineComment) {
648
+ out += ch;
649
+ if (ch === "\n") inLineComment = false;
650
+ continue;
651
+ }
652
+ if (inBlockComment) {
653
+ out += ch;
654
+ if (ch === "*" && next === "/") {
655
+ out += next;
656
+ inBlockComment = false;
657
+ i += 1;
658
+ }
659
+ continue;
660
+ }
661
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
662
+ out += ch + next;
663
+ inLineComment = true;
664
+ i += 1;
665
+ continue;
666
+ }
667
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
668
+ out += ch + next;
669
+ inBlockComment = true;
670
+ i += 1;
671
+ continue;
672
+ }
673
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
674
+ inSingle = !inSingle;
675
+ out += ch;
676
+ continue;
677
+ }
678
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
679
+ inDouble = !inDouble;
680
+ out += ch;
681
+ continue;
682
+ }
683
+ if (!inSingle && !inDouble && ch === "?") {
684
+ placeholder += 1;
685
+ out += `$${placeholder}`;
686
+ continue;
687
+ }
688
+ out += ch;
689
+ }
690
+ return out;
691
+ }
692
+ function translateStatementForPostgres(stmt) {
693
+ const normalized = normalizeStatement(stmt);
694
+ if (normalized.kind === "named") {
695
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
696
+ }
697
+ const rewrittenSql = rewriteSql(normalized.sql);
698
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
699
+ return {
700
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
701
+ args: coercedArgs
702
+ };
703
+ }
704
+ function shouldBypassPostgres(stmt) {
705
+ const normalized = normalizeStatement(stmt);
706
+ if (normalized.kind === "named") {
707
+ return true;
708
+ }
709
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
710
+ }
711
+ function shouldFallbackOnError(error) {
712
+ const message = error instanceof Error ? error.message : String(error);
713
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
714
+ }
715
+ function isReadQuery(sql) {
716
+ const trimmed = sql.trimStart();
717
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
718
+ }
719
+ function buildRow(row, columns) {
720
+ const values = columns.map((column) => row[column]);
721
+ return Object.assign(values, row);
722
+ }
723
+ function buildResultSet(rows, rowsAffected = 0) {
724
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
725
+ const resultRows = rows.map((row) => buildRow(row, columns));
726
+ return {
727
+ columns,
728
+ columnTypes: columns.map(() => ""),
729
+ rows: resultRows,
730
+ rowsAffected,
731
+ lastInsertRowid: void 0,
732
+ toJSON() {
733
+ return {
734
+ columns,
735
+ columnTypes: columns.map(() => ""),
736
+ rows,
737
+ rowsAffected,
738
+ lastInsertRowid: void 0
739
+ };
740
+ }
741
+ };
742
+ }
743
+ async function loadPrismaClient() {
744
+ if (!prismaClientPromise) {
745
+ prismaClientPromise = (async () => {
746
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
747
+ if (explicitPath) {
748
+ const module2 = await import(pathToFileURL(explicitPath).href);
749
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
750
+ if (!PrismaClient2) {
751
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
752
+ }
753
+ return new PrismaClient2();
754
+ }
755
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
756
+ const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
757
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
758
+ const module = await import(pathToFileURL(prismaEntry).href);
759
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
760
+ if (!PrismaClient) {
761
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
762
+ }
763
+ return new PrismaClient();
764
+ })();
765
+ }
766
+ return prismaClientPromise;
767
+ }
768
+ async function ensureCompatibilityViews(prisma) {
769
+ if (!compatibilityBootstrapPromise) {
770
+ compatibilityBootstrapPromise = (async () => {
771
+ for (const mapping of VIEW_MAPPINGS) {
772
+ const relation = mapping.source.replace(/"/g, "");
773
+ const rows = await prisma.$queryRawUnsafe(
774
+ "SELECT to_regclass($1) AS regclass",
775
+ relation
776
+ );
777
+ if (!rows[0]?.regclass) {
778
+ continue;
779
+ }
780
+ await prisma.$executeRawUnsafe(
781
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
782
+ );
783
+ }
784
+ })();
785
+ }
786
+ return compatibilityBootstrapPromise;
787
+ }
788
+ async function executeOnPrisma(executor, stmt) {
789
+ const translated = translateStatementForPostgres(stmt);
790
+ if (isReadQuery(translated.sql)) {
791
+ const rows = await executor.$queryRawUnsafe(
792
+ translated.sql,
793
+ ...translated.args
794
+ );
795
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
796
+ }
797
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
798
+ return buildResultSet([], rowsAffected);
799
+ }
800
+ function splitSqlStatements(sql) {
801
+ const parts = [];
802
+ let current = "";
803
+ let inSingle = false;
804
+ let inDouble = false;
805
+ let inLineComment = false;
806
+ let inBlockComment = false;
807
+ for (let i = 0; i < sql.length; i++) {
808
+ const ch = sql[i];
809
+ const next = sql[i + 1];
810
+ if (inLineComment) {
811
+ current += ch;
812
+ if (ch === "\n") inLineComment = false;
813
+ continue;
814
+ }
815
+ if (inBlockComment) {
816
+ current += ch;
817
+ if (ch === "*" && next === "/") {
818
+ current += next;
819
+ inBlockComment = false;
820
+ i += 1;
821
+ }
822
+ continue;
823
+ }
824
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
825
+ current += ch + next;
826
+ inLineComment = true;
827
+ i += 1;
828
+ continue;
829
+ }
830
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
831
+ current += ch + next;
832
+ inBlockComment = true;
833
+ i += 1;
834
+ continue;
835
+ }
836
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
837
+ inSingle = !inSingle;
838
+ current += ch;
839
+ continue;
840
+ }
841
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
842
+ inDouble = !inDouble;
843
+ current += ch;
844
+ continue;
845
+ }
846
+ if (!inSingle && !inDouble && ch === ";") {
847
+ if (current.trim()) {
848
+ parts.push(current.trim());
849
+ }
850
+ current = "";
851
+ continue;
852
+ }
853
+ current += ch;
854
+ }
855
+ if (current.trim()) {
856
+ parts.push(current.trim());
857
+ }
858
+ return parts;
859
+ }
860
+ async function createPrismaDbAdapter(fallbackClient) {
861
+ const prisma = await loadPrismaClient();
862
+ await ensureCompatibilityViews(prisma);
863
+ let closed = false;
864
+ let adapter;
865
+ const fallbackExecute = async (stmt, error) => {
866
+ if (!fallbackClient) {
867
+ if (error) throw error;
868
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
869
+ }
870
+ if (error) {
871
+ process.stderr.write(
872
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
873
+ `
874
+ );
875
+ }
876
+ return fallbackClient.execute(stmt);
877
+ };
878
+ adapter = {
879
+ async execute(stmt) {
880
+ if (shouldBypassPostgres(stmt)) {
881
+ return fallbackExecute(stmt);
882
+ }
883
+ try {
884
+ return await executeOnPrisma(prisma, stmt);
885
+ } catch (error) {
886
+ if (shouldFallbackOnError(error)) {
887
+ return fallbackExecute(stmt, error);
888
+ }
889
+ throw error;
890
+ }
891
+ },
892
+ async batch(stmts, mode) {
893
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
894
+ if (!fallbackClient) {
895
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
896
+ }
897
+ return fallbackClient.batch(stmts, mode);
898
+ }
899
+ try {
900
+ if (prisma.$transaction) {
901
+ return await prisma.$transaction(async (tx) => {
902
+ const results2 = [];
903
+ for (const stmt of stmts) {
904
+ results2.push(await executeOnPrisma(tx, stmt));
905
+ }
906
+ return results2;
907
+ });
908
+ }
909
+ const results = [];
910
+ for (const stmt of stmts) {
911
+ results.push(await executeOnPrisma(prisma, stmt));
912
+ }
913
+ return results;
914
+ } catch (error) {
915
+ if (fallbackClient && shouldFallbackOnError(error)) {
916
+ process.stderr.write(
917
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
918
+ `
919
+ );
920
+ return fallbackClient.batch(stmts, mode);
921
+ }
922
+ throw error;
923
+ }
924
+ },
925
+ async migrate(stmts) {
926
+ if (fallbackClient) {
927
+ return fallbackClient.migrate(stmts);
928
+ }
929
+ return adapter.batch(stmts, "deferred");
930
+ },
931
+ async transaction(mode) {
932
+ if (!fallbackClient) {
933
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
934
+ }
935
+ return fallbackClient.transaction(mode);
936
+ },
937
+ async executeMultiple(sql) {
938
+ if (fallbackClient && shouldBypassPostgres(sql)) {
939
+ return fallbackClient.executeMultiple(sql);
940
+ }
941
+ for (const statement of splitSqlStatements(sql)) {
942
+ await adapter.execute(statement);
943
+ }
944
+ },
945
+ async sync() {
946
+ if (fallbackClient) {
947
+ return fallbackClient.sync();
948
+ }
949
+ return { frame_no: 0, frames_synced: 0 };
950
+ },
951
+ close() {
952
+ closed = true;
953
+ prismaClientPromise = null;
954
+ compatibilityBootstrapPromise = null;
955
+ void prisma.$disconnect?.();
956
+ },
957
+ get closed() {
958
+ return closed;
959
+ },
960
+ get protocol() {
961
+ return "prisma-postgres";
962
+ }
963
+ };
964
+ return adapter;
965
+ }
966
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
967
+ var init_database_adapter = __esm({
968
+ "src/lib/database-adapter.ts"() {
969
+ "use strict";
970
+ VIEW_MAPPINGS = [
971
+ { view: "memories", source: "memory.memory_records" },
972
+ { view: "tasks", source: "memory.tasks" },
973
+ { view: "behaviors", source: "memory.behaviors" },
974
+ { view: "entities", source: "memory.entities" },
975
+ { view: "relationships", source: "memory.relationships" },
976
+ { view: "entity_memories", source: "memory.entity_memories" },
977
+ { view: "entity_aliases", source: "memory.entity_aliases" },
978
+ { view: "notifications", source: "memory.notifications" },
979
+ { view: "messages", source: "memory.messages" },
980
+ { view: "users", source: "wiki.users" },
981
+ { view: "workspaces", source: "wiki.workspaces" },
982
+ { view: "workspace_users", source: "wiki.workspace_users" },
983
+ { view: "documents", source: "wiki.workspace_documents" },
984
+ { view: "chats", source: "wiki.workspace_chats" }
985
+ ];
986
+ UPSERT_KEYS = {
987
+ memories: ["id"],
988
+ tasks: ["id"],
989
+ behaviors: ["id"],
990
+ entities: ["id"],
991
+ relationships: ["id"],
992
+ entity_aliases: ["alias"],
993
+ notifications: ["id"],
994
+ messages: ["id"],
995
+ users: ["id"],
996
+ workspaces: ["id"],
997
+ workspace_users: ["id"],
998
+ documents: ["id"],
999
+ chats: ["id"]
1000
+ };
1001
+ BOOLEAN_COLUMNS_BY_TABLE = {
1002
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
1003
+ behaviors: /* @__PURE__ */ new Set(["active"]),
1004
+ notifications: /* @__PURE__ */ new Set(["read"]),
1005
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
1006
+ };
1007
+ BOOLEAN_COLUMN_NAMES = new Set(
1008
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
1009
+ );
1010
+ IMMEDIATE_FALLBACK_PATTERNS = [
1011
+ /\bPRAGMA\b/i,
1012
+ /\bsqlite_master\b/i,
1013
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
1014
+ /\bMATCH\b/i,
1015
+ /\bvector_distance_cos\s*\(/i,
1016
+ /\bjson_extract\s*\(/i,
1017
+ /\bjulianday\s*\(/i,
1018
+ /\bstrftime\s*\(/i,
1019
+ /\blast_insert_rowid\s*\(/i
1020
+ ];
1021
+ prismaClientPromise = null;
1022
+ compatibilityBootstrapPromise = null;
1023
+ }
1024
+ });
1025
+
1026
+ // src/lib/daemon-auth.ts
1027
+ import crypto from "crypto";
1028
+ import path4 from "path";
1029
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1030
+ function normalizeToken(token) {
1031
+ if (!token) return null;
1032
+ const trimmed = token.trim();
1033
+ return trimmed.length > 0 ? trimmed : null;
1034
+ }
1035
+ function readDaemonToken() {
1036
+ try {
1037
+ if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
1038
+ return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
1039
+ } catch {
1040
+ return null;
1041
+ }
1042
+ }
1043
+ function ensureDaemonToken(seed) {
1044
+ const existing = readDaemonToken();
1045
+ if (existing) return existing;
1046
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1047
+ ensurePrivateDirSync(EXE_AI_DIR);
1048
+ writeFileSync2(DAEMON_TOKEN_PATH, `${token}
1049
+ `, "utf8");
1050
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1051
+ return token;
1052
+ }
1053
+ var DAEMON_TOKEN_PATH;
1054
+ var init_daemon_auth = __esm({
1055
+ "src/lib/daemon-auth.ts"() {
1056
+ "use strict";
1057
+ init_config();
1058
+ init_secure_files();
1059
+ DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
400
1060
  }
401
1061
  });
402
1062
 
403
1063
  // src/lib/exe-daemon-client.ts
404
1064
  import net from "net";
405
- import os3 from "os";
1065
+ import os4 from "os";
406
1066
  import { spawn } from "child_process";
407
1067
  import { randomUUID } from "crypto";
408
- import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
409
- import path3 from "path";
1068
+ import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
1069
+ import path5 from "path";
410
1070
  import { fileURLToPath } from "url";
411
1071
  function handleData(chunk) {
412
1072
  _buffer += chunk.toString();
@@ -434,9 +1094,9 @@ function handleData(chunk) {
434
1094
  }
435
1095
  }
436
1096
  function cleanupStaleFiles() {
437
- if (existsSync3(PID_PATH)) {
1097
+ if (existsSync5(PID_PATH)) {
438
1098
  try {
439
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
1099
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
440
1100
  if (pid > 0) {
441
1101
  try {
442
1102
  process.kill(pid, 0);
@@ -457,17 +1117,17 @@ function cleanupStaleFiles() {
457
1117
  }
458
1118
  }
459
1119
  function findPackageRoot() {
460
- let dir = path3.dirname(fileURLToPath(import.meta.url));
461
- const { root } = path3.parse(dir);
1120
+ let dir = path5.dirname(fileURLToPath(import.meta.url));
1121
+ const { root } = path5.parse(dir);
462
1122
  while (dir !== root) {
463
- if (existsSync3(path3.join(dir, "package.json"))) return dir;
464
- dir = path3.dirname(dir);
1123
+ if (existsSync5(path5.join(dir, "package.json"))) return dir;
1124
+ dir = path5.dirname(dir);
465
1125
  }
466
1126
  return null;
467
1127
  }
468
1128
  function spawnDaemon() {
469
- const freeGB = os3.freemem() / (1024 * 1024 * 1024);
470
- const totalGB = os3.totalmem() / (1024 * 1024 * 1024);
1129
+ const freeGB = os4.freemem() / (1024 * 1024 * 1024);
1130
+ const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
471
1131
  if (totalGB <= 8) {
472
1132
  process.stderr.write(
473
1133
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -487,16 +1147,17 @@ function spawnDaemon() {
487
1147
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
488
1148
  return;
489
1149
  }
490
- const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
491
- if (!existsSync3(daemonPath)) {
1150
+ const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1151
+ if (!existsSync5(daemonPath)) {
492
1152
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
493
1153
  `);
494
1154
  return;
495
1155
  }
496
1156
  const resolvedPath = daemonPath;
1157
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
497
1158
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
498
1159
  `);
499
- const logPath = path3.join(path3.dirname(SOCKET_PATH), "exed.log");
1160
+ const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
500
1161
  let stderrFd = "ignore";
501
1162
  try {
502
1163
  stderrFd = openSync(logPath, "a");
@@ -514,7 +1175,8 @@ function spawnDaemon() {
514
1175
  TMUX_PANE: void 0,
515
1176
  // Prevents resolveExeSession() from scoping to one session
516
1177
  EXE_DAEMON_SOCK: SOCKET_PATH,
517
- EXE_DAEMON_PID: PID_PATH
1178
+ EXE_DAEMON_PID: PID_PATH,
1179
+ [DAEMON_TOKEN_ENV]: daemonToken
518
1180
  }
519
1181
  });
520
1182
  child.unref();
@@ -624,13 +1286,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
624
1286
  return;
625
1287
  }
626
1288
  const id = randomUUID();
1289
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
627
1290
  const timer = setTimeout(() => {
628
1291
  _pending.delete(id);
629
1292
  resolve({ error: "Request timeout" });
630
1293
  }, timeoutMs);
631
1294
  _pending.set(id, { resolve, timer });
632
1295
  try {
633
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1296
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
634
1297
  } catch {
635
1298
  clearTimeout(timer);
636
1299
  _pending.delete(id);
@@ -647,74 +1310,123 @@ async function pingDaemon() {
647
1310
  return null;
648
1311
  }
649
1312
  function killAndRespawnDaemon() {
650
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
651
- if (existsSync3(PID_PATH)) {
652
- try {
653
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
654
- if (pid > 0) {
655
- try {
656
- process.kill(pid, "SIGKILL");
657
- } catch {
1313
+ if (!acquireSpawnLock()) {
1314
+ process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
1315
+ if (_socket) {
1316
+ _socket.destroy();
1317
+ _socket = null;
1318
+ }
1319
+ _connected = false;
1320
+ _buffer = "";
1321
+ return;
1322
+ }
1323
+ try {
1324
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
1325
+ if (existsSync5(PID_PATH)) {
1326
+ try {
1327
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1328
+ if (pid > 0) {
1329
+ try {
1330
+ process.kill(pid, "SIGKILL");
1331
+ } catch {
1332
+ }
658
1333
  }
1334
+ } catch {
659
1335
  }
1336
+ }
1337
+ if (_socket) {
1338
+ _socket.destroy();
1339
+ _socket = null;
1340
+ }
1341
+ _connected = false;
1342
+ _buffer = "";
1343
+ try {
1344
+ unlinkSync2(PID_PATH);
660
1345
  } catch {
661
1346
  }
1347
+ try {
1348
+ unlinkSync2(SOCKET_PATH);
1349
+ } catch {
1350
+ }
1351
+ spawnDaemon();
1352
+ } finally {
1353
+ releaseSpawnLock();
662
1354
  }
663
- if (_socket) {
664
- _socket.destroy();
665
- _socket = null;
666
- }
667
- _connected = false;
668
- _buffer = "";
1355
+ }
1356
+ function isDaemonTooYoung() {
669
1357
  try {
670
- unlinkSync2(PID_PATH);
1358
+ const stat = statSync(PID_PATH);
1359
+ return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
671
1360
  } catch {
1361
+ return false;
672
1362
  }
673
- try {
674
- unlinkSync2(SOCKET_PATH);
675
- } catch {
1363
+ }
1364
+ async function retryThenRestart(doRequest, label) {
1365
+ const result = await doRequest();
1366
+ if (!result.error) {
1367
+ _consecutiveFailures = 0;
1368
+ return result;
676
1369
  }
677
- spawnDaemon();
1370
+ _consecutiveFailures++;
1371
+ for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
1372
+ const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
1373
+ process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
1374
+ `);
1375
+ await new Promise((r) => setTimeout(r, delayMs));
1376
+ if (!_connected) {
1377
+ if (!await connectToSocket()) continue;
1378
+ }
1379
+ const retry = await doRequest();
1380
+ if (!retry.error) {
1381
+ _consecutiveFailures = 0;
1382
+ return retry;
1383
+ }
1384
+ _consecutiveFailures++;
1385
+ }
1386
+ if (isDaemonTooYoung()) {
1387
+ process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
1388
+ `);
1389
+ return { error: result.error };
1390
+ }
1391
+ process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
1392
+ `);
1393
+ killAndRespawnDaemon();
1394
+ const start = Date.now();
1395
+ let delay2 = 200;
1396
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1397
+ await new Promise((r) => setTimeout(r, delay2));
1398
+ if (await connectToSocket()) break;
1399
+ delay2 = Math.min(delay2 * 2, 3e3);
1400
+ }
1401
+ if (!_connected) return { error: "Daemon restart failed" };
1402
+ const final = await doRequest();
1403
+ if (!final.error) _consecutiveFailures = 0;
1404
+ return final;
678
1405
  }
679
1406
  async function embedViaClient(text, priority = "high") {
680
1407
  if (!_connected && !await connectEmbedDaemon()) return null;
681
1408
  _requestCount++;
682
1409
  if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
683
1410
  const health = await pingDaemon();
684
- if (!health) {
1411
+ if (!health && !isDaemonTooYoung()) {
685
1412
  process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
686
1413
  `);
687
1414
  killAndRespawnDaemon();
688
1415
  const start = Date.now();
689
- let delay2 = 200;
1416
+ let d = 200;
690
1417
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
691
- await new Promise((r) => setTimeout(r, delay2));
1418
+ await new Promise((r) => setTimeout(r, d));
692
1419
  if (await connectToSocket()) break;
693
- delay2 = Math.min(delay2 * 2, 3e3);
1420
+ d = Math.min(d * 2, 3e3);
694
1421
  }
695
1422
  if (!_connected) return null;
696
1423
  }
697
1424
  }
698
- const result = await sendRequest([text], priority);
699
- if (!result.error && result.vectors?.[0]) return result.vectors[0];
700
- if (result.error) {
701
- process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
702
- `);
703
- killAndRespawnDaemon();
704
- const start = Date.now();
705
- let delay2 = 200;
706
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
707
- await new Promise((r) => setTimeout(r, delay2));
708
- if (await connectToSocket()) break;
709
- delay2 = Math.min(delay2 * 2, 3e3);
710
- }
711
- if (!_connected) return null;
712
- const retry = await sendRequest([text], priority);
713
- if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
714
- process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
715
- `);
716
- }
717
- return null;
1425
+ const result = await retryThenRestart(
1426
+ () => sendRequest([text], priority),
1427
+ "Embed"
1428
+ );
1429
+ return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
718
1430
  }
719
1431
  function disconnectClient() {
720
1432
  if (_socket) {
@@ -732,22 +1444,28 @@ function disconnectClient() {
732
1444
  function isClientConnected() {
733
1445
  return _connected;
734
1446
  }
735
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
1447
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
736
1448
  var init_exe_daemon_client = __esm({
737
1449
  "src/lib/exe-daemon-client.ts"() {
738
1450
  "use strict";
739
1451
  init_config();
740
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
741
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
742
- SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
1452
+ init_daemon_auth();
1453
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1454
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1455
+ SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
743
1456
  SPAWN_LOCK_STALE_MS = 3e4;
744
1457
  CONNECT_TIMEOUT_MS = 15e3;
745
1458
  REQUEST_TIMEOUT_MS = 3e4;
1459
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
746
1460
  _socket = null;
747
1461
  _connected = false;
748
1462
  _buffer = "";
749
1463
  _requestCount = 0;
1464
+ _consecutiveFailures = 0;
750
1465
  HEALTH_CHECK_INTERVAL = 100;
1466
+ MAX_RETRIES_BEFORE_RESTART = 3;
1467
+ RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
1468
+ MIN_DAEMON_AGE_MS = 3e4;
751
1469
  _pending = /* @__PURE__ */ new Map();
752
1470
  MAX_BUFFER = 1e7;
753
1471
  }
@@ -823,7 +1541,7 @@ __export(db_daemon_client_exports, {
823
1541
  createDaemonDbClient: () => createDaemonDbClient,
824
1542
  initDaemonDbClient: () => initDaemonDbClient
825
1543
  });
826
- function normalizeStatement(stmt) {
1544
+ function normalizeStatement2(stmt) {
827
1545
  if (typeof stmt === "string") {
828
1546
  return { sql: stmt, args: [] };
829
1547
  }
@@ -847,7 +1565,7 @@ function createDaemonDbClient(fallbackClient) {
847
1565
  if (!_useDaemon || !isClientConnected()) {
848
1566
  return fallbackClient.execute(stmt);
849
1567
  }
850
- const { sql, args } = normalizeStatement(stmt);
1568
+ const { sql, args } = normalizeStatement2(stmt);
851
1569
  const response = await sendDaemonRequest({
852
1570
  type: "db-execute",
853
1571
  sql,
@@ -872,7 +1590,7 @@ function createDaemonDbClient(fallbackClient) {
872
1590
  if (!_useDaemon || !isClientConnected()) {
873
1591
  return fallbackClient.batch(stmts, mode);
874
1592
  }
875
- const statements = stmts.map(normalizeStatement);
1593
+ const statements = stmts.map(normalizeStatement2);
876
1594
  const response = await sendDaemonRequest({
877
1595
  type: "db-batch",
878
1596
  statements,
@@ -967,6 +1685,18 @@ __export(database_exports, {
967
1685
  });
968
1686
  import { createClient } from "@libsql/client";
969
1687
  async function initDatabase(config) {
1688
+ if (_walCheckpointTimer) {
1689
+ clearInterval(_walCheckpointTimer);
1690
+ _walCheckpointTimer = null;
1691
+ }
1692
+ if (_daemonClient) {
1693
+ _daemonClient.close();
1694
+ _daemonClient = null;
1695
+ }
1696
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1697
+ _adapterClient.close();
1698
+ }
1699
+ _adapterClient = null;
970
1700
  if (_client) {
971
1701
  _client.close();
972
1702
  _client = null;
@@ -980,6 +1710,7 @@ async function initDatabase(config) {
980
1710
  }
981
1711
  _client = createClient(opts);
982
1712
  _resilientClient = wrapWithRetry(_client);
1713
+ _adapterClient = _resilientClient;
983
1714
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
984
1715
  });
985
1716
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -990,14 +1721,20 @@ async function initDatabase(config) {
990
1721
  });
991
1722
  }, 3e4);
992
1723
  _walCheckpointTimer.unref();
1724
+ if (process.env.DATABASE_URL) {
1725
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1726
+ }
993
1727
  }
994
1728
  function isInitialized() {
995
- return _client !== null;
1729
+ return _adapterClient !== null || _client !== null;
996
1730
  }
997
1731
  function getClient() {
998
- if (!_resilientClient) {
1732
+ if (!_adapterClient) {
999
1733
  throw new Error("Database client not initialized. Call initDatabase() first.");
1000
1734
  }
1735
+ if (process.env.DATABASE_URL) {
1736
+ return _adapterClient;
1737
+ }
1001
1738
  if (process.env.EXE_IS_DAEMON === "1") {
1002
1739
  return _resilientClient;
1003
1740
  }
@@ -1007,6 +1744,7 @@ function getClient() {
1007
1744
  return _resilientClient;
1008
1745
  }
1009
1746
  async function initDaemonClient() {
1747
+ if (process.env.DATABASE_URL) return;
1010
1748
  if (process.env.EXE_IS_DAEMON === "1") return;
1011
1749
  if (!_resilientClient) return;
1012
1750
  try {
@@ -1303,6 +2041,7 @@ async function ensureSchema() {
1303
2041
  project TEXT NOT NULL,
1304
2042
  summary TEXT NOT NULL,
1305
2043
  task_file TEXT,
2044
+ session_scope TEXT,
1306
2045
  read INTEGER NOT NULL DEFAULT 0,
1307
2046
  created_at TEXT NOT NULL
1308
2047
  );
@@ -1311,7 +2050,7 @@ async function ensureSchema() {
1311
2050
  ON notifications(read);
1312
2051
 
1313
2052
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1314
- ON notifications(agent_id);
2053
+ ON notifications(agent_id, session_scope);
1315
2054
 
1316
2055
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1317
2056
  ON notifications(task_file);
@@ -1349,6 +2088,7 @@ async function ensureSchema() {
1349
2088
  target_agent TEXT NOT NULL,
1350
2089
  target_project TEXT,
1351
2090
  target_device TEXT NOT NULL DEFAULT 'local',
2091
+ session_scope TEXT,
1352
2092
  content TEXT NOT NULL,
1353
2093
  priority TEXT DEFAULT 'normal',
1354
2094
  status TEXT DEFAULT 'pending',
@@ -1362,10 +2102,31 @@ async function ensureSchema() {
1362
2102
  );
1363
2103
 
1364
2104
  CREATE INDEX IF NOT EXISTS idx_messages_target
1365
- ON messages(target_agent, status);
2105
+ ON messages(target_agent, session_scope, status);
1366
2106
 
1367
2107
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1368
- ON messages(target_agent, from_agent, server_seq);
2108
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2109
+ `);
2110
+ try {
2111
+ await client.execute({
2112
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2113
+ args: []
2114
+ });
2115
+ } catch {
2116
+ }
2117
+ try {
2118
+ await client.execute({
2119
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2120
+ args: []
2121
+ });
2122
+ } catch {
2123
+ }
2124
+ await client.executeMultiple(`
2125
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2126
+ ON notifications(agent_id, session_scope, read, created_at);
2127
+
2128
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2129
+ ON messages(target_agent, session_scope, status, created_at);
1369
2130
  `);
1370
2131
  try {
1371
2132
  await client.execute({
@@ -1949,28 +2710,45 @@ async function ensureSchema() {
1949
2710
  } catch {
1950
2711
  }
1951
2712
  }
2713
+ try {
2714
+ await client.execute({
2715
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2716
+ args: []
2717
+ });
2718
+ } catch {
2719
+ }
1952
2720
  }
1953
2721
  async function disposeDatabase() {
2722
+ if (_walCheckpointTimer) {
2723
+ clearInterval(_walCheckpointTimer);
2724
+ _walCheckpointTimer = null;
2725
+ }
1954
2726
  if (_daemonClient) {
1955
2727
  _daemonClient.close();
1956
2728
  _daemonClient = null;
1957
2729
  }
2730
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2731
+ _adapterClient.close();
2732
+ }
2733
+ _adapterClient = null;
1958
2734
  if (_client) {
1959
2735
  _client.close();
1960
2736
  _client = null;
1961
2737
  _resilientClient = null;
1962
2738
  }
1963
2739
  }
1964
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
2740
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
1965
2741
  var init_database = __esm({
1966
2742
  "src/lib/database.ts"() {
1967
2743
  "use strict";
1968
2744
  init_db_retry();
1969
2745
  init_employees();
2746
+ init_database_adapter();
1970
2747
  _client = null;
1971
2748
  _resilientClient = null;
1972
2749
  _walCheckpointTimer = null;
1973
2750
  _daemonClient = null;
2751
+ _adapterClient = null;
1974
2752
  initTurso = initDatabase;
1975
2753
  disposeTurso = disposeDatabase;
1976
2754
  }
@@ -1978,14 +2756,14 @@ var init_database = __esm({
1978
2756
 
1979
2757
  // src/lib/keychain.ts
1980
2758
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
1981
- import { existsSync as existsSync4 } from "fs";
1982
- import path4 from "path";
1983
- import os4 from "os";
2759
+ import { existsSync as existsSync6 } from "fs";
2760
+ import path6 from "path";
2761
+ import os5 from "os";
1984
2762
  function getKeyDir() {
1985
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os4.homedir(), ".exe-os");
2763
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
1986
2764
  }
1987
2765
  function getKeyPath() {
1988
- return path4.join(getKeyDir(), "master.key");
2766
+ return path6.join(getKeyDir(), "master.key");
1989
2767
  }
1990
2768
  async function tryKeytar() {
1991
2769
  try {
@@ -2006,9 +2784,9 @@ async function getMasterKey() {
2006
2784
  }
2007
2785
  }
2008
2786
  const keyPath = getKeyPath();
2009
- if (!existsSync4(keyPath)) {
2787
+ if (!existsSync6(keyPath)) {
2010
2788
  process.stderr.write(
2011
- `[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2789
+ `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2012
2790
  `
2013
2791
  );
2014
2792
  return null;
@@ -2093,6 +2871,7 @@ var shard_manager_exports = {};
2093
2871
  __export(shard_manager_exports, {
2094
2872
  disposeShards: () => disposeShards,
2095
2873
  ensureShardSchema: () => ensureShardSchema,
2874
+ getOpenShardCount: () => getOpenShardCount,
2096
2875
  getReadyShardClient: () => getReadyShardClient,
2097
2876
  getShardClient: () => getShardClient,
2098
2877
  getShardsDir: () => getShardsDir,
@@ -2101,15 +2880,18 @@ __export(shard_manager_exports, {
2101
2880
  listShards: () => listShards,
2102
2881
  shardExists: () => shardExists
2103
2882
  });
2104
- import path5 from "path";
2105
- import { existsSync as existsSync5, mkdirSync, readdirSync } from "fs";
2883
+ import path7 from "path";
2884
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync } from "fs";
2106
2885
  import { createClient as createClient2 } from "@libsql/client";
2107
2886
  function initShardManager(encryptionKey) {
2108
2887
  _encryptionKey = encryptionKey;
2109
- if (!existsSync5(SHARDS_DIR)) {
2110
- mkdirSync(SHARDS_DIR, { recursive: true });
2888
+ if (!existsSync7(SHARDS_DIR)) {
2889
+ mkdirSync2(SHARDS_DIR, { recursive: true });
2111
2890
  }
2112
2891
  _shardingEnabled = true;
2892
+ if (_evictionTimer) clearInterval(_evictionTimer);
2893
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
2894
+ _evictionTimer.unref();
2113
2895
  }
2114
2896
  function isShardingEnabled() {
2115
2897
  return _shardingEnabled;
@@ -2126,21 +2908,28 @@ function getShardClient(projectName) {
2126
2908
  throw new Error(`Invalid project name for shard: "${projectName}"`);
2127
2909
  }
2128
2910
  const cached = _shards.get(safeName);
2129
- if (cached) return cached;
2130
- const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
2911
+ if (cached) {
2912
+ _shardLastAccess.set(safeName, Date.now());
2913
+ return cached;
2914
+ }
2915
+ while (_shards.size >= MAX_OPEN_SHARDS) {
2916
+ evictLRU();
2917
+ }
2918
+ const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
2131
2919
  const client = createClient2({
2132
2920
  url: `file:${dbPath}`,
2133
2921
  encryptionKey: _encryptionKey
2134
2922
  });
2135
2923
  _shards.set(safeName, client);
2924
+ _shardLastAccess.set(safeName, Date.now());
2136
2925
  return client;
2137
2926
  }
2138
2927
  function shardExists(projectName) {
2139
2928
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2140
- return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
2929
+ return existsSync7(path7.join(SHARDS_DIR, `${safeName}.db`));
2141
2930
  }
2142
2931
  function listShards() {
2143
- if (!existsSync5(SHARDS_DIR)) return [];
2932
+ if (!existsSync7(SHARDS_DIR)) return [];
2144
2933
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2145
2934
  }
2146
2935
  async function ensureShardSchema(client) {
@@ -2192,6 +2981,8 @@ async function ensureShardSchema(client) {
2192
2981
  for (const col of [
2193
2982
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
2194
2983
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
2984
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
2985
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
2195
2986
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
2196
2987
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
2197
2988
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -2214,7 +3005,23 @@ async function ensureShardSchema(client) {
2214
3005
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
2215
3006
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
2216
3007
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
2217
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
3008
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
3009
+ // Metadata enrichment columns (must match database.ts)
3010
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
3011
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
3012
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
3013
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
3014
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
3015
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
3016
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
3017
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
3018
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
3019
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
3020
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
3021
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
3022
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
3023
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
3024
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2218
3025
  ]) {
2219
3026
  try {
2220
3027
  await client.execute(col);
@@ -2313,21 +3120,69 @@ async function getReadyShardClient(projectName) {
2313
3120
  await ensureShardSchema(client);
2314
3121
  return client;
2315
3122
  }
3123
+ function evictLRU() {
3124
+ let oldest = null;
3125
+ let oldestTime = Infinity;
3126
+ for (const [name, time] of _shardLastAccess) {
3127
+ if (time < oldestTime) {
3128
+ oldestTime = time;
3129
+ oldest = name;
3130
+ }
3131
+ }
3132
+ if (oldest) {
3133
+ const client = _shards.get(oldest);
3134
+ if (client) {
3135
+ client.close();
3136
+ }
3137
+ _shards.delete(oldest);
3138
+ _shardLastAccess.delete(oldest);
3139
+ }
3140
+ }
3141
+ function evictIdleShards() {
3142
+ const now = Date.now();
3143
+ const toEvict = [];
3144
+ for (const [name, lastAccess] of _shardLastAccess) {
3145
+ if (now - lastAccess > SHARD_IDLE_MS) {
3146
+ toEvict.push(name);
3147
+ }
3148
+ }
3149
+ for (const name of toEvict) {
3150
+ const client = _shards.get(name);
3151
+ if (client) {
3152
+ client.close();
3153
+ }
3154
+ _shards.delete(name);
3155
+ _shardLastAccess.delete(name);
3156
+ }
3157
+ }
3158
+ function getOpenShardCount() {
3159
+ return _shards.size;
3160
+ }
2316
3161
  function disposeShards() {
3162
+ if (_evictionTimer) {
3163
+ clearInterval(_evictionTimer);
3164
+ _evictionTimer = null;
3165
+ }
2317
3166
  for (const [, client] of _shards) {
2318
3167
  client.close();
2319
3168
  }
2320
3169
  _shards.clear();
3170
+ _shardLastAccess.clear();
2321
3171
  _shardingEnabled = false;
2322
3172
  _encryptionKey = null;
2323
3173
  }
2324
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
3174
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
2325
3175
  var init_shard_manager = __esm({
2326
3176
  "src/lib/shard-manager.ts"() {
2327
3177
  "use strict";
2328
3178
  init_config();
2329
- SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
3179
+ SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
3180
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
3181
+ MAX_OPEN_SHARDS = 10;
3182
+ EVICTION_INTERVAL_MS = 60 * 1e3;
2330
3183
  _shards = /* @__PURE__ */ new Map();
3184
+ _shardLastAccess = /* @__PURE__ */ new Map();
3185
+ _evictionTimer = null;
2331
3186
  _encryptionKey = null;
2332
3187
  _shardingEnabled = false;
2333
3188
  }
@@ -3091,13 +3946,13 @@ var init_store = __esm({
3091
3946
  });
3092
3947
 
3093
3948
  // src/lib/session-registry.ts
3094
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync6 } from "fs";
3095
- import path6 from "path";
3096
- import os5 from "os";
3949
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync8 } from "fs";
3950
+ import path8 from "path";
3951
+ import os6 from "os";
3097
3952
  function registerSession(entry) {
3098
- const dir = path6.dirname(REGISTRY_PATH);
3099
- if (!existsSync6(dir)) {
3100
- mkdirSync2(dir, { recursive: true });
3953
+ const dir = path8.dirname(REGISTRY_PATH);
3954
+ if (!existsSync8(dir)) {
3955
+ mkdirSync3(dir, { recursive: true });
3101
3956
  }
3102
3957
  const sessions = listSessions();
3103
3958
  const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
@@ -3106,11 +3961,11 @@ function registerSession(entry) {
3106
3961
  } else {
3107
3962
  sessions.push(entry);
3108
3963
  }
3109
- writeFileSync2(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
3964
+ writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
3110
3965
  }
3111
3966
  function listSessions() {
3112
3967
  try {
3113
- const raw = readFileSync4(REGISTRY_PATH, "utf8");
3968
+ const raw = readFileSync5(REGISTRY_PATH, "utf8");
3114
3969
  return JSON.parse(raw);
3115
3970
  } catch {
3116
3971
  return [];
@@ -3120,7 +3975,7 @@ var REGISTRY_PATH;
3120
3975
  var init_session_registry = __esm({
3121
3976
  "src/lib/session-registry.ts"() {
3122
3977
  "use strict";
3123
- REGISTRY_PATH = path6.join(os5.homedir(), ".exe-os", "session-registry.json");
3978
+ REGISTRY_PATH = path8.join(os6.homedir(), ".exe-os", "session-registry.json");
3124
3979
  }
3125
3980
  });
3126
3981
 
@@ -3400,12 +4255,12 @@ var init_runtime_table = __esm({
3400
4255
  });
3401
4256
 
3402
4257
  // src/lib/agent-config.ts
3403
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
3404
- import path7 from "path";
4258
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync9 } from "fs";
4259
+ import path9 from "path";
3405
4260
  function loadAgentConfig() {
3406
- if (!existsSync7(AGENT_CONFIG_PATH)) return {};
4261
+ if (!existsSync9(AGENT_CONFIG_PATH)) return {};
3407
4262
  try {
3408
- return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
4263
+ return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
3409
4264
  } catch {
3410
4265
  return {};
3411
4266
  }
@@ -3424,7 +4279,8 @@ var init_agent_config = __esm({
3424
4279
  "use strict";
3425
4280
  init_config();
3426
4281
  init_runtime_table();
3427
- AGENT_CONFIG_PATH = path7.join(EXE_AI_DIR, "agent-config.json");
4282
+ init_secure_files();
4283
+ AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
3428
4284
  DEFAULT_MODELS = {
3429
4285
  claude: "claude-opus-4",
3430
4286
  codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
@@ -3442,17 +4298,17 @@ __export(intercom_queue_exports, {
3442
4298
  queueIntercom: () => queueIntercom,
3443
4299
  readQueue: () => readQueue
3444
4300
  });
3445
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
3446
- import path8 from "path";
3447
- import os6 from "os";
4301
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync10, mkdirSync as mkdirSync4 } from "fs";
4302
+ import path10 from "path";
4303
+ import os7 from "os";
3448
4304
  function ensureDir() {
3449
- const dir = path8.dirname(QUEUE_PATH);
3450
- if (!existsSync8(dir)) mkdirSync4(dir, { recursive: true });
4305
+ const dir = path10.dirname(QUEUE_PATH);
4306
+ if (!existsSync10(dir)) mkdirSync4(dir, { recursive: true });
3451
4307
  }
3452
4308
  function readQueue() {
3453
4309
  try {
3454
- if (!existsSync8(QUEUE_PATH)) return [];
3455
- return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
4310
+ if (!existsSync10(QUEUE_PATH)) return [];
4311
+ return JSON.parse(readFileSync7(QUEUE_PATH, "utf8"));
3456
4312
  } catch {
3457
4313
  return [];
3458
4314
  }
@@ -3460,7 +4316,7 @@ function readQueue() {
3460
4316
  function writeQueue(queue) {
3461
4317
  ensureDir();
3462
4318
  const tmp = `${QUEUE_PATH}.tmp`;
3463
- writeFileSync4(tmp, JSON.stringify(queue, null, 2));
4319
+ writeFileSync5(tmp, JSON.stringify(queue, null, 2));
3464
4320
  renameSync3(tmp, QUEUE_PATH);
3465
4321
  }
3466
4322
  function queueIntercom(targetSession, reason) {
@@ -3552,26 +4408,29 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
3552
4408
  var init_intercom_queue = __esm({
3553
4409
  "src/lib/intercom-queue.ts"() {
3554
4410
  "use strict";
3555
- QUEUE_PATH = path8.join(os6.homedir(), ".exe-os", "intercom-queue.json");
4411
+ QUEUE_PATH = path10.join(os7.homedir(), ".exe-os", "intercom-queue.json");
3556
4412
  MAX_RETRIES2 = 5;
3557
4413
  TTL_MS = 60 * 60 * 1e3;
3558
- INTERCOM_LOG = path8.join(os6.homedir(), ".exe-os", "intercom.log");
4414
+ INTERCOM_LOG = path10.join(os7.homedir(), ".exe-os", "intercom.log");
3559
4415
  }
3560
4416
  });
3561
4417
 
3562
4418
  // src/lib/license.ts
3563
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
4419
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync5 } from "fs";
3564
4420
  import { randomUUID as randomUUID3 } from "crypto";
3565
- import path9 from "path";
4421
+ import { createRequire as createRequire2 } from "module";
4422
+ import { pathToFileURL as pathToFileURL2 } from "url";
4423
+ import os8 from "os";
4424
+ import path11 from "path";
3566
4425
  import { jwtVerify, importSPKI } from "jose";
3567
4426
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
3568
4427
  var init_license = __esm({
3569
4428
  "src/lib/license.ts"() {
3570
4429
  "use strict";
3571
4430
  init_config();
3572
- LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
3573
- CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
3574
- DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
4431
+ LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
4432
+ CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
4433
+ DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
3575
4434
  PLAN_LIMITS = {
3576
4435
  free: { devices: 1, employees: 1, memories: 5e3 },
3577
4436
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -3583,12 +4442,12 @@ var init_license = __esm({
3583
4442
  });
3584
4443
 
3585
4444
  // src/lib/plan-limits.ts
3586
- import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
3587
- import path10 from "path";
4445
+ import { readFileSync as readFileSync9, existsSync as existsSync12 } from "fs";
4446
+ import path12 from "path";
3588
4447
  function getLicenseSync() {
3589
4448
  try {
3590
- if (!existsSync10(CACHE_PATH2)) return freeLicense();
3591
- const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
4449
+ if (!existsSync12(CACHE_PATH2)) return freeLicense();
4450
+ const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
3592
4451
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
3593
4452
  const parts = raw.token.split(".");
3594
4453
  if (parts.length !== 3) return freeLicense();
@@ -3626,8 +4485,8 @@ function assertEmployeeLimitSync(rosterPath) {
3626
4485
  const filePath = rosterPath ?? EMPLOYEES_PATH;
3627
4486
  let count = 0;
3628
4487
  try {
3629
- if (existsSync10(filePath)) {
3630
- const raw = readFileSync8(filePath, "utf8");
4488
+ if (existsSync12(filePath)) {
4489
+ const raw = readFileSync9(filePath, "utf8");
3631
4490
  const employees = JSON.parse(raw);
3632
4491
  count = Array.isArray(employees) ? employees.length : 0;
3633
4492
  }
@@ -3656,29 +4515,30 @@ var init_plan_limits = __esm({
3656
4515
  this.name = "PlanLimitError";
3657
4516
  }
3658
4517
  };
3659
- CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
4518
+ CACHE_PATH2 = path12.join(EXE_AI_DIR, "license-cache.json");
3660
4519
  }
3661
4520
  });
3662
4521
 
3663
4522
  // src/lib/notifications.ts
3664
- import crypto from "crypto";
3665
- import path11 from "path";
3666
- import os7 from "os";
4523
+ import crypto2 from "crypto";
4524
+ import path13 from "path";
4525
+ import os9 from "os";
3667
4526
  import {
3668
- readFileSync as readFileSync9,
4527
+ readFileSync as readFileSync10,
3669
4528
  readdirSync as readdirSync2,
3670
4529
  unlinkSync as unlinkSync3,
3671
- existsSync as existsSync11,
4530
+ existsSync as existsSync13,
3672
4531
  rmdirSync
3673
4532
  } from "fs";
3674
4533
  async function writeNotification(notification) {
3675
4534
  try {
3676
4535
  const client = getClient();
3677
- const id = crypto.randomUUID();
4536
+ const id = crypto2.randomUUID();
3678
4537
  const now = (/* @__PURE__ */ new Date()).toISOString();
4538
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
3679
4539
  await client.execute({
3680
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
3681
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
4540
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
4541
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3682
4542
  args: [
3683
4543
  id,
3684
4544
  notification.agentId,
@@ -3687,6 +4547,7 @@ async function writeNotification(notification) {
3687
4547
  notification.project,
3688
4548
  notification.summary,
3689
4549
  notification.taskFile ?? null,
4550
+ sessionScope,
3690
4551
  now
3691
4552
  ]
3692
4553
  });
@@ -3695,12 +4556,14 @@ async function writeNotification(notification) {
3695
4556
  `);
3696
4557
  }
3697
4558
  }
3698
- async function markAsReadByTaskFile(taskFile) {
4559
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
3699
4560
  try {
3700
4561
  const client = getClient();
4562
+ const scope = strictSessionScopeFilter(sessionScope);
3701
4563
  await client.execute({
3702
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
3703
- args: [taskFile]
4564
+ sql: `UPDATE notifications SET read = 1
4565
+ WHERE task_file = ? AND read = 0${scope.sql}`,
4566
+ args: [taskFile, ...scope.args]
3704
4567
  });
3705
4568
  } catch {
3706
4569
  }
@@ -3709,11 +4572,12 @@ var init_notifications = __esm({
3709
4572
  "src/lib/notifications.ts"() {
3710
4573
  "use strict";
3711
4574
  init_database();
4575
+ init_task_scope();
3712
4576
  }
3713
4577
  });
3714
4578
 
3715
4579
  // src/lib/session-kill-telemetry.ts
3716
- import crypto2 from "crypto";
4580
+ import crypto3 from "crypto";
3717
4581
  async function recordSessionKill(input) {
3718
4582
  try {
3719
4583
  const client = getClient();
@@ -3723,7 +4587,7 @@ async function recordSessionKill(input) {
3723
4587
  ticks_idle, estimated_tokens_saved)
3724
4588
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
3725
4589
  args: [
3726
- crypto2.randomUUID(),
4590
+ crypto3.randomUUID(),
3727
4591
  input.sessionName,
3728
4592
  input.agentId,
3729
4593
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -3763,12 +4627,12 @@ __export(tasks_crud_exports, {
3763
4627
  updateTaskStatus: () => updateTaskStatus,
3764
4628
  writeCheckpoint: () => writeCheckpoint
3765
4629
  });
3766
- import crypto3 from "crypto";
3767
- import path12 from "path";
3768
- import os8 from "os";
4630
+ import crypto4 from "crypto";
4631
+ import path14 from "path";
4632
+ import os10 from "os";
3769
4633
  import { execSync as execSync4 } from "child_process";
3770
4634
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
3771
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
4635
+ import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
3772
4636
  async function writeCheckpoint(input) {
3773
4637
  const client = getClient();
3774
4638
  const row = await resolveTask(client, input.taskId);
@@ -3884,7 +4748,7 @@ async function resolveTask(client, identifier, scopeSession) {
3884
4748
  }
3885
4749
  async function createTaskCore(input) {
3886
4750
  const client = getClient();
3887
- const id = crypto3.randomUUID();
4751
+ const id = crypto4.randomUUID();
3888
4752
  const now = (/* @__PURE__ */ new Date()).toISOString();
3889
4753
  const slug = slugify(input.title);
3890
4754
  let earlySessionScope = null;
@@ -3943,8 +4807,8 @@ ${laneWarning}` : laneWarning;
3943
4807
  }
3944
4808
  if (input.baseDir) {
3945
4809
  try {
3946
- await mkdir4(path12.join(input.baseDir, "exe", "output"), { recursive: true });
3947
- await mkdir4(path12.join(input.baseDir, "exe", "research"), { recursive: true });
4810
+ await mkdir4(path14.join(input.baseDir, "exe", "output"), { recursive: true });
4811
+ await mkdir4(path14.join(input.baseDir, "exe", "research"), { recursive: true });
3948
4812
  await ensureArchitectureDoc(input.baseDir, input.projectName);
3949
4813
  await ensureGitignoreExe(input.baseDir);
3950
4814
  } catch {
@@ -3980,13 +4844,19 @@ ${laneWarning}` : laneWarning;
3980
4844
  });
3981
4845
  if (input.baseDir) {
3982
4846
  try {
3983
- const EXE_OS_DIR = path12.join(os8.homedir(), ".exe-os");
3984
- const mdPath = path12.join(EXE_OS_DIR, taskFile);
3985
- const mdDir = path12.dirname(mdPath);
3986
- if (!existsSync12(mdDir)) await mkdir4(mdDir, { recursive: true });
4847
+ const EXE_OS_DIR = path14.join(os10.homedir(), ".exe-os");
4848
+ const mdPath = path14.join(EXE_OS_DIR, taskFile);
4849
+ const mdDir = path14.dirname(mdPath);
4850
+ if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
3987
4851
  const reviewer = input.reviewer ?? input.assignedBy;
3988
4852
  const mdContent = `# ${input.title}
3989
4853
 
4854
+ ## MANDATORY: When done
4855
+
4856
+ You MUST call update_task with status "done" and a result summary when finished.
4857
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
4858
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
4859
+
3990
4860
  **ID:** ${id}
3991
4861
  **Status:** ${initialStatus}
3992
4862
  **Priority:** ${input.priority}
@@ -4000,12 +4870,6 @@ ${laneWarning}` : laneWarning;
4000
4870
  ## Context
4001
4871
 
4002
4872
  ${input.context}
4003
-
4004
- ## MANDATORY: When done
4005
-
4006
- You MUST call update_task with status "done" and a result summary when finished.
4007
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
4008
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
4009
4873
  `;
4010
4874
  await writeFile4(mdPath, mdContent, "utf-8");
4011
4875
  } catch (err) {
@@ -4254,7 +5118,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
4254
5118
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
4255
5119
  } catch {
4256
5120
  }
4257
- if (input.status === "done" || input.status === "cancelled") {
5121
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
4258
5122
  try {
4259
5123
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
4260
5124
  clearQueueForAgent2(String(row.assigned_to));
@@ -4283,9 +5147,9 @@ async function deleteTaskCore(taskId, _baseDir) {
4283
5147
  return { taskFile, assignedTo, assignedBy, taskSlug };
4284
5148
  }
4285
5149
  async function ensureArchitectureDoc(baseDir, projectName) {
4286
- const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
5150
+ const archPath = path14.join(baseDir, "exe", "ARCHITECTURE.md");
4287
5151
  try {
4288
- if (existsSync12(archPath)) return;
5152
+ if (existsSync14(archPath)) return;
4289
5153
  const template = [
4290
5154
  `# ${projectName} \u2014 System Architecture`,
4291
5155
  "",
@@ -4318,10 +5182,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
4318
5182
  }
4319
5183
  }
4320
5184
  async function ensureGitignoreExe(baseDir) {
4321
- const gitignorePath = path12.join(baseDir, ".gitignore");
5185
+ const gitignorePath = path14.join(baseDir, ".gitignore");
4322
5186
  try {
4323
- if (existsSync12(gitignorePath)) {
4324
- const content = readFileSync10(gitignorePath, "utf-8");
5187
+ if (existsSync14(gitignorePath)) {
5188
+ const content = readFileSync11(gitignorePath, "utf-8");
4325
5189
  if (/^\/?exe\/?$/m.test(content)) return;
4326
5190
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
4327
5191
  } else {
@@ -4364,8 +5228,8 @@ __export(tasks_review_exports, {
4364
5228
  isStale: () => isStale,
4365
5229
  listPendingReviews: () => listPendingReviews
4366
5230
  });
4367
- import path13 from "path";
4368
- import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
5231
+ import path15 from "path";
5232
+ import { existsSync as existsSync15, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
4369
5233
  function formatAge(isoTimestamp) {
4370
5234
  if (!isoTimestamp) return "";
4371
5235
  const ms = Date.now() - new Date(isoTimestamp).getTime();
@@ -4383,54 +5247,38 @@ function isStale(isoTimestamp) {
4383
5247
  }
4384
5248
  async function countPendingReviews(sessionScope) {
4385
5249
  const client = getClient();
4386
- if (sessionScope) {
4387
- const result2 = await client.execute({
4388
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
4389
- args: [sessionScope]
4390
- });
4391
- return Number(result2.rows[0]?.cnt) || 0;
4392
- }
5250
+ const scope = strictSessionScopeFilter(
5251
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
5252
+ );
4393
5253
  const result = await client.execute({
4394
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
4395
- args: []
5254
+ sql: `SELECT COUNT(*) as cnt FROM tasks
5255
+ WHERE status = 'needs_review'${scope.sql}`,
5256
+ args: [...scope.args]
4396
5257
  });
4397
5258
  return Number(result.rows[0]?.cnt) || 0;
4398
5259
  }
4399
5260
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
4400
5261
  const client = getClient();
4401
- if (sessionScope) {
4402
- const result2 = await client.execute({
4403
- sql: `SELECT COUNT(*) as cnt FROM tasks
4404
- WHERE status = 'needs_review' AND updated_at > ?
4405
- AND session_scope = ?`,
4406
- args: [sinceIso, sessionScope]
4407
- });
4408
- return Number(result2.rows[0]?.cnt) || 0;
4409
- }
5262
+ const scope = strictSessionScopeFilter(
5263
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
5264
+ );
4410
5265
  const result = await client.execute({
4411
5266
  sql: `SELECT COUNT(*) as cnt FROM tasks
4412
- WHERE status = 'needs_review' AND updated_at > ?`,
4413
- args: [sinceIso]
5267
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
5268
+ args: [sinceIso, ...scope.args]
4414
5269
  });
4415
5270
  return Number(result.rows[0]?.cnt) || 0;
4416
5271
  }
4417
5272
  async function listPendingReviews(limit, sessionScope) {
4418
5273
  const client = getClient();
4419
- if (sessionScope) {
4420
- const result2 = await client.execute({
4421
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
4422
- WHERE status = 'needs_review'
4423
- AND session_scope = ?
4424
- ORDER BY updated_at ASC LIMIT ?`,
4425
- args: [sessionScope, limit]
4426
- });
4427
- return result2.rows;
4428
- }
5274
+ const scope = strictSessionScopeFilter(
5275
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
5276
+ );
4429
5277
  const result = await client.execute({
4430
5278
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
4431
- WHERE status = 'needs_review'
5279
+ WHERE status = 'needs_review'${scope.sql}
4432
5280
  ORDER BY updated_at ASC LIMIT ?`,
4433
- args: [limit]
5281
+ args: [...scope.args, limit]
4434
5282
  });
4435
5283
  return result.rows;
4436
5284
  }
@@ -4442,7 +5290,7 @@ async function cleanupOrphanedReviews() {
4442
5290
  WHERE status IN ('open', 'needs_review', 'in_progress')
4443
5291
  AND assigned_by = 'system'
4444
5292
  AND title LIKE 'Review:%'
4445
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
5293
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
4446
5294
  args: [now]
4447
5295
  });
4448
5296
  const r1b = await client.execute({
@@ -4650,11 +5498,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
4650
5498
  );
4651
5499
  }
4652
5500
  try {
4653
- const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
4654
- if (existsSync13(cacheDir)) {
5501
+ const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
5502
+ if (existsSync15(cacheDir)) {
4655
5503
  for (const f of readdirSync3(cacheDir)) {
4656
5504
  if (f.startsWith("review-notified-")) {
4657
- unlinkSync4(path13.join(cacheDir, f));
5505
+ unlinkSync4(path15.join(cacheDir, f));
4658
5506
  }
4659
5507
  }
4660
5508
  }
@@ -4671,11 +5519,12 @@ var init_tasks_review = __esm({
4671
5519
  init_tmux_routing();
4672
5520
  init_session_key();
4673
5521
  init_state_bus();
5522
+ init_task_scope();
4674
5523
  }
4675
5524
  });
4676
5525
 
4677
5526
  // src/lib/tasks-chain.ts
4678
- import path14 from "path";
5527
+ import path16 from "path";
4679
5528
  import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
4680
5529
  async function cascadeUnblock(taskId, baseDir, now) {
4681
5530
  const client = getClient();
@@ -4692,7 +5541,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
4692
5541
  });
4693
5542
  for (const ur of unblockedRows.rows) {
4694
5543
  try {
4695
- const ubFile = path14.join(baseDir, String(ur.task_file));
5544
+ const ubFile = path16.join(baseDir, String(ur.task_file));
4696
5545
  let ubContent = await readFile4(ubFile, "utf-8");
4697
5546
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
4698
5547
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -4727,7 +5576,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
4727
5576
  const scScope = sessionScopeFilter();
4728
5577
  const remaining = await client.execute({
4729
5578
  sql: `SELECT COUNT(*) as cnt FROM tasks
4730
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
5579
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
4731
5580
  args: [parentTaskId, ...scScope.args]
4732
5581
  });
4733
5582
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -4761,7 +5610,7 @@ var init_tasks_chain = __esm({
4761
5610
 
4762
5611
  // src/lib/project-name.ts
4763
5612
  import { execSync as execSync5 } from "child_process";
4764
- import path15 from "path";
5613
+ import path17 from "path";
4765
5614
  function getProjectName(cwd) {
4766
5615
  const dir = cwd ?? process.cwd();
4767
5616
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -4774,7 +5623,7 @@ function getProjectName(cwd) {
4774
5623
  timeout: 2e3,
4775
5624
  stdio: ["pipe", "pipe", "pipe"]
4776
5625
  }).trim();
4777
- repoRoot = path15.dirname(gitCommonDir);
5626
+ repoRoot = path17.dirname(gitCommonDir);
4778
5627
  } catch {
4779
5628
  repoRoot = execSync5("git rev-parse --show-toplevel", {
4780
5629
  cwd: dir,
@@ -4783,11 +5632,11 @@ function getProjectName(cwd) {
4783
5632
  stdio: ["pipe", "pipe", "pipe"]
4784
5633
  }).trim();
4785
5634
  }
4786
- _cached2 = path15.basename(repoRoot);
5635
+ _cached2 = path17.basename(repoRoot);
4787
5636
  _cachedCwd = dir;
4788
5637
  return _cached2;
4789
5638
  } catch {
4790
- _cached2 = path15.basename(dir);
5639
+ _cached2 = path17.basename(dir);
4791
5640
  _cachedCwd = dir;
4792
5641
  return _cached2;
4793
5642
  }
@@ -4930,10 +5779,10 @@ var init_tasks_notify = __esm({
4930
5779
  });
4931
5780
 
4932
5781
  // src/lib/behaviors.ts
4933
- import crypto4 from "crypto";
5782
+ import crypto5 from "crypto";
4934
5783
  async function storeBehavior(opts) {
4935
5784
  const client = getClient();
4936
- const id = crypto4.randomUUID();
5785
+ const id = crypto5.randomUUID();
4937
5786
  const now = (/* @__PURE__ */ new Date()).toISOString();
4938
5787
  await client.execute({
4939
5788
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -4962,7 +5811,7 @@ __export(skill_learning_exports, {
4962
5811
  storeTrajectory: () => storeTrajectory,
4963
5812
  sweepTrajectories: () => sweepTrajectories
4964
5813
  });
4965
- import crypto5 from "crypto";
5814
+ import crypto6 from "crypto";
4966
5815
  async function extractTrajectory(taskId, agentId) {
4967
5816
  const client = getClient();
4968
5817
  const result = await client.execute({
@@ -4991,11 +5840,11 @@ async function extractTrajectory(taskId, agentId) {
4991
5840
  return signature;
4992
5841
  }
4993
5842
  function hashSignature(signature) {
4994
- return crypto5.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
5843
+ return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4995
5844
  }
4996
5845
  async function storeTrajectory(opts) {
4997
5846
  const client = getClient();
4998
- const id = crypto5.randomUUID();
5847
+ const id = crypto6.randomUUID();
4999
5848
  const now = (/* @__PURE__ */ new Date()).toISOString();
5000
5849
  const signatureHash = hashSignature(opts.signature);
5001
5850
  await client.execute({
@@ -5260,8 +6109,8 @@ __export(tasks_exports, {
5260
6109
  updateTaskStatus: () => updateTaskStatus,
5261
6110
  writeCheckpoint: () => writeCheckpoint
5262
6111
  });
5263
- import path16 from "path";
5264
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
6112
+ import path18 from "path";
6113
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
5265
6114
  async function createTask(input) {
5266
6115
  const result = await createTaskCore(input);
5267
6116
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -5280,12 +6129,12 @@ async function updateTask(input) {
5280
6129
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
5281
6130
  try {
5282
6131
  const agent = String(row.assigned_to);
5283
- const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
5284
- const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
6132
+ const cacheDir = path18.join(EXE_AI_DIR, "session-cache");
6133
+ const cachePath = path18.join(cacheDir, `current-task-${agent}.json`);
5285
6134
  if (input.status === "in_progress") {
5286
6135
  mkdirSync6(cacheDir, { recursive: true });
5287
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
5288
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
6136
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
6137
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
5289
6138
  try {
5290
6139
  unlinkSync5(cachePath);
5291
6140
  } catch {
@@ -5293,10 +6142,10 @@ async function updateTask(input) {
5293
6142
  }
5294
6143
  } catch {
5295
6144
  }
5296
- if (input.status === "done") {
6145
+ if (input.status === "done" || input.status === "closed") {
5297
6146
  await cleanupReviewFile(row, taskFile, input.baseDir);
5298
6147
  }
5299
- if (input.status === "done" || input.status === "cancelled") {
6148
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
5300
6149
  try {
5301
6150
  const client = getClient();
5302
6151
  const taskTitle = String(row.title);
@@ -5312,7 +6161,7 @@ async function updateTask(input) {
5312
6161
  if (!isCoordinatorName(assignedAgent)) {
5313
6162
  try {
5314
6163
  const draftClient = getClient();
5315
- if (input.status === "done") {
6164
+ if (input.status === "done" || input.status === "closed") {
5316
6165
  await draftClient.execute({
5317
6166
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
5318
6167
  args: [assignedAgent]
@@ -5329,7 +6178,7 @@ async function updateTask(input) {
5329
6178
  try {
5330
6179
  const client = getClient();
5331
6180
  const cascaded = await client.execute({
5332
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
6181
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
5333
6182
  WHERE parent_task_id = ? AND status = 'needs_review'`,
5334
6183
  args: [now, taskId]
5335
6184
  });
@@ -5342,14 +6191,14 @@ async function updateTask(input) {
5342
6191
  } catch {
5343
6192
  }
5344
6193
  }
5345
- const isTerminal = input.status === "done" || input.status === "needs_review";
6194
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
5346
6195
  if (isTerminal) {
5347
6196
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
5348
6197
  if (!isCoordinator) {
5349
6198
  notifyTaskDone();
5350
6199
  }
5351
6200
  await markTaskNotificationsRead(taskFile);
5352
- if (input.status === "done") {
6201
+ if (input.status === "done" || input.status === "closed") {
5353
6202
  try {
5354
6203
  await cascadeUnblock(taskId, input.baseDir, now);
5355
6204
  } catch {
@@ -5369,7 +6218,7 @@ async function updateTask(input) {
5369
6218
  }
5370
6219
  }
5371
6220
  }
5372
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
6221
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
5373
6222
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
5374
6223
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
5375
6224
  taskId,
@@ -5741,6 +6590,7 @@ __export(tmux_routing_exports, {
5741
6590
  isEmployeeAlive: () => isEmployeeAlive,
5742
6591
  isExeSession: () => isExeSession,
5743
6592
  isSessionBusy: () => isSessionBusy,
6593
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
5744
6594
  notifyParentExe: () => notifyParentExe,
5745
6595
  parseParentExe: () => parseParentExe,
5746
6596
  registerParentExe: () => registerParentExe,
@@ -5751,13 +6601,13 @@ __export(tmux_routing_exports, {
5751
6601
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
5752
6602
  });
5753
6603
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
5754
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync4 } from "fs";
5755
- import path17 from "path";
5756
- import os9 from "os";
6604
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync4 } from "fs";
6605
+ import path19 from "path";
6606
+ import os11 from "os";
5757
6607
  import { fileURLToPath as fileURLToPath2 } from "url";
5758
6608
  import { unlinkSync as unlinkSync6 } from "fs";
5759
6609
  function spawnLockPath(sessionName) {
5760
- return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
6610
+ return path19.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5761
6611
  }
5762
6612
  function isProcessAlive(pid) {
5763
6613
  try {
@@ -5768,13 +6618,13 @@ function isProcessAlive(pid) {
5768
6618
  }
5769
6619
  }
5770
6620
  function acquireSpawnLock2(sessionName) {
5771
- if (!existsSync14(SPAWN_LOCK_DIR)) {
6621
+ if (!existsSync16(SPAWN_LOCK_DIR)) {
5772
6622
  mkdirSync7(SPAWN_LOCK_DIR, { recursive: true });
5773
6623
  }
5774
6624
  const lockFile = spawnLockPath(sessionName);
5775
- if (existsSync14(lockFile)) {
6625
+ if (existsSync16(lockFile)) {
5776
6626
  try {
5777
- const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
6627
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
5778
6628
  const age = Date.now() - lock.timestamp;
5779
6629
  if (isProcessAlive(lock.pid) && age < 6e4) {
5780
6630
  return false;
@@ -5782,7 +6632,7 @@ function acquireSpawnLock2(sessionName) {
5782
6632
  } catch {
5783
6633
  }
5784
6634
  }
5785
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
6635
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5786
6636
  return true;
5787
6637
  }
5788
6638
  function releaseSpawnLock2(sessionName) {
@@ -5794,13 +6644,13 @@ function releaseSpawnLock2(sessionName) {
5794
6644
  function resolveBehaviorsExporterScript() {
5795
6645
  try {
5796
6646
  const thisFile = fileURLToPath2(import.meta.url);
5797
- const scriptPath = path17.join(
5798
- path17.dirname(thisFile),
6647
+ const scriptPath = path19.join(
6648
+ path19.dirname(thisFile),
5799
6649
  "..",
5800
6650
  "bin",
5801
6651
  "exe-export-behaviors.js"
5802
6652
  );
5803
- return existsSync14(scriptPath) ? scriptPath : null;
6653
+ return existsSync16(scriptPath) ? scriptPath : null;
5804
6654
  } catch {
5805
6655
  return null;
5806
6656
  }
@@ -5866,12 +6716,12 @@ function extractRootExe(name) {
5866
6716
  return parts.length > 0 ? parts[parts.length - 1] : null;
5867
6717
  }
5868
6718
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5869
- if (!existsSync14(SESSION_CACHE)) {
6719
+ if (!existsSync16(SESSION_CACHE)) {
5870
6720
  mkdirSync7(SESSION_CACHE, { recursive: true });
5871
6721
  }
5872
6722
  const rootExe = extractRootExe(parentExe) ?? parentExe;
5873
- const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5874
- writeFileSync7(filePath, JSON.stringify({
6723
+ const filePath = path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
6724
+ writeFileSync8(filePath, JSON.stringify({
5875
6725
  parentExe: rootExe,
5876
6726
  dispatchedBy: dispatchedBy || rootExe,
5877
6727
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5879,7 +6729,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5879
6729
  }
5880
6730
  function getParentExe(sessionKey) {
5881
6731
  try {
5882
- const data = JSON.parse(readFileSync11(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
6732
+ const data = JSON.parse(readFileSync12(path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5883
6733
  return data.parentExe || null;
5884
6734
  } catch {
5885
6735
  return null;
@@ -5887,8 +6737,8 @@ function getParentExe(sessionKey) {
5887
6737
  }
5888
6738
  function getDispatchedBy(sessionKey) {
5889
6739
  try {
5890
- const data = JSON.parse(readFileSync11(
5891
- path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
6740
+ const data = JSON.parse(readFileSync12(
6741
+ path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5892
6742
  "utf8"
5893
6743
  ));
5894
6744
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -5958,8 +6808,8 @@ async function verifyPaneAtCapacity(sessionName) {
5958
6808
  }
5959
6809
  function readDebounceState() {
5960
6810
  try {
5961
- if (!existsSync14(DEBOUNCE_FILE)) return {};
5962
- const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
6811
+ if (!existsSync16(DEBOUNCE_FILE)) return {};
6812
+ const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
5963
6813
  const state = {};
5964
6814
  for (const [key, val] of Object.entries(raw)) {
5965
6815
  if (typeof val === "number") {
@@ -5975,8 +6825,8 @@ function readDebounceState() {
5975
6825
  }
5976
6826
  function writeDebounceState(state) {
5977
6827
  try {
5978
- if (!existsSync14(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
5979
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
6828
+ if (!existsSync16(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
6829
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
5980
6830
  } catch {
5981
6831
  }
5982
6832
  }
@@ -6074,8 +6924,8 @@ function sendIntercom(targetSession) {
6074
6924
  try {
6075
6925
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
6076
6926
  const agent = baseAgentName(rawAgent);
6077
- const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
6078
- if (existsSync14(markerPath)) {
6927
+ const markerPath = path19.join(SESSION_CACHE, `current-task-${agent}.json`);
6928
+ if (existsSync16(markerPath)) {
6079
6929
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
6080
6930
  return "debounced";
6081
6931
  }
@@ -6084,8 +6934,8 @@ function sendIntercom(targetSession) {
6084
6934
  try {
6085
6935
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
6086
6936
  const agent = baseAgentName(rawAgent);
6087
- const taskDir = path17.join(process.cwd(), "exe", agent);
6088
- if (existsSync14(taskDir)) {
6937
+ const taskDir = path19.join(process.cwd(), "exe", agent);
6938
+ if (existsSync16(taskDir)) {
6089
6939
  const files = readdirSync4(taskDir).filter(
6090
6940
  (f) => f.endsWith(".md") && f !== "DONE.txt"
6091
6941
  );
@@ -6145,6 +6995,21 @@ function notifyParentExe(sessionKey) {
6145
6995
  }
6146
6996
  return true;
6147
6997
  }
6998
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName2, taskTitle) {
6999
+ const transport = getTransport();
7000
+ try {
7001
+ const sessions = transport.listSessions();
7002
+ if (!sessions.includes(coordinatorSession)) return false;
7003
+ execSync6(
7004
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
7005
+ { timeout: 3e3 }
7006
+ );
7007
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName2} completed "${taskTitle.slice(0, 50)}")`);
7008
+ return true;
7009
+ } catch {
7010
+ return false;
7011
+ }
7012
+ }
6148
7013
  function ensureEmployee(employeeName, exeSession2, projectDir, opts) {
6149
7014
  if (isCoordinatorName(employeeName)) {
6150
7015
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -6218,26 +7083,26 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
6218
7083
  const transport = getTransport();
6219
7084
  const sessionName = employeeSessionName(employeeName, exeSession2, opts?.instance);
6220
7085
  const instanceLabel2 = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
6221
- const logDir = path17.join(os9.homedir(), ".exe-os", "session-logs");
6222
- const logFile = path17.join(logDir, `${instanceLabel2}-${Date.now()}.log`);
6223
- if (!existsSync14(logDir)) {
7086
+ const logDir = path19.join(os11.homedir(), ".exe-os", "session-logs");
7087
+ const logFile = path19.join(logDir, `${instanceLabel2}-${Date.now()}.log`);
7088
+ if (!existsSync16(logDir)) {
6224
7089
  mkdirSync7(logDir, { recursive: true });
6225
7090
  }
6226
7091
  transport.kill(sessionName);
6227
7092
  let cleanupSuffix = "";
6228
7093
  try {
6229
7094
  const thisFile = fileURLToPath2(import.meta.url);
6230
- const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
6231
- if (existsSync14(cleanupScript)) {
7095
+ const cleanupScript = path19.join(path19.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
7096
+ if (existsSync16(cleanupScript)) {
6232
7097
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession2}"`;
6233
7098
  }
6234
7099
  } catch {
6235
7100
  }
6236
7101
  try {
6237
- const claudeJsonPath = path17.join(os9.homedir(), ".claude.json");
7102
+ const claudeJsonPath = path19.join(os11.homedir(), ".claude.json");
6238
7103
  let claudeJson = {};
6239
7104
  try {
6240
- claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
7105
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
6241
7106
  } catch {
6242
7107
  }
6243
7108
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -6245,17 +7110,17 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
6245
7110
  const trustDir = opts?.cwd ?? projectDir;
6246
7111
  if (!projects[trustDir]) projects[trustDir] = {};
6247
7112
  projects[trustDir].hasTrustDialogAccepted = true;
6248
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
7113
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6249
7114
  } catch {
6250
7115
  }
6251
7116
  try {
6252
- const settingsDir = path17.join(os9.homedir(), ".claude", "projects");
7117
+ const settingsDir = path19.join(os11.homedir(), ".claude", "projects");
6253
7118
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
6254
- const projSettingsDir = path17.join(settingsDir, normalizedKey);
6255
- const settingsPath = path17.join(projSettingsDir, "settings.json");
7119
+ const projSettingsDir = path19.join(settingsDir, normalizedKey);
7120
+ const settingsPath = path19.join(projSettingsDir, "settings.json");
6256
7121
  let settings = {};
6257
7122
  try {
6258
- settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
7123
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
6259
7124
  } catch {
6260
7125
  }
6261
7126
  const perms = settings.permissions ?? {};
@@ -6284,7 +7149,7 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
6284
7149
  perms.allow = allow;
6285
7150
  settings.permissions = perms;
6286
7151
  mkdirSync7(projSettingsDir, { recursive: true });
6287
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
7152
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6288
7153
  }
6289
7154
  } catch {
6290
7155
  }
@@ -6299,8 +7164,8 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
6299
7164
  let behaviorsFlag = "";
6300
7165
  let legacyFallbackWarned = false;
6301
7166
  if (!useExeAgent && !useBinSymlink) {
6302
- const identityPath = path17.join(
6303
- os9.homedir(),
7167
+ const identityPath = path19.join(
7168
+ os11.homedir(),
6304
7169
  ".exe-os",
6305
7170
  "identity",
6306
7171
  `${employeeName}.md`
@@ -6309,13 +7174,13 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
6309
7174
  const hasAgentFlag = claudeSupportsAgentFlag();
6310
7175
  if (hasAgentFlag) {
6311
7176
  identityFlag = ` --agent ${employeeName}`;
6312
- } else if (existsSync14(identityPath)) {
7177
+ } else if (existsSync16(identityPath)) {
6313
7178
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
6314
7179
  legacyFallbackWarned = true;
6315
7180
  }
6316
7181
  const behaviorsFile = exportBehaviorsSync(
6317
7182
  employeeName,
6318
- path17.basename(spawnCwd),
7183
+ path19.basename(spawnCwd),
6319
7184
  sessionName
6320
7185
  );
6321
7186
  if (behaviorsFile) {
@@ -6330,16 +7195,16 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
6330
7195
  }
6331
7196
  let sessionContextFlag = "";
6332
7197
  try {
6333
- const ctxDir = path17.join(os9.homedir(), ".exe-os", "session-cache");
7198
+ const ctxDir = path19.join(os11.homedir(), ".exe-os", "session-cache");
6334
7199
  mkdirSync7(ctxDir, { recursive: true });
6335
- const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
7200
+ const ctxFile = path19.join(ctxDir, `session-context-${sessionName}.md`);
6336
7201
  const ctxContent = [
6337
7202
  `## Session Context`,
6338
7203
  `You are running in tmux session: ${sessionName}.`,
6339
7204
  `Your parent coordinator session is ${exeSession2}.`,
6340
7205
  `Your employees (if any) use the -${exeSession2} suffix.`
6341
7206
  ].join("\n");
6342
- writeFileSync7(ctxFile, ctxContent);
7207
+ writeFileSync8(ctxFile, ctxContent);
6343
7208
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
6344
7209
  } catch {
6345
7210
  }
@@ -6416,8 +7281,8 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
6416
7281
  transport.pipeLog(sessionName, logFile);
6417
7282
  try {
6418
7283
  const mySession = getMySession();
6419
- const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6420
- writeFileSync7(dispatchInfo, JSON.stringify({
7284
+ const dispatchInfo = path19.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
7285
+ writeFileSync8(dispatchInfo, JSON.stringify({
6421
7286
  dispatchedBy: mySession,
6422
7287
  rootExe: exeSession2,
6423
7288
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -6491,15 +7356,15 @@ var init_tmux_routing = __esm({
6491
7356
  init_intercom_queue();
6492
7357
  init_plan_limits();
6493
7358
  init_employees();
6494
- SPAWN_LOCK_DIR = path17.join(os9.homedir(), ".exe-os", "spawn-locks");
6495
- SESSION_CACHE = path17.join(os9.homedir(), ".exe-os", "session-cache");
7359
+ SPAWN_LOCK_DIR = path19.join(os11.homedir(), ".exe-os", "spawn-locks");
7360
+ SESSION_CACHE = path19.join(os11.homedir(), ".exe-os", "session-cache");
6496
7361
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
6497
7362
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
6498
7363
  VERIFY_PANE_LINES = 200;
6499
7364
  INTERCOM_DEBOUNCE_MS = 3e4;
6500
7365
  CODEX_DEBOUNCE_MS = 12e4;
6501
- INTERCOM_LOG2 = path17.join(os9.homedir(), ".exe-os", "intercom.log");
6502
- DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
7366
+ INTERCOM_LOG2 = path19.join(os11.homedir(), ".exe-os", "intercom.log");
7367
+ DEBOUNCE_FILE = path19.join(SESSION_CACHE, "intercom-debounce.json");
6503
7368
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
6504
7369
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
6505
7370
  }
@@ -6522,6 +7387,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
6522
7387
  args: [scope]
6523
7388
  };
6524
7389
  }
7390
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
7391
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
7392
+ if (!scope) return { sql: "", args: [] };
7393
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
7394
+ return {
7395
+ sql: ` AND ${col} = ?`,
7396
+ args: [scope]
7397
+ };
7398
+ }
6525
7399
  var init_task_scope = __esm({
6526
7400
  "src/lib/task-scope.ts"() {
6527
7401
  "use strict";
@@ -6566,10 +7440,10 @@ async function disposeEmbedder() {
6566
7440
  async function embedDirect(text) {
6567
7441
  const llamaCpp = await import("node-llama-cpp");
6568
7442
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
6569
- const { existsSync: existsSync16 } = await import("fs");
6570
- const path19 = await import("path");
6571
- const modelPath = path19.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
6572
- if (!existsSync16(modelPath)) {
7443
+ const { existsSync: existsSync18 } = await import("fs");
7444
+ const path21 = await import("path");
7445
+ const modelPath = path21.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
7446
+ if (!existsSync18(modelPath)) {
6573
7447
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
6574
7448
  }
6575
7449
  const llama = await llamaCpp.getLlama();
@@ -6827,8 +7701,8 @@ __export(worktree_exports, {
6827
7701
  worktreePath: () => worktreePath
6828
7702
  });
6829
7703
  import { execSync as execSync8 } from "child_process";
6830
- import { existsSync as existsSync15, readFileSync as readFileSync12, appendFileSync as appendFileSync2, mkdirSync as mkdirSync8, realpathSync } from "fs";
6831
- import path18 from "path";
7704
+ import { existsSync as existsSync17, readFileSync as readFileSync13, appendFileSync as appendFileSync2, mkdirSync as mkdirSync8, realpathSync } from "fs";
7705
+ import path20 from "path";
6832
7706
  function getGitRoot(dir) {
6833
7707
  try {
6834
7708
  const root = execSync8("git rev-parse --show-toplevel", {
@@ -6848,14 +7722,14 @@ function getMainRepoRoot(dir) {
6848
7722
  "git rev-parse --path-format=absolute --git-common-dir",
6849
7723
  { cwd: dir, encoding: "utf-8", timeout: GIT_TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] }
6850
7724
  ).trim();
6851
- return realpath(path18.dirname(commonDir));
7725
+ return realpath(path20.dirname(commonDir));
6852
7726
  } catch {
6853
7727
  return null;
6854
7728
  }
6855
7729
  }
6856
7730
  function worktreePath(repoRoot, employeeName, instance) {
6857
7731
  const label = instanceLabel(employeeName, instance);
6858
- return path18.join(repoRoot, ".worktrees", label);
7732
+ return path20.join(repoRoot, ".worktrees", label);
6859
7733
  }
6860
7734
  function worktreeBranch(employeeName, instance) {
6861
7735
  return `${instanceLabel(employeeName, instance)}-work`;
@@ -6868,10 +7742,10 @@ function ensureWorktree(projectDir, employeeName, instance) {
6868
7742
  if (!repoRoot) return null;
6869
7743
  const wtPath = worktreePath(repoRoot, employeeName, instance);
6870
7744
  const branch = worktreeBranch(employeeName, instance);
6871
- if (existsSync15(path18.join(wtPath, ".git"))) {
7745
+ if (existsSync17(path20.join(wtPath, ".git"))) {
6872
7746
  return wtPath;
6873
7747
  }
6874
- const worktreesDir = path18.join(repoRoot, ".worktrees");
7748
+ const worktreesDir = path20.join(repoRoot, ".worktrees");
6875
7749
  mkdirSync8(worktreesDir, { recursive: true });
6876
7750
  ensureGitignoreEntry(repoRoot, "/.worktrees/");
6877
7751
  try {
@@ -6927,7 +7801,7 @@ function cleanupWorktree(projectDir, employeeName, instance) {
6927
7801
  if (!repoRoot) return { cleaned: false, reason: "not a git repo" };
6928
7802
  const wtPath = worktreePath(repoRoot, employeeName, instance);
6929
7803
  const branch = worktreeBranch(employeeName, instance);
6930
- if (!existsSync15(wtPath)) {
7804
+ if (!existsSync17(wtPath)) {
6931
7805
  return { cleaned: false, reason: "worktree does not exist" };
6932
7806
  }
6933
7807
  if (isWorktreeDirty(wtPath)) {
@@ -7005,9 +7879,9 @@ function realpath(p) {
7005
7879
  }
7006
7880
  function ensureGitignoreEntry(repoRoot, entry) {
7007
7881
  try {
7008
- const gitignorePath = path18.join(repoRoot, ".gitignore");
7009
- if (existsSync15(gitignorePath)) {
7010
- const content = readFileSync12(gitignorePath, "utf-8");
7882
+ const gitignorePath = path20.join(repoRoot, ".gitignore");
7883
+ if (existsSync17(gitignorePath)) {
7884
+ const content = readFileSync13(gitignorePath, "utf-8");
7011
7885
  if (content.includes(entry)) return;
7012
7886
  appendFileSync2(gitignorePath, `
7013
7887
  # Agent worktrees (exe-os)
@@ -7031,7 +7905,7 @@ init_store();
7031
7905
  init_database();
7032
7906
  init_task_scope();
7033
7907
  init_project_name();
7034
- import crypto6 from "crypto";
7908
+ import crypto7 from "crypto";
7035
7909
  import { execSync as execSync9 } from "child_process";
7036
7910
  var agentName = process.argv[2];
7037
7911
  var exeSession = process.argv[3];
@@ -7085,7 +7959,7 @@ try {
7085
7959
  } catch {
7086
7960
  }
7087
7961
  await writeMemory({
7088
- id: crypto6.randomUUID(),
7962
+ id: crypto7.randomUUID(),
7089
7963
  agent_id: agentName,
7090
7964
  agent_role: "employee",
7091
7965
  session_id: `cleanup-${Date.now()}`,