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