@askexenow/exe-os 0.9.7 → 0.9.8

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 (95) hide show
  1. package/dist/bin/backfill-conversations.js +754 -79
  2. package/dist/bin/backfill-responses.js +752 -77
  3. package/dist/bin/backfill-vectors.js +752 -77
  4. package/dist/bin/cleanup-stale-review-tasks.js +657 -35
  5. package/dist/bin/cli.js +1388 -605
  6. package/dist/bin/exe-agent-config.js +123 -95
  7. package/dist/bin/exe-agent.js +41 -25
  8. package/dist/bin/exe-assign.js +732 -57
  9. package/dist/bin/exe-boot.js +784 -153
  10. package/dist/bin/exe-call.js +209 -138
  11. package/dist/bin/exe-cloud.js +35 -12
  12. package/dist/bin/exe-dispatch.js +692 -70
  13. package/dist/bin/exe-doctor.js +648 -26
  14. package/dist/bin/exe-export-behaviors.js +650 -20
  15. package/dist/bin/exe-forget.js +635 -13
  16. package/dist/bin/exe-gateway.js +1053 -271
  17. package/dist/bin/exe-heartbeat.js +665 -43
  18. package/dist/bin/exe-kill.js +646 -16
  19. package/dist/bin/exe-launch-agent.js +887 -97
  20. package/dist/bin/exe-link.js +658 -43
  21. package/dist/bin/exe-new-employee.js +378 -177
  22. package/dist/bin/exe-pending-messages.js +656 -34
  23. package/dist/bin/exe-pending-notifications.js +635 -13
  24. package/dist/bin/exe-pending-reviews.js +659 -37
  25. package/dist/bin/exe-rename.js +645 -30
  26. package/dist/bin/exe-review.js +635 -13
  27. package/dist/bin/exe-search.js +771 -88
  28. package/dist/bin/exe-session-cleanup.js +834 -150
  29. package/dist/bin/exe-settings.js +127 -91
  30. package/dist/bin/exe-start-codex.js +729 -94
  31. package/dist/bin/exe-start-opencode.js +717 -82
  32. package/dist/bin/exe-status.js +657 -35
  33. package/dist/bin/exe-team.js +635 -13
  34. package/dist/bin/git-sweep.js +720 -89
  35. package/dist/bin/graph-backfill.js +643 -13
  36. package/dist/bin/graph-export.js +646 -16
  37. package/dist/bin/install.js +596 -193
  38. package/dist/bin/scan-tasks.js +724 -93
  39. package/dist/bin/setup.js +1038 -210
  40. package/dist/bin/shard-migrate.js +645 -15
  41. package/dist/bin/wiki-sync.js +646 -16
  42. package/dist/gateway/index.js +1027 -245
  43. package/dist/hooks/bug-report-worker.js +891 -170
  44. package/dist/hooks/commit-complete.js +718 -87
  45. package/dist/hooks/error-recall.js +776 -93
  46. package/dist/hooks/exe-heartbeat-hook.js +85 -71
  47. package/dist/hooks/ingest-worker.js +840 -156
  48. package/dist/hooks/ingest.js +90 -73
  49. package/dist/hooks/instructions-loaded.js +669 -38
  50. package/dist/hooks/notification.js +661 -30
  51. package/dist/hooks/post-compact.js +674 -43
  52. package/dist/hooks/pre-compact.js +718 -87
  53. package/dist/hooks/pre-tool-use.js +872 -125
  54. package/dist/hooks/prompt-ingest-worker.js +758 -83
  55. package/dist/hooks/prompt-submit.js +1060 -319
  56. package/dist/hooks/response-ingest-worker.js +758 -83
  57. package/dist/hooks/session-end.js +721 -90
  58. package/dist/hooks/session-start.js +1031 -207
  59. package/dist/hooks/stop.js +680 -49
  60. package/dist/hooks/subagent-stop.js +674 -43
  61. package/dist/hooks/summary-worker.js +816 -132
  62. package/dist/index.js +1015 -232
  63. package/dist/lib/cloud-sync.js +663 -48
  64. package/dist/lib/consolidation.js +26 -3
  65. package/dist/lib/database.js +626 -18
  66. package/dist/lib/db.js +2261 -0
  67. package/dist/lib/device-registry.js +640 -25
  68. package/dist/lib/embedder.js +96 -43
  69. package/dist/lib/employee-templates.js +16 -0
  70. package/dist/lib/employees.js +259 -83
  71. package/dist/lib/exe-daemon-client.js +101 -63
  72. package/dist/lib/exe-daemon.js +894 -162
  73. package/dist/lib/hybrid-search.js +771 -88
  74. package/dist/lib/identity.js +27 -7
  75. package/dist/lib/messaging.js +55 -28
  76. package/dist/lib/reminders.js +21 -1
  77. package/dist/lib/schedules.js +636 -14
  78. package/dist/lib/skill-learning.js +21 -1
  79. package/dist/lib/store.js +643 -13
  80. package/dist/lib/task-router.js +82 -71
  81. package/dist/lib/tasks.js +98 -71
  82. package/dist/lib/tmux-routing.js +87 -60
  83. package/dist/lib/token-spend.js +26 -6
  84. package/dist/mcp/server.js +1784 -458
  85. package/dist/mcp/tools/complete-reminder.js +21 -1
  86. package/dist/mcp/tools/create-reminder.js +21 -1
  87. package/dist/mcp/tools/create-task.js +290 -164
  88. package/dist/mcp/tools/deactivate-behavior.js +24 -4
  89. package/dist/mcp/tools/list-reminders.js +21 -1
  90. package/dist/mcp/tools/list-tasks.js +195 -38
  91. package/dist/mcp/tools/send-message.js +58 -31
  92. package/dist/mcp/tools/update-task.js +75 -48
  93. package/dist/runtime/index.js +720 -89
  94. package/dist/tui/App.js +853 -123
  95. package/package.json +3 -2
@@ -780,7 +780,7 @@ function isMultiInstance(agentName, employees) {
780
780
  if (!emp) return false;
781
781
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
782
782
  }
783
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
783
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
784
784
  var init_employees = __esm({
785
785
  "src/lib/employees.ts"() {
786
786
  "use strict";
@@ -789,16 +789,601 @@ var init_employees = __esm({
789
789
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
790
790
  COORDINATOR_ROLE = "COO";
791
791
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
792
+ IDENTITY_DIR = path5.join(EXE_AI_DIR, "identity");
793
+ }
794
+ });
795
+
796
+ // src/lib/database-adapter.ts
797
+ import os5 from "os";
798
+ import path6 from "path";
799
+ import { createRequire } from "module";
800
+ import { pathToFileURL } from "url";
801
+ function quotedIdentifier(identifier) {
802
+ return `"${identifier.replace(/"/g, '""')}"`;
803
+ }
804
+ function unqualifiedTableName(name) {
805
+ const raw = name.trim().replace(/^"|"$/g, "");
806
+ const parts = raw.split(".");
807
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
808
+ }
809
+ function stripTrailingSemicolon(sql) {
810
+ return sql.trim().replace(/;+\s*$/u, "");
811
+ }
812
+ function appendClause(sql, clause) {
813
+ const trimmed = stripTrailingSemicolon(sql);
814
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
815
+ if (!returningMatch) {
816
+ return `${trimmed}${clause}`;
817
+ }
818
+ const idx = returningMatch.index;
819
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
820
+ }
821
+ function normalizeStatement(stmt) {
822
+ if (typeof stmt === "string") {
823
+ return { kind: "positional", sql: stmt, args: [] };
824
+ }
825
+ const sql = stmt.sql;
826
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
827
+ return { kind: "positional", sql, args: stmt.args ?? [] };
828
+ }
829
+ return { kind: "named", sql, args: stmt.args };
830
+ }
831
+ function rewriteBooleanLiterals(sql) {
832
+ let out = sql;
833
+ for (const column of BOOLEAN_COLUMN_NAMES) {
834
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
835
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
836
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
837
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
838
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
839
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
840
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
841
+ }
842
+ return out;
843
+ }
844
+ function rewriteInsertOrIgnore(sql) {
845
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
846
+ return sql;
847
+ }
848
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
849
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
850
+ }
851
+ function rewriteInsertOrReplace(sql) {
852
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
853
+ if (!match) {
854
+ return sql;
855
+ }
856
+ const rawTable = match[1];
857
+ const rawColumns = match[2];
858
+ const remainder = match[3];
859
+ const tableName = unqualifiedTableName(rawTable);
860
+ const conflictKeys = UPSERT_KEYS[tableName];
861
+ if (!conflictKeys?.length) {
862
+ return sql;
863
+ }
864
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
865
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
866
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
867
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
868
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
869
+ }
870
+ function rewriteSql(sql) {
871
+ let out = sql;
872
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
873
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
874
+ out = rewriteBooleanLiterals(out);
875
+ out = rewriteInsertOrReplace(out);
876
+ out = rewriteInsertOrIgnore(out);
877
+ return stripTrailingSemicolon(out);
878
+ }
879
+ function toBoolean(value) {
880
+ if (value === null || value === void 0) return value;
881
+ if (typeof value === "boolean") return value;
882
+ if (typeof value === "number") return value !== 0;
883
+ if (typeof value === "bigint") return value !== 0n;
884
+ if (typeof value === "string") {
885
+ const normalized = value.trim().toLowerCase();
886
+ if (normalized === "0" || normalized === "false") return false;
887
+ if (normalized === "1" || normalized === "true") return true;
888
+ }
889
+ return Boolean(value);
890
+ }
891
+ function countQuestionMarks(sql, end) {
892
+ let count = 0;
893
+ let inSingle = false;
894
+ let inDouble = false;
895
+ let inLineComment = false;
896
+ let inBlockComment = false;
897
+ for (let i = 0; i < end; i++) {
898
+ const ch = sql[i];
899
+ const next = sql[i + 1];
900
+ if (inLineComment) {
901
+ if (ch === "\n") inLineComment = false;
902
+ continue;
903
+ }
904
+ if (inBlockComment) {
905
+ if (ch === "*" && next === "/") {
906
+ inBlockComment = false;
907
+ i += 1;
908
+ }
909
+ continue;
910
+ }
911
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
912
+ inLineComment = true;
913
+ i += 1;
914
+ continue;
915
+ }
916
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
917
+ inBlockComment = true;
918
+ i += 1;
919
+ continue;
920
+ }
921
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
922
+ inSingle = !inSingle;
923
+ continue;
924
+ }
925
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
926
+ inDouble = !inDouble;
927
+ continue;
928
+ }
929
+ if (!inSingle && !inDouble && ch === "?") {
930
+ count += 1;
931
+ }
932
+ }
933
+ return count;
934
+ }
935
+ function findBooleanPlaceholderIndexes(sql) {
936
+ const indexes = /* @__PURE__ */ new Set();
937
+ for (const column of BOOLEAN_COLUMN_NAMES) {
938
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
939
+ for (const match of sql.matchAll(pattern)) {
940
+ const matchText = match[0];
941
+ const qIndex = match.index + matchText.lastIndexOf("?");
942
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
943
+ }
944
+ }
945
+ return indexes;
946
+ }
947
+ function coerceInsertBooleanArgs(sql, args2) {
948
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
949
+ if (!match) return;
950
+ const rawTable = match[1];
951
+ const rawColumns = match[2];
952
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
953
+ if (!boolColumns?.size) return;
954
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
955
+ for (const [index, column] of columns.entries()) {
956
+ if (boolColumns.has(column) && index < args2.length) {
957
+ args2[index] = toBoolean(args2[index]);
958
+ }
959
+ }
960
+ }
961
+ function coerceUpdateBooleanArgs(sql, args2) {
962
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
963
+ if (!match) return;
964
+ const rawTable = match[1];
965
+ const setClause = match[2];
966
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
967
+ if (!boolColumns?.size) return;
968
+ const assignments = setClause.split(",");
969
+ let placeholderIndex = 0;
970
+ for (const assignment of assignments) {
971
+ if (!assignment.includes("?")) continue;
972
+ placeholderIndex += 1;
973
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
974
+ if (colMatch && boolColumns.has(colMatch[1])) {
975
+ args2[placeholderIndex - 1] = toBoolean(args2[placeholderIndex - 1]);
976
+ }
977
+ }
978
+ }
979
+ function coerceBooleanArgs(sql, args2) {
980
+ const nextArgs = [...args2];
981
+ coerceInsertBooleanArgs(sql, nextArgs);
982
+ coerceUpdateBooleanArgs(sql, nextArgs);
983
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
984
+ for (const index of placeholderIndexes) {
985
+ if (index > 0 && index <= nextArgs.length) {
986
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
987
+ }
988
+ }
989
+ return nextArgs;
990
+ }
991
+ function convertQuestionMarksToDollarParams(sql) {
992
+ let out = "";
993
+ let placeholder = 0;
994
+ let inSingle = false;
995
+ let inDouble = false;
996
+ let inLineComment = false;
997
+ let inBlockComment = false;
998
+ for (let i = 0; i < sql.length; i++) {
999
+ const ch = sql[i];
1000
+ const next = sql[i + 1];
1001
+ if (inLineComment) {
1002
+ out += ch;
1003
+ if (ch === "\n") inLineComment = false;
1004
+ continue;
1005
+ }
1006
+ if (inBlockComment) {
1007
+ out += ch;
1008
+ if (ch === "*" && next === "/") {
1009
+ out += next;
1010
+ inBlockComment = false;
1011
+ i += 1;
1012
+ }
1013
+ continue;
1014
+ }
1015
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1016
+ out += ch + next;
1017
+ inLineComment = true;
1018
+ i += 1;
1019
+ continue;
1020
+ }
1021
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1022
+ out += ch + next;
1023
+ inBlockComment = true;
1024
+ i += 1;
1025
+ continue;
1026
+ }
1027
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1028
+ inSingle = !inSingle;
1029
+ out += ch;
1030
+ continue;
1031
+ }
1032
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1033
+ inDouble = !inDouble;
1034
+ out += ch;
1035
+ continue;
1036
+ }
1037
+ if (!inSingle && !inDouble && ch === "?") {
1038
+ placeholder += 1;
1039
+ out += `$${placeholder}`;
1040
+ continue;
1041
+ }
1042
+ out += ch;
1043
+ }
1044
+ return out;
1045
+ }
1046
+ function translateStatementForPostgres(stmt) {
1047
+ const normalized = normalizeStatement(stmt);
1048
+ if (normalized.kind === "named") {
1049
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
1050
+ }
1051
+ const rewrittenSql = rewriteSql(normalized.sql);
1052
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
1053
+ return {
1054
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
1055
+ args: coercedArgs
1056
+ };
1057
+ }
1058
+ function shouldBypassPostgres(stmt) {
1059
+ const normalized = normalizeStatement(stmt);
1060
+ if (normalized.kind === "named") {
1061
+ return true;
1062
+ }
1063
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
1064
+ }
1065
+ function shouldFallbackOnError(error) {
1066
+ const message = error instanceof Error ? error.message : String(error);
1067
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
1068
+ }
1069
+ function isReadQuery(sql) {
1070
+ const trimmed = sql.trimStart();
1071
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
1072
+ }
1073
+ function buildRow(row, columns) {
1074
+ const values = columns.map((column) => row[column]);
1075
+ return Object.assign(values, row);
1076
+ }
1077
+ function buildResultSet(rows, rowsAffected = 0) {
1078
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
1079
+ const resultRows = rows.map((row) => buildRow(row, columns));
1080
+ return {
1081
+ columns,
1082
+ columnTypes: columns.map(() => ""),
1083
+ rows: resultRows,
1084
+ rowsAffected,
1085
+ lastInsertRowid: void 0,
1086
+ toJSON() {
1087
+ return {
1088
+ columns,
1089
+ columnTypes: columns.map(() => ""),
1090
+ rows,
1091
+ rowsAffected,
1092
+ lastInsertRowid: void 0
1093
+ };
1094
+ }
1095
+ };
1096
+ }
1097
+ async function loadPrismaClient() {
1098
+ if (!prismaClientPromise) {
1099
+ prismaClientPromise = (async () => {
1100
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
1101
+ if (explicitPath) {
1102
+ const module2 = await import(pathToFileURL(explicitPath).href);
1103
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
1104
+ if (!PrismaClient2) {
1105
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
1106
+ }
1107
+ return new PrismaClient2();
1108
+ }
1109
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path6.join(os5.homedir(), "exe-db");
1110
+ const requireFromExeDb = createRequire(path6.join(exeDbRoot, "package.json"));
1111
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1112
+ const module = await import(pathToFileURL(prismaEntry).href);
1113
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
1114
+ if (!PrismaClient) {
1115
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
1116
+ }
1117
+ return new PrismaClient();
1118
+ })();
1119
+ }
1120
+ return prismaClientPromise;
1121
+ }
1122
+ async function ensureCompatibilityViews(prisma) {
1123
+ if (!compatibilityBootstrapPromise) {
1124
+ compatibilityBootstrapPromise = (async () => {
1125
+ for (const mapping of VIEW_MAPPINGS) {
1126
+ const relation = mapping.source.replace(/"/g, "");
1127
+ const rows = await prisma.$queryRawUnsafe(
1128
+ "SELECT to_regclass($1) AS regclass",
1129
+ relation
1130
+ );
1131
+ if (!rows[0]?.regclass) {
1132
+ continue;
1133
+ }
1134
+ await prisma.$executeRawUnsafe(
1135
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
1136
+ );
1137
+ }
1138
+ })();
1139
+ }
1140
+ return compatibilityBootstrapPromise;
1141
+ }
1142
+ async function executeOnPrisma(executor, stmt) {
1143
+ const translated = translateStatementForPostgres(stmt);
1144
+ if (isReadQuery(translated.sql)) {
1145
+ const rows = await executor.$queryRawUnsafe(
1146
+ translated.sql,
1147
+ ...translated.args
1148
+ );
1149
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
1150
+ }
1151
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
1152
+ return buildResultSet([], rowsAffected);
1153
+ }
1154
+ function splitSqlStatements(sql) {
1155
+ const parts = [];
1156
+ let current = "";
1157
+ let inSingle = false;
1158
+ let inDouble = false;
1159
+ let inLineComment = false;
1160
+ let inBlockComment = false;
1161
+ for (let i = 0; i < sql.length; i++) {
1162
+ const ch = sql[i];
1163
+ const next = sql[i + 1];
1164
+ if (inLineComment) {
1165
+ current += ch;
1166
+ if (ch === "\n") inLineComment = false;
1167
+ continue;
1168
+ }
1169
+ if (inBlockComment) {
1170
+ current += ch;
1171
+ if (ch === "*" && next === "/") {
1172
+ current += next;
1173
+ inBlockComment = false;
1174
+ i += 1;
1175
+ }
1176
+ continue;
1177
+ }
1178
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1179
+ current += ch + next;
1180
+ inLineComment = true;
1181
+ i += 1;
1182
+ continue;
1183
+ }
1184
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1185
+ current += ch + next;
1186
+ inBlockComment = true;
1187
+ i += 1;
1188
+ continue;
1189
+ }
1190
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1191
+ inSingle = !inSingle;
1192
+ current += ch;
1193
+ continue;
1194
+ }
1195
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1196
+ inDouble = !inDouble;
1197
+ current += ch;
1198
+ continue;
1199
+ }
1200
+ if (!inSingle && !inDouble && ch === ";") {
1201
+ if (current.trim()) {
1202
+ parts.push(current.trim());
1203
+ }
1204
+ current = "";
1205
+ continue;
1206
+ }
1207
+ current += ch;
1208
+ }
1209
+ if (current.trim()) {
1210
+ parts.push(current.trim());
1211
+ }
1212
+ return parts;
1213
+ }
1214
+ async function createPrismaDbAdapter(fallbackClient) {
1215
+ const prisma = await loadPrismaClient();
1216
+ await ensureCompatibilityViews(prisma);
1217
+ let closed = false;
1218
+ let adapter;
1219
+ const fallbackExecute = async (stmt, error) => {
1220
+ if (!fallbackClient) {
1221
+ if (error) throw error;
1222
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
1223
+ }
1224
+ if (error) {
1225
+ process.stderr.write(
1226
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
1227
+ `
1228
+ );
1229
+ }
1230
+ return fallbackClient.execute(stmt);
1231
+ };
1232
+ adapter = {
1233
+ async execute(stmt) {
1234
+ if (shouldBypassPostgres(stmt)) {
1235
+ return fallbackExecute(stmt);
1236
+ }
1237
+ try {
1238
+ return await executeOnPrisma(prisma, stmt);
1239
+ } catch (error) {
1240
+ if (shouldFallbackOnError(error)) {
1241
+ return fallbackExecute(stmt, error);
1242
+ }
1243
+ throw error;
1244
+ }
1245
+ },
1246
+ async batch(stmts, mode) {
1247
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
1248
+ if (!fallbackClient) {
1249
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
1250
+ }
1251
+ return fallbackClient.batch(stmts, mode);
1252
+ }
1253
+ try {
1254
+ if (prisma.$transaction) {
1255
+ return await prisma.$transaction(async (tx) => {
1256
+ const results2 = [];
1257
+ for (const stmt of stmts) {
1258
+ results2.push(await executeOnPrisma(tx, stmt));
1259
+ }
1260
+ return results2;
1261
+ });
1262
+ }
1263
+ const results = [];
1264
+ for (const stmt of stmts) {
1265
+ results.push(await executeOnPrisma(prisma, stmt));
1266
+ }
1267
+ return results;
1268
+ } catch (error) {
1269
+ if (fallbackClient && shouldFallbackOnError(error)) {
1270
+ process.stderr.write(
1271
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
1272
+ `
1273
+ );
1274
+ return fallbackClient.batch(stmts, mode);
1275
+ }
1276
+ throw error;
1277
+ }
1278
+ },
1279
+ async migrate(stmts) {
1280
+ if (fallbackClient) {
1281
+ return fallbackClient.migrate(stmts);
1282
+ }
1283
+ return adapter.batch(stmts, "deferred");
1284
+ },
1285
+ async transaction(mode) {
1286
+ if (!fallbackClient) {
1287
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
1288
+ }
1289
+ return fallbackClient.transaction(mode);
1290
+ },
1291
+ async executeMultiple(sql) {
1292
+ if (fallbackClient && shouldBypassPostgres(sql)) {
1293
+ return fallbackClient.executeMultiple(sql);
1294
+ }
1295
+ for (const statement of splitSqlStatements(sql)) {
1296
+ await adapter.execute(statement);
1297
+ }
1298
+ },
1299
+ async sync() {
1300
+ if (fallbackClient) {
1301
+ return fallbackClient.sync();
1302
+ }
1303
+ return { frame_no: 0, frames_synced: 0 };
1304
+ },
1305
+ close() {
1306
+ closed = true;
1307
+ prismaClientPromise = null;
1308
+ compatibilityBootstrapPromise = null;
1309
+ void prisma.$disconnect?.();
1310
+ },
1311
+ get closed() {
1312
+ return closed;
1313
+ },
1314
+ get protocol() {
1315
+ return "prisma-postgres";
1316
+ }
1317
+ };
1318
+ return adapter;
1319
+ }
1320
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
1321
+ var init_database_adapter = __esm({
1322
+ "src/lib/database-adapter.ts"() {
1323
+ "use strict";
1324
+ VIEW_MAPPINGS = [
1325
+ { view: "memories", source: "memory.memory_records" },
1326
+ { view: "tasks", source: "memory.tasks" },
1327
+ { view: "behaviors", source: "memory.behaviors" },
1328
+ { view: "entities", source: "memory.entities" },
1329
+ { view: "relationships", source: "memory.relationships" },
1330
+ { view: "entity_memories", source: "memory.entity_memories" },
1331
+ { view: "entity_aliases", source: "memory.entity_aliases" },
1332
+ { view: "notifications", source: "memory.notifications" },
1333
+ { view: "messages", source: "memory.messages" },
1334
+ { view: "users", source: "wiki.users" },
1335
+ { view: "workspaces", source: "wiki.workspaces" },
1336
+ { view: "workspace_users", source: "wiki.workspace_users" },
1337
+ { view: "documents", source: "wiki.workspace_documents" },
1338
+ { view: "chats", source: "wiki.workspace_chats" }
1339
+ ];
1340
+ UPSERT_KEYS = {
1341
+ memories: ["id"],
1342
+ tasks: ["id"],
1343
+ behaviors: ["id"],
1344
+ entities: ["id"],
1345
+ relationships: ["id"],
1346
+ entity_aliases: ["alias"],
1347
+ notifications: ["id"],
1348
+ messages: ["id"],
1349
+ users: ["id"],
1350
+ workspaces: ["id"],
1351
+ workspace_users: ["id"],
1352
+ documents: ["id"],
1353
+ chats: ["id"]
1354
+ };
1355
+ BOOLEAN_COLUMNS_BY_TABLE = {
1356
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
1357
+ behaviors: /* @__PURE__ */ new Set(["active"]),
1358
+ notifications: /* @__PURE__ */ new Set(["read"]),
1359
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
1360
+ };
1361
+ BOOLEAN_COLUMN_NAMES = new Set(
1362
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
1363
+ );
1364
+ IMMEDIATE_FALLBACK_PATTERNS = [
1365
+ /\bPRAGMA\b/i,
1366
+ /\bsqlite_master\b/i,
1367
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
1368
+ /\bMATCH\b/i,
1369
+ /\bvector_distance_cos\s*\(/i,
1370
+ /\bjson_extract\s*\(/i,
1371
+ /\bjulianday\s*\(/i,
1372
+ /\bstrftime\s*\(/i,
1373
+ /\blast_insert_rowid\s*\(/i
1374
+ ];
1375
+ prismaClientPromise = null;
1376
+ compatibilityBootstrapPromise = null;
792
1377
  }
793
1378
  });
794
1379
 
795
1380
  // src/lib/exe-daemon-client.ts
796
1381
  import net from "net";
797
- import os5 from "os";
1382
+ import os6 from "os";
798
1383
  import { spawn } from "child_process";
799
1384
  import { randomUUID } from "crypto";
800
1385
  import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
801
- import path6 from "path";
1386
+ import path7 from "path";
802
1387
  import { fileURLToPath } from "url";
803
1388
  function handleData(chunk) {
804
1389
  _buffer += chunk.toString();
@@ -849,17 +1434,17 @@ function cleanupStaleFiles() {
849
1434
  }
850
1435
  }
851
1436
  function findPackageRoot() {
852
- let dir = path6.dirname(fileURLToPath(import.meta.url));
853
- const { root } = path6.parse(dir);
1437
+ let dir = path7.dirname(fileURLToPath(import.meta.url));
1438
+ const { root } = path7.parse(dir);
854
1439
  while (dir !== root) {
855
- if (existsSync6(path6.join(dir, "package.json"))) return dir;
856
- dir = path6.dirname(dir);
1440
+ if (existsSync6(path7.join(dir, "package.json"))) return dir;
1441
+ dir = path7.dirname(dir);
857
1442
  }
858
1443
  return null;
859
1444
  }
860
1445
  function spawnDaemon() {
861
- const freeGB = os5.freemem() / (1024 * 1024 * 1024);
862
- const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
1446
+ const freeGB = os6.freemem() / (1024 * 1024 * 1024);
1447
+ const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
863
1448
  if (totalGB <= 8) {
864
1449
  process.stderr.write(
865
1450
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -879,7 +1464,7 @@ function spawnDaemon() {
879
1464
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
880
1465
  return;
881
1466
  }
882
- const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1467
+ const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
883
1468
  if (!existsSync6(daemonPath)) {
884
1469
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
885
1470
  `);
@@ -888,7 +1473,7 @@ function spawnDaemon() {
888
1473
  const resolvedPath = daemonPath;
889
1474
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
890
1475
  `);
891
- const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
1476
+ const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
892
1477
  let stderrFd = "ignore";
893
1478
  try {
894
1479
  stderrFd = openSync(logPath, "a");
@@ -1035,9 +1620,9 @@ var init_exe_daemon_client = __esm({
1035
1620
  "src/lib/exe-daemon-client.ts"() {
1036
1621
  "use strict";
1037
1622
  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");
1623
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
1624
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
1625
+ SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
1041
1626
  SPAWN_LOCK_STALE_MS = 3e4;
1042
1627
  CONNECT_TIMEOUT_MS = 15e3;
1043
1628
  REQUEST_TIMEOUT_MS = 3e4;
@@ -1119,7 +1704,7 @@ __export(db_daemon_client_exports, {
1119
1704
  createDaemonDbClient: () => createDaemonDbClient,
1120
1705
  initDaemonDbClient: () => initDaemonDbClient
1121
1706
  });
1122
- function normalizeStatement(stmt) {
1707
+ function normalizeStatement2(stmt) {
1123
1708
  if (typeof stmt === "string") {
1124
1709
  return { sql: stmt, args: [] };
1125
1710
  }
@@ -1143,7 +1728,7 @@ function createDaemonDbClient(fallbackClient) {
1143
1728
  if (!_useDaemon || !isClientConnected()) {
1144
1729
  return fallbackClient.execute(stmt);
1145
1730
  }
1146
- const { sql, args: args2 } = normalizeStatement(stmt);
1731
+ const { sql, args: args2 } = normalizeStatement2(stmt);
1147
1732
  const response = await sendDaemonRequest({
1148
1733
  type: "db-execute",
1149
1734
  sql,
@@ -1168,7 +1753,7 @@ function createDaemonDbClient(fallbackClient) {
1168
1753
  if (!_useDaemon || !isClientConnected()) {
1169
1754
  return fallbackClient.batch(stmts, mode);
1170
1755
  }
1171
- const statements = stmts.map(normalizeStatement);
1756
+ const statements = stmts.map(normalizeStatement2);
1172
1757
  const response = await sendDaemonRequest({
1173
1758
  type: "db-batch",
1174
1759
  statements,
@@ -1263,6 +1848,18 @@ __export(database_exports, {
1263
1848
  });
1264
1849
  import { createClient } from "@libsql/client";
1265
1850
  async function initDatabase(config) {
1851
+ if (_walCheckpointTimer) {
1852
+ clearInterval(_walCheckpointTimer);
1853
+ _walCheckpointTimer = null;
1854
+ }
1855
+ if (_daemonClient) {
1856
+ _daemonClient.close();
1857
+ _daemonClient = null;
1858
+ }
1859
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1860
+ _adapterClient.close();
1861
+ }
1862
+ _adapterClient = null;
1266
1863
  if (_client) {
1267
1864
  _client.close();
1268
1865
  _client = null;
@@ -1276,6 +1873,7 @@ async function initDatabase(config) {
1276
1873
  }
1277
1874
  _client = createClient(opts);
1278
1875
  _resilientClient = wrapWithRetry(_client);
1876
+ _adapterClient = _resilientClient;
1279
1877
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
1280
1878
  });
1281
1879
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -1286,14 +1884,20 @@ async function initDatabase(config) {
1286
1884
  });
1287
1885
  }, 3e4);
1288
1886
  _walCheckpointTimer.unref();
1887
+ if (process.env.DATABASE_URL) {
1888
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1889
+ }
1289
1890
  }
1290
1891
  function isInitialized() {
1291
- return _client !== null;
1892
+ return _adapterClient !== null || _client !== null;
1292
1893
  }
1293
1894
  function getClient() {
1294
- if (!_resilientClient) {
1895
+ if (!_adapterClient) {
1295
1896
  throw new Error("Database client not initialized. Call initDatabase() first.");
1296
1897
  }
1898
+ if (process.env.DATABASE_URL) {
1899
+ return _adapterClient;
1900
+ }
1297
1901
  if (process.env.EXE_IS_DAEMON === "1") {
1298
1902
  return _resilientClient;
1299
1903
  }
@@ -1303,6 +1907,7 @@ function getClient() {
1303
1907
  return _resilientClient;
1304
1908
  }
1305
1909
  async function initDaemonClient() {
1910
+ if (process.env.DATABASE_URL) return;
1306
1911
  if (process.env.EXE_IS_DAEMON === "1") return;
1307
1912
  if (!_resilientClient) return;
1308
1913
  try {
@@ -2247,26 +2852,36 @@ async function ensureSchema() {
2247
2852
  }
2248
2853
  }
2249
2854
  async function disposeDatabase() {
2855
+ if (_walCheckpointTimer) {
2856
+ clearInterval(_walCheckpointTimer);
2857
+ _walCheckpointTimer = null;
2858
+ }
2250
2859
  if (_daemonClient) {
2251
2860
  _daemonClient.close();
2252
2861
  _daemonClient = null;
2253
2862
  }
2863
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2864
+ _adapterClient.close();
2865
+ }
2866
+ _adapterClient = null;
2254
2867
  if (_client) {
2255
2868
  _client.close();
2256
2869
  _client = null;
2257
2870
  _resilientClient = null;
2258
2871
  }
2259
2872
  }
2260
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
2873
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
2261
2874
  var init_database = __esm({
2262
2875
  "src/lib/database.ts"() {
2263
2876
  "use strict";
2264
2877
  init_db_retry();
2265
2878
  init_employees();
2879
+ init_database_adapter();
2266
2880
  _client = null;
2267
2881
  _resilientClient = null;
2268
2882
  _walCheckpointTimer = null;
2269
2883
  _daemonClient = null;
2884
+ _adapterClient = null;
2270
2885
  initTurso = initDatabase;
2271
2886
  disposeTurso = disposeDatabase;
2272
2887
  }
@@ -2275,16 +2890,16 @@ var init_database = __esm({
2275
2890
  // src/lib/license.ts
2276
2891
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2277
2892
  import { randomUUID as randomUUID2 } from "crypto";
2278
- import path7 from "path";
2893
+ import path8 from "path";
2279
2894
  import { jwtVerify, importSPKI } from "jose";
2280
2895
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
2281
2896
  var init_license = __esm({
2282
2897
  "src/lib/license.ts"() {
2283
2898
  "use strict";
2284
2899
  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");
2900
+ LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
2901
+ CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
2902
+ DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
2288
2903
  PLAN_LIMITS = {
2289
2904
  free: { devices: 1, employees: 1, memories: 5e3 },
2290
2905
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -2297,7 +2912,7 @@ var init_license = __esm({
2297
2912
 
2298
2913
  // src/lib/plan-limits.ts
2299
2914
  import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
2300
- import path8 from "path";
2915
+ import path9 from "path";
2301
2916
  function getLicenseSync() {
2302
2917
  try {
2303
2918
  if (!existsSync8(CACHE_PATH2)) return freeLicense();
@@ -2369,14 +2984,14 @@ var init_plan_limits = __esm({
2369
2984
  this.name = "PlanLimitError";
2370
2985
  }
2371
2986
  };
2372
- CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
2987
+ CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
2373
2988
  }
2374
2989
  });
2375
2990
 
2376
2991
  // src/lib/notifications.ts
2377
2992
  import crypto from "crypto";
2378
- import path9 from "path";
2379
- import os6 from "os";
2993
+ import path10 from "path";
2994
+ import os7 from "os";
2380
2995
  import {
2381
2996
  readFileSync as readFileSync9,
2382
2997
  readdirSync,
@@ -2532,8 +3147,8 @@ __export(tasks_crud_exports, {
2532
3147
  writeCheckpoint: () => writeCheckpoint
2533
3148
  });
2534
3149
  import crypto3 from "crypto";
2535
- import path10 from "path";
2536
- import os7 from "os";
3150
+ import path11 from "path";
3151
+ import os8 from "os";
2537
3152
  import { execSync as execSync4 } from "child_process";
2538
3153
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
2539
3154
  import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
@@ -2711,8 +3326,8 @@ ${laneWarning}` : laneWarning;
2711
3326
  }
2712
3327
  if (input.baseDir) {
2713
3328
  try {
2714
- await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
2715
- await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
3329
+ await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
3330
+ await mkdir3(path11.join(input.baseDir, "exe", "research"), { recursive: true });
2716
3331
  await ensureArchitectureDoc(input.baseDir, input.projectName);
2717
3332
  await ensureGitignoreExe(input.baseDir);
2718
3333
  } catch {
@@ -2748,9 +3363,9 @@ ${laneWarning}` : laneWarning;
2748
3363
  });
2749
3364
  if (input.baseDir) {
2750
3365
  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);
3366
+ const EXE_OS_DIR = path11.join(os8.homedir(), ".exe-os");
3367
+ const mdPath = path11.join(EXE_OS_DIR, taskFile);
3368
+ const mdDir = path11.dirname(mdPath);
2754
3369
  if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
2755
3370
  const reviewer = input.reviewer ?? input.assignedBy;
2756
3371
  const mdContent = `# ${input.title}
@@ -3051,7 +3666,7 @@ async function deleteTaskCore(taskId, _baseDir) {
3051
3666
  return { taskFile, assignedTo, assignedBy, taskSlug };
3052
3667
  }
3053
3668
  async function ensureArchitectureDoc(baseDir, projectName2) {
3054
- const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
3669
+ const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
3055
3670
  try {
3056
3671
  if (existsSync10(archPath)) return;
3057
3672
  const template = [
@@ -3086,7 +3701,7 @@ async function ensureArchitectureDoc(baseDir, projectName2) {
3086
3701
  }
3087
3702
  }
3088
3703
  async function ensureGitignoreExe(baseDir) {
3089
- const gitignorePath = path10.join(baseDir, ".gitignore");
3704
+ const gitignorePath = path11.join(baseDir, ".gitignore");
3090
3705
  try {
3091
3706
  if (existsSync10(gitignorePath)) {
3092
3707
  const content = readFileSync10(gitignorePath, "utf-8");
@@ -3120,13 +3735,13 @@ var init_tasks_crud = __esm({
3120
3735
  });
3121
3736
 
3122
3737
  // src/lib/tasks-review.ts
3123
- import path11 from "path";
3738
+ import path12 from "path";
3124
3739
  import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
3125
3740
  async function countPendingReviews(sessionScope) {
3126
3741
  const client = getClient();
3127
3742
  if (sessionScope) {
3128
3743
  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)",
3744
+ sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND session_scope = ?",
3130
3745
  args: [sessionScope]
3131
3746
  });
3132
3747
  return Number(result2.rows[0]?.cnt) || 0;
@@ -3302,11 +3917,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3302
3917
  );
3303
3918
  }
3304
3919
  try {
3305
- const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
3920
+ const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
3306
3921
  if (existsSync11(cacheDir)) {
3307
3922
  for (const f of readdirSync2(cacheDir)) {
3308
3923
  if (f.startsWith("review-notified-")) {
3309
- unlinkSync4(path11.join(cacheDir, f));
3924
+ unlinkSync4(path12.join(cacheDir, f));
3310
3925
  }
3311
3926
  }
3312
3927
  }
@@ -3327,7 +3942,7 @@ var init_tasks_review = __esm({
3327
3942
  });
3328
3943
 
3329
3944
  // src/lib/tasks-chain.ts
3330
- import path12 from "path";
3945
+ import path13 from "path";
3331
3946
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3332
3947
  async function cascadeUnblock(taskId, baseDir, now) {
3333
3948
  const client = getClient();
@@ -3344,7 +3959,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3344
3959
  });
3345
3960
  for (const ur of unblockedRows.rows) {
3346
3961
  try {
3347
- const ubFile = path12.join(baseDir, String(ur.task_file));
3962
+ const ubFile = path13.join(baseDir, String(ur.task_file));
3348
3963
  let ubContent = await readFile3(ubFile, "utf-8");
3349
3964
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3350
3965
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3413,7 +4028,7 @@ var init_tasks_chain = __esm({
3413
4028
 
3414
4029
  // src/lib/project-name.ts
3415
4030
  import { execSync as execSync5 } from "child_process";
3416
- import path13 from "path";
4031
+ import path14 from "path";
3417
4032
  function getProjectName(cwd) {
3418
4033
  const dir = cwd ?? process.cwd();
3419
4034
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -3426,7 +4041,7 @@ function getProjectName(cwd) {
3426
4041
  timeout: 2e3,
3427
4042
  stdio: ["pipe", "pipe", "pipe"]
3428
4043
  }).trim();
3429
- repoRoot = path13.dirname(gitCommonDir);
4044
+ repoRoot = path14.dirname(gitCommonDir);
3430
4045
  } catch {
3431
4046
  repoRoot = execSync5("git rev-parse --show-toplevel", {
3432
4047
  cwd: dir,
@@ -3435,11 +4050,11 @@ function getProjectName(cwd) {
3435
4050
  stdio: ["pipe", "pipe", "pipe"]
3436
4051
  }).trim();
3437
4052
  }
3438
- _cached2 = path13.basename(repoRoot);
4053
+ _cached2 = path14.basename(repoRoot);
3439
4054
  _cachedCwd = dir;
3440
4055
  return _cached2;
3441
4056
  } catch {
3442
- _cached2 = path13.basename(dir);
4057
+ _cached2 = path14.basename(dir);
3443
4058
  _cachedCwd = dir;
3444
4059
  return _cached2;
3445
4060
  }
@@ -3912,7 +4527,7 @@ __export(tasks_exports, {
3912
4527
  updateTaskStatus: () => updateTaskStatus,
3913
4528
  writeCheckpoint: () => writeCheckpoint
3914
4529
  });
3915
- import path14 from "path";
4530
+ import path15 from "path";
3916
4531
  import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
3917
4532
  async function createTask(input) {
3918
4533
  const result = await createTaskCore(input);
@@ -3932,8 +4547,8 @@ async function updateTask(input) {
3932
4547
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
3933
4548
  try {
3934
4549
  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`);
4550
+ const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
4551
+ const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
3937
4552
  if (input.status === "in_progress") {
3938
4553
  mkdirSync5(cacheDir, { recursive: true });
3939
4554
  writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
@@ -4404,12 +5019,12 @@ __export(tmux_routing_exports, {
4404
5019
  });
4405
5020
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
4406
5021
  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";
5022
+ import path16 from "path";
5023
+ import os9 from "os";
4409
5024
  import { fileURLToPath as fileURLToPath2 } from "url";
4410
5025
  import { unlinkSync as unlinkSync6 } from "fs";
4411
5026
  function spawnLockPath(sessionName) {
4412
- return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5027
+ return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4413
5028
  }
4414
5029
  function isProcessAlive(pid) {
4415
5030
  try {
@@ -4446,8 +5061,8 @@ function releaseSpawnLock2(sessionName) {
4446
5061
  function resolveBehaviorsExporterScript() {
4447
5062
  try {
4448
5063
  const thisFile = fileURLToPath2(import.meta.url);
4449
- const scriptPath = path15.join(
4450
- path15.dirname(thisFile),
5064
+ const scriptPath = path16.join(
5065
+ path16.dirname(thisFile),
4451
5066
  "..",
4452
5067
  "bin",
4453
5068
  "exe-export-behaviors.js"
@@ -4522,7 +5137,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4522
5137
  mkdirSync6(SESSION_CACHE, { recursive: true });
4523
5138
  }
4524
5139
  const rootExe = extractRootExe(parentExe) ?? parentExe;
4525
- const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5140
+ const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4526
5141
  writeFileSync7(filePath, JSON.stringify({
4527
5142
  parentExe: rootExe,
4528
5143
  dispatchedBy: dispatchedBy || rootExe,
@@ -4531,7 +5146,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4531
5146
  }
4532
5147
  function getParentExe(sessionKey) {
4533
5148
  try {
4534
- const data = JSON.parse(readFileSync11(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5149
+ const data = JSON.parse(readFileSync11(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4535
5150
  return data.parentExe || null;
4536
5151
  } catch {
4537
5152
  return null;
@@ -4540,7 +5155,7 @@ function getParentExe(sessionKey) {
4540
5155
  function getDispatchedBy(sessionKey) {
4541
5156
  try {
4542
5157
  const data = JSON.parse(readFileSync11(
4543
- path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5158
+ path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4544
5159
  "utf8"
4545
5160
  ));
4546
5161
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -4726,7 +5341,7 @@ function sendIntercom(targetSession) {
4726
5341
  try {
4727
5342
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
4728
5343
  const agent = baseAgentName(rawAgent);
4729
- const markerPath = path15.join(SESSION_CACHE, `current-task-${agent}.json`);
5344
+ const markerPath = path16.join(SESSION_CACHE, `current-task-${agent}.json`);
4730
5345
  if (existsSync12(markerPath)) {
4731
5346
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
4732
5347
  return "debounced";
@@ -4736,7 +5351,7 @@ function sendIntercom(targetSession) {
4736
5351
  try {
4737
5352
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
4738
5353
  const agent = baseAgentName(rawAgent);
4739
- const taskDir = path15.join(process.cwd(), "exe", agent);
5354
+ const taskDir = path16.join(process.cwd(), "exe", agent);
4740
5355
  if (existsSync12(taskDir)) {
4741
5356
  const files = readdirSync3(taskDir).filter(
4742
5357
  (f) => f.endsWith(".md") && f !== "DONE.txt"
@@ -4870,8 +5485,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4870
5485
  const transport = getTransport();
4871
5486
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
4872
5487
  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`);
5488
+ const logDir = path16.join(os9.homedir(), ".exe-os", "session-logs");
5489
+ const logFile = path16.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4875
5490
  if (!existsSync12(logDir)) {
4876
5491
  mkdirSync6(logDir, { recursive: true });
4877
5492
  }
@@ -4879,14 +5494,14 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4879
5494
  let cleanupSuffix = "";
4880
5495
  try {
4881
5496
  const thisFile = fileURLToPath2(import.meta.url);
4882
- const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5497
+ const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4883
5498
  if (existsSync12(cleanupScript)) {
4884
5499
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
4885
5500
  }
4886
5501
  } catch {
4887
5502
  }
4888
5503
  try {
4889
- const claudeJsonPath = path15.join(os8.homedir(), ".claude.json");
5504
+ const claudeJsonPath = path16.join(os9.homedir(), ".claude.json");
4890
5505
  let claudeJson = {};
4891
5506
  try {
4892
5507
  claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
@@ -4901,10 +5516,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4901
5516
  } catch {
4902
5517
  }
4903
5518
  try {
4904
- const settingsDir = path15.join(os8.homedir(), ".claude", "projects");
5519
+ const settingsDir = path16.join(os9.homedir(), ".claude", "projects");
4905
5520
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
4906
- const projSettingsDir = path15.join(settingsDir, normalizedKey);
4907
- const settingsPath = path15.join(projSettingsDir, "settings.json");
5521
+ const projSettingsDir = path16.join(settingsDir, normalizedKey);
5522
+ const settingsPath = path16.join(projSettingsDir, "settings.json");
4908
5523
  let settings = {};
4909
5524
  try {
4910
5525
  settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
@@ -4951,8 +5566,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4951
5566
  let behaviorsFlag = "";
4952
5567
  let legacyFallbackWarned = false;
4953
5568
  if (!useExeAgent && !useBinSymlink) {
4954
- const identityPath = path15.join(
4955
- os8.homedir(),
5569
+ const identityPath = path16.join(
5570
+ os9.homedir(),
4956
5571
  ".exe-os",
4957
5572
  "identity",
4958
5573
  `${employeeName}.md`
@@ -4967,7 +5582,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4967
5582
  }
4968
5583
  const behaviorsFile = exportBehaviorsSync(
4969
5584
  employeeName,
4970
- path15.basename(spawnCwd),
5585
+ path16.basename(spawnCwd),
4971
5586
  sessionName
4972
5587
  );
4973
5588
  if (behaviorsFile) {
@@ -4982,9 +5597,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4982
5597
  }
4983
5598
  let sessionContextFlag = "";
4984
5599
  try {
4985
- const ctxDir = path15.join(os8.homedir(), ".exe-os", "session-cache");
5600
+ const ctxDir = path16.join(os9.homedir(), ".exe-os", "session-cache");
4986
5601
  mkdirSync6(ctxDir, { recursive: true });
4987
- const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
5602
+ const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
4988
5603
  const ctxContent = [
4989
5604
  `## Session Context`,
4990
5605
  `You are running in tmux session: ${sessionName}.`,
@@ -5068,7 +5683,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5068
5683
  transport.pipeLog(sessionName, logFile);
5069
5684
  try {
5070
5685
  const mySession = getMySession();
5071
- const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5686
+ const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5072
5687
  writeFileSync7(dispatchInfo, JSON.stringify({
5073
5688
  dispatchedBy: mySession,
5074
5689
  rootExe: exeSession,
@@ -5143,15 +5758,15 @@ var init_tmux_routing = __esm({
5143
5758
  init_intercom_queue();
5144
5759
  init_plan_limits();
5145
5760
  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");
5761
+ SPAWN_LOCK_DIR = path16.join(os9.homedir(), ".exe-os", "spawn-locks");
5762
+ SESSION_CACHE = path16.join(os9.homedir(), ".exe-os", "session-cache");
5148
5763
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5149
5764
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
5150
5765
  VERIFY_PANE_LINES = 200;
5151
5766
  INTERCOM_DEBOUNCE_MS = 3e4;
5152
5767
  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");
5768
+ INTERCOM_LOG2 = path16.join(os9.homedir(), ".exe-os", "intercom.log");
5769
+ DEBOUNCE_FILE = path16.join(SESSION_CACHE, "intercom-debounce.json");
5155
5770
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
5156
5771
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
5157
5772
  }
@@ -5193,13 +5808,13 @@ var init_memory = __esm({
5193
5808
  // src/lib/keychain.ts
5194
5809
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
5195
5810
  import { existsSync as existsSync13 } from "fs";
5196
- import path16 from "path";
5197
- import os9 from "os";
5811
+ import path17 from "path";
5812
+ import os10 from "os";
5198
5813
  function getKeyDir() {
5199
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os9.homedir(), ".exe-os");
5814
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path17.join(os10.homedir(), ".exe-os");
5200
5815
  }
5201
5816
  function getKeyPath() {
5202
- return path16.join(getKeyDir(), "master.key");
5817
+ return path17.join(getKeyDir(), "master.key");
5203
5818
  }
5204
5819
  async function tryKeytar() {
5205
5820
  try {
@@ -5222,7 +5837,7 @@ async function getMasterKey() {
5222
5837
  const keyPath = getKeyPath();
5223
5838
  if (!existsSync13(keyPath)) {
5224
5839
  process.stderr.write(
5225
- `[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5840
+ `[keychain] Key not found at ${keyPath} (HOME=${os10.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5226
5841
  `
5227
5842
  );
5228
5843
  return null;
@@ -5260,7 +5875,7 @@ __export(shard_manager_exports, {
5260
5875
  listShards: () => listShards,
5261
5876
  shardExists: () => shardExists
5262
5877
  });
5263
- import path17 from "path";
5878
+ import path18 from "path";
5264
5879
  import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
5265
5880
  import { createClient as createClient2 } from "@libsql/client";
5266
5881
  function initShardManager(encryptionKey) {
@@ -5286,7 +5901,7 @@ function getShardClient(projectName2) {
5286
5901
  }
5287
5902
  const cached = _shards.get(safeName);
5288
5903
  if (cached) return cached;
5289
- const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
5904
+ const dbPath = path18.join(SHARDS_DIR, `${safeName}.db`);
5290
5905
  const client = createClient2({
5291
5906
  url: `file:${dbPath}`,
5292
5907
  encryptionKey: _encryptionKey
@@ -5296,7 +5911,7 @@ function getShardClient(projectName2) {
5296
5911
  }
5297
5912
  function shardExists(projectName2) {
5298
5913
  const safeName = projectName2.replace(/[^a-zA-Z0-9_-]/g, "_");
5299
- return existsSync14(path17.join(SHARDS_DIR, `${safeName}.db`));
5914
+ return existsSync14(path18.join(SHARDS_DIR, `${safeName}.db`));
5300
5915
  }
5301
5916
  function listShards() {
5302
5917
  if (!existsSync14(SHARDS_DIR)) return [];
@@ -5373,7 +5988,23 @@ async function ensureShardSchema(client) {
5373
5988
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
5374
5989
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
5375
5990
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
5376
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
5991
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
5992
+ // Metadata enrichment columns (must match database.ts)
5993
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
5994
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
5995
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
5996
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
5997
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
5998
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
5999
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
6000
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
6001
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
6002
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
6003
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
6004
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
6005
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
6006
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
6007
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
5377
6008
  ]) {
5378
6009
  try {
5379
6010
  await client.execute(col);
@@ -5485,7 +6116,7 @@ var init_shard_manager = __esm({
5485
6116
  "src/lib/shard-manager.ts"() {
5486
6117
  "use strict";
5487
6118
  init_config();
5488
- SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
6119
+ SHARDS_DIR = path18.join(EXE_AI_DIR, "shards");
5489
6120
  _shards = /* @__PURE__ */ new Map();
5490
6121
  _encryptionKey = null;
5491
6122
  _shardingEnabled = false;
@@ -6454,13 +7085,13 @@ async function sweepTasks(projectName2, options = {}) {
6454
7085
  }
6455
7086
 
6456
7087
  // src/bin/git-sweep.ts
6457
- import path18 from "path";
7088
+ import path19 from "path";
6458
7089
  var args = process.argv.slice(2);
6459
7090
  var dryRun = args.includes("--dry-run");
6460
7091
  var projectIdx = args.indexOf("--project");
6461
7092
  var limitIdx = args.indexOf("--limit");
6462
7093
  var staleIdx = args.indexOf("--stale");
6463
- var projectName = projectIdx >= 0 ? args[projectIdx + 1] : process.cwd().split(path18.sep).pop();
7094
+ var projectName = projectIdx >= 0 ? args[projectIdx + 1] : process.cwd().split(path19.sep).pop();
6464
7095
  var commitLimit = limitIdx >= 0 ? Number(args[limitIdx + 1]) : void 0;
6465
7096
  var staleMinutes = staleIdx >= 0 ? Number(args[staleIdx + 1]) : void 0;
6466
7097
  async function main() {