@askexenow/exe-os 0.9.6 → 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 +668 -37
  5. package/dist/bin/cli.js +1399 -607
  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 +795 -155
  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 +703 -72
  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 +1064 -273
  17. package/dist/bin/exe-heartbeat.js +676 -45
  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 +845 -152
  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 +668 -37
  33. package/dist/bin/exe-team.js +635 -13
  34. package/dist/bin/git-sweep.js +731 -91
  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 +735 -95
  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 +1038 -247
  43. package/dist/hooks/bug-report-worker.js +902 -172
  44. package/dist/hooks/commit-complete.js +729 -89
  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 +851 -158
  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 +685 -45
  52. package/dist/hooks/pre-compact.js +729 -89
  53. package/dist/hooks/pre-tool-use.js +883 -127
  54. package/dist/hooks/prompt-ingest-worker.js +758 -83
  55. package/dist/hooks/prompt-submit.js +1071 -321
  56. package/dist/hooks/response-ingest-worker.js +758 -83
  57. package/dist/hooks/session-end.js +732 -92
  58. package/dist/hooks/session-start.js +1042 -209
  59. package/dist/hooks/stop.js +691 -51
  60. package/dist/hooks/subagent-stop.js +685 -45
  61. package/dist/hooks/summary-worker.js +827 -134
  62. package/dist/index.js +1026 -234
  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 +905 -164
  73. package/dist/lib/hybrid-search.js +771 -88
  74. package/dist/lib/identity.js +27 -7
  75. package/dist/lib/messaging.js +66 -30
  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 +109 -73
  82. package/dist/lib/tmux-routing.js +98 -62
  83. package/dist/lib/token-spend.js +26 -6
  84. package/dist/mcp/server.js +1807 -472
  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 +301 -166
  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 +206 -40
  91. package/dist/mcp/tools/send-message.js +69 -33
  92. package/dist/mcp/tools/update-task.js +86 -50
  93. package/dist/runtime/index.js +731 -91
  94. package/dist/tui/App.js +864 -125
  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,12 +789,609 @@ 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, args) {
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 < args.length) {
957
+ args[index] = toBoolean(args[index]);
958
+ }
959
+ }
960
+ }
961
+ function coerceUpdateBooleanArgs(sql, args) {
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
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
976
+ }
977
+ }
978
+ }
979
+ function coerceBooleanArgs(sql, args) {
980
+ const nextArgs = [...args];
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/database.ts
796
1381
  import { createClient } from "@libsql/client";
797
1382
  async function initDatabase(config) {
1383
+ if (_walCheckpointTimer) {
1384
+ clearInterval(_walCheckpointTimer);
1385
+ _walCheckpointTimer = null;
1386
+ }
1387
+ if (_daemonClient) {
1388
+ _daemonClient.close();
1389
+ _daemonClient = null;
1390
+ }
1391
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1392
+ _adapterClient.close();
1393
+ }
1394
+ _adapterClient = null;
798
1395
  if (_client) {
799
1396
  _client.close();
800
1397
  _client = null;
@@ -808,6 +1405,7 @@ async function initDatabase(config) {
808
1405
  }
809
1406
  _client = createClient(opts);
810
1407
  _resilientClient = wrapWithRetry(_client);
1408
+ _adapterClient = _resilientClient;
811
1409
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
812
1410
  });
813
1411
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -818,11 +1416,17 @@ async function initDatabase(config) {
818
1416
  });
819
1417
  }, 3e4);
820
1418
  _walCheckpointTimer.unref();
1419
+ if (process.env.DATABASE_URL) {
1420
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1421
+ }
821
1422
  }
822
1423
  function getClient() {
823
- if (!_resilientClient) {
1424
+ if (!_adapterClient) {
824
1425
  throw new Error("Database client not initialized. Call initDatabase() first.");
825
1426
  }
1427
+ if (process.env.DATABASE_URL) {
1428
+ return _adapterClient;
1429
+ }
826
1430
  if (process.env.EXE_IS_DAEMON === "1") {
827
1431
  return _resilientClient;
828
1432
  }
@@ -1762,16 +2366,18 @@ async function ensureSchema() {
1762
2366
  }
1763
2367
  }
1764
2368
  }
1765
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso;
2369
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso;
1766
2370
  var init_database = __esm({
1767
2371
  "src/lib/database.ts"() {
1768
2372
  "use strict";
1769
2373
  init_db_retry();
1770
2374
  init_employees();
2375
+ init_database_adapter();
1771
2376
  _client = null;
1772
2377
  _resilientClient = null;
1773
2378
  _walCheckpointTimer = null;
1774
2379
  _daemonClient = null;
2380
+ _adapterClient = null;
1775
2381
  initTurso = initDatabase;
1776
2382
  }
1777
2383
  });
@@ -1779,16 +2385,16 @@ var init_database = __esm({
1779
2385
  // src/lib/license.ts
1780
2386
  import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
1781
2387
  import { randomUUID } from "crypto";
1782
- import path6 from "path";
2388
+ import path7 from "path";
1783
2389
  import { jwtVerify, importSPKI } from "jose";
1784
2390
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
1785
2391
  var init_license = __esm({
1786
2392
  "src/lib/license.ts"() {
1787
2393
  "use strict";
1788
2394
  init_config();
1789
- LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
1790
- CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
1791
- DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
2395
+ LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
2396
+ CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
2397
+ DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
1792
2398
  PLAN_LIMITS = {
1793
2399
  free: { devices: 1, employees: 1, memories: 5e3 },
1794
2400
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -1801,7 +2407,7 @@ var init_license = __esm({
1801
2407
 
1802
2408
  // src/lib/plan-limits.ts
1803
2409
  import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
1804
- import path7 from "path";
2410
+ import path8 from "path";
1805
2411
  function getLicenseSync() {
1806
2412
  try {
1807
2413
  if (!existsSync7(CACHE_PATH2)) return freeLicense();
@@ -1873,14 +2479,14 @@ var init_plan_limits = __esm({
1873
2479
  this.name = "PlanLimitError";
1874
2480
  }
1875
2481
  };
1876
- CACHE_PATH2 = path7.join(EXE_AI_DIR, "license-cache.json");
2482
+ CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
1877
2483
  }
1878
2484
  });
1879
2485
 
1880
2486
  // src/lib/notifications.ts
1881
2487
  import crypto from "crypto";
1882
- import path8 from "path";
1883
- import os5 from "os";
2488
+ import path9 from "path";
2489
+ import os6 from "os";
1884
2490
  import {
1885
2491
  readFileSync as readFileSync8,
1886
2492
  readdirSync,
@@ -2044,8 +2650,8 @@ var init_state_bus = __esm({
2044
2650
 
2045
2651
  // src/lib/tasks-crud.ts
2046
2652
  import crypto3 from "crypto";
2047
- import path9 from "path";
2048
- import os6 from "os";
2653
+ import path10 from "path";
2654
+ import os7 from "os";
2049
2655
  import { execSync as execSync4 } from "child_process";
2050
2656
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
2051
2657
  import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
@@ -2223,8 +2829,8 @@ ${laneWarning}` : laneWarning;
2223
2829
  }
2224
2830
  if (input.baseDir) {
2225
2831
  try {
2226
- await mkdir3(path9.join(input.baseDir, "exe", "output"), { recursive: true });
2227
- await mkdir3(path9.join(input.baseDir, "exe", "research"), { recursive: true });
2832
+ await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
2833
+ await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
2228
2834
  await ensureArchitectureDoc(input.baseDir, input.projectName);
2229
2835
  await ensureGitignoreExe(input.baseDir);
2230
2836
  } catch {
@@ -2260,9 +2866,9 @@ ${laneWarning}` : laneWarning;
2260
2866
  });
2261
2867
  if (input.baseDir) {
2262
2868
  try {
2263
- const EXE_OS_DIR = path9.join(os6.homedir(), ".exe-os");
2264
- const mdPath = path9.join(EXE_OS_DIR, taskFile);
2265
- const mdDir = path9.dirname(mdPath);
2869
+ const EXE_OS_DIR = path10.join(os7.homedir(), ".exe-os");
2870
+ const mdPath = path10.join(EXE_OS_DIR, taskFile);
2871
+ const mdDir = path10.dirname(mdPath);
2266
2872
  if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
2267
2873
  const reviewer = input.reviewer ?? input.assignedBy;
2268
2874
  const mdContent = `# ${input.title}
@@ -2563,7 +3169,7 @@ async function deleteTaskCore(taskId, _baseDir) {
2563
3169
  return { taskFile, assignedTo, assignedBy, taskSlug };
2564
3170
  }
2565
3171
  async function ensureArchitectureDoc(baseDir, projectName) {
2566
- const archPath = path9.join(baseDir, "exe", "ARCHITECTURE.md");
3172
+ const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
2567
3173
  try {
2568
3174
  if (existsSync9(archPath)) return;
2569
3175
  const template = [
@@ -2598,7 +3204,7 @@ async function ensureArchitectureDoc(baseDir, projectName) {
2598
3204
  }
2599
3205
  }
2600
3206
  async function ensureGitignoreExe(baseDir) {
2601
- const gitignorePath = path9.join(baseDir, ".gitignore");
3207
+ const gitignorePath = path10.join(baseDir, ".gitignore");
2602
3208
  try {
2603
3209
  if (existsSync9(gitignorePath)) {
2604
3210
  const content = readFileSync9(gitignorePath, "utf-8");
@@ -2632,13 +3238,13 @@ var init_tasks_crud = __esm({
2632
3238
  });
2633
3239
 
2634
3240
  // src/lib/tasks-review.ts
2635
- import path10 from "path";
3241
+ import path11 from "path";
2636
3242
  import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
2637
3243
  async function countPendingReviews(sessionScope) {
2638
3244
  const client = getClient();
2639
3245
  if (sessionScope) {
2640
3246
  const result3 = await client.execute({
2641
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
3247
+ sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND session_scope = ?",
2642
3248
  args: [sessionScope]
2643
3249
  });
2644
3250
  return Number(result3.rows[0]?.cnt) || 0;
@@ -2814,11 +3420,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
2814
3420
  );
2815
3421
  }
2816
3422
  try {
2817
- const cacheDir = path10.join(EXE_AI_DIR, "session-cache");
3423
+ const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
2818
3424
  if (existsSync10(cacheDir)) {
2819
3425
  for (const f of readdirSync2(cacheDir)) {
2820
3426
  if (f.startsWith("review-notified-")) {
2821
- unlinkSync3(path10.join(cacheDir, f));
3427
+ unlinkSync3(path11.join(cacheDir, f));
2822
3428
  }
2823
3429
  }
2824
3430
  }
@@ -2839,7 +3445,7 @@ var init_tasks_review = __esm({
2839
3445
  });
2840
3446
 
2841
3447
  // src/lib/tasks-chain.ts
2842
- import path11 from "path";
3448
+ import path12 from "path";
2843
3449
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
2844
3450
  async function cascadeUnblock(taskId, baseDir, now) {
2845
3451
  const client = getClient();
@@ -2856,7 +3462,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
2856
3462
  });
2857
3463
  for (const ur of unblockedRows.rows) {
2858
3464
  try {
2859
- const ubFile = path11.join(baseDir, String(ur.task_file));
3465
+ const ubFile = path12.join(baseDir, String(ur.task_file));
2860
3466
  let ubContent = await readFile3(ubFile, "utf-8");
2861
3467
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
2862
3468
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -2925,7 +3531,7 @@ var init_tasks_chain = __esm({
2925
3531
 
2926
3532
  // src/lib/project-name.ts
2927
3533
  import { execSync as execSync5 } from "child_process";
2928
- import path12 from "path";
3534
+ import path13 from "path";
2929
3535
  function getProjectName(cwd) {
2930
3536
  const dir = cwd ?? process.cwd();
2931
3537
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -2938,7 +3544,7 @@ function getProjectName(cwd) {
2938
3544
  timeout: 2e3,
2939
3545
  stdio: ["pipe", "pipe", "pipe"]
2940
3546
  }).trim();
2941
- repoRoot = path12.dirname(gitCommonDir);
3547
+ repoRoot = path13.dirname(gitCommonDir);
2942
3548
  } catch {
2943
3549
  repoRoot = execSync5("git rev-parse --show-toplevel", {
2944
3550
  cwd: dir,
@@ -2947,11 +3553,11 @@ function getProjectName(cwd) {
2947
3553
  stdio: ["pipe", "pipe", "pipe"]
2948
3554
  }).trim();
2949
3555
  }
2950
- _cached2 = path12.basename(repoRoot);
3556
+ _cached2 = path13.basename(repoRoot);
2951
3557
  _cachedCwd = dir;
2952
3558
  return _cached2;
2953
3559
  } catch {
2954
- _cached2 = path12.basename(dir);
3560
+ _cached2 = path13.basename(dir);
2955
3561
  _cachedCwd = dir;
2956
3562
  return _cached2;
2957
3563
  }
@@ -3424,7 +4030,7 @@ __export(tasks_exports, {
3424
4030
  updateTaskStatus: () => updateTaskStatus,
3425
4031
  writeCheckpoint: () => writeCheckpoint
3426
4032
  });
3427
- import path13 from "path";
4033
+ import path14 from "path";
3428
4034
  import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync4 } from "fs";
3429
4035
  async function createTask(input) {
3430
4036
  const result2 = await createTaskCore(input);
@@ -3444,8 +4050,8 @@ async function updateTask(input) {
3444
4050
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
3445
4051
  try {
3446
4052
  const agent = String(row.assigned_to);
3447
- const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
3448
- const cachePath = path13.join(cacheDir, `current-task-${agent}.json`);
4053
+ const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
4054
+ const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
3449
4055
  if (input.status === "in_progress") {
3450
4056
  mkdirSync5(cacheDir, { recursive: true });
3451
4057
  writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
@@ -3916,12 +4522,12 @@ __export(tmux_routing_exports, {
3916
4522
  });
3917
4523
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
3918
4524
  import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync11, appendFileSync, readdirSync as readdirSync3 } from "fs";
3919
- import path14 from "path";
3920
- import os7 from "os";
4525
+ import path15 from "path";
4526
+ import os8 from "os";
3921
4527
  import { fileURLToPath } from "url";
3922
4528
  import { unlinkSync as unlinkSync5 } from "fs";
3923
4529
  function spawnLockPath(sessionName) {
3924
- return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4530
+ return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
3925
4531
  }
3926
4532
  function isProcessAlive(pid) {
3927
4533
  try {
@@ -3958,8 +4564,8 @@ function releaseSpawnLock(sessionName) {
3958
4564
  function resolveBehaviorsExporterScript() {
3959
4565
  try {
3960
4566
  const thisFile = fileURLToPath(import.meta.url);
3961
- const scriptPath = path14.join(
3962
- path14.dirname(thisFile),
4567
+ const scriptPath = path15.join(
4568
+ path15.dirname(thisFile),
3963
4569
  "..",
3964
4570
  "bin",
3965
4571
  "exe-export-behaviors.js"
@@ -4034,7 +4640,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4034
4640
  mkdirSync6(SESSION_CACHE, { recursive: true });
4035
4641
  }
4036
4642
  const rootExe = extractRootExe(parentExe) ?? parentExe;
4037
- const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4643
+ const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4038
4644
  writeFileSync7(filePath, JSON.stringify({
4039
4645
  parentExe: rootExe,
4040
4646
  dispatchedBy: dispatchedBy || rootExe,
@@ -4043,7 +4649,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4043
4649
  }
4044
4650
  function getParentExe(sessionKey) {
4045
4651
  try {
4046
- const data = JSON.parse(readFileSync10(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4652
+ const data = JSON.parse(readFileSync10(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4047
4653
  return data.parentExe || null;
4048
4654
  } catch {
4049
4655
  return null;
@@ -4052,7 +4658,7 @@ function getParentExe(sessionKey) {
4052
4658
  function getDispatchedBy(sessionKey) {
4053
4659
  try {
4054
4660
  const data = JSON.parse(readFileSync10(
4055
- path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4661
+ path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4056
4662
  "utf8"
4057
4663
  ));
4058
4664
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -4063,15 +4669,24 @@ function getDispatchedBy(sessionKey) {
4063
4669
  function resolveExeSession() {
4064
4670
  const mySession = getMySession();
4065
4671
  if (!mySession) return null;
4672
+ const fromSessionName = extractRootExe(mySession);
4066
4673
  try {
4067
4674
  const key = getSessionKey();
4068
4675
  const parentExe = getParentExe(key);
4069
4676
  if (parentExe) {
4070
- return extractRootExe(parentExe) ?? parentExe;
4677
+ const fromCache = extractRootExe(parentExe) ?? parentExe;
4678
+ if (fromSessionName && fromCache !== fromSessionName) {
4679
+ process.stderr.write(
4680
+ `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
4681
+ `
4682
+ );
4683
+ return fromSessionName;
4684
+ }
4685
+ return fromCache;
4071
4686
  }
4072
4687
  } catch {
4073
4688
  }
4074
- return extractRootExe(mySession) ?? mySession;
4689
+ return fromSessionName ?? mySession;
4075
4690
  }
4076
4691
  function isEmployeeAlive(sessionName) {
4077
4692
  return getTransport().isAlive(sessionName);
@@ -4229,7 +4844,7 @@ function sendIntercom(targetSession) {
4229
4844
  try {
4230
4845
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
4231
4846
  const agent = baseAgentName(rawAgent);
4232
- const markerPath = path14.join(SESSION_CACHE, `current-task-${agent}.json`);
4847
+ const markerPath = path15.join(SESSION_CACHE, `current-task-${agent}.json`);
4233
4848
  if (existsSync11(markerPath)) {
4234
4849
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
4235
4850
  return "debounced";
@@ -4239,7 +4854,7 @@ function sendIntercom(targetSession) {
4239
4854
  try {
4240
4855
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
4241
4856
  const agent = baseAgentName(rawAgent);
4242
- const taskDir = path14.join(process.cwd(), "exe", agent);
4857
+ const taskDir = path15.join(process.cwd(), "exe", agent);
4243
4858
  if (existsSync11(taskDir)) {
4244
4859
  const files = readdirSync3(taskDir).filter(
4245
4860
  (f) => f.endsWith(".md") && f !== "DONE.txt"
@@ -4373,8 +4988,8 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
4373
4988
  const transport = getTransport();
4374
4989
  const sessionName = employeeSessionName(employeeName2, exeSession2, opts?.instance);
4375
4990
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName2}${opts.instance}` : employeeName2;
4376
- const logDir = path14.join(os7.homedir(), ".exe-os", "session-logs");
4377
- const logFile = path14.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4991
+ const logDir = path15.join(os8.homedir(), ".exe-os", "session-logs");
4992
+ const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4378
4993
  if (!existsSync11(logDir)) {
4379
4994
  mkdirSync6(logDir, { recursive: true });
4380
4995
  }
@@ -4382,14 +4997,14 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
4382
4997
  let cleanupSuffix = "";
4383
4998
  try {
4384
4999
  const thisFile = fileURLToPath(import.meta.url);
4385
- const cleanupScript = path14.join(path14.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5000
+ const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4386
5001
  if (existsSync11(cleanupScript)) {
4387
5002
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName2}" "${exeSession2}"`;
4388
5003
  }
4389
5004
  } catch {
4390
5005
  }
4391
5006
  try {
4392
- const claudeJsonPath = path14.join(os7.homedir(), ".claude.json");
5007
+ const claudeJsonPath = path15.join(os8.homedir(), ".claude.json");
4393
5008
  let claudeJson = {};
4394
5009
  try {
4395
5010
  claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
@@ -4404,10 +5019,10 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
4404
5019
  } catch {
4405
5020
  }
4406
5021
  try {
4407
- const settingsDir = path14.join(os7.homedir(), ".claude", "projects");
5022
+ const settingsDir = path15.join(os8.homedir(), ".claude", "projects");
4408
5023
  const normalizedKey = (opts?.cwd ?? projectDir2).replace(/\//g, "-").replace(/^-/, "");
4409
- const projSettingsDir = path14.join(settingsDir, normalizedKey);
4410
- const settingsPath = path14.join(projSettingsDir, "settings.json");
5024
+ const projSettingsDir = path15.join(settingsDir, normalizedKey);
5025
+ const settingsPath = path15.join(projSettingsDir, "settings.json");
4411
5026
  let settings = {};
4412
5027
  try {
4413
5028
  settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
@@ -4454,8 +5069,8 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
4454
5069
  let behaviorsFlag = "";
4455
5070
  let legacyFallbackWarned = false;
4456
5071
  if (!useExeAgent && !useBinSymlink) {
4457
- const identityPath = path14.join(
4458
- os7.homedir(),
5072
+ const identityPath = path15.join(
5073
+ os8.homedir(),
4459
5074
  ".exe-os",
4460
5075
  "identity",
4461
5076
  `${employeeName2}.md`
@@ -4470,7 +5085,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
4470
5085
  }
4471
5086
  const behaviorsFile = exportBehaviorsSync(
4472
5087
  employeeName2,
4473
- path14.basename(spawnCwd),
5088
+ path15.basename(spawnCwd),
4474
5089
  sessionName
4475
5090
  );
4476
5091
  if (behaviorsFile) {
@@ -4485,9 +5100,9 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
4485
5100
  }
4486
5101
  let sessionContextFlag = "";
4487
5102
  try {
4488
- const ctxDir = path14.join(os7.homedir(), ".exe-os", "session-cache");
5103
+ const ctxDir = path15.join(os8.homedir(), ".exe-os", "session-cache");
4489
5104
  mkdirSync6(ctxDir, { recursive: true });
4490
- const ctxFile = path14.join(ctxDir, `session-context-${sessionName}.md`);
5105
+ const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
4491
5106
  const ctxContent = [
4492
5107
  `## Session Context`,
4493
5108
  `You are running in tmux session: ${sessionName}.`,
@@ -4571,7 +5186,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
4571
5186
  transport.pipeLog(sessionName, logFile);
4572
5187
  try {
4573
5188
  const mySession = getMySession();
4574
- const dispatchInfo = path14.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5189
+ const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4575
5190
  writeFileSync7(dispatchInfo, JSON.stringify({
4576
5191
  dispatchedBy: mySession,
4577
5192
  rootExe: exeSession2,
@@ -4646,15 +5261,15 @@ var init_tmux_routing = __esm({
4646
5261
  init_intercom_queue();
4647
5262
  init_plan_limits();
4648
5263
  init_employees();
4649
- SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
4650
- SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
5264
+ SPAWN_LOCK_DIR = path15.join(os8.homedir(), ".exe-os", "spawn-locks");
5265
+ SESSION_CACHE = path15.join(os8.homedir(), ".exe-os", "session-cache");
4651
5266
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
4652
5267
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
4653
5268
  VERIFY_PANE_LINES = 200;
4654
5269
  INTERCOM_DEBOUNCE_MS = 3e4;
4655
5270
  CODEX_DEBOUNCE_MS = 12e4;
4656
- INTERCOM_LOG2 = path14.join(os7.homedir(), ".exe-os", "intercom.log");
4657
- DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
5271
+ INTERCOM_LOG2 = path15.join(os8.homedir(), ".exe-os", "intercom.log");
5272
+ DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
4658
5273
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
4659
5274
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
4660
5275
  }
@@ -4673,7 +5288,7 @@ __export(shard_manager_exports, {
4673
5288
  listShards: () => listShards,
4674
5289
  shardExists: () => shardExists
4675
5290
  });
4676
- import path16 from "path";
5291
+ import path17 from "path";
4677
5292
  import { existsSync as existsSync13, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
4678
5293
  import { createClient as createClient2 } from "@libsql/client";
4679
5294
  function initShardManager(encryptionKey) {
@@ -4699,7 +5314,7 @@ function getShardClient(projectName) {
4699
5314
  }
4700
5315
  const cached = _shards.get(safeName);
4701
5316
  if (cached) return cached;
4702
- const dbPath = path16.join(SHARDS_DIR, `${safeName}.db`);
5317
+ const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
4703
5318
  const client = createClient2({
4704
5319
  url: `file:${dbPath}`,
4705
5320
  encryptionKey: _encryptionKey
@@ -4709,7 +5324,7 @@ function getShardClient(projectName) {
4709
5324
  }
4710
5325
  function shardExists(projectName) {
4711
5326
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
4712
- return existsSync13(path16.join(SHARDS_DIR, `${safeName}.db`));
5327
+ return existsSync13(path17.join(SHARDS_DIR, `${safeName}.db`));
4713
5328
  }
4714
5329
  function listShards() {
4715
5330
  if (!existsSync13(SHARDS_DIR)) return [];
@@ -4786,7 +5401,23 @@ async function ensureShardSchema(client) {
4786
5401
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
4787
5402
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
4788
5403
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
4789
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
5404
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
5405
+ // Metadata enrichment columns (must match database.ts)
5406
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
5407
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
5408
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
5409
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
5410
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
5411
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
5412
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
5413
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
5414
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
5415
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
5416
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
5417
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
5418
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
5419
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
5420
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
4790
5421
  ]) {
4791
5422
  try {
4792
5423
  await client.execute(col);
@@ -4898,7 +5529,7 @@ var init_shard_manager = __esm({
4898
5529
  "src/lib/shard-manager.ts"() {
4899
5530
  "use strict";
4900
5531
  init_config();
4901
- SHARDS_DIR = path16.join(EXE_AI_DIR, "shards");
5532
+ SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
4902
5533
  _shards = /* @__PURE__ */ new Map();
4903
5534
  _encryptionKey = null;
4904
5535
  _shardingEnabled = false;
@@ -5103,15 +5734,15 @@ init_database();
5103
5734
  // src/lib/keychain.ts
5104
5735
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
5105
5736
  import { existsSync as existsSync12 } from "fs";
5106
- import path15 from "path";
5107
- import os8 from "os";
5737
+ import path16 from "path";
5738
+ import os9 from "os";
5108
5739
  var SERVICE = "exe-mem";
5109
5740
  var ACCOUNT = "master-key";
5110
5741
  function getKeyDir() {
5111
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path15.join(os8.homedir(), ".exe-os");
5742
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os9.homedir(), ".exe-os");
5112
5743
  }
5113
5744
  function getKeyPath() {
5114
- return path15.join(getKeyDir(), "master.key");
5745
+ return path16.join(getKeyDir(), "master.key");
5115
5746
  }
5116
5747
  async function tryKeytar() {
5117
5748
  try {
@@ -5134,7 +5765,7 @@ async function getMasterKey() {
5134
5765
  const keyPath = getKeyPath();
5135
5766
  if (!existsSync12(keyPath)) {
5136
5767
  process.stderr.write(
5137
- `[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5768
+ `[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5138
5769
  `
5139
5770
  );
5140
5771
  return null;