@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
@@ -985,6 +985,7 @@ __export(employees_exports, {
985
985
  getEmployeeByRole: () => getEmployeeByRole,
986
986
  getEmployeeNamesByRole: () => getEmployeeNamesByRole,
987
987
  hasRole: () => hasRole,
988
+ hireEmployee: () => hireEmployee,
988
989
  isCoordinatorName: () => isCoordinatorName,
989
990
  isCoordinatorRole: () => isCoordinatorRole,
990
991
  isMultiInstance: () => isMultiInstance,
@@ -1095,6 +1096,52 @@ function addEmployee(employees, employee) {
1095
1096
  }
1096
1097
  return [...employees, normalized];
1097
1098
  }
1099
+ function appendToCoordinatorTeam(employee) {
1100
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
1101
+ if (!coordinator) return;
1102
+ const idPath = path5.join(IDENTITY_DIR, `${coordinator.name}.md`);
1103
+ if (!existsSync5(idPath)) return;
1104
+ const content = readFileSync5(idPath, "utf-8");
1105
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
1106
+ const teamMatch = content.match(TEAM_SECTION_RE);
1107
+ if (!teamMatch || teamMatch.index === void 0) return;
1108
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
1109
+ const nextHeading = afterTeam.match(/\n## /);
1110
+ const entry = `
1111
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
1112
+ `;
1113
+ let updated;
1114
+ if (nextHeading && nextHeading.index !== void 0) {
1115
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
1116
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
1117
+ } else {
1118
+ updated = content.trimEnd() + "\n" + entry;
1119
+ }
1120
+ writeFileSync4(idPath, updated, "utf-8");
1121
+ }
1122
+ function capitalize(s) {
1123
+ return s.charAt(0).toUpperCase() + s.slice(1);
1124
+ }
1125
+ async function hireEmployee(employee) {
1126
+ const employees = await loadEmployees();
1127
+ const updated = addEmployee(employees, employee);
1128
+ await saveEmployees(updated);
1129
+ try {
1130
+ appendToCoordinatorTeam(employee);
1131
+ } catch {
1132
+ }
1133
+ try {
1134
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
1135
+ const config = loadAgentConfig2();
1136
+ const name = employee.name.toLowerCase();
1137
+ if (!config[name] && config["default"]) {
1138
+ config[name] = { ...config["default"] };
1139
+ saveAgentConfig2(config);
1140
+ }
1141
+ } catch {
1142
+ }
1143
+ return updated;
1144
+ }
1098
1145
  async function normalizeRosterCase(rosterPath) {
1099
1146
  const employees = await loadEmployees(rosterPath);
1100
1147
  let changed = false;
@@ -1165,7 +1212,7 @@ function registerBinSymlinks(name) {
1165
1212
  }
1166
1213
  return { created, skipped, errors };
1167
1214
  }
1168
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
1215
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
1169
1216
  var init_employees = __esm({
1170
1217
  "src/lib/employees.ts"() {
1171
1218
  "use strict";
@@ -1174,16 +1221,602 @@ var init_employees = __esm({
1174
1221
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
1175
1222
  COORDINATOR_ROLE = "COO";
1176
1223
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
1224
+ IDENTITY_DIR = path5.join(EXE_AI_DIR, "identity");
1225
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
1226
+ }
1227
+ });
1228
+
1229
+ // src/lib/database-adapter.ts
1230
+ import os5 from "os";
1231
+ import path6 from "path";
1232
+ import { createRequire } from "module";
1233
+ import { pathToFileURL } from "url";
1234
+ function quotedIdentifier(identifier) {
1235
+ return `"${identifier.replace(/"/g, '""')}"`;
1236
+ }
1237
+ function unqualifiedTableName(name) {
1238
+ const raw = name.trim().replace(/^"|"$/g, "");
1239
+ const parts = raw.split(".");
1240
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
1241
+ }
1242
+ function stripTrailingSemicolon(sql) {
1243
+ return sql.trim().replace(/;+\s*$/u, "");
1244
+ }
1245
+ function appendClause(sql, clause) {
1246
+ const trimmed = stripTrailingSemicolon(sql);
1247
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
1248
+ if (!returningMatch) {
1249
+ return `${trimmed}${clause}`;
1250
+ }
1251
+ const idx = returningMatch.index;
1252
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
1253
+ }
1254
+ function normalizeStatement(stmt) {
1255
+ if (typeof stmt === "string") {
1256
+ return { kind: "positional", sql: stmt, args: [] };
1257
+ }
1258
+ const sql = stmt.sql;
1259
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
1260
+ return { kind: "positional", sql, args: stmt.args ?? [] };
1261
+ }
1262
+ return { kind: "named", sql, args: stmt.args };
1263
+ }
1264
+ function rewriteBooleanLiterals(sql) {
1265
+ let out = sql;
1266
+ for (const column of BOOLEAN_COLUMN_NAMES) {
1267
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
1268
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
1269
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
1270
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
1271
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
1272
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
1273
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
1274
+ }
1275
+ return out;
1276
+ }
1277
+ function rewriteInsertOrIgnore(sql) {
1278
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
1279
+ return sql;
1280
+ }
1281
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
1282
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
1283
+ }
1284
+ function rewriteInsertOrReplace(sql) {
1285
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
1286
+ if (!match) {
1287
+ return sql;
1288
+ }
1289
+ const rawTable = match[1];
1290
+ const rawColumns = match[2];
1291
+ const remainder = match[3];
1292
+ const tableName = unqualifiedTableName(rawTable);
1293
+ const conflictKeys = UPSERT_KEYS[tableName];
1294
+ if (!conflictKeys?.length) {
1295
+ return sql;
1296
+ }
1297
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
1298
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
1299
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
1300
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
1301
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
1302
+ }
1303
+ function rewriteSql(sql) {
1304
+ let out = sql;
1305
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
1306
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
1307
+ out = rewriteBooleanLiterals(out);
1308
+ out = rewriteInsertOrReplace(out);
1309
+ out = rewriteInsertOrIgnore(out);
1310
+ return stripTrailingSemicolon(out);
1311
+ }
1312
+ function toBoolean(value) {
1313
+ if (value === null || value === void 0) return value;
1314
+ if (typeof value === "boolean") return value;
1315
+ if (typeof value === "number") return value !== 0;
1316
+ if (typeof value === "bigint") return value !== 0n;
1317
+ if (typeof value === "string") {
1318
+ const normalized = value.trim().toLowerCase();
1319
+ if (normalized === "0" || normalized === "false") return false;
1320
+ if (normalized === "1" || normalized === "true") return true;
1321
+ }
1322
+ return Boolean(value);
1323
+ }
1324
+ function countQuestionMarks(sql, end) {
1325
+ let count = 0;
1326
+ let inSingle = false;
1327
+ let inDouble = false;
1328
+ let inLineComment = false;
1329
+ let inBlockComment = false;
1330
+ for (let i = 0; i < end; i++) {
1331
+ const ch = sql[i];
1332
+ const next = sql[i + 1];
1333
+ if (inLineComment) {
1334
+ if (ch === "\n") inLineComment = false;
1335
+ continue;
1336
+ }
1337
+ if (inBlockComment) {
1338
+ if (ch === "*" && next === "/") {
1339
+ inBlockComment = false;
1340
+ i += 1;
1341
+ }
1342
+ continue;
1343
+ }
1344
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1345
+ inLineComment = true;
1346
+ i += 1;
1347
+ continue;
1348
+ }
1349
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1350
+ inBlockComment = true;
1351
+ i += 1;
1352
+ continue;
1353
+ }
1354
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1355
+ inSingle = !inSingle;
1356
+ continue;
1357
+ }
1358
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1359
+ inDouble = !inDouble;
1360
+ continue;
1361
+ }
1362
+ if (!inSingle && !inDouble && ch === "?") {
1363
+ count += 1;
1364
+ }
1365
+ }
1366
+ return count;
1367
+ }
1368
+ function findBooleanPlaceholderIndexes(sql) {
1369
+ const indexes = /* @__PURE__ */ new Set();
1370
+ for (const column of BOOLEAN_COLUMN_NAMES) {
1371
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
1372
+ for (const match of sql.matchAll(pattern)) {
1373
+ const matchText = match[0];
1374
+ const qIndex = match.index + matchText.lastIndexOf("?");
1375
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
1376
+ }
1377
+ }
1378
+ return indexes;
1379
+ }
1380
+ function coerceInsertBooleanArgs(sql, args) {
1381
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
1382
+ if (!match) return;
1383
+ const rawTable = match[1];
1384
+ const rawColumns = match[2];
1385
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
1386
+ if (!boolColumns?.size) return;
1387
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
1388
+ for (const [index, column] of columns.entries()) {
1389
+ if (boolColumns.has(column) && index < args.length) {
1390
+ args[index] = toBoolean(args[index]);
1391
+ }
1392
+ }
1393
+ }
1394
+ function coerceUpdateBooleanArgs(sql, args) {
1395
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
1396
+ if (!match) return;
1397
+ const rawTable = match[1];
1398
+ const setClause = match[2];
1399
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
1400
+ if (!boolColumns?.size) return;
1401
+ const assignments = setClause.split(",");
1402
+ let placeholderIndex = 0;
1403
+ for (const assignment of assignments) {
1404
+ if (!assignment.includes("?")) continue;
1405
+ placeholderIndex += 1;
1406
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
1407
+ if (colMatch && boolColumns.has(colMatch[1])) {
1408
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
1409
+ }
1410
+ }
1411
+ }
1412
+ function coerceBooleanArgs(sql, args) {
1413
+ const nextArgs = [...args];
1414
+ coerceInsertBooleanArgs(sql, nextArgs);
1415
+ coerceUpdateBooleanArgs(sql, nextArgs);
1416
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
1417
+ for (const index of placeholderIndexes) {
1418
+ if (index > 0 && index <= nextArgs.length) {
1419
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
1420
+ }
1421
+ }
1422
+ return nextArgs;
1423
+ }
1424
+ function convertQuestionMarksToDollarParams(sql) {
1425
+ let out = "";
1426
+ let placeholder = 0;
1427
+ let inSingle = false;
1428
+ let inDouble = false;
1429
+ let inLineComment = false;
1430
+ let inBlockComment = false;
1431
+ for (let i = 0; i < sql.length; i++) {
1432
+ const ch = sql[i];
1433
+ const next = sql[i + 1];
1434
+ if (inLineComment) {
1435
+ out += ch;
1436
+ if (ch === "\n") inLineComment = false;
1437
+ continue;
1438
+ }
1439
+ if (inBlockComment) {
1440
+ out += ch;
1441
+ if (ch === "*" && next === "/") {
1442
+ out += next;
1443
+ inBlockComment = false;
1444
+ i += 1;
1445
+ }
1446
+ continue;
1447
+ }
1448
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1449
+ out += ch + next;
1450
+ inLineComment = true;
1451
+ i += 1;
1452
+ continue;
1453
+ }
1454
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1455
+ out += ch + next;
1456
+ inBlockComment = true;
1457
+ i += 1;
1458
+ continue;
1459
+ }
1460
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1461
+ inSingle = !inSingle;
1462
+ out += ch;
1463
+ continue;
1464
+ }
1465
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1466
+ inDouble = !inDouble;
1467
+ out += ch;
1468
+ continue;
1469
+ }
1470
+ if (!inSingle && !inDouble && ch === "?") {
1471
+ placeholder += 1;
1472
+ out += `$${placeholder}`;
1473
+ continue;
1474
+ }
1475
+ out += ch;
1476
+ }
1477
+ return out;
1478
+ }
1479
+ function translateStatementForPostgres(stmt) {
1480
+ const normalized = normalizeStatement(stmt);
1481
+ if (normalized.kind === "named") {
1482
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
1483
+ }
1484
+ const rewrittenSql = rewriteSql(normalized.sql);
1485
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
1486
+ return {
1487
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
1488
+ args: coercedArgs
1489
+ };
1490
+ }
1491
+ function shouldBypassPostgres(stmt) {
1492
+ const normalized = normalizeStatement(stmt);
1493
+ if (normalized.kind === "named") {
1494
+ return true;
1495
+ }
1496
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
1497
+ }
1498
+ function shouldFallbackOnError(error) {
1499
+ const message = error instanceof Error ? error.message : String(error);
1500
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
1501
+ }
1502
+ function isReadQuery(sql) {
1503
+ const trimmed = sql.trimStart();
1504
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
1505
+ }
1506
+ function buildRow(row, columns) {
1507
+ const values = columns.map((column) => row[column]);
1508
+ return Object.assign(values, row);
1509
+ }
1510
+ function buildResultSet(rows, rowsAffected = 0) {
1511
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
1512
+ const resultRows = rows.map((row) => buildRow(row, columns));
1513
+ return {
1514
+ columns,
1515
+ columnTypes: columns.map(() => ""),
1516
+ rows: resultRows,
1517
+ rowsAffected,
1518
+ lastInsertRowid: void 0,
1519
+ toJSON() {
1520
+ return {
1521
+ columns,
1522
+ columnTypes: columns.map(() => ""),
1523
+ rows,
1524
+ rowsAffected,
1525
+ lastInsertRowid: void 0
1526
+ };
1527
+ }
1528
+ };
1529
+ }
1530
+ async function loadPrismaClient() {
1531
+ if (!prismaClientPromise) {
1532
+ prismaClientPromise = (async () => {
1533
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
1534
+ if (explicitPath) {
1535
+ const module2 = await import(pathToFileURL(explicitPath).href);
1536
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
1537
+ if (!PrismaClient2) {
1538
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
1539
+ }
1540
+ return new PrismaClient2();
1541
+ }
1542
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path6.join(os5.homedir(), "exe-db");
1543
+ const requireFromExeDb = createRequire(path6.join(exeDbRoot, "package.json"));
1544
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1545
+ const module = await import(pathToFileURL(prismaEntry).href);
1546
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
1547
+ if (!PrismaClient) {
1548
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
1549
+ }
1550
+ return new PrismaClient();
1551
+ })();
1552
+ }
1553
+ return prismaClientPromise;
1554
+ }
1555
+ async function ensureCompatibilityViews(prisma) {
1556
+ if (!compatibilityBootstrapPromise) {
1557
+ compatibilityBootstrapPromise = (async () => {
1558
+ for (const mapping of VIEW_MAPPINGS) {
1559
+ const relation = mapping.source.replace(/"/g, "");
1560
+ const rows = await prisma.$queryRawUnsafe(
1561
+ "SELECT to_regclass($1) AS regclass",
1562
+ relation
1563
+ );
1564
+ if (!rows[0]?.regclass) {
1565
+ continue;
1566
+ }
1567
+ await prisma.$executeRawUnsafe(
1568
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
1569
+ );
1570
+ }
1571
+ })();
1572
+ }
1573
+ return compatibilityBootstrapPromise;
1574
+ }
1575
+ async function executeOnPrisma(executor, stmt) {
1576
+ const translated = translateStatementForPostgres(stmt);
1577
+ if (isReadQuery(translated.sql)) {
1578
+ const rows = await executor.$queryRawUnsafe(
1579
+ translated.sql,
1580
+ ...translated.args
1581
+ );
1582
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
1583
+ }
1584
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
1585
+ return buildResultSet([], rowsAffected);
1586
+ }
1587
+ function splitSqlStatements(sql) {
1588
+ const parts = [];
1589
+ let current = "";
1590
+ let inSingle = false;
1591
+ let inDouble = false;
1592
+ let inLineComment = false;
1593
+ let inBlockComment = false;
1594
+ for (let i = 0; i < sql.length; i++) {
1595
+ const ch = sql[i];
1596
+ const next = sql[i + 1];
1597
+ if (inLineComment) {
1598
+ current += ch;
1599
+ if (ch === "\n") inLineComment = false;
1600
+ continue;
1601
+ }
1602
+ if (inBlockComment) {
1603
+ current += ch;
1604
+ if (ch === "*" && next === "/") {
1605
+ current += next;
1606
+ inBlockComment = false;
1607
+ i += 1;
1608
+ }
1609
+ continue;
1610
+ }
1611
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1612
+ current += ch + next;
1613
+ inLineComment = true;
1614
+ i += 1;
1615
+ continue;
1616
+ }
1617
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1618
+ current += ch + next;
1619
+ inBlockComment = true;
1620
+ i += 1;
1621
+ continue;
1622
+ }
1623
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1624
+ inSingle = !inSingle;
1625
+ current += ch;
1626
+ continue;
1627
+ }
1628
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1629
+ inDouble = !inDouble;
1630
+ current += ch;
1631
+ continue;
1632
+ }
1633
+ if (!inSingle && !inDouble && ch === ";") {
1634
+ if (current.trim()) {
1635
+ parts.push(current.trim());
1636
+ }
1637
+ current = "";
1638
+ continue;
1639
+ }
1640
+ current += ch;
1641
+ }
1642
+ if (current.trim()) {
1643
+ parts.push(current.trim());
1644
+ }
1645
+ return parts;
1646
+ }
1647
+ async function createPrismaDbAdapter(fallbackClient) {
1648
+ const prisma = await loadPrismaClient();
1649
+ await ensureCompatibilityViews(prisma);
1650
+ let closed = false;
1651
+ let adapter;
1652
+ const fallbackExecute = async (stmt, error) => {
1653
+ if (!fallbackClient) {
1654
+ if (error) throw error;
1655
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
1656
+ }
1657
+ if (error) {
1658
+ process.stderr.write(
1659
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
1660
+ `
1661
+ );
1662
+ }
1663
+ return fallbackClient.execute(stmt);
1664
+ };
1665
+ adapter = {
1666
+ async execute(stmt) {
1667
+ if (shouldBypassPostgres(stmt)) {
1668
+ return fallbackExecute(stmt);
1669
+ }
1670
+ try {
1671
+ return await executeOnPrisma(prisma, stmt);
1672
+ } catch (error) {
1673
+ if (shouldFallbackOnError(error)) {
1674
+ return fallbackExecute(stmt, error);
1675
+ }
1676
+ throw error;
1677
+ }
1678
+ },
1679
+ async batch(stmts, mode) {
1680
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
1681
+ if (!fallbackClient) {
1682
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
1683
+ }
1684
+ return fallbackClient.batch(stmts, mode);
1685
+ }
1686
+ try {
1687
+ if (prisma.$transaction) {
1688
+ return await prisma.$transaction(async (tx) => {
1689
+ const results2 = [];
1690
+ for (const stmt of stmts) {
1691
+ results2.push(await executeOnPrisma(tx, stmt));
1692
+ }
1693
+ return results2;
1694
+ });
1695
+ }
1696
+ const results = [];
1697
+ for (const stmt of stmts) {
1698
+ results.push(await executeOnPrisma(prisma, stmt));
1699
+ }
1700
+ return results;
1701
+ } catch (error) {
1702
+ if (fallbackClient && shouldFallbackOnError(error)) {
1703
+ process.stderr.write(
1704
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
1705
+ `
1706
+ );
1707
+ return fallbackClient.batch(stmts, mode);
1708
+ }
1709
+ throw error;
1710
+ }
1711
+ },
1712
+ async migrate(stmts) {
1713
+ if (fallbackClient) {
1714
+ return fallbackClient.migrate(stmts);
1715
+ }
1716
+ return adapter.batch(stmts, "deferred");
1717
+ },
1718
+ async transaction(mode) {
1719
+ if (!fallbackClient) {
1720
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
1721
+ }
1722
+ return fallbackClient.transaction(mode);
1723
+ },
1724
+ async executeMultiple(sql) {
1725
+ if (fallbackClient && shouldBypassPostgres(sql)) {
1726
+ return fallbackClient.executeMultiple(sql);
1727
+ }
1728
+ for (const statement of splitSqlStatements(sql)) {
1729
+ await adapter.execute(statement);
1730
+ }
1731
+ },
1732
+ async sync() {
1733
+ if (fallbackClient) {
1734
+ return fallbackClient.sync();
1735
+ }
1736
+ return { frame_no: 0, frames_synced: 0 };
1737
+ },
1738
+ close() {
1739
+ closed = true;
1740
+ prismaClientPromise = null;
1741
+ compatibilityBootstrapPromise = null;
1742
+ void prisma.$disconnect?.();
1743
+ },
1744
+ get closed() {
1745
+ return closed;
1746
+ },
1747
+ get protocol() {
1748
+ return "prisma-postgres";
1749
+ }
1750
+ };
1751
+ return adapter;
1752
+ }
1753
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
1754
+ var init_database_adapter = __esm({
1755
+ "src/lib/database-adapter.ts"() {
1756
+ "use strict";
1757
+ VIEW_MAPPINGS = [
1758
+ { view: "memories", source: "memory.memory_records" },
1759
+ { view: "tasks", source: "memory.tasks" },
1760
+ { view: "behaviors", source: "memory.behaviors" },
1761
+ { view: "entities", source: "memory.entities" },
1762
+ { view: "relationships", source: "memory.relationships" },
1763
+ { view: "entity_memories", source: "memory.entity_memories" },
1764
+ { view: "entity_aliases", source: "memory.entity_aliases" },
1765
+ { view: "notifications", source: "memory.notifications" },
1766
+ { view: "messages", source: "memory.messages" },
1767
+ { view: "users", source: "wiki.users" },
1768
+ { view: "workspaces", source: "wiki.workspaces" },
1769
+ { view: "workspace_users", source: "wiki.workspace_users" },
1770
+ { view: "documents", source: "wiki.workspace_documents" },
1771
+ { view: "chats", source: "wiki.workspace_chats" }
1772
+ ];
1773
+ UPSERT_KEYS = {
1774
+ memories: ["id"],
1775
+ tasks: ["id"],
1776
+ behaviors: ["id"],
1777
+ entities: ["id"],
1778
+ relationships: ["id"],
1779
+ entity_aliases: ["alias"],
1780
+ notifications: ["id"],
1781
+ messages: ["id"],
1782
+ users: ["id"],
1783
+ workspaces: ["id"],
1784
+ workspace_users: ["id"],
1785
+ documents: ["id"],
1786
+ chats: ["id"]
1787
+ };
1788
+ BOOLEAN_COLUMNS_BY_TABLE = {
1789
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
1790
+ behaviors: /* @__PURE__ */ new Set(["active"]),
1791
+ notifications: /* @__PURE__ */ new Set(["read"]),
1792
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
1793
+ };
1794
+ BOOLEAN_COLUMN_NAMES = new Set(
1795
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
1796
+ );
1797
+ IMMEDIATE_FALLBACK_PATTERNS = [
1798
+ /\bPRAGMA\b/i,
1799
+ /\bsqlite_master\b/i,
1800
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
1801
+ /\bMATCH\b/i,
1802
+ /\bvector_distance_cos\s*\(/i,
1803
+ /\bjson_extract\s*\(/i,
1804
+ /\bjulianday\s*\(/i,
1805
+ /\bstrftime\s*\(/i,
1806
+ /\blast_insert_rowid\s*\(/i
1807
+ ];
1808
+ prismaClientPromise = null;
1809
+ compatibilityBootstrapPromise = null;
1177
1810
  }
1178
1811
  });
1179
1812
 
1180
1813
  // src/lib/exe-daemon-client.ts
1181
1814
  import net from "net";
1182
- import os5 from "os";
1815
+ import os6 from "os";
1183
1816
  import { spawn } from "child_process";
1184
1817
  import { randomUUID } from "crypto";
1185
1818
  import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
1186
- import path6 from "path";
1819
+ import path7 from "path";
1187
1820
  import { fileURLToPath } from "url";
1188
1821
  function handleData(chunk) {
1189
1822
  _buffer += chunk.toString();
@@ -1234,17 +1867,17 @@ function cleanupStaleFiles() {
1234
1867
  }
1235
1868
  }
1236
1869
  function findPackageRoot() {
1237
- let dir = path6.dirname(fileURLToPath(import.meta.url));
1238
- const { root } = path6.parse(dir);
1870
+ let dir = path7.dirname(fileURLToPath(import.meta.url));
1871
+ const { root } = path7.parse(dir);
1239
1872
  while (dir !== root) {
1240
- if (existsSync6(path6.join(dir, "package.json"))) return dir;
1241
- dir = path6.dirname(dir);
1873
+ if (existsSync6(path7.join(dir, "package.json"))) return dir;
1874
+ dir = path7.dirname(dir);
1242
1875
  }
1243
1876
  return null;
1244
1877
  }
1245
1878
  function spawnDaemon() {
1246
- const freeGB = os5.freemem() / (1024 * 1024 * 1024);
1247
- const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
1879
+ const freeGB = os6.freemem() / (1024 * 1024 * 1024);
1880
+ const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
1248
1881
  if (totalGB <= 8) {
1249
1882
  process.stderr.write(
1250
1883
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -1264,7 +1897,7 @@ function spawnDaemon() {
1264
1897
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1265
1898
  return;
1266
1899
  }
1267
- const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1900
+ const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1268
1901
  if (!existsSync6(daemonPath)) {
1269
1902
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1270
1903
  `);
@@ -1273,7 +1906,7 @@ function spawnDaemon() {
1273
1906
  const resolvedPath = daemonPath;
1274
1907
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1275
1908
  `);
1276
- const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
1909
+ const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
1277
1910
  let stderrFd = "ignore";
1278
1911
  try {
1279
1912
  stderrFd = openSync(logPath, "a");
@@ -1424,74 +2057,123 @@ async function pingDaemon() {
1424
2057
  return null;
1425
2058
  }
1426
2059
  function killAndRespawnDaemon() {
1427
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
1428
- if (existsSync6(PID_PATH)) {
1429
- try {
1430
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
1431
- if (pid > 0) {
1432
- try {
1433
- process.kill(pid, "SIGKILL");
1434
- } catch {
2060
+ if (!acquireSpawnLock()) {
2061
+ process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
2062
+ if (_socket) {
2063
+ _socket.destroy();
2064
+ _socket = null;
2065
+ }
2066
+ _connected = false;
2067
+ _buffer = "";
2068
+ return;
2069
+ }
2070
+ try {
2071
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
2072
+ if (existsSync6(PID_PATH)) {
2073
+ try {
2074
+ const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
2075
+ if (pid > 0) {
2076
+ try {
2077
+ process.kill(pid, "SIGKILL");
2078
+ } catch {
2079
+ }
1435
2080
  }
2081
+ } catch {
1436
2082
  }
2083
+ }
2084
+ if (_socket) {
2085
+ _socket.destroy();
2086
+ _socket = null;
2087
+ }
2088
+ _connected = false;
2089
+ _buffer = "";
2090
+ try {
2091
+ unlinkSync2(PID_PATH);
1437
2092
  } catch {
1438
2093
  }
2094
+ try {
2095
+ unlinkSync2(SOCKET_PATH);
2096
+ } catch {
2097
+ }
2098
+ spawnDaemon();
2099
+ } finally {
2100
+ releaseSpawnLock();
1439
2101
  }
1440
- if (_socket) {
1441
- _socket.destroy();
1442
- _socket = null;
1443
- }
1444
- _connected = false;
1445
- _buffer = "";
2102
+ }
2103
+ function isDaemonTooYoung() {
1446
2104
  try {
1447
- unlinkSync2(PID_PATH);
2105
+ const stat = statSync(PID_PATH);
2106
+ return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
1448
2107
  } catch {
2108
+ return false;
1449
2109
  }
1450
- try {
1451
- unlinkSync2(SOCKET_PATH);
1452
- } catch {
2110
+ }
2111
+ async function retryThenRestart(doRequest, label) {
2112
+ const result = await doRequest();
2113
+ if (!result.error) {
2114
+ _consecutiveFailures = 0;
2115
+ return result;
2116
+ }
2117
+ _consecutiveFailures++;
2118
+ for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
2119
+ const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
2120
+ process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
2121
+ `);
2122
+ await new Promise((r) => setTimeout(r, delayMs));
2123
+ if (!_connected) {
2124
+ if (!await connectToSocket()) continue;
2125
+ }
2126
+ const retry = await doRequest();
2127
+ if (!retry.error) {
2128
+ _consecutiveFailures = 0;
2129
+ return retry;
2130
+ }
2131
+ _consecutiveFailures++;
2132
+ }
2133
+ if (isDaemonTooYoung()) {
2134
+ process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
2135
+ `);
2136
+ return { error: result.error };
2137
+ }
2138
+ process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
2139
+ `);
2140
+ killAndRespawnDaemon();
2141
+ const start = Date.now();
2142
+ let delay2 = 200;
2143
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2144
+ await new Promise((r) => setTimeout(r, delay2));
2145
+ if (await connectToSocket()) break;
2146
+ delay2 = Math.min(delay2 * 2, 3e3);
1453
2147
  }
1454
- spawnDaemon();
2148
+ if (!_connected) return { error: "Daemon restart failed" };
2149
+ const final = await doRequest();
2150
+ if (!final.error) _consecutiveFailures = 0;
2151
+ return final;
1455
2152
  }
1456
2153
  async function embedViaClient(text, priority = "high") {
1457
2154
  if (!_connected && !await connectEmbedDaemon()) return null;
1458
2155
  _requestCount++;
1459
2156
  if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
1460
2157
  const health = await pingDaemon();
1461
- if (!health) {
2158
+ if (!health && !isDaemonTooYoung()) {
1462
2159
  process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
1463
2160
  `);
1464
2161
  killAndRespawnDaemon();
1465
2162
  const start = Date.now();
1466
- let delay2 = 200;
2163
+ let d = 200;
1467
2164
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1468
- await new Promise((r) => setTimeout(r, delay2));
2165
+ await new Promise((r) => setTimeout(r, d));
1469
2166
  if (await connectToSocket()) break;
1470
- delay2 = Math.min(delay2 * 2, 3e3);
2167
+ d = Math.min(d * 2, 3e3);
1471
2168
  }
1472
2169
  if (!_connected) return null;
1473
2170
  }
1474
2171
  }
1475
- const result = await sendRequest([text], priority);
1476
- if (!result.error && result.vectors?.[0]) return result.vectors[0];
1477
- if (result.error) {
1478
- process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
1479
- `);
1480
- killAndRespawnDaemon();
1481
- const start = Date.now();
1482
- let delay2 = 200;
1483
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1484
- await new Promise((r) => setTimeout(r, delay2));
1485
- if (await connectToSocket()) break;
1486
- delay2 = Math.min(delay2 * 2, 3e3);
1487
- }
1488
- if (!_connected) return null;
1489
- const retry = await sendRequest([text], priority);
1490
- if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
1491
- process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
1492
- `);
1493
- }
1494
- return null;
2172
+ const result = await retryThenRestart(
2173
+ () => sendRequest([text], priority),
2174
+ "Embed"
2175
+ );
2176
+ return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
1495
2177
  }
1496
2178
  function disconnectClient() {
1497
2179
  if (_socket) {
@@ -1509,14 +2191,14 @@ function disconnectClient() {
1509
2191
  function isClientConnected() {
1510
2192
  return _connected;
1511
2193
  }
1512
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
2194
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
1513
2195
  var init_exe_daemon_client = __esm({
1514
2196
  "src/lib/exe-daemon-client.ts"() {
1515
2197
  "use strict";
1516
2198
  init_config();
1517
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
1518
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
1519
- SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
2199
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
2200
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
2201
+ SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
1520
2202
  SPAWN_LOCK_STALE_MS = 3e4;
1521
2203
  CONNECT_TIMEOUT_MS = 15e3;
1522
2204
  REQUEST_TIMEOUT_MS = 3e4;
@@ -1524,7 +2206,11 @@ var init_exe_daemon_client = __esm({
1524
2206
  _connected = false;
1525
2207
  _buffer = "";
1526
2208
  _requestCount = 0;
2209
+ _consecutiveFailures = 0;
1527
2210
  HEALTH_CHECK_INTERVAL = 100;
2211
+ MAX_RETRIES_BEFORE_RESTART = 3;
2212
+ RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
2213
+ MIN_DAEMON_AGE_MS = 3e4;
1528
2214
  _pending = /* @__PURE__ */ new Map();
1529
2215
  MAX_BUFFER = 1e7;
1530
2216
  }
@@ -1536,7 +2222,7 @@ __export(db_daemon_client_exports, {
1536
2222
  createDaemonDbClient: () => createDaemonDbClient,
1537
2223
  initDaemonDbClient: () => initDaemonDbClient
1538
2224
  });
1539
- function normalizeStatement(stmt) {
2225
+ function normalizeStatement2(stmt) {
1540
2226
  if (typeof stmt === "string") {
1541
2227
  return { sql: stmt, args: [] };
1542
2228
  }
@@ -1560,7 +2246,7 @@ function createDaemonDbClient(fallbackClient) {
1560
2246
  if (!_useDaemon || !isClientConnected()) {
1561
2247
  return fallbackClient.execute(stmt);
1562
2248
  }
1563
- const { sql, args } = normalizeStatement(stmt);
2249
+ const { sql, args } = normalizeStatement2(stmt);
1564
2250
  const response = await sendDaemonRequest({
1565
2251
  type: "db-execute",
1566
2252
  sql,
@@ -1585,7 +2271,7 @@ function createDaemonDbClient(fallbackClient) {
1585
2271
  if (!_useDaemon || !isClientConnected()) {
1586
2272
  return fallbackClient.batch(stmts, mode);
1587
2273
  }
1588
- const statements = stmts.map(normalizeStatement);
2274
+ const statements = stmts.map(normalizeStatement2);
1589
2275
  const response = await sendDaemonRequest({
1590
2276
  type: "db-batch",
1591
2277
  statements,
@@ -1680,6 +2366,18 @@ __export(database_exports, {
1680
2366
  });
1681
2367
  import { createClient } from "@libsql/client";
1682
2368
  async function initDatabase(config) {
2369
+ if (_walCheckpointTimer) {
2370
+ clearInterval(_walCheckpointTimer);
2371
+ _walCheckpointTimer = null;
2372
+ }
2373
+ if (_daemonClient) {
2374
+ _daemonClient.close();
2375
+ _daemonClient = null;
2376
+ }
2377
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2378
+ _adapterClient.close();
2379
+ }
2380
+ _adapterClient = null;
1683
2381
  if (_client) {
1684
2382
  _client.close();
1685
2383
  _client = null;
@@ -1693,6 +2391,7 @@ async function initDatabase(config) {
1693
2391
  }
1694
2392
  _client = createClient(opts);
1695
2393
  _resilientClient = wrapWithRetry(_client);
2394
+ _adapterClient = _resilientClient;
1696
2395
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
1697
2396
  });
1698
2397
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -1703,14 +2402,20 @@ async function initDatabase(config) {
1703
2402
  });
1704
2403
  }, 3e4);
1705
2404
  _walCheckpointTimer.unref();
2405
+ if (process.env.DATABASE_URL) {
2406
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
2407
+ }
1706
2408
  }
1707
2409
  function isInitialized() {
1708
- return _client !== null;
2410
+ return _adapterClient !== null || _client !== null;
1709
2411
  }
1710
2412
  function getClient() {
1711
- if (!_resilientClient) {
2413
+ if (!_adapterClient) {
1712
2414
  throw new Error("Database client not initialized. Call initDatabase() first.");
1713
2415
  }
2416
+ if (process.env.DATABASE_URL) {
2417
+ return _adapterClient;
2418
+ }
1714
2419
  if (process.env.EXE_IS_DAEMON === "1") {
1715
2420
  return _resilientClient;
1716
2421
  }
@@ -1720,6 +2425,7 @@ function getClient() {
1720
2425
  return _resilientClient;
1721
2426
  }
1722
2427
  async function initDaemonClient() {
2428
+ if (process.env.DATABASE_URL) return;
1723
2429
  if (process.env.EXE_IS_DAEMON === "1") return;
1724
2430
  if (!_resilientClient) return;
1725
2431
  try {
@@ -2664,26 +3370,36 @@ async function ensureSchema() {
2664
3370
  }
2665
3371
  }
2666
3372
  async function disposeDatabase() {
3373
+ if (_walCheckpointTimer) {
3374
+ clearInterval(_walCheckpointTimer);
3375
+ _walCheckpointTimer = null;
3376
+ }
2667
3377
  if (_daemonClient) {
2668
3378
  _daemonClient.close();
2669
3379
  _daemonClient = null;
2670
3380
  }
3381
+ if (_adapterClient && _adapterClient !== _resilientClient) {
3382
+ _adapterClient.close();
3383
+ }
3384
+ _adapterClient = null;
2671
3385
  if (_client) {
2672
3386
  _client.close();
2673
3387
  _client = null;
2674
3388
  _resilientClient = null;
2675
3389
  }
2676
3390
  }
2677
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
3391
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
2678
3392
  var init_database = __esm({
2679
3393
  "src/lib/database.ts"() {
2680
3394
  "use strict";
2681
3395
  init_db_retry();
2682
3396
  init_employees();
3397
+ init_database_adapter();
2683
3398
  _client = null;
2684
3399
  _resilientClient = null;
2685
3400
  _walCheckpointTimer = null;
2686
3401
  _daemonClient = null;
3402
+ _adapterClient = null;
2687
3403
  initTurso = initDatabase;
2688
3404
  disposeTurso = disposeDatabase;
2689
3405
  }
@@ -2692,16 +3408,16 @@ var init_database = __esm({
2692
3408
  // src/lib/license.ts
2693
3409
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2694
3410
  import { randomUUID as randomUUID2 } from "crypto";
2695
- import path7 from "path";
3411
+ import path8 from "path";
2696
3412
  import { jwtVerify, importSPKI } from "jose";
2697
3413
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
2698
3414
  var init_license = __esm({
2699
3415
  "src/lib/license.ts"() {
2700
3416
  "use strict";
2701
3417
  init_config();
2702
- LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
2703
- CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
2704
- DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
3418
+ LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
3419
+ CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
3420
+ DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
2705
3421
  PLAN_LIMITS = {
2706
3422
  free: { devices: 1, employees: 1, memories: 5e3 },
2707
3423
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -2714,7 +3430,7 @@ var init_license = __esm({
2714
3430
 
2715
3431
  // src/lib/plan-limits.ts
2716
3432
  import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
2717
- import path8 from "path";
3433
+ import path9 from "path";
2718
3434
  function getLicenseSync() {
2719
3435
  try {
2720
3436
  if (!existsSync8(CACHE_PATH2)) return freeLicense();
@@ -2786,14 +3502,14 @@ var init_plan_limits = __esm({
2786
3502
  this.name = "PlanLimitError";
2787
3503
  }
2788
3504
  };
2789
- CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
3505
+ CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
2790
3506
  }
2791
3507
  });
2792
3508
 
2793
3509
  // src/lib/notifications.ts
2794
3510
  import crypto from "crypto";
2795
- import path9 from "path";
2796
- import os6 from "os";
3511
+ import path10 from "path";
3512
+ import os7 from "os";
2797
3513
  import {
2798
3514
  readFileSync as readFileSync9,
2799
3515
  readdirSync,
@@ -3027,8 +3743,8 @@ var init_state_bus = __esm({
3027
3743
 
3028
3744
  // src/lib/tasks-crud.ts
3029
3745
  import crypto3 from "crypto";
3030
- import path10 from "path";
3031
- import os7 from "os";
3746
+ import path11 from "path";
3747
+ import os8 from "os";
3032
3748
  import { execSync as execSync5 } from "child_process";
3033
3749
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
3034
3750
  import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
@@ -3206,8 +3922,8 @@ ${laneWarning}` : laneWarning;
3206
3922
  }
3207
3923
  if (input.baseDir) {
3208
3924
  try {
3209
- await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
3210
- await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
3925
+ await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
3926
+ await mkdir3(path11.join(input.baseDir, "exe", "research"), { recursive: true });
3211
3927
  await ensureArchitectureDoc(input.baseDir, input.projectName);
3212
3928
  await ensureGitignoreExe(input.baseDir);
3213
3929
  } catch {
@@ -3243,9 +3959,9 @@ ${laneWarning}` : laneWarning;
3243
3959
  });
3244
3960
  if (input.baseDir) {
3245
3961
  try {
3246
- const EXE_OS_DIR = path10.join(os7.homedir(), ".exe-os");
3247
- const mdPath = path10.join(EXE_OS_DIR, taskFile);
3248
- const mdDir = path10.dirname(mdPath);
3962
+ const EXE_OS_DIR = path11.join(os8.homedir(), ".exe-os");
3963
+ const mdPath = path11.join(EXE_OS_DIR, taskFile);
3964
+ const mdDir = path11.dirname(mdPath);
3249
3965
  if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
3250
3966
  const reviewer = input.reviewer ?? input.assignedBy;
3251
3967
  const mdContent = `# ${input.title}
@@ -3546,7 +4262,7 @@ async function deleteTaskCore(taskId, _baseDir) {
3546
4262
  return { taskFile, assignedTo, assignedBy, taskSlug };
3547
4263
  }
3548
4264
  async function ensureArchitectureDoc(baseDir, projectName) {
3549
- const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
4265
+ const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
3550
4266
  try {
3551
4267
  if (existsSync10(archPath)) return;
3552
4268
  const template = [
@@ -3581,7 +4297,7 @@ async function ensureArchitectureDoc(baseDir, projectName) {
3581
4297
  }
3582
4298
  }
3583
4299
  async function ensureGitignoreExe(baseDir) {
3584
- const gitignorePath = path10.join(baseDir, ".gitignore");
4300
+ const gitignorePath = path11.join(baseDir, ".gitignore");
3585
4301
  try {
3586
4302
  if (existsSync10(gitignorePath)) {
3587
4303
  const content = readFileSync10(gitignorePath, "utf-8");
@@ -3627,7 +4343,7 @@ __export(tasks_review_exports, {
3627
4343
  isStale: () => isStale,
3628
4344
  listPendingReviews: () => listPendingReviews
3629
4345
  });
3630
- import path11 from "path";
4346
+ import path12 from "path";
3631
4347
  import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
3632
4348
  function formatAge(isoTimestamp) {
3633
4349
  if (!isoTimestamp) return "";
@@ -3648,7 +4364,7 @@ async function countPendingReviews(sessionScope) {
3648
4364
  const client = getClient();
3649
4365
  if (sessionScope) {
3650
4366
  const result2 = await client.execute({
3651
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
4367
+ sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND session_scope = ?",
3652
4368
  args: [sessionScope]
3653
4369
  });
3654
4370
  return Number(result2.rows[0]?.cnt) || 0;
@@ -3913,11 +4629,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3913
4629
  );
3914
4630
  }
3915
4631
  try {
3916
- const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
4632
+ const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
3917
4633
  if (existsSync11(cacheDir)) {
3918
4634
  for (const f of readdirSync2(cacheDir)) {
3919
4635
  if (f.startsWith("review-notified-")) {
3920
- unlinkSync4(path11.join(cacheDir, f));
4636
+ unlinkSync4(path12.join(cacheDir, f));
3921
4637
  }
3922
4638
  }
3923
4639
  }
@@ -3938,7 +4654,7 @@ var init_tasks_review = __esm({
3938
4654
  });
3939
4655
 
3940
4656
  // src/lib/tasks-chain.ts
3941
- import path12 from "path";
4657
+ import path13 from "path";
3942
4658
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3943
4659
  async function cascadeUnblock(taskId, baseDir, now) {
3944
4660
  const client = getClient();
@@ -3955,7 +4671,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3955
4671
  });
3956
4672
  for (const ur of unblockedRows.rows) {
3957
4673
  try {
3958
- const ubFile = path12.join(baseDir, String(ur.task_file));
4674
+ const ubFile = path13.join(baseDir, String(ur.task_file));
3959
4675
  let ubContent = await readFile3(ubFile, "utf-8");
3960
4676
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3961
4677
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -4024,7 +4740,7 @@ var init_tasks_chain = __esm({
4024
4740
 
4025
4741
  // src/lib/project-name.ts
4026
4742
  import { execSync as execSync6 } from "child_process";
4027
- import path13 from "path";
4743
+ import path14 from "path";
4028
4744
  function getProjectName(cwd) {
4029
4745
  const dir = cwd ?? process.cwd();
4030
4746
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -4037,7 +4753,7 @@ function getProjectName(cwd) {
4037
4753
  timeout: 2e3,
4038
4754
  stdio: ["pipe", "pipe", "pipe"]
4039
4755
  }).trim();
4040
- repoRoot = path13.dirname(gitCommonDir);
4756
+ repoRoot = path14.dirname(gitCommonDir);
4041
4757
  } catch {
4042
4758
  repoRoot = execSync6("git rev-parse --show-toplevel", {
4043
4759
  cwd: dir,
@@ -4046,11 +4762,11 @@ function getProjectName(cwd) {
4046
4762
  stdio: ["pipe", "pipe", "pipe"]
4047
4763
  }).trim();
4048
4764
  }
4049
- _cached2 = path13.basename(repoRoot);
4765
+ _cached2 = path14.basename(repoRoot);
4050
4766
  _cachedCwd = dir;
4051
4767
  return _cached2;
4052
4768
  } catch {
4053
- _cached2 = path13.basename(dir);
4769
+ _cached2 = path14.basename(dir);
4054
4770
  _cachedCwd = dir;
4055
4771
  return _cached2;
4056
4772
  }
@@ -4523,7 +5239,7 @@ __export(tasks_exports, {
4523
5239
  updateTaskStatus: () => updateTaskStatus,
4524
5240
  writeCheckpoint: () => writeCheckpoint
4525
5241
  });
4526
- import path14 from "path";
5242
+ import path15 from "path";
4527
5243
  import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
4528
5244
  async function createTask(input) {
4529
5245
  const result = await createTaskCore(input);
@@ -4543,8 +5259,8 @@ async function updateTask(input) {
4543
5259
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
4544
5260
  try {
4545
5261
  const agent = String(row.assigned_to);
4546
- const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
4547
- const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
5262
+ const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
5263
+ const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
4548
5264
  if (input.status === "in_progress") {
4549
5265
  mkdirSync5(cacheDir, { recursive: true });
4550
5266
  writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
@@ -5015,12 +5731,12 @@ __export(tmux_routing_exports, {
5015
5731
  });
5016
5732
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
5017
5733
  import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync3 } from "fs";
5018
- import path15 from "path";
5019
- import os8 from "os";
5734
+ import path16 from "path";
5735
+ import os9 from "os";
5020
5736
  import { fileURLToPath as fileURLToPath2 } from "url";
5021
5737
  import { unlinkSync as unlinkSync6 } from "fs";
5022
5738
  function spawnLockPath(sessionName) {
5023
- return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5739
+ return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5024
5740
  }
5025
5741
  function isProcessAlive(pid) {
5026
5742
  try {
@@ -5057,8 +5773,8 @@ function releaseSpawnLock2(sessionName) {
5057
5773
  function resolveBehaviorsExporterScript() {
5058
5774
  try {
5059
5775
  const thisFile = fileURLToPath2(import.meta.url);
5060
- const scriptPath = path15.join(
5061
- path15.dirname(thisFile),
5776
+ const scriptPath = path16.join(
5777
+ path16.dirname(thisFile),
5062
5778
  "..",
5063
5779
  "bin",
5064
5780
  "exe-export-behaviors.js"
@@ -5133,7 +5849,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5133
5849
  mkdirSync6(SESSION_CACHE, { recursive: true });
5134
5850
  }
5135
5851
  const rootExe = extractRootExe(parentExe) ?? parentExe;
5136
- const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5852
+ const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5137
5853
  writeFileSync7(filePath, JSON.stringify({
5138
5854
  parentExe: rootExe,
5139
5855
  dispatchedBy: dispatchedBy || rootExe,
@@ -5142,7 +5858,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5142
5858
  }
5143
5859
  function getParentExe(sessionKey) {
5144
5860
  try {
5145
- const data = JSON.parse(readFileSync11(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5861
+ const data = JSON.parse(readFileSync11(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5146
5862
  return data.parentExe || null;
5147
5863
  } catch {
5148
5864
  return null;
@@ -5151,7 +5867,7 @@ function getParentExe(sessionKey) {
5151
5867
  function getDispatchedBy(sessionKey) {
5152
5868
  try {
5153
5869
  const data = JSON.parse(readFileSync11(
5154
- path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5870
+ path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5155
5871
  "utf8"
5156
5872
  ));
5157
5873
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -5337,7 +6053,7 @@ function sendIntercom(targetSession) {
5337
6053
  try {
5338
6054
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5339
6055
  const agent = baseAgentName(rawAgent);
5340
- const markerPath = path15.join(SESSION_CACHE, `current-task-${agent}.json`);
6056
+ const markerPath = path16.join(SESSION_CACHE, `current-task-${agent}.json`);
5341
6057
  if (existsSync12(markerPath)) {
5342
6058
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
5343
6059
  return "debounced";
@@ -5347,7 +6063,7 @@ function sendIntercom(targetSession) {
5347
6063
  try {
5348
6064
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5349
6065
  const agent = baseAgentName(rawAgent);
5350
- const taskDir = path15.join(process.cwd(), "exe", agent);
6066
+ const taskDir = path16.join(process.cwd(), "exe", agent);
5351
6067
  if (existsSync12(taskDir)) {
5352
6068
  const files = readdirSync3(taskDir).filter(
5353
6069
  (f) => f.endsWith(".md") && f !== "DONE.txt"
@@ -5481,8 +6197,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5481
6197
  const transport = getTransport();
5482
6198
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
5483
6199
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
5484
- const logDir = path15.join(os8.homedir(), ".exe-os", "session-logs");
5485
- const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
6200
+ const logDir = path16.join(os9.homedir(), ".exe-os", "session-logs");
6201
+ const logFile = path16.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5486
6202
  if (!existsSync12(logDir)) {
5487
6203
  mkdirSync6(logDir, { recursive: true });
5488
6204
  }
@@ -5490,14 +6206,14 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5490
6206
  let cleanupSuffix = "";
5491
6207
  try {
5492
6208
  const thisFile = fileURLToPath2(import.meta.url);
5493
- const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
6209
+ const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5494
6210
  if (existsSync12(cleanupScript)) {
5495
6211
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
5496
6212
  }
5497
6213
  } catch {
5498
6214
  }
5499
6215
  try {
5500
- const claudeJsonPath = path15.join(os8.homedir(), ".claude.json");
6216
+ const claudeJsonPath = path16.join(os9.homedir(), ".claude.json");
5501
6217
  let claudeJson = {};
5502
6218
  try {
5503
6219
  claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
@@ -5512,10 +6228,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5512
6228
  } catch {
5513
6229
  }
5514
6230
  try {
5515
- const settingsDir = path15.join(os8.homedir(), ".claude", "projects");
6231
+ const settingsDir = path16.join(os9.homedir(), ".claude", "projects");
5516
6232
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
5517
- const projSettingsDir = path15.join(settingsDir, normalizedKey);
5518
- const settingsPath = path15.join(projSettingsDir, "settings.json");
6233
+ const projSettingsDir = path16.join(settingsDir, normalizedKey);
6234
+ const settingsPath = path16.join(projSettingsDir, "settings.json");
5519
6235
  let settings = {};
5520
6236
  try {
5521
6237
  settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
@@ -5562,8 +6278,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5562
6278
  let behaviorsFlag = "";
5563
6279
  let legacyFallbackWarned = false;
5564
6280
  if (!useExeAgent && !useBinSymlink) {
5565
- const identityPath = path15.join(
5566
- os8.homedir(),
6281
+ const identityPath = path16.join(
6282
+ os9.homedir(),
5567
6283
  ".exe-os",
5568
6284
  "identity",
5569
6285
  `${employeeName}.md`
@@ -5578,7 +6294,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5578
6294
  }
5579
6295
  const behaviorsFile = exportBehaviorsSync(
5580
6296
  employeeName,
5581
- path15.basename(spawnCwd),
6297
+ path16.basename(spawnCwd),
5582
6298
  sessionName
5583
6299
  );
5584
6300
  if (behaviorsFile) {
@@ -5593,9 +6309,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5593
6309
  }
5594
6310
  let sessionContextFlag = "";
5595
6311
  try {
5596
- const ctxDir = path15.join(os8.homedir(), ".exe-os", "session-cache");
6312
+ const ctxDir = path16.join(os9.homedir(), ".exe-os", "session-cache");
5597
6313
  mkdirSync6(ctxDir, { recursive: true });
5598
- const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
6314
+ const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
5599
6315
  const ctxContent = [
5600
6316
  `## Session Context`,
5601
6317
  `You are running in tmux session: ${sessionName}.`,
@@ -5679,7 +6395,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5679
6395
  transport.pipeLog(sessionName, logFile);
5680
6396
  try {
5681
6397
  const mySession = getMySession();
5682
- const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6398
+ const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5683
6399
  writeFileSync7(dispatchInfo, JSON.stringify({
5684
6400
  dispatchedBy: mySession,
5685
6401
  rootExe: exeSession,
@@ -5754,15 +6470,15 @@ var init_tmux_routing = __esm({
5754
6470
  init_intercom_queue();
5755
6471
  init_plan_limits();
5756
6472
  init_employees();
5757
- SPAWN_LOCK_DIR = path15.join(os8.homedir(), ".exe-os", "spawn-locks");
5758
- SESSION_CACHE = path15.join(os8.homedir(), ".exe-os", "session-cache");
6473
+ SPAWN_LOCK_DIR = path16.join(os9.homedir(), ".exe-os", "spawn-locks");
6474
+ SESSION_CACHE = path16.join(os9.homedir(), ".exe-os", "session-cache");
5759
6475
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5760
6476
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
5761
6477
  VERIFY_PANE_LINES = 200;
5762
6478
  INTERCOM_DEBOUNCE_MS = 3e4;
5763
6479
  CODEX_DEBOUNCE_MS = 12e4;
5764
- INTERCOM_LOG2 = path15.join(os8.homedir(), ".exe-os", "intercom.log");
5765
- DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
6480
+ INTERCOM_LOG2 = path16.join(os9.homedir(), ".exe-os", "intercom.log");
6481
+ DEBOUNCE_FILE = path16.join(SESSION_CACHE, "intercom-debounce.json");
5766
6482
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
5767
6483
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
5768
6484
  }
@@ -6047,8 +6763,8 @@ __export(agent_signals_exports, {
6047
6763
  hasUnreadInbox: () => hasUnreadInbox
6048
6764
  });
6049
6765
  import { readFileSync as readFileSync12, existsSync as existsSync13 } from "fs";
6050
- import os9 from "os";
6051
- import path16 from "path";
6766
+ import os10 from "os";
6767
+ import path17 from "path";
6052
6768
  async function hasOpenTasks(client, agentId) {
6053
6769
  try {
6054
6770
  const scope = sessionScopeFilter(null);
@@ -6090,7 +6806,7 @@ async function hasUnreadInbox(client, agentId) {
6090
6806
  return CONSERVATIVE_ON_ERROR;
6091
6807
  }
6092
6808
  }
6093
- function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog = path16.join(os9.homedir(), ".exe-os", "intercom.log")) {
6809
+ function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog = path17.join(os10.homedir(), ".exe-os", "intercom.log")) {
6094
6810
  if (!existsSync13(intercomLog)) return false;
6095
6811
  try {
6096
6812
  const raw = readFileSync12(intercomLog, "utf8");
@@ -6420,7 +7136,7 @@ function createIdleNudgeRealDeps(getClient2) {
6420
7136
  const doScope = sessionScopeFilter(null);
6421
7137
  const result = await client.execute({
6422
7138
  sql: `SELECT id, title, priority FROM tasks
6423
- WHERE assigned_to = ? AND status IN ('open', 'in_progress', 'needs_review')${doScope.sql}
7139
+ WHERE assigned_to = ? AND status IN ('open', 'in_progress')${doScope.sql}
6424
7140
  ORDER BY CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 ELSE 2 END
6425
7141
  LIMIT 1`,
6426
7142
  args: [agentId, ...doScope.args]
@@ -6721,13 +7437,13 @@ __export(keychain_exports, {
6721
7437
  });
6722
7438
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
6723
7439
  import { existsSync as existsSync15 } from "fs";
6724
- import path17 from "path";
6725
- import os10 from "os";
7440
+ import path18 from "path";
7441
+ import os11 from "os";
6726
7442
  function getKeyDir() {
6727
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path17.join(os10.homedir(), ".exe-os");
7443
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path18.join(os11.homedir(), ".exe-os");
6728
7444
  }
6729
7445
  function getKeyPath() {
6730
- return path17.join(getKeyDir(), "master.key");
7446
+ return path18.join(getKeyDir(), "master.key");
6731
7447
  }
6732
7448
  async function tryKeytar() {
6733
7449
  try {
@@ -6750,7 +7466,7 @@ async function getMasterKey() {
6750
7466
  const keyPath = getKeyPath();
6751
7467
  if (!existsSync15(keyPath)) {
6752
7468
  process.stderr.write(
6753
- `[keychain] Key not found at ${keyPath} (HOME=${os10.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
7469
+ `[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
6754
7470
  `
6755
7471
  );
6756
7472
  return null;
@@ -6846,7 +7562,7 @@ __export(shard_manager_exports, {
6846
7562
  listShards: () => listShards,
6847
7563
  shardExists: () => shardExists
6848
7564
  });
6849
- import path18 from "path";
7565
+ import path19 from "path";
6850
7566
  import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
6851
7567
  import { createClient as createClient2 } from "@libsql/client";
6852
7568
  function initShardManager(encryptionKey) {
@@ -6872,7 +7588,7 @@ function getShardClient(projectName) {
6872
7588
  }
6873
7589
  const cached = _shards.get(safeName);
6874
7590
  if (cached) return cached;
6875
- const dbPath = path18.join(SHARDS_DIR, `${safeName}.db`);
7591
+ const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
6876
7592
  const client = createClient2({
6877
7593
  url: `file:${dbPath}`,
6878
7594
  encryptionKey: _encryptionKey
@@ -6882,7 +7598,7 @@ function getShardClient(projectName) {
6882
7598
  }
6883
7599
  function shardExists(projectName) {
6884
7600
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
6885
- return existsSync16(path18.join(SHARDS_DIR, `${safeName}.db`));
7601
+ return existsSync16(path19.join(SHARDS_DIR, `${safeName}.db`));
6886
7602
  }
6887
7603
  function listShards() {
6888
7604
  if (!existsSync16(SHARDS_DIR)) return [];
@@ -6959,7 +7675,23 @@ async function ensureShardSchema(client) {
6959
7675
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
6960
7676
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
6961
7677
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
6962
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
7678
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
7679
+ // Metadata enrichment columns (must match database.ts)
7680
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
7681
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
7682
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
7683
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
7684
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
7685
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
7686
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
7687
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
7688
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
7689
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
7690
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
7691
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
7692
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
7693
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
7694
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
6963
7695
  ]) {
6964
7696
  try {
6965
7697
  await client.execute(col);
@@ -7071,7 +7803,7 @@ var init_shard_manager = __esm({
7071
7803
  "src/lib/shard-manager.ts"() {
7072
7804
  "use strict";
7073
7805
  init_config();
7074
- SHARDS_DIR = path18.join(EXE_AI_DIR, "shards");
7806
+ SHARDS_DIR = path19.join(EXE_AI_DIR, "shards");
7075
7807
  _shards = /* @__PURE__ */ new Map();
7076
7808
  _encryptionKey = null;
7077
7809
  _shardingEnabled = false;
@@ -8445,8 +9177,8 @@ async function embedDirect(text) {
8445
9177
  const llamaCpp = await import("node-llama-cpp");
8446
9178
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
8447
9179
  const { existsSync: existsSync19 } = await import("fs");
8448
- const path23 = await import("path");
8449
- const modelPath = path23.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
9180
+ const path24 = await import("path");
9181
+ const modelPath = path24.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
8450
9182
  if (!existsSync19(modelPath)) {
8451
9183
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
8452
9184
  }
@@ -9169,8 +9901,8 @@ __export(wiki_sync_exports, {
9169
9901
  listWorkspaces: () => listWorkspaces,
9170
9902
  syncMemories: () => syncMemories
9171
9903
  });
9172
- async function wikiRequest(config, path23, method = "GET", body) {
9173
- const url = `${config.wikiUrl}/api/v1${path23}`;
9904
+ async function wikiRequest(config, path24, method = "GET", body) {
9905
+ const url = `${config.wikiUrl}/api/v1${path24}`;
9174
9906
  const headers = {
9175
9907
  "Authorization": `Bearer ${config.wikiApiKey}`,
9176
9908
  "Content-Type": "application/json"
@@ -9182,7 +9914,7 @@ async function wikiRequest(config, path23, method = "GET", body) {
9182
9914
  signal: AbortSignal.timeout(3e4)
9183
9915
  });
9184
9916
  if (!response.ok) {
9185
- throw new Error(`Wiki API ${method} ${path23}: ${response.status} ${response.statusText}`);
9917
+ throw new Error(`Wiki API ${method} ${path24}: ${response.status} ${response.statusText}`);
9186
9918
  }
9187
9919
  return response.json();
9188
9920
  }
@@ -9294,8 +10026,8 @@ __export(token_spend_exports, {
9294
10026
  import { readdir } from "fs/promises";
9295
10027
  import { createReadStream } from "fs";
9296
10028
  import { createInterface } from "readline";
9297
- import path19 from "path";
9298
- import os11 from "os";
10029
+ import path20 from "path";
10030
+ import os12 from "os";
9299
10031
  function getPricing(model) {
9300
10032
  if (MODEL_PRICING[model]) return MODEL_PRICING[model];
9301
10033
  const stripped = model.replace(/-\d{8}$/, "");
@@ -9318,18 +10050,18 @@ async function getAgentSpend(period = "7d") {
9318
10050
  for (const row of result.rows) {
9319
10051
  sessionAgent.set(row.session_uuid, row.agent_id);
9320
10052
  }
9321
- const claudeDir = path19.join(os11.homedir(), ".claude", "projects");
10053
+ const claudeDir = path20.join(os12.homedir(), ".claude", "projects");
9322
10054
  let projectDirs = [];
9323
10055
  try {
9324
10056
  const entries = await readdir(claudeDir);
9325
- projectDirs = entries.map((e) => path19.join(claudeDir, e));
10057
+ projectDirs = entries.map((e) => path20.join(claudeDir, e));
9326
10058
  } catch {
9327
10059
  return [];
9328
10060
  }
9329
10061
  const agentTotals = /* @__PURE__ */ new Map();
9330
10062
  for (const [sessionUuid, agentId] of sessionAgent) {
9331
10063
  for (const dir of projectDirs) {
9332
- const jsonlPath = path19.join(dir, `${sessionUuid}.jsonl`);
10064
+ const jsonlPath = path20.join(dir, `${sessionUuid}.jsonl`);
9333
10065
  try {
9334
10066
  const usage = await extractSessionUsage(jsonlPath);
9335
10067
  if (usage.input === 0 && usage.output === 0) continue;
@@ -9451,9 +10183,9 @@ __export(update_check_exports, {
9451
10183
  });
9452
10184
  import { execSync as execSync11 } from "child_process";
9453
10185
  import { readFileSync as readFileSync14 } from "fs";
9454
- import path20 from "path";
10186
+ import path21 from "path";
9455
10187
  function getLocalVersion(packageRoot) {
9456
- const pkgPath = path20.join(packageRoot, "package.json");
10188
+ const pkgPath = path21.join(packageRoot, "package.json");
9457
10189
  const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
9458
10190
  return pkg.version;
9459
10191
  }
@@ -9525,9 +10257,9 @@ __export(device_registry_exports, {
9525
10257
  setFriendlyName: () => setFriendlyName
9526
10258
  });
9527
10259
  import crypto8 from "crypto";
9528
- import os12 from "os";
10260
+ import os13 from "os";
9529
10261
  import { readFileSync as readFileSync15, writeFileSync as writeFileSync9, mkdirSync as mkdirSync8, existsSync as existsSync17 } from "fs";
9530
- import path21 from "path";
10262
+ import path22 from "path";
9531
10263
  function getDeviceInfo() {
9532
10264
  if (existsSync17(DEVICE_JSON_PATH)) {
9533
10265
  try {
@@ -9539,13 +10271,13 @@ function getDeviceInfo() {
9539
10271
  } catch {
9540
10272
  }
9541
10273
  }
9542
- const hostname = os12.hostname();
10274
+ const hostname = os13.hostname();
9543
10275
  const info = {
9544
10276
  deviceId: crypto8.randomUUID(),
9545
10277
  friendlyName: hostname.replace(/\./g, "-").toLowerCase(),
9546
10278
  hostname
9547
10279
  };
9548
- mkdirSync8(path21.dirname(DEVICE_JSON_PATH), { recursive: true });
10280
+ mkdirSync8(path22.dirname(DEVICE_JSON_PATH), { recursive: true });
9549
10281
  writeFileSync9(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
9550
10282
  return info;
9551
10283
  }
@@ -9586,7 +10318,7 @@ var init_device_registry = __esm({
9586
10318
  "src/lib/device-registry.ts"() {
9587
10319
  "use strict";
9588
10320
  init_config();
9589
- DEVICE_JSON_PATH = path21.join(EXE_AI_DIR, "device.json");
10321
+ DEVICE_JSON_PATH = path22.join(EXE_AI_DIR, "device.json");
9590
10322
  }
9591
10323
  });
9592
10324
 
@@ -10051,10 +10783,10 @@ init_daemon_protocol();
10051
10783
  init_daemon_orchestration();
10052
10784
  import net2 from "net";
10053
10785
  import { writeFileSync as writeFileSync10, unlinkSync as unlinkSync7, mkdirSync as mkdirSync9, existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
10054
- import path22 from "path";
10786
+ import path23 from "path";
10055
10787
  import { getLlama } from "node-llama-cpp";
10056
- var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path22.join(EXE_AI_DIR, "exed.sock");
10057
- var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path22.join(EXE_AI_DIR, "exed.pid");
10788
+ var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path23.join(EXE_AI_DIR, "exed.sock");
10789
+ var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path23.join(EXE_AI_DIR, "exed.pid");
10058
10790
  var MODEL_FILE = "jina-embeddings-v5-small-q4_k_m.gguf";
10059
10791
  var IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
10060
10792
  var REVIEW_POLL_INTERVAL_MS = 60 * 1e3;
@@ -10080,7 +10812,7 @@ function enqueue(queue, entry) {
10080
10812
  queue.push(entry);
10081
10813
  }
10082
10814
  async function loadModel() {
10083
- const modelPath = path22.join(MODELS_DIR, MODEL_FILE);
10815
+ const modelPath = path23.join(MODELS_DIR, MODEL_FILE);
10084
10816
  if (!existsSync18(modelPath)) {
10085
10817
  process.stderr.write(`[exed] FATAL: model not found at ${modelPath}
10086
10818
  `);
@@ -10250,9 +10982,9 @@ async function handleDbBatch(socket, requestId, statements, mode) {
10250
10982
  }
10251
10983
  }
10252
10984
  function startServer() {
10253
- mkdirSync9(path22.dirname(SOCKET_PATH2), { recursive: true });
10985
+ mkdirSync9(path23.dirname(SOCKET_PATH2), { recursive: true });
10254
10986
  for (const oldFile of ["embed.sock", "embed.pid"]) {
10255
- const oldPath = path22.join(path22.dirname(SOCKET_PATH2), oldFile);
10987
+ const oldPath = path23.join(path23.dirname(SOCKET_PATH2), oldFile);
10256
10988
  try {
10257
10989
  if (oldFile.endsWith(".pid")) {
10258
10990
  const pid = parseInt(readFileSync16(oldPath, "utf8").trim(), 10);
@@ -10637,7 +11369,7 @@ function startWikiSync() {
10637
11369
  });
10638
11370
  }
10639
11371
  var AGENT_STATS_INTERVAL_MS = 60 * 1e3;
10640
- var AGENT_STATS_PATH = path22.join(EXE_AI_DIR, "agent-stats.json");
11372
+ var AGENT_STATS_PATH = path23.join(EXE_AI_DIR, "agent-stats.json");
10641
11373
  async function writeAgentStats() {
10642
11374
  if (!await ensureStoreForPolling()) return;
10643
11375
  try {
@@ -10767,11 +11499,11 @@ function startIntercomQueueDrain() {
10767
11499
  const hasInProgressTask = (session) => {
10768
11500
  try {
10769
11501
  const { baseAgentName: ban } = (init_employees(), __toCommonJS(employees_exports));
10770
- const path23 = __require("path");
11502
+ const path24 = __require("path");
10771
11503
  const { existsSync: existsSync19 } = __require("fs");
10772
- const os13 = __require("os");
11504
+ const os14 = __require("os");
10773
11505
  const agent = ban(session.split("-")[0] ?? session);
10774
- const markerPath = path23.join(os13.homedir(), ".exe-os", "session-cache", `current-task-${agent}.json`);
11506
+ const markerPath = path24.join(os14.homedir(), ".exe-os", "session-cache", `current-task-${agent}.json`);
10775
11507
  return existsSync19(markerPath);
10776
11508
  } catch {
10777
11509
  return false;