@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
@@ -307,9 +307,47 @@ var init_provider_table = __esm({
307
307
  }
308
308
  });
309
309
 
310
+ // src/lib/secure-files.ts
311
+ import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
312
+ import { chmod, mkdir } from "fs/promises";
313
+ async function ensurePrivateDir(dirPath) {
314
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
315
+ try {
316
+ await chmod(dirPath, PRIVATE_DIR_MODE);
317
+ } catch {
318
+ }
319
+ }
320
+ function ensurePrivateDirSync(dirPath) {
321
+ mkdirSync2(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
322
+ try {
323
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
324
+ } catch {
325
+ }
326
+ }
327
+ async function enforcePrivateFile(filePath) {
328
+ try {
329
+ await chmod(filePath, PRIVATE_FILE_MODE);
330
+ } catch {
331
+ }
332
+ }
333
+ function enforcePrivateFileSync(filePath) {
334
+ try {
335
+ if (existsSync2(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
336
+ } catch {
337
+ }
338
+ }
339
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
340
+ var init_secure_files = __esm({
341
+ "src/lib/secure-files.ts"() {
342
+ "use strict";
343
+ PRIVATE_DIR_MODE = 448;
344
+ PRIVATE_FILE_MODE = 384;
345
+ }
346
+ });
347
+
310
348
  // src/lib/config.ts
311
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
312
- import { readFileSync as readFileSync2, existsSync as existsSync2, renameSync } from "fs";
349
+ import { readFile, writeFile } from "fs/promises";
350
+ import { readFileSync as readFileSync2, existsSync as existsSync3, renameSync } from "fs";
313
351
  import path2 from "path";
314
352
  import os2 from "os";
315
353
  function resolveDataDir() {
@@ -317,7 +355,7 @@ function resolveDataDir() {
317
355
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
318
356
  const newDir = path2.join(os2.homedir(), ".exe-os");
319
357
  const legacyDir = path2.join(os2.homedir(), ".exe-mem");
320
- if (!existsSync2(newDir) && existsSync2(legacyDir)) {
358
+ if (!existsSync3(newDir) && existsSync3(legacyDir)) {
321
359
  try {
322
360
  renameSync(legacyDir, newDir);
323
361
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -380,9 +418,9 @@ function normalizeAutoUpdate(raw) {
380
418
  }
381
419
  async function loadConfig() {
382
420
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
383
- await mkdir(dir, { recursive: true });
421
+ await ensurePrivateDir(dir);
384
422
  const configPath = path2.join(dir, "config.json");
385
- if (!existsSync2(configPath)) {
423
+ if (!existsSync3(configPath)) {
386
424
  return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
387
425
  }
388
426
  const raw = await readFile(configPath, "utf-8");
@@ -395,6 +433,7 @@ async function loadConfig() {
395
433
  `);
396
434
  try {
397
435
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
436
+ await enforcePrivateFile(configPath);
398
437
  } catch {
399
438
  }
400
439
  }
@@ -414,6 +453,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
414
453
  var init_config = __esm({
415
454
  "src/lib/config.ts"() {
416
455
  "use strict";
456
+ init_secure_files();
417
457
  EXE_AI_DIR = resolveDataDir();
418
458
  DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
419
459
  MODELS_DIR = path2.join(EXE_AI_DIR, "models");
@@ -518,10 +558,10 @@ var init_runtime_table = __esm({
518
558
  });
519
559
 
520
560
  // src/lib/agent-config.ts
521
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
561
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
522
562
  import path3 from "path";
523
563
  function loadAgentConfig() {
524
- if (!existsSync3(AGENT_CONFIG_PATH)) return {};
564
+ if (!existsSync4(AGENT_CONFIG_PATH)) return {};
525
565
  try {
526
566
  return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
527
567
  } catch {
@@ -542,6 +582,7 @@ var init_agent_config = __esm({
542
582
  "use strict";
543
583
  init_config();
544
584
  init_runtime_table();
585
+ init_secure_files();
545
586
  AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
546
587
  DEFAULT_MODELS = {
547
588
  claude: "claude-opus-4",
@@ -560,16 +601,16 @@ __export(intercom_queue_exports, {
560
601
  queueIntercom: () => queueIntercom,
561
602
  readQueue: () => readQueue
562
603
  });
563
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
604
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
564
605
  import path4 from "path";
565
606
  import os3 from "os";
566
607
  function ensureDir() {
567
608
  const dir = path4.dirname(QUEUE_PATH);
568
- if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
609
+ if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
569
610
  }
570
611
  function readQueue() {
571
612
  try {
572
- if (!existsSync4(QUEUE_PATH)) return [];
613
+ if (!existsSync5(QUEUE_PATH)) return [];
573
614
  return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
574
615
  } catch {
575
616
  return [];
@@ -734,7 +775,7 @@ var init_db_retry = __esm({
734
775
 
735
776
  // src/lib/employees.ts
736
777
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
737
- import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
778
+ import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
738
779
  import { execSync as execSync3 } from "child_process";
739
780
  import path5 from "path";
740
781
  import os4 from "os";
@@ -755,7 +796,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
755
796
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
756
797
  }
757
798
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
758
- if (!existsSync5(employeesPath)) return [];
799
+ if (!existsSync6(employeesPath)) return [];
759
800
  try {
760
801
  return JSON.parse(readFileSync5(employeesPath, "utf-8"));
761
802
  } catch {
@@ -779,7 +820,7 @@ function isMultiInstance(agentName, employees) {
779
820
  if (!emp) return false;
780
821
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
781
822
  }
782
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
823
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
783
824
  var init_employees = __esm({
784
825
  "src/lib/employees.ts"() {
785
826
  "use strict";
@@ -788,16 +829,638 @@ var init_employees = __esm({
788
829
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
789
830
  COORDINATOR_ROLE = "COO";
790
831
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
832
+ IDENTITY_DIR = path5.join(EXE_AI_DIR, "identity");
833
+ }
834
+ });
835
+
836
+ // src/lib/database-adapter.ts
837
+ import os5 from "os";
838
+ import path6 from "path";
839
+ import { createRequire } from "module";
840
+ import { pathToFileURL } from "url";
841
+ function quotedIdentifier(identifier) {
842
+ return `"${identifier.replace(/"/g, '""')}"`;
843
+ }
844
+ function unqualifiedTableName(name) {
845
+ const raw = name.trim().replace(/^"|"$/g, "");
846
+ const parts = raw.split(".");
847
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
848
+ }
849
+ function stripTrailingSemicolon(sql) {
850
+ return sql.trim().replace(/;+\s*$/u, "");
851
+ }
852
+ function appendClause(sql, clause) {
853
+ const trimmed = stripTrailingSemicolon(sql);
854
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
855
+ if (!returningMatch) {
856
+ return `${trimmed}${clause}`;
857
+ }
858
+ const idx = returningMatch.index;
859
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
860
+ }
861
+ function normalizeStatement(stmt) {
862
+ if (typeof stmt === "string") {
863
+ return { kind: "positional", sql: stmt, args: [] };
864
+ }
865
+ const sql = stmt.sql;
866
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
867
+ return { kind: "positional", sql, args: stmt.args ?? [] };
868
+ }
869
+ return { kind: "named", sql, args: stmt.args };
870
+ }
871
+ function rewriteBooleanLiterals(sql) {
872
+ let out = sql;
873
+ for (const column of BOOLEAN_COLUMN_NAMES) {
874
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
875
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
876
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
877
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
878
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
879
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
880
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
881
+ }
882
+ return out;
883
+ }
884
+ function rewriteInsertOrIgnore(sql) {
885
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
886
+ return sql;
887
+ }
888
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
889
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
890
+ }
891
+ function rewriteInsertOrReplace(sql) {
892
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
893
+ if (!match) {
894
+ return sql;
895
+ }
896
+ const rawTable = match[1];
897
+ const rawColumns = match[2];
898
+ const remainder = match[3];
899
+ const tableName = unqualifiedTableName(rawTable);
900
+ const conflictKeys = UPSERT_KEYS[tableName];
901
+ if (!conflictKeys?.length) {
902
+ return sql;
903
+ }
904
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
905
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
906
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
907
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
908
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
909
+ }
910
+ function rewriteSql(sql) {
911
+ let out = sql;
912
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
913
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
914
+ out = rewriteBooleanLiterals(out);
915
+ out = rewriteInsertOrReplace(out);
916
+ out = rewriteInsertOrIgnore(out);
917
+ return stripTrailingSemicolon(out);
918
+ }
919
+ function toBoolean(value) {
920
+ if (value === null || value === void 0) return value;
921
+ if (typeof value === "boolean") return value;
922
+ if (typeof value === "number") return value !== 0;
923
+ if (typeof value === "bigint") return value !== 0n;
924
+ if (typeof value === "string") {
925
+ const normalized = value.trim().toLowerCase();
926
+ if (normalized === "0" || normalized === "false") return false;
927
+ if (normalized === "1" || normalized === "true") return true;
928
+ }
929
+ return Boolean(value);
930
+ }
931
+ function countQuestionMarks(sql, end) {
932
+ let count = 0;
933
+ let inSingle = false;
934
+ let inDouble = false;
935
+ let inLineComment = false;
936
+ let inBlockComment = false;
937
+ for (let i = 0; i < end; i++) {
938
+ const ch = sql[i];
939
+ const next = sql[i + 1];
940
+ if (inLineComment) {
941
+ if (ch === "\n") inLineComment = false;
942
+ continue;
943
+ }
944
+ if (inBlockComment) {
945
+ if (ch === "*" && next === "/") {
946
+ inBlockComment = false;
947
+ i += 1;
948
+ }
949
+ continue;
950
+ }
951
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
952
+ inLineComment = true;
953
+ i += 1;
954
+ continue;
955
+ }
956
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
957
+ inBlockComment = true;
958
+ i += 1;
959
+ continue;
960
+ }
961
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
962
+ inSingle = !inSingle;
963
+ continue;
964
+ }
965
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
966
+ inDouble = !inDouble;
967
+ continue;
968
+ }
969
+ if (!inSingle && !inDouble && ch === "?") {
970
+ count += 1;
971
+ }
972
+ }
973
+ return count;
974
+ }
975
+ function findBooleanPlaceholderIndexes(sql) {
976
+ const indexes = /* @__PURE__ */ new Set();
977
+ for (const column of BOOLEAN_COLUMN_NAMES) {
978
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
979
+ for (const match of sql.matchAll(pattern)) {
980
+ const matchText = match[0];
981
+ const qIndex = match.index + matchText.lastIndexOf("?");
982
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
983
+ }
984
+ }
985
+ return indexes;
986
+ }
987
+ function coerceInsertBooleanArgs(sql, args) {
988
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
989
+ if (!match) return;
990
+ const rawTable = match[1];
991
+ const rawColumns = match[2];
992
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
993
+ if (!boolColumns?.size) return;
994
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
995
+ for (const [index, column] of columns.entries()) {
996
+ if (boolColumns.has(column) && index < args.length) {
997
+ args[index] = toBoolean(args[index]);
998
+ }
999
+ }
1000
+ }
1001
+ function coerceUpdateBooleanArgs(sql, args) {
1002
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
1003
+ if (!match) return;
1004
+ const rawTable = match[1];
1005
+ const setClause = match[2];
1006
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
1007
+ if (!boolColumns?.size) return;
1008
+ const assignments = setClause.split(",");
1009
+ let placeholderIndex = 0;
1010
+ for (const assignment of assignments) {
1011
+ if (!assignment.includes("?")) continue;
1012
+ placeholderIndex += 1;
1013
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
1014
+ if (colMatch && boolColumns.has(colMatch[1])) {
1015
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
1016
+ }
1017
+ }
1018
+ }
1019
+ function coerceBooleanArgs(sql, args) {
1020
+ const nextArgs = [...args];
1021
+ coerceInsertBooleanArgs(sql, nextArgs);
1022
+ coerceUpdateBooleanArgs(sql, nextArgs);
1023
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
1024
+ for (const index of placeholderIndexes) {
1025
+ if (index > 0 && index <= nextArgs.length) {
1026
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
1027
+ }
1028
+ }
1029
+ return nextArgs;
1030
+ }
1031
+ function convertQuestionMarksToDollarParams(sql) {
1032
+ let out = "";
1033
+ let placeholder = 0;
1034
+ let inSingle = false;
1035
+ let inDouble = false;
1036
+ let inLineComment = false;
1037
+ let inBlockComment = false;
1038
+ for (let i = 0; i < sql.length; i++) {
1039
+ const ch = sql[i];
1040
+ const next = sql[i + 1];
1041
+ if (inLineComment) {
1042
+ out += ch;
1043
+ if (ch === "\n") inLineComment = false;
1044
+ continue;
1045
+ }
1046
+ if (inBlockComment) {
1047
+ out += ch;
1048
+ if (ch === "*" && next === "/") {
1049
+ out += next;
1050
+ inBlockComment = false;
1051
+ i += 1;
1052
+ }
1053
+ continue;
1054
+ }
1055
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1056
+ out += ch + next;
1057
+ inLineComment = true;
1058
+ i += 1;
1059
+ continue;
1060
+ }
1061
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1062
+ out += ch + next;
1063
+ inBlockComment = true;
1064
+ i += 1;
1065
+ continue;
1066
+ }
1067
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1068
+ inSingle = !inSingle;
1069
+ out += ch;
1070
+ continue;
1071
+ }
1072
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1073
+ inDouble = !inDouble;
1074
+ out += ch;
1075
+ continue;
1076
+ }
1077
+ if (!inSingle && !inDouble && ch === "?") {
1078
+ placeholder += 1;
1079
+ out += `$${placeholder}`;
1080
+ continue;
1081
+ }
1082
+ out += ch;
1083
+ }
1084
+ return out;
1085
+ }
1086
+ function translateStatementForPostgres(stmt) {
1087
+ const normalized = normalizeStatement(stmt);
1088
+ if (normalized.kind === "named") {
1089
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
1090
+ }
1091
+ const rewrittenSql = rewriteSql(normalized.sql);
1092
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
1093
+ return {
1094
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
1095
+ args: coercedArgs
1096
+ };
1097
+ }
1098
+ function shouldBypassPostgres(stmt) {
1099
+ const normalized = normalizeStatement(stmt);
1100
+ if (normalized.kind === "named") {
1101
+ return true;
1102
+ }
1103
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
1104
+ }
1105
+ function shouldFallbackOnError(error) {
1106
+ const message = error instanceof Error ? error.message : String(error);
1107
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
1108
+ }
1109
+ function isReadQuery(sql) {
1110
+ const trimmed = sql.trimStart();
1111
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
1112
+ }
1113
+ function buildRow(row, columns) {
1114
+ const values = columns.map((column) => row[column]);
1115
+ return Object.assign(values, row);
1116
+ }
1117
+ function buildResultSet(rows, rowsAffected = 0) {
1118
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
1119
+ const resultRows = rows.map((row) => buildRow(row, columns));
1120
+ return {
1121
+ columns,
1122
+ columnTypes: columns.map(() => ""),
1123
+ rows: resultRows,
1124
+ rowsAffected,
1125
+ lastInsertRowid: void 0,
1126
+ toJSON() {
1127
+ return {
1128
+ columns,
1129
+ columnTypes: columns.map(() => ""),
1130
+ rows,
1131
+ rowsAffected,
1132
+ lastInsertRowid: void 0
1133
+ };
1134
+ }
1135
+ };
1136
+ }
1137
+ async function loadPrismaClient() {
1138
+ if (!prismaClientPromise) {
1139
+ prismaClientPromise = (async () => {
1140
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
1141
+ if (explicitPath) {
1142
+ const module2 = await import(pathToFileURL(explicitPath).href);
1143
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
1144
+ if (!PrismaClient2) {
1145
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
1146
+ }
1147
+ return new PrismaClient2();
1148
+ }
1149
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path6.join(os5.homedir(), "exe-db");
1150
+ const requireFromExeDb = createRequire(path6.join(exeDbRoot, "package.json"));
1151
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1152
+ const module = await import(pathToFileURL(prismaEntry).href);
1153
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
1154
+ if (!PrismaClient) {
1155
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
1156
+ }
1157
+ return new PrismaClient();
1158
+ })();
1159
+ }
1160
+ return prismaClientPromise;
1161
+ }
1162
+ async function ensureCompatibilityViews(prisma) {
1163
+ if (!compatibilityBootstrapPromise) {
1164
+ compatibilityBootstrapPromise = (async () => {
1165
+ for (const mapping of VIEW_MAPPINGS) {
1166
+ const relation = mapping.source.replace(/"/g, "");
1167
+ const rows = await prisma.$queryRawUnsafe(
1168
+ "SELECT to_regclass($1) AS regclass",
1169
+ relation
1170
+ );
1171
+ if (!rows[0]?.regclass) {
1172
+ continue;
1173
+ }
1174
+ await prisma.$executeRawUnsafe(
1175
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
1176
+ );
1177
+ }
1178
+ })();
1179
+ }
1180
+ return compatibilityBootstrapPromise;
1181
+ }
1182
+ async function executeOnPrisma(executor, stmt) {
1183
+ const translated = translateStatementForPostgres(stmt);
1184
+ if (isReadQuery(translated.sql)) {
1185
+ const rows = await executor.$queryRawUnsafe(
1186
+ translated.sql,
1187
+ ...translated.args
1188
+ );
1189
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
1190
+ }
1191
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
1192
+ return buildResultSet([], rowsAffected);
1193
+ }
1194
+ function splitSqlStatements(sql) {
1195
+ const parts = [];
1196
+ let current = "";
1197
+ let inSingle = false;
1198
+ let inDouble = false;
1199
+ let inLineComment = false;
1200
+ let inBlockComment = false;
1201
+ for (let i = 0; i < sql.length; i++) {
1202
+ const ch = sql[i];
1203
+ const next = sql[i + 1];
1204
+ if (inLineComment) {
1205
+ current += ch;
1206
+ if (ch === "\n") inLineComment = false;
1207
+ continue;
1208
+ }
1209
+ if (inBlockComment) {
1210
+ current += ch;
1211
+ if (ch === "*" && next === "/") {
1212
+ current += next;
1213
+ inBlockComment = false;
1214
+ i += 1;
1215
+ }
1216
+ continue;
1217
+ }
1218
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1219
+ current += ch + next;
1220
+ inLineComment = true;
1221
+ i += 1;
1222
+ continue;
1223
+ }
1224
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1225
+ current += ch + next;
1226
+ inBlockComment = true;
1227
+ i += 1;
1228
+ continue;
1229
+ }
1230
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1231
+ inSingle = !inSingle;
1232
+ current += ch;
1233
+ continue;
1234
+ }
1235
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1236
+ inDouble = !inDouble;
1237
+ current += ch;
1238
+ continue;
1239
+ }
1240
+ if (!inSingle && !inDouble && ch === ";") {
1241
+ if (current.trim()) {
1242
+ parts.push(current.trim());
1243
+ }
1244
+ current = "";
1245
+ continue;
1246
+ }
1247
+ current += ch;
1248
+ }
1249
+ if (current.trim()) {
1250
+ parts.push(current.trim());
1251
+ }
1252
+ return parts;
1253
+ }
1254
+ async function createPrismaDbAdapter(fallbackClient) {
1255
+ const prisma = await loadPrismaClient();
1256
+ await ensureCompatibilityViews(prisma);
1257
+ let closed = false;
1258
+ let adapter;
1259
+ const fallbackExecute = async (stmt, error) => {
1260
+ if (!fallbackClient) {
1261
+ if (error) throw error;
1262
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
1263
+ }
1264
+ if (error) {
1265
+ process.stderr.write(
1266
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
1267
+ `
1268
+ );
1269
+ }
1270
+ return fallbackClient.execute(stmt);
1271
+ };
1272
+ adapter = {
1273
+ async execute(stmt) {
1274
+ if (shouldBypassPostgres(stmt)) {
1275
+ return fallbackExecute(stmt);
1276
+ }
1277
+ try {
1278
+ return await executeOnPrisma(prisma, stmt);
1279
+ } catch (error) {
1280
+ if (shouldFallbackOnError(error)) {
1281
+ return fallbackExecute(stmt, error);
1282
+ }
1283
+ throw error;
1284
+ }
1285
+ },
1286
+ async batch(stmts, mode) {
1287
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
1288
+ if (!fallbackClient) {
1289
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
1290
+ }
1291
+ return fallbackClient.batch(stmts, mode);
1292
+ }
1293
+ try {
1294
+ if (prisma.$transaction) {
1295
+ return await prisma.$transaction(async (tx) => {
1296
+ const results2 = [];
1297
+ for (const stmt of stmts) {
1298
+ results2.push(await executeOnPrisma(tx, stmt));
1299
+ }
1300
+ return results2;
1301
+ });
1302
+ }
1303
+ const results = [];
1304
+ for (const stmt of stmts) {
1305
+ results.push(await executeOnPrisma(prisma, stmt));
1306
+ }
1307
+ return results;
1308
+ } catch (error) {
1309
+ if (fallbackClient && shouldFallbackOnError(error)) {
1310
+ process.stderr.write(
1311
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
1312
+ `
1313
+ );
1314
+ return fallbackClient.batch(stmts, mode);
1315
+ }
1316
+ throw error;
1317
+ }
1318
+ },
1319
+ async migrate(stmts) {
1320
+ if (fallbackClient) {
1321
+ return fallbackClient.migrate(stmts);
1322
+ }
1323
+ return adapter.batch(stmts, "deferred");
1324
+ },
1325
+ async transaction(mode) {
1326
+ if (!fallbackClient) {
1327
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
1328
+ }
1329
+ return fallbackClient.transaction(mode);
1330
+ },
1331
+ async executeMultiple(sql) {
1332
+ if (fallbackClient && shouldBypassPostgres(sql)) {
1333
+ return fallbackClient.executeMultiple(sql);
1334
+ }
1335
+ for (const statement of splitSqlStatements(sql)) {
1336
+ await adapter.execute(statement);
1337
+ }
1338
+ },
1339
+ async sync() {
1340
+ if (fallbackClient) {
1341
+ return fallbackClient.sync();
1342
+ }
1343
+ return { frame_no: 0, frames_synced: 0 };
1344
+ },
1345
+ close() {
1346
+ closed = true;
1347
+ prismaClientPromise = null;
1348
+ compatibilityBootstrapPromise = null;
1349
+ void prisma.$disconnect?.();
1350
+ },
1351
+ get closed() {
1352
+ return closed;
1353
+ },
1354
+ get protocol() {
1355
+ return "prisma-postgres";
1356
+ }
1357
+ };
1358
+ return adapter;
1359
+ }
1360
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
1361
+ var init_database_adapter = __esm({
1362
+ "src/lib/database-adapter.ts"() {
1363
+ "use strict";
1364
+ VIEW_MAPPINGS = [
1365
+ { view: "memories", source: "memory.memory_records" },
1366
+ { view: "tasks", source: "memory.tasks" },
1367
+ { view: "behaviors", source: "memory.behaviors" },
1368
+ { view: "entities", source: "memory.entities" },
1369
+ { view: "relationships", source: "memory.relationships" },
1370
+ { view: "entity_memories", source: "memory.entity_memories" },
1371
+ { view: "entity_aliases", source: "memory.entity_aliases" },
1372
+ { view: "notifications", source: "memory.notifications" },
1373
+ { view: "messages", source: "memory.messages" },
1374
+ { view: "users", source: "wiki.users" },
1375
+ { view: "workspaces", source: "wiki.workspaces" },
1376
+ { view: "workspace_users", source: "wiki.workspace_users" },
1377
+ { view: "documents", source: "wiki.workspace_documents" },
1378
+ { view: "chats", source: "wiki.workspace_chats" }
1379
+ ];
1380
+ UPSERT_KEYS = {
1381
+ memories: ["id"],
1382
+ tasks: ["id"],
1383
+ behaviors: ["id"],
1384
+ entities: ["id"],
1385
+ relationships: ["id"],
1386
+ entity_aliases: ["alias"],
1387
+ notifications: ["id"],
1388
+ messages: ["id"],
1389
+ users: ["id"],
1390
+ workspaces: ["id"],
1391
+ workspace_users: ["id"],
1392
+ documents: ["id"],
1393
+ chats: ["id"]
1394
+ };
1395
+ BOOLEAN_COLUMNS_BY_TABLE = {
1396
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
1397
+ behaviors: /* @__PURE__ */ new Set(["active"]),
1398
+ notifications: /* @__PURE__ */ new Set(["read"]),
1399
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
1400
+ };
1401
+ BOOLEAN_COLUMN_NAMES = new Set(
1402
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
1403
+ );
1404
+ IMMEDIATE_FALLBACK_PATTERNS = [
1405
+ /\bPRAGMA\b/i,
1406
+ /\bsqlite_master\b/i,
1407
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
1408
+ /\bMATCH\b/i,
1409
+ /\bvector_distance_cos\s*\(/i,
1410
+ /\bjson_extract\s*\(/i,
1411
+ /\bjulianday\s*\(/i,
1412
+ /\bstrftime\s*\(/i,
1413
+ /\blast_insert_rowid\s*\(/i
1414
+ ];
1415
+ prismaClientPromise = null;
1416
+ compatibilityBootstrapPromise = null;
1417
+ }
1418
+ });
1419
+
1420
+ // src/lib/daemon-auth.ts
1421
+ import crypto from "crypto";
1422
+ import path7 from "path";
1423
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
1424
+ function normalizeToken(token) {
1425
+ if (!token) return null;
1426
+ const trimmed = token.trim();
1427
+ return trimmed.length > 0 ? trimmed : null;
1428
+ }
1429
+ function readDaemonToken() {
1430
+ try {
1431
+ if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
1432
+ return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
1433
+ } catch {
1434
+ return null;
1435
+ }
1436
+ }
1437
+ function ensureDaemonToken(seed) {
1438
+ const existing = readDaemonToken();
1439
+ if (existing) return existing;
1440
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1441
+ ensurePrivateDirSync(EXE_AI_DIR);
1442
+ writeFileSync5(DAEMON_TOKEN_PATH, `${token}
1443
+ `, "utf8");
1444
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1445
+ return token;
1446
+ }
1447
+ var DAEMON_TOKEN_PATH;
1448
+ var init_daemon_auth = __esm({
1449
+ "src/lib/daemon-auth.ts"() {
1450
+ "use strict";
1451
+ init_config();
1452
+ init_secure_files();
1453
+ DAEMON_TOKEN_PATH = path7.join(EXE_AI_DIR, "exed.token");
791
1454
  }
792
1455
  });
793
1456
 
794
1457
  // src/lib/exe-daemon-client.ts
795
1458
  import net from "net";
796
- import os5 from "os";
1459
+ import os6 from "os";
797
1460
  import { spawn } from "child_process";
798
1461
  import { randomUUID } from "crypto";
799
- import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
800
- import path6 from "path";
1462
+ import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
1463
+ import path8 from "path";
801
1464
  import { fileURLToPath } from "url";
802
1465
  function handleData(chunk) {
803
1466
  _buffer += chunk.toString();
@@ -825,9 +1488,9 @@ function handleData(chunk) {
825
1488
  }
826
1489
  }
827
1490
  function cleanupStaleFiles() {
828
- if (existsSync6(PID_PATH)) {
1491
+ if (existsSync8(PID_PATH)) {
829
1492
  try {
830
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
1493
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
831
1494
  if (pid > 0) {
832
1495
  try {
833
1496
  process.kill(pid, 0);
@@ -848,17 +1511,17 @@ function cleanupStaleFiles() {
848
1511
  }
849
1512
  }
850
1513
  function findPackageRoot() {
851
- let dir = path6.dirname(fileURLToPath(import.meta.url));
852
- const { root } = path6.parse(dir);
1514
+ let dir = path8.dirname(fileURLToPath(import.meta.url));
1515
+ const { root } = path8.parse(dir);
853
1516
  while (dir !== root) {
854
- if (existsSync6(path6.join(dir, "package.json"))) return dir;
855
- dir = path6.dirname(dir);
1517
+ if (existsSync8(path8.join(dir, "package.json"))) return dir;
1518
+ dir = path8.dirname(dir);
856
1519
  }
857
1520
  return null;
858
1521
  }
859
1522
  function spawnDaemon() {
860
- const freeGB = os5.freemem() / (1024 * 1024 * 1024);
861
- const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
1523
+ const freeGB = os6.freemem() / (1024 * 1024 * 1024);
1524
+ const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
862
1525
  if (totalGB <= 8) {
863
1526
  process.stderr.write(
864
1527
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -878,16 +1541,17 @@ function spawnDaemon() {
878
1541
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
879
1542
  return;
880
1543
  }
881
- const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
882
- if (!existsSync6(daemonPath)) {
1544
+ const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1545
+ if (!existsSync8(daemonPath)) {
883
1546
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
884
1547
  `);
885
1548
  return;
886
1549
  }
887
1550
  const resolvedPath = daemonPath;
1551
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
888
1552
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
889
1553
  `);
890
- const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
1554
+ const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
891
1555
  let stderrFd = "ignore";
892
1556
  try {
893
1557
  stderrFd = openSync(logPath, "a");
@@ -905,7 +1569,8 @@ function spawnDaemon() {
905
1569
  TMUX_PANE: void 0,
906
1570
  // Prevents resolveExeSession() from scoping to one session
907
1571
  EXE_DAEMON_SOCK: SOCKET_PATH,
908
- EXE_DAEMON_PID: PID_PATH
1572
+ EXE_DAEMON_PID: PID_PATH,
1573
+ [DAEMON_TOKEN_ENV]: daemonToken
909
1574
  }
910
1575
  });
911
1576
  child.unref();
@@ -1012,13 +1677,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1012
1677
  return;
1013
1678
  }
1014
1679
  const id = randomUUID();
1680
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1015
1681
  const timer = setTimeout(() => {
1016
1682
  _pending.delete(id);
1017
1683
  resolve({ error: "Request timeout" });
1018
1684
  }, timeoutMs);
1019
1685
  _pending.set(id, { resolve, timer });
1020
1686
  try {
1021
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1687
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1022
1688
  } catch {
1023
1689
  clearTimeout(timer);
1024
1690
  _pending.delete(id);
@@ -1029,17 +1695,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1029
1695
  function isClientConnected() {
1030
1696
  return _connected;
1031
1697
  }
1032
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1698
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1033
1699
  var init_exe_daemon_client = __esm({
1034
1700
  "src/lib/exe-daemon-client.ts"() {
1035
1701
  "use strict";
1036
1702
  init_config();
1037
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
1038
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
1039
- SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
1703
+ init_daemon_auth();
1704
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
1705
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
1706
+ SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
1040
1707
  SPAWN_LOCK_STALE_MS = 3e4;
1041
1708
  CONNECT_TIMEOUT_MS = 15e3;
1042
1709
  REQUEST_TIMEOUT_MS = 3e4;
1710
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1043
1711
  _socket = null;
1044
1712
  _connected = false;
1045
1713
  _buffer = "";
@@ -1118,7 +1786,7 @@ __export(db_daemon_client_exports, {
1118
1786
  createDaemonDbClient: () => createDaemonDbClient,
1119
1787
  initDaemonDbClient: () => initDaemonDbClient
1120
1788
  });
1121
- function normalizeStatement(stmt) {
1789
+ function normalizeStatement2(stmt) {
1122
1790
  if (typeof stmt === "string") {
1123
1791
  return { sql: stmt, args: [] };
1124
1792
  }
@@ -1142,7 +1810,7 @@ function createDaemonDbClient(fallbackClient) {
1142
1810
  if (!_useDaemon || !isClientConnected()) {
1143
1811
  return fallbackClient.execute(stmt);
1144
1812
  }
1145
- const { sql, args } = normalizeStatement(stmt);
1813
+ const { sql, args } = normalizeStatement2(stmt);
1146
1814
  const response = await sendDaemonRequest({
1147
1815
  type: "db-execute",
1148
1816
  sql,
@@ -1167,7 +1835,7 @@ function createDaemonDbClient(fallbackClient) {
1167
1835
  if (!_useDaemon || !isClientConnected()) {
1168
1836
  return fallbackClient.batch(stmts, mode);
1169
1837
  }
1170
- const statements = stmts.map(normalizeStatement);
1838
+ const statements = stmts.map(normalizeStatement2);
1171
1839
  const response = await sendDaemonRequest({
1172
1840
  type: "db-batch",
1173
1841
  statements,
@@ -1262,6 +1930,18 @@ __export(database_exports, {
1262
1930
  });
1263
1931
  import { createClient } from "@libsql/client";
1264
1932
  async function initDatabase(config) {
1933
+ if (_walCheckpointTimer) {
1934
+ clearInterval(_walCheckpointTimer);
1935
+ _walCheckpointTimer = null;
1936
+ }
1937
+ if (_daemonClient) {
1938
+ _daemonClient.close();
1939
+ _daemonClient = null;
1940
+ }
1941
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1942
+ _adapterClient.close();
1943
+ }
1944
+ _adapterClient = null;
1265
1945
  if (_client) {
1266
1946
  _client.close();
1267
1947
  _client = null;
@@ -1275,6 +1955,7 @@ async function initDatabase(config) {
1275
1955
  }
1276
1956
  _client = createClient(opts);
1277
1957
  _resilientClient = wrapWithRetry(_client);
1958
+ _adapterClient = _resilientClient;
1278
1959
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
1279
1960
  });
1280
1961
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -1285,14 +1966,20 @@ async function initDatabase(config) {
1285
1966
  });
1286
1967
  }, 3e4);
1287
1968
  _walCheckpointTimer.unref();
1969
+ if (process.env.DATABASE_URL) {
1970
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1971
+ }
1288
1972
  }
1289
1973
  function isInitialized() {
1290
- return _client !== null;
1974
+ return _adapterClient !== null || _client !== null;
1291
1975
  }
1292
1976
  function getClient() {
1293
- if (!_resilientClient) {
1977
+ if (!_adapterClient) {
1294
1978
  throw new Error("Database client not initialized. Call initDatabase() first.");
1295
1979
  }
1980
+ if (process.env.DATABASE_URL) {
1981
+ return _adapterClient;
1982
+ }
1296
1983
  if (process.env.EXE_IS_DAEMON === "1") {
1297
1984
  return _resilientClient;
1298
1985
  }
@@ -1302,6 +1989,7 @@ function getClient() {
1302
1989
  return _resilientClient;
1303
1990
  }
1304
1991
  async function initDaemonClient() {
1992
+ if (process.env.DATABASE_URL) return;
1305
1993
  if (process.env.EXE_IS_DAEMON === "1") return;
1306
1994
  if (!_resilientClient) return;
1307
1995
  try {
@@ -1598,6 +2286,7 @@ async function ensureSchema() {
1598
2286
  project TEXT NOT NULL,
1599
2287
  summary TEXT NOT NULL,
1600
2288
  task_file TEXT,
2289
+ session_scope TEXT,
1601
2290
  read INTEGER NOT NULL DEFAULT 0,
1602
2291
  created_at TEXT NOT NULL
1603
2292
  );
@@ -1606,7 +2295,7 @@ async function ensureSchema() {
1606
2295
  ON notifications(read);
1607
2296
 
1608
2297
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1609
- ON notifications(agent_id);
2298
+ ON notifications(agent_id, session_scope);
1610
2299
 
1611
2300
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1612
2301
  ON notifications(task_file);
@@ -1644,6 +2333,7 @@ async function ensureSchema() {
1644
2333
  target_agent TEXT NOT NULL,
1645
2334
  target_project TEXT,
1646
2335
  target_device TEXT NOT NULL DEFAULT 'local',
2336
+ session_scope TEXT,
1647
2337
  content TEXT NOT NULL,
1648
2338
  priority TEXT DEFAULT 'normal',
1649
2339
  status TEXT DEFAULT 'pending',
@@ -1657,10 +2347,31 @@ async function ensureSchema() {
1657
2347
  );
1658
2348
 
1659
2349
  CREATE INDEX IF NOT EXISTS idx_messages_target
1660
- ON messages(target_agent, status);
2350
+ ON messages(target_agent, session_scope, status);
1661
2351
 
1662
2352
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1663
- ON messages(target_agent, from_agent, server_seq);
2353
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2354
+ `);
2355
+ try {
2356
+ await client.execute({
2357
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2358
+ args: []
2359
+ });
2360
+ } catch {
2361
+ }
2362
+ try {
2363
+ await client.execute({
2364
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2365
+ args: []
2366
+ });
2367
+ } catch {
2368
+ }
2369
+ await client.executeMultiple(`
2370
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2371
+ ON notifications(agent_id, session_scope, read, created_at);
2372
+
2373
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2374
+ ON messages(target_agent, session_scope, status, created_at);
1664
2375
  `);
1665
2376
  try {
1666
2377
  await client.execute({
@@ -2244,46 +2955,66 @@ async function ensureSchema() {
2244
2955
  } catch {
2245
2956
  }
2246
2957
  }
2958
+ try {
2959
+ await client.execute({
2960
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2961
+ args: []
2962
+ });
2963
+ } catch {
2964
+ }
2247
2965
  }
2248
2966
  async function disposeDatabase() {
2967
+ if (_walCheckpointTimer) {
2968
+ clearInterval(_walCheckpointTimer);
2969
+ _walCheckpointTimer = null;
2970
+ }
2249
2971
  if (_daemonClient) {
2250
2972
  _daemonClient.close();
2251
2973
  _daemonClient = null;
2252
2974
  }
2975
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2976
+ _adapterClient.close();
2977
+ }
2978
+ _adapterClient = null;
2253
2979
  if (_client) {
2254
2980
  _client.close();
2255
2981
  _client = null;
2256
2982
  _resilientClient = null;
2257
2983
  }
2258
2984
  }
2259
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
2985
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
2260
2986
  var init_database = __esm({
2261
2987
  "src/lib/database.ts"() {
2262
2988
  "use strict";
2263
2989
  init_db_retry();
2264
2990
  init_employees();
2991
+ init_database_adapter();
2265
2992
  _client = null;
2266
2993
  _resilientClient = null;
2267
2994
  _walCheckpointTimer = null;
2268
2995
  _daemonClient = null;
2996
+ _adapterClient = null;
2269
2997
  initTurso = initDatabase;
2270
2998
  disposeTurso = disposeDatabase;
2271
2999
  }
2272
3000
  });
2273
3001
 
2274
3002
  // src/lib/license.ts
2275
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
3003
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
2276
3004
  import { randomUUID as randomUUID2 } from "crypto";
2277
- import path7 from "path";
3005
+ import { createRequire as createRequire2 } from "module";
3006
+ import { pathToFileURL as pathToFileURL2 } from "url";
3007
+ import os7 from "os";
3008
+ import path9 from "path";
2278
3009
  import { jwtVerify, importSPKI } from "jose";
2279
3010
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
2280
3011
  var init_license = __esm({
2281
3012
  "src/lib/license.ts"() {
2282
3013
  "use strict";
2283
3014
  init_config();
2284
- LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
2285
- CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
2286
- DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
3015
+ LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
3016
+ CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
3017
+ DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
2287
3018
  PLAN_LIMITS = {
2288
3019
  free: { devices: 1, employees: 1, memories: 5e3 },
2289
3020
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -2295,12 +3026,12 @@ var init_license = __esm({
2295
3026
  });
2296
3027
 
2297
3028
  // src/lib/plan-limits.ts
2298
- import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
2299
- import path8 from "path";
3029
+ import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
3030
+ import path10 from "path";
2300
3031
  function getLicenseSync() {
2301
3032
  try {
2302
- if (!existsSync8(CACHE_PATH2)) return freeLicense();
2303
- const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
3033
+ if (!existsSync10(CACHE_PATH2)) return freeLicense();
3034
+ const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
2304
3035
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2305
3036
  const parts = raw.token.split(".");
2306
3037
  if (parts.length !== 3) return freeLicense();
@@ -2338,8 +3069,8 @@ function assertEmployeeLimitSync(rosterPath) {
2338
3069
  const filePath = rosterPath ?? EMPLOYEES_PATH;
2339
3070
  let count = 0;
2340
3071
  try {
2341
- if (existsSync8(filePath)) {
2342
- const raw = readFileSync8(filePath, "utf8");
3072
+ if (existsSync10(filePath)) {
3073
+ const raw = readFileSync9(filePath, "utf8");
2343
3074
  const employees = JSON.parse(raw);
2344
3075
  count = Array.isArray(employees) ? employees.length : 0;
2345
3076
  }
@@ -2368,29 +3099,30 @@ var init_plan_limits = __esm({
2368
3099
  this.name = "PlanLimitError";
2369
3100
  }
2370
3101
  };
2371
- CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
3102
+ CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
2372
3103
  }
2373
3104
  });
2374
3105
 
2375
3106
  // src/lib/notifications.ts
2376
- import crypto from "crypto";
2377
- import path9 from "path";
2378
- import os6 from "os";
3107
+ import crypto2 from "crypto";
3108
+ import path11 from "path";
3109
+ import os8 from "os";
2379
3110
  import {
2380
- readFileSync as readFileSync9,
3111
+ readFileSync as readFileSync10,
2381
3112
  readdirSync,
2382
3113
  unlinkSync as unlinkSync3,
2383
- existsSync as existsSync9,
3114
+ existsSync as existsSync11,
2384
3115
  rmdirSync
2385
3116
  } from "fs";
2386
3117
  async function writeNotification(notification) {
2387
3118
  try {
2388
3119
  const client = getClient();
2389
- const id = crypto.randomUUID();
3120
+ const id = crypto2.randomUUID();
2390
3121
  const now = (/* @__PURE__ */ new Date()).toISOString();
3122
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
2391
3123
  await client.execute({
2392
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
2393
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3124
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
3125
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
2394
3126
  args: [
2395
3127
  id,
2396
3128
  notification.agentId,
@@ -2399,6 +3131,7 @@ async function writeNotification(notification) {
2399
3131
  notification.project,
2400
3132
  notification.summary,
2401
3133
  notification.taskFile ?? null,
3134
+ sessionScope,
2402
3135
  now
2403
3136
  ]
2404
3137
  });
@@ -2407,12 +3140,14 @@ async function writeNotification(notification) {
2407
3140
  `);
2408
3141
  }
2409
3142
  }
2410
- async function markAsReadByTaskFile(taskFile) {
3143
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
2411
3144
  try {
2412
3145
  const client = getClient();
3146
+ const scope = strictSessionScopeFilter(sessionScope);
2413
3147
  await client.execute({
2414
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
2415
- args: [taskFile]
3148
+ sql: `UPDATE notifications SET read = 1
3149
+ WHERE task_file = ? AND read = 0${scope.sql}`,
3150
+ args: [taskFile, ...scope.args]
2416
3151
  });
2417
3152
  } catch {
2418
3153
  }
@@ -2421,11 +3156,12 @@ var init_notifications = __esm({
2421
3156
  "src/lib/notifications.ts"() {
2422
3157
  "use strict";
2423
3158
  init_database();
3159
+ init_task_scope();
2424
3160
  }
2425
3161
  });
2426
3162
 
2427
3163
  // src/lib/session-kill-telemetry.ts
2428
- import crypto2 from "crypto";
3164
+ import crypto3 from "crypto";
2429
3165
  async function recordSessionKill(input) {
2430
3166
  try {
2431
3167
  const client = getClient();
@@ -2435,7 +3171,7 @@ async function recordSessionKill(input) {
2435
3171
  ticks_idle, estimated_tokens_saved)
2436
3172
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
2437
3173
  args: [
2438
- crypto2.randomUUID(),
3174
+ crypto3.randomUUID(),
2439
3175
  input.sessionName,
2440
3176
  input.agentId,
2441
3177
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -2530,12 +3266,12 @@ __export(tasks_crud_exports, {
2530
3266
  updateTaskStatus: () => updateTaskStatus,
2531
3267
  writeCheckpoint: () => writeCheckpoint
2532
3268
  });
2533
- import crypto3 from "crypto";
2534
- import path10 from "path";
2535
- import os7 from "os";
3269
+ import crypto4 from "crypto";
3270
+ import path12 from "path";
3271
+ import os9 from "os";
2536
3272
  import { execSync as execSync4 } from "child_process";
2537
3273
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
2538
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
3274
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
2539
3275
  async function writeCheckpoint(input) {
2540
3276
  const client = getClient();
2541
3277
  const row = await resolveTask(client, input.taskId);
@@ -2651,7 +3387,7 @@ async function resolveTask(client, identifier, scopeSession) {
2651
3387
  }
2652
3388
  async function createTaskCore(input) {
2653
3389
  const client = getClient();
2654
- const id = crypto3.randomUUID();
3390
+ const id = crypto4.randomUUID();
2655
3391
  const now = (/* @__PURE__ */ new Date()).toISOString();
2656
3392
  const slug = slugify(input.title);
2657
3393
  let earlySessionScope = null;
@@ -2710,8 +3446,8 @@ ${laneWarning}` : laneWarning;
2710
3446
  }
2711
3447
  if (input.baseDir) {
2712
3448
  try {
2713
- await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
2714
- await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
3449
+ await mkdir3(path12.join(input.baseDir, "exe", "output"), { recursive: true });
3450
+ await mkdir3(path12.join(input.baseDir, "exe", "research"), { recursive: true });
2715
3451
  await ensureArchitectureDoc(input.baseDir, input.projectName);
2716
3452
  await ensureGitignoreExe(input.baseDir);
2717
3453
  } catch {
@@ -2747,13 +3483,19 @@ ${laneWarning}` : laneWarning;
2747
3483
  });
2748
3484
  if (input.baseDir) {
2749
3485
  try {
2750
- const EXE_OS_DIR = path10.join(os7.homedir(), ".exe-os");
2751
- const mdPath = path10.join(EXE_OS_DIR, taskFile);
2752
- const mdDir = path10.dirname(mdPath);
2753
- if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
3486
+ const EXE_OS_DIR = path12.join(os9.homedir(), ".exe-os");
3487
+ const mdPath = path12.join(EXE_OS_DIR, taskFile);
3488
+ const mdDir = path12.dirname(mdPath);
3489
+ if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
2754
3490
  const reviewer = input.reviewer ?? input.assignedBy;
2755
3491
  const mdContent = `# ${input.title}
2756
3492
 
3493
+ ## MANDATORY: When done
3494
+
3495
+ You MUST call update_task with status "done" and a result summary when finished.
3496
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
3497
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
3498
+
2757
3499
  **ID:** ${id}
2758
3500
  **Status:** ${initialStatus}
2759
3501
  **Priority:** ${input.priority}
@@ -2767,12 +3509,6 @@ ${laneWarning}` : laneWarning;
2767
3509
  ## Context
2768
3510
 
2769
3511
  ${input.context}
2770
-
2771
- ## MANDATORY: When done
2772
-
2773
- You MUST call update_task with status "done" and a result summary when finished.
2774
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
2775
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
2776
3512
  `;
2777
3513
  await writeFile3(mdPath, mdContent, "utf-8");
2778
3514
  } catch (err) {
@@ -3021,7 +3757,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
3021
3757
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
3022
3758
  } catch {
3023
3759
  }
3024
- if (input.status === "done" || input.status === "cancelled") {
3760
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
3025
3761
  try {
3026
3762
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
3027
3763
  clearQueueForAgent2(String(row.assigned_to));
@@ -3050,9 +3786,9 @@ async function deleteTaskCore(taskId, _baseDir) {
3050
3786
  return { taskFile, assignedTo, assignedBy, taskSlug };
3051
3787
  }
3052
3788
  async function ensureArchitectureDoc(baseDir, projectName) {
3053
- const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
3789
+ const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
3054
3790
  try {
3055
- if (existsSync10(archPath)) return;
3791
+ if (existsSync12(archPath)) return;
3056
3792
  const template = [
3057
3793
  `# ${projectName} \u2014 System Architecture`,
3058
3794
  "",
@@ -3085,10 +3821,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
3085
3821
  }
3086
3822
  }
3087
3823
  async function ensureGitignoreExe(baseDir) {
3088
- const gitignorePath = path10.join(baseDir, ".gitignore");
3824
+ const gitignorePath = path12.join(baseDir, ".gitignore");
3089
3825
  try {
3090
- if (existsSync10(gitignorePath)) {
3091
- const content = readFileSync10(gitignorePath, "utf-8");
3826
+ if (existsSync12(gitignorePath)) {
3827
+ const content = readFileSync11(gitignorePath, "utf-8");
3092
3828
  if (/^\/?exe\/?$/m.test(content)) return;
3093
3829
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
3094
3830
  } else {
@@ -3119,58 +3855,42 @@ var init_tasks_crud = __esm({
3119
3855
  });
3120
3856
 
3121
3857
  // src/lib/tasks-review.ts
3122
- import path11 from "path";
3123
- import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
3858
+ import path13 from "path";
3859
+ import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
3124
3860
  async function countPendingReviews(sessionScope) {
3125
3861
  const client = getClient();
3126
- if (sessionScope) {
3127
- const result2 = await client.execute({
3128
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
3129
- args: [sessionScope]
3130
- });
3131
- return Number(result2.rows[0]?.cnt) || 0;
3132
- }
3862
+ const scope = strictSessionScopeFilter(
3863
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3864
+ );
3133
3865
  const result = await client.execute({
3134
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
3135
- args: []
3866
+ sql: `SELECT COUNT(*) as cnt FROM tasks
3867
+ WHERE status = 'needs_review'${scope.sql}`,
3868
+ args: [...scope.args]
3136
3869
  });
3137
3870
  return Number(result.rows[0]?.cnt) || 0;
3138
3871
  }
3139
3872
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
3140
3873
  const client = getClient();
3141
- if (sessionScope) {
3142
- const result2 = await client.execute({
3143
- sql: `SELECT COUNT(*) as cnt FROM tasks
3144
- WHERE status = 'needs_review' AND updated_at > ?
3145
- AND session_scope = ?`,
3146
- args: [sinceIso, sessionScope]
3147
- });
3148
- return Number(result2.rows[0]?.cnt) || 0;
3149
- }
3874
+ const scope = strictSessionScopeFilter(
3875
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3876
+ );
3150
3877
  const result = await client.execute({
3151
3878
  sql: `SELECT COUNT(*) as cnt FROM tasks
3152
- WHERE status = 'needs_review' AND updated_at > ?`,
3153
- args: [sinceIso]
3879
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
3880
+ args: [sinceIso, ...scope.args]
3154
3881
  });
3155
3882
  return Number(result.rows[0]?.cnt) || 0;
3156
3883
  }
3157
3884
  async function listPendingReviews(limit, sessionScope) {
3158
3885
  const client = getClient();
3159
- if (sessionScope) {
3160
- const result2 = await client.execute({
3161
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3162
- WHERE status = 'needs_review'
3163
- AND session_scope = ?
3164
- ORDER BY updated_at ASC LIMIT ?`,
3165
- args: [sessionScope, limit]
3166
- });
3167
- return result2.rows;
3168
- }
3886
+ const scope = strictSessionScopeFilter(
3887
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3888
+ );
3169
3889
  const result = await client.execute({
3170
3890
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3171
- WHERE status = 'needs_review'
3891
+ WHERE status = 'needs_review'${scope.sql}
3172
3892
  ORDER BY updated_at ASC LIMIT ?`,
3173
- args: [limit]
3893
+ args: [...scope.args, limit]
3174
3894
  });
3175
3895
  return result.rows;
3176
3896
  }
@@ -3182,7 +3902,7 @@ async function cleanupOrphanedReviews() {
3182
3902
  WHERE status IN ('open', 'needs_review', 'in_progress')
3183
3903
  AND assigned_by = 'system'
3184
3904
  AND title LIKE 'Review:%'
3185
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
3905
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
3186
3906
  args: [now]
3187
3907
  });
3188
3908
  const r1b = await client.execute({
@@ -3301,11 +4021,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3301
4021
  );
3302
4022
  }
3303
4023
  try {
3304
- const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
3305
- if (existsSync11(cacheDir)) {
4024
+ const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
4025
+ if (existsSync13(cacheDir)) {
3306
4026
  for (const f of readdirSync2(cacheDir)) {
3307
4027
  if (f.startsWith("review-notified-")) {
3308
- unlinkSync4(path11.join(cacheDir, f));
4028
+ unlinkSync4(path13.join(cacheDir, f));
3309
4029
  }
3310
4030
  }
3311
4031
  }
@@ -3322,11 +4042,12 @@ var init_tasks_review = __esm({
3322
4042
  init_tmux_routing();
3323
4043
  init_session_key();
3324
4044
  init_state_bus();
4045
+ init_task_scope();
3325
4046
  }
3326
4047
  });
3327
4048
 
3328
4049
  // src/lib/tasks-chain.ts
3329
- import path12 from "path";
4050
+ import path14 from "path";
3330
4051
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3331
4052
  async function cascadeUnblock(taskId, baseDir, now) {
3332
4053
  const client = getClient();
@@ -3343,7 +4064,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3343
4064
  });
3344
4065
  for (const ur of unblockedRows.rows) {
3345
4066
  try {
3346
- const ubFile = path12.join(baseDir, String(ur.task_file));
4067
+ const ubFile = path14.join(baseDir, String(ur.task_file));
3347
4068
  let ubContent = await readFile3(ubFile, "utf-8");
3348
4069
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3349
4070
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3378,7 +4099,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
3378
4099
  const scScope = sessionScopeFilter();
3379
4100
  const remaining = await client.execute({
3380
4101
  sql: `SELECT COUNT(*) as cnt FROM tasks
3381
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
4102
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
3382
4103
  args: [parentTaskId, ...scScope.args]
3383
4104
  });
3384
4105
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -3412,7 +4133,7 @@ var init_tasks_chain = __esm({
3412
4133
 
3413
4134
  // src/lib/project-name.ts
3414
4135
  import { execSync as execSync5 } from "child_process";
3415
- import path13 from "path";
4136
+ import path15 from "path";
3416
4137
  function getProjectName(cwd) {
3417
4138
  const dir = cwd ?? process.cwd();
3418
4139
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -3425,7 +4146,7 @@ function getProjectName(cwd) {
3425
4146
  timeout: 2e3,
3426
4147
  stdio: ["pipe", "pipe", "pipe"]
3427
4148
  }).trim();
3428
- repoRoot = path13.dirname(gitCommonDir);
4149
+ repoRoot = path15.dirname(gitCommonDir);
3429
4150
  } catch {
3430
4151
  repoRoot = execSync5("git rev-parse --show-toplevel", {
3431
4152
  cwd: dir,
@@ -3434,11 +4155,11 @@ function getProjectName(cwd) {
3434
4155
  stdio: ["pipe", "pipe", "pipe"]
3435
4156
  }).trim();
3436
4157
  }
3437
- _cached2 = path13.basename(repoRoot);
4158
+ _cached2 = path15.basename(repoRoot);
3438
4159
  _cachedCwd = dir;
3439
4160
  return _cached2;
3440
4161
  } catch {
3441
- _cached2 = path13.basename(dir);
4162
+ _cached2 = path15.basename(dir);
3442
4163
  _cachedCwd = dir;
3443
4164
  return _cached2;
3444
4165
  }
@@ -3581,10 +4302,10 @@ var init_tasks_notify = __esm({
3581
4302
  });
3582
4303
 
3583
4304
  // src/lib/behaviors.ts
3584
- import crypto4 from "crypto";
4305
+ import crypto5 from "crypto";
3585
4306
  async function storeBehavior(opts) {
3586
4307
  const client = getClient();
3587
- const id = crypto4.randomUUID();
4308
+ const id = crypto5.randomUUID();
3588
4309
  const now = (/* @__PURE__ */ new Date()).toISOString();
3589
4310
  await client.execute({
3590
4311
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -3613,7 +4334,7 @@ __export(skill_learning_exports, {
3613
4334
  storeTrajectory: () => storeTrajectory,
3614
4335
  sweepTrajectories: () => sweepTrajectories
3615
4336
  });
3616
- import crypto5 from "crypto";
4337
+ import crypto6 from "crypto";
3617
4338
  async function extractTrajectory(taskId, agentId) {
3618
4339
  const client = getClient();
3619
4340
  const result = await client.execute({
@@ -3642,11 +4363,11 @@ async function extractTrajectory(taskId, agentId) {
3642
4363
  return signature;
3643
4364
  }
3644
4365
  function hashSignature(signature) {
3645
- return crypto5.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4366
+ return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
3646
4367
  }
3647
4368
  async function storeTrajectory(opts) {
3648
4369
  const client = getClient();
3649
- const id = crypto5.randomUUID();
4370
+ const id = crypto6.randomUUID();
3650
4371
  const now = (/* @__PURE__ */ new Date()).toISOString();
3651
4372
  const signatureHash = hashSignature(opts.signature);
3652
4373
  await client.execute({
@@ -3911,8 +4632,8 @@ __export(tasks_exports, {
3911
4632
  updateTaskStatus: () => updateTaskStatus,
3912
4633
  writeCheckpoint: () => writeCheckpoint
3913
4634
  });
3914
- import path14 from "path";
3915
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
4635
+ import path16 from "path";
4636
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
3916
4637
  async function createTask(input) {
3917
4638
  const result = await createTaskCore(input);
3918
4639
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -3931,12 +4652,12 @@ async function updateTask(input) {
3931
4652
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
3932
4653
  try {
3933
4654
  const agent = String(row.assigned_to);
3934
- const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
3935
- const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
4655
+ const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
4656
+ const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
3936
4657
  if (input.status === "in_progress") {
3937
4658
  mkdirSync5(cacheDir, { recursive: true });
3938
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3939
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
4659
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4660
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
3940
4661
  try {
3941
4662
  unlinkSync5(cachePath);
3942
4663
  } catch {
@@ -3944,10 +4665,10 @@ async function updateTask(input) {
3944
4665
  }
3945
4666
  } catch {
3946
4667
  }
3947
- if (input.status === "done") {
4668
+ if (input.status === "done" || input.status === "closed") {
3948
4669
  await cleanupReviewFile(row, taskFile, input.baseDir);
3949
4670
  }
3950
- if (input.status === "done" || input.status === "cancelled") {
4671
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
3951
4672
  try {
3952
4673
  const client = getClient();
3953
4674
  const taskTitle = String(row.title);
@@ -3963,7 +4684,7 @@ async function updateTask(input) {
3963
4684
  if (!isCoordinatorName(assignedAgent)) {
3964
4685
  try {
3965
4686
  const draftClient = getClient();
3966
- if (input.status === "done") {
4687
+ if (input.status === "done" || input.status === "closed") {
3967
4688
  await draftClient.execute({
3968
4689
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
3969
4690
  args: [assignedAgent]
@@ -3980,7 +4701,7 @@ async function updateTask(input) {
3980
4701
  try {
3981
4702
  const client = getClient();
3982
4703
  const cascaded = await client.execute({
3983
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
4704
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
3984
4705
  WHERE parent_task_id = ? AND status = 'needs_review'`,
3985
4706
  args: [now, taskId]
3986
4707
  });
@@ -3993,14 +4714,14 @@ async function updateTask(input) {
3993
4714
  } catch {
3994
4715
  }
3995
4716
  }
3996
- const isTerminal = input.status === "done" || input.status === "needs_review";
4717
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
3997
4718
  if (isTerminal) {
3998
4719
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
3999
4720
  if (!isCoordinator) {
4000
4721
  notifyTaskDone();
4001
4722
  }
4002
4723
  await markTaskNotificationsRead(taskFile);
4003
- if (input.status === "done") {
4724
+ if (input.status === "done" || input.status === "closed") {
4004
4725
  try {
4005
4726
  await cascadeUnblock(taskId, input.baseDir, now);
4006
4727
  } catch {
@@ -4020,7 +4741,7 @@ async function updateTask(input) {
4020
4741
  }
4021
4742
  }
4022
4743
  }
4023
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4744
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4024
4745
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
4025
4746
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
4026
4747
  taskId,
@@ -4392,6 +5113,7 @@ __export(tmux_routing_exports, {
4392
5113
  isEmployeeAlive: () => isEmployeeAlive,
4393
5114
  isExeSession: () => isExeSession,
4394
5115
  isSessionBusy: () => isSessionBusy,
5116
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
4395
5117
  notifyParentExe: () => notifyParentExe,
4396
5118
  parseParentExe: () => parseParentExe,
4397
5119
  registerParentExe: () => registerParentExe,
@@ -4402,13 +5124,13 @@ __export(tmux_routing_exports, {
4402
5124
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
4403
5125
  });
4404
5126
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
4405
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync3 } from "fs";
4406
- import path15 from "path";
4407
- import os8 from "os";
5127
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
5128
+ import path17 from "path";
5129
+ import os10 from "os";
4408
5130
  import { fileURLToPath as fileURLToPath2 } from "url";
4409
5131
  import { unlinkSync as unlinkSync6 } from "fs";
4410
5132
  function spawnLockPath(sessionName) {
4411
- return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5133
+ return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4412
5134
  }
4413
5135
  function isProcessAlive(pid) {
4414
5136
  try {
@@ -4419,13 +5141,13 @@ function isProcessAlive(pid) {
4419
5141
  }
4420
5142
  }
4421
5143
  function acquireSpawnLock2(sessionName) {
4422
- if (!existsSync12(SPAWN_LOCK_DIR)) {
5144
+ if (!existsSync14(SPAWN_LOCK_DIR)) {
4423
5145
  mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
4424
5146
  }
4425
5147
  const lockFile = spawnLockPath(sessionName);
4426
- if (existsSync12(lockFile)) {
5148
+ if (existsSync14(lockFile)) {
4427
5149
  try {
4428
- const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
5150
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
4429
5151
  const age = Date.now() - lock.timestamp;
4430
5152
  if (isProcessAlive(lock.pid) && age < 6e4) {
4431
5153
  return false;
@@ -4433,7 +5155,7 @@ function acquireSpawnLock2(sessionName) {
4433
5155
  } catch {
4434
5156
  }
4435
5157
  }
4436
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5158
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4437
5159
  return true;
4438
5160
  }
4439
5161
  function releaseSpawnLock2(sessionName) {
@@ -4445,13 +5167,13 @@ function releaseSpawnLock2(sessionName) {
4445
5167
  function resolveBehaviorsExporterScript() {
4446
5168
  try {
4447
5169
  const thisFile = fileURLToPath2(import.meta.url);
4448
- const scriptPath = path15.join(
4449
- path15.dirname(thisFile),
5170
+ const scriptPath = path17.join(
5171
+ path17.dirname(thisFile),
4450
5172
  "..",
4451
5173
  "bin",
4452
5174
  "exe-export-behaviors.js"
4453
5175
  );
4454
- return existsSync12(scriptPath) ? scriptPath : null;
5176
+ return existsSync14(scriptPath) ? scriptPath : null;
4455
5177
  } catch {
4456
5178
  return null;
4457
5179
  }
@@ -4517,12 +5239,12 @@ function extractRootExe(name) {
4517
5239
  return parts.length > 0 ? parts[parts.length - 1] : null;
4518
5240
  }
4519
5241
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4520
- if (!existsSync12(SESSION_CACHE)) {
5242
+ if (!existsSync14(SESSION_CACHE)) {
4521
5243
  mkdirSync6(SESSION_CACHE, { recursive: true });
4522
5244
  }
4523
5245
  const rootExe = extractRootExe(parentExe) ?? parentExe;
4524
- const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4525
- writeFileSync7(filePath, JSON.stringify({
5246
+ const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5247
+ writeFileSync8(filePath, JSON.stringify({
4526
5248
  parentExe: rootExe,
4527
5249
  dispatchedBy: dispatchedBy || rootExe,
4528
5250
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -4530,7 +5252,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4530
5252
  }
4531
5253
  function getParentExe(sessionKey) {
4532
5254
  try {
4533
- const data = JSON.parse(readFileSync11(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5255
+ const data = JSON.parse(readFileSync12(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4534
5256
  return data.parentExe || null;
4535
5257
  } catch {
4536
5258
  return null;
@@ -4538,8 +5260,8 @@ function getParentExe(sessionKey) {
4538
5260
  }
4539
5261
  function getDispatchedBy(sessionKey) {
4540
5262
  try {
4541
- const data = JSON.parse(readFileSync11(
4542
- path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5263
+ const data = JSON.parse(readFileSync12(
5264
+ path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4543
5265
  "utf8"
4544
5266
  ));
4545
5267
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -4609,8 +5331,8 @@ async function verifyPaneAtCapacity(sessionName) {
4609
5331
  }
4610
5332
  function readDebounceState() {
4611
5333
  try {
4612
- if (!existsSync12(DEBOUNCE_FILE)) return {};
4613
- const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
5334
+ if (!existsSync14(DEBOUNCE_FILE)) return {};
5335
+ const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
4614
5336
  const state = {};
4615
5337
  for (const [key, val] of Object.entries(raw)) {
4616
5338
  if (typeof val === "number") {
@@ -4626,8 +5348,8 @@ function readDebounceState() {
4626
5348
  }
4627
5349
  function writeDebounceState(state) {
4628
5350
  try {
4629
- if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
4630
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
5351
+ if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5352
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
4631
5353
  } catch {
4632
5354
  }
4633
5355
  }
@@ -4725,8 +5447,8 @@ function sendIntercom(targetSession) {
4725
5447
  try {
4726
5448
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
4727
5449
  const agent = baseAgentName(rawAgent);
4728
- const markerPath = path15.join(SESSION_CACHE, `current-task-${agent}.json`);
4729
- if (existsSync12(markerPath)) {
5450
+ const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
5451
+ if (existsSync14(markerPath)) {
4730
5452
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
4731
5453
  return "debounced";
4732
5454
  }
@@ -4735,8 +5457,8 @@ function sendIntercom(targetSession) {
4735
5457
  try {
4736
5458
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
4737
5459
  const agent = baseAgentName(rawAgent);
4738
- const taskDir = path15.join(process.cwd(), "exe", agent);
4739
- if (existsSync12(taskDir)) {
5460
+ const taskDir = path17.join(process.cwd(), "exe", agent);
5461
+ if (existsSync14(taskDir)) {
4740
5462
  const files = readdirSync3(taskDir).filter(
4741
5463
  (f) => f.endsWith(".md") && f !== "DONE.txt"
4742
5464
  );
@@ -4796,6 +5518,21 @@ function notifyParentExe(sessionKey) {
4796
5518
  }
4797
5519
  return true;
4798
5520
  }
5521
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
5522
+ const transport = getTransport();
5523
+ try {
5524
+ const sessions = transport.listSessions();
5525
+ if (!sessions.includes(coordinatorSession)) return false;
5526
+ execSync6(
5527
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
5528
+ { timeout: 3e3 }
5529
+ );
5530
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
5531
+ return true;
5532
+ } catch {
5533
+ return false;
5534
+ }
5535
+ }
4799
5536
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
4800
5537
  if (isCoordinatorName(employeeName)) {
4801
5538
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -4869,26 +5606,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4869
5606
  const transport = getTransport();
4870
5607
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
4871
5608
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
4872
- const logDir = path15.join(os8.homedir(), ".exe-os", "session-logs");
4873
- const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4874
- if (!existsSync12(logDir)) {
5609
+ const logDir = path17.join(os10.homedir(), ".exe-os", "session-logs");
5610
+ const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5611
+ if (!existsSync14(logDir)) {
4875
5612
  mkdirSync6(logDir, { recursive: true });
4876
5613
  }
4877
5614
  transport.kill(sessionName);
4878
5615
  let cleanupSuffix = "";
4879
5616
  try {
4880
5617
  const thisFile = fileURLToPath2(import.meta.url);
4881
- const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4882
- if (existsSync12(cleanupScript)) {
5618
+ const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5619
+ if (existsSync14(cleanupScript)) {
4883
5620
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
4884
5621
  }
4885
5622
  } catch {
4886
5623
  }
4887
5624
  try {
4888
- const claudeJsonPath = path15.join(os8.homedir(), ".claude.json");
5625
+ const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
4889
5626
  let claudeJson = {};
4890
5627
  try {
4891
- claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
5628
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
4892
5629
  } catch {
4893
5630
  }
4894
5631
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -4896,17 +5633,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4896
5633
  const trustDir = opts?.cwd ?? projectDir;
4897
5634
  if (!projects[trustDir]) projects[trustDir] = {};
4898
5635
  projects[trustDir].hasTrustDialogAccepted = true;
4899
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5636
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4900
5637
  } catch {
4901
5638
  }
4902
5639
  try {
4903
- const settingsDir = path15.join(os8.homedir(), ".claude", "projects");
5640
+ const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
4904
5641
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
4905
- const projSettingsDir = path15.join(settingsDir, normalizedKey);
4906
- const settingsPath = path15.join(projSettingsDir, "settings.json");
5642
+ const projSettingsDir = path17.join(settingsDir, normalizedKey);
5643
+ const settingsPath = path17.join(projSettingsDir, "settings.json");
4907
5644
  let settings = {};
4908
5645
  try {
4909
- settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
5646
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
4910
5647
  } catch {
4911
5648
  }
4912
5649
  const perms = settings.permissions ?? {};
@@ -4935,7 +5672,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4935
5672
  perms.allow = allow;
4936
5673
  settings.permissions = perms;
4937
5674
  mkdirSync6(projSettingsDir, { recursive: true });
4938
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5675
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4939
5676
  }
4940
5677
  } catch {
4941
5678
  }
@@ -4950,8 +5687,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4950
5687
  let behaviorsFlag = "";
4951
5688
  let legacyFallbackWarned = false;
4952
5689
  if (!useExeAgent && !useBinSymlink) {
4953
- const identityPath = path15.join(
4954
- os8.homedir(),
5690
+ const identityPath = path17.join(
5691
+ os10.homedir(),
4955
5692
  ".exe-os",
4956
5693
  "identity",
4957
5694
  `${employeeName}.md`
@@ -4960,13 +5697,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4960
5697
  const hasAgentFlag = claudeSupportsAgentFlag();
4961
5698
  if (hasAgentFlag) {
4962
5699
  identityFlag = ` --agent ${employeeName}`;
4963
- } else if (existsSync12(identityPath)) {
5700
+ } else if (existsSync14(identityPath)) {
4964
5701
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
4965
5702
  legacyFallbackWarned = true;
4966
5703
  }
4967
5704
  const behaviorsFile = exportBehaviorsSync(
4968
5705
  employeeName,
4969
- path15.basename(spawnCwd),
5706
+ path17.basename(spawnCwd),
4970
5707
  sessionName
4971
5708
  );
4972
5709
  if (behaviorsFile) {
@@ -4981,16 +5718,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4981
5718
  }
4982
5719
  let sessionContextFlag = "";
4983
5720
  try {
4984
- const ctxDir = path15.join(os8.homedir(), ".exe-os", "session-cache");
5721
+ const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
4985
5722
  mkdirSync6(ctxDir, { recursive: true });
4986
- const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
5723
+ const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
4987
5724
  const ctxContent = [
4988
5725
  `## Session Context`,
4989
5726
  `You are running in tmux session: ${sessionName}.`,
4990
5727
  `Your parent coordinator session is ${exeSession}.`,
4991
5728
  `Your employees (if any) use the -${exeSession} suffix.`
4992
5729
  ].join("\n");
4993
- writeFileSync7(ctxFile, ctxContent);
5730
+ writeFileSync8(ctxFile, ctxContent);
4994
5731
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
4995
5732
  } catch {
4996
5733
  }
@@ -5067,8 +5804,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5067
5804
  transport.pipeLog(sessionName, logFile);
5068
5805
  try {
5069
5806
  const mySession = getMySession();
5070
- const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5071
- writeFileSync7(dispatchInfo, JSON.stringify({
5807
+ const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5808
+ writeFileSync8(dispatchInfo, JSON.stringify({
5072
5809
  dispatchedBy: mySession,
5073
5810
  rootExe: exeSession,
5074
5811
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -5142,15 +5879,15 @@ var init_tmux_routing = __esm({
5142
5879
  init_intercom_queue();
5143
5880
  init_plan_limits();
5144
5881
  init_employees();
5145
- SPAWN_LOCK_DIR = path15.join(os8.homedir(), ".exe-os", "spawn-locks");
5146
- SESSION_CACHE = path15.join(os8.homedir(), ".exe-os", "session-cache");
5882
+ SPAWN_LOCK_DIR = path17.join(os10.homedir(), ".exe-os", "spawn-locks");
5883
+ SESSION_CACHE = path17.join(os10.homedir(), ".exe-os", "session-cache");
5147
5884
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5148
5885
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
5149
5886
  VERIFY_PANE_LINES = 200;
5150
5887
  INTERCOM_DEBOUNCE_MS = 3e4;
5151
5888
  CODEX_DEBOUNCE_MS = 12e4;
5152
- INTERCOM_LOG2 = path15.join(os8.homedir(), ".exe-os", "intercom.log");
5153
- DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
5889
+ INTERCOM_LOG2 = path17.join(os10.homedir(), ".exe-os", "intercom.log");
5890
+ DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
5154
5891
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
5155
5892
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
5156
5893
  }
@@ -5173,6 +5910,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
5173
5910
  args: [scope]
5174
5911
  };
5175
5912
  }
5913
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
5914
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
5915
+ if (!scope) return { sql: "", args: [] };
5916
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
5917
+ return {
5918
+ sql: ` AND ${col} = ?`,
5919
+ args: [scope]
5920
+ };
5921
+ }
5176
5922
  var init_task_scope = __esm({
5177
5923
  "src/lib/task-scope.ts"() {
5178
5924
  "use strict";
@@ -5191,14 +5937,14 @@ var init_memory = __esm({
5191
5937
 
5192
5938
  // src/lib/keychain.ts
5193
5939
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
5194
- import { existsSync as existsSync13 } from "fs";
5195
- import path16 from "path";
5196
- import os9 from "os";
5940
+ import { existsSync as existsSync15 } from "fs";
5941
+ import path18 from "path";
5942
+ import os11 from "os";
5197
5943
  function getKeyDir() {
5198
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os9.homedir(), ".exe-os");
5944
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path18.join(os11.homedir(), ".exe-os");
5199
5945
  }
5200
5946
  function getKeyPath() {
5201
- return path16.join(getKeyDir(), "master.key");
5947
+ return path18.join(getKeyDir(), "master.key");
5202
5948
  }
5203
5949
  async function tryKeytar() {
5204
5950
  try {
@@ -5219,9 +5965,9 @@ async function getMasterKey() {
5219
5965
  }
5220
5966
  }
5221
5967
  const keyPath = getKeyPath();
5222
- if (!existsSync13(keyPath)) {
5968
+ if (!existsSync15(keyPath)) {
5223
5969
  process.stderr.write(
5224
- `[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5970
+ `[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5225
5971
  `
5226
5972
  );
5227
5973
  return null;
@@ -5251,6 +5997,7 @@ var shard_manager_exports = {};
5251
5997
  __export(shard_manager_exports, {
5252
5998
  disposeShards: () => disposeShards,
5253
5999
  ensureShardSchema: () => ensureShardSchema,
6000
+ getOpenShardCount: () => getOpenShardCount,
5254
6001
  getReadyShardClient: () => getReadyShardClient,
5255
6002
  getShardClient: () => getShardClient,
5256
6003
  getShardsDir: () => getShardsDir,
@@ -5259,15 +6006,18 @@ __export(shard_manager_exports, {
5259
6006
  listShards: () => listShards,
5260
6007
  shardExists: () => shardExists
5261
6008
  });
5262
- import path17 from "path";
5263
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
6009
+ import path19 from "path";
6010
+ import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
5264
6011
  import { createClient as createClient2 } from "@libsql/client";
5265
6012
  function initShardManager(encryptionKey) {
5266
6013
  _encryptionKey = encryptionKey;
5267
- if (!existsSync14(SHARDS_DIR)) {
6014
+ if (!existsSync16(SHARDS_DIR)) {
5268
6015
  mkdirSync7(SHARDS_DIR, { recursive: true });
5269
6016
  }
5270
6017
  _shardingEnabled = true;
6018
+ if (_evictionTimer) clearInterval(_evictionTimer);
6019
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
6020
+ _evictionTimer.unref();
5271
6021
  }
5272
6022
  function isShardingEnabled() {
5273
6023
  return _shardingEnabled;
@@ -5284,21 +6034,28 @@ function getShardClient(projectName) {
5284
6034
  throw new Error(`Invalid project name for shard: "${projectName}"`);
5285
6035
  }
5286
6036
  const cached = _shards.get(safeName);
5287
- if (cached) return cached;
5288
- const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
6037
+ if (cached) {
6038
+ _shardLastAccess.set(safeName, Date.now());
6039
+ return cached;
6040
+ }
6041
+ while (_shards.size >= MAX_OPEN_SHARDS) {
6042
+ evictLRU();
6043
+ }
6044
+ const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
5289
6045
  const client = createClient2({
5290
6046
  url: `file:${dbPath}`,
5291
6047
  encryptionKey: _encryptionKey
5292
6048
  });
5293
6049
  _shards.set(safeName, client);
6050
+ _shardLastAccess.set(safeName, Date.now());
5294
6051
  return client;
5295
6052
  }
5296
6053
  function shardExists(projectName) {
5297
6054
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
5298
- return existsSync14(path17.join(SHARDS_DIR, `${safeName}.db`));
6055
+ return existsSync16(path19.join(SHARDS_DIR, `${safeName}.db`));
5299
6056
  }
5300
6057
  function listShards() {
5301
- if (!existsSync14(SHARDS_DIR)) return [];
6058
+ if (!existsSync16(SHARDS_DIR)) return [];
5302
6059
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
5303
6060
  }
5304
6061
  async function ensureShardSchema(client) {
@@ -5350,6 +6107,8 @@ async function ensureShardSchema(client) {
5350
6107
  for (const col of [
5351
6108
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
5352
6109
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
6110
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
6111
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
5353
6112
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
5354
6113
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
5355
6114
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -5372,7 +6131,23 @@ async function ensureShardSchema(client) {
5372
6131
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
5373
6132
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
5374
6133
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
5375
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
6134
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
6135
+ // Metadata enrichment columns (must match database.ts)
6136
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
6137
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
6138
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
6139
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
6140
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
6141
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
6142
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
6143
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
6144
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
6145
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
6146
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
6147
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
6148
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
6149
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
6150
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
5376
6151
  ]) {
5377
6152
  try {
5378
6153
  await client.execute(col);
@@ -5471,21 +6246,69 @@ async function getReadyShardClient(projectName) {
5471
6246
  await ensureShardSchema(client);
5472
6247
  return client;
5473
6248
  }
6249
+ function evictLRU() {
6250
+ let oldest = null;
6251
+ let oldestTime = Infinity;
6252
+ for (const [name, time] of _shardLastAccess) {
6253
+ if (time < oldestTime) {
6254
+ oldestTime = time;
6255
+ oldest = name;
6256
+ }
6257
+ }
6258
+ if (oldest) {
6259
+ const client = _shards.get(oldest);
6260
+ if (client) {
6261
+ client.close();
6262
+ }
6263
+ _shards.delete(oldest);
6264
+ _shardLastAccess.delete(oldest);
6265
+ }
6266
+ }
6267
+ function evictIdleShards() {
6268
+ const now = Date.now();
6269
+ const toEvict = [];
6270
+ for (const [name, lastAccess] of _shardLastAccess) {
6271
+ if (now - lastAccess > SHARD_IDLE_MS) {
6272
+ toEvict.push(name);
6273
+ }
6274
+ }
6275
+ for (const name of toEvict) {
6276
+ const client = _shards.get(name);
6277
+ if (client) {
6278
+ client.close();
6279
+ }
6280
+ _shards.delete(name);
6281
+ _shardLastAccess.delete(name);
6282
+ }
6283
+ }
6284
+ function getOpenShardCount() {
6285
+ return _shards.size;
6286
+ }
5474
6287
  function disposeShards() {
6288
+ if (_evictionTimer) {
6289
+ clearInterval(_evictionTimer);
6290
+ _evictionTimer = null;
6291
+ }
5475
6292
  for (const [, client] of _shards) {
5476
6293
  client.close();
5477
6294
  }
5478
6295
  _shards.clear();
6296
+ _shardLastAccess.clear();
5479
6297
  _shardingEnabled = false;
5480
6298
  _encryptionKey = null;
5481
6299
  }
5482
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
6300
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
5483
6301
  var init_shard_manager = __esm({
5484
6302
  "src/lib/shard-manager.ts"() {
5485
6303
  "use strict";
5486
6304
  init_config();
5487
- SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
6305
+ SHARDS_DIR = path19.join(EXE_AI_DIR, "shards");
6306
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
6307
+ MAX_OPEN_SHARDS = 10;
6308
+ EVICTION_INTERVAL_MS = 60 * 1e3;
5488
6309
  _shards = /* @__PURE__ */ new Map();
6310
+ _shardLastAccess = /* @__PURE__ */ new Map();
6311
+ _evictionTimer = null;
5489
6312
  _encryptionKey = null;
5490
6313
  _shardingEnabled = false;
5491
6314
  }