@askexenow/exe-os 0.8.33 → 0.8.37

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 (89) hide show
  1. package/dist/bin/backfill-conversations.js +341 -349
  2. package/dist/bin/backfill-responses.js +81 -13
  3. package/dist/bin/backfill-vectors.js +72 -12
  4. package/dist/bin/cleanup-stale-review-tasks.js +63 -3
  5. package/dist/bin/cli.js +1737 -1117
  6. package/dist/bin/exe-assign.js +89 -19
  7. package/dist/bin/exe-boot.js +951 -101
  8. package/dist/bin/exe-call.js +61 -2
  9. package/dist/bin/exe-dispatch.js +61 -13
  10. package/dist/bin/exe-doctor.js +63 -3
  11. package/dist/bin/exe-export-behaviors.js +71 -3
  12. package/dist/bin/exe-forget.js +69 -4
  13. package/dist/bin/exe-gateway.js +178 -45
  14. package/dist/bin/exe-heartbeat.js +79 -14
  15. package/dist/bin/exe-kill.js +71 -3
  16. package/dist/bin/exe-launch-agent.js +148 -14
  17. package/dist/bin/exe-link.js +1437 -0
  18. package/dist/bin/exe-new-employee.js +98 -13
  19. package/dist/bin/exe-pending-messages.js +74 -8
  20. package/dist/bin/exe-pending-notifications.js +63 -3
  21. package/dist/bin/exe-pending-reviews.js +77 -11
  22. package/dist/bin/exe-rename.js +1287 -0
  23. package/dist/bin/exe-review.js +73 -5
  24. package/dist/bin/exe-search.js +88 -14
  25. package/dist/bin/exe-session-cleanup.js +102 -28
  26. package/dist/bin/exe-status.js +64 -4
  27. package/dist/bin/exe-team.js +64 -4
  28. package/dist/bin/git-sweep.js +80 -5
  29. package/dist/bin/graph-backfill.js +71 -3
  30. package/dist/bin/graph-export.js +71 -3
  31. package/dist/bin/install.js +38 -8
  32. package/dist/bin/scan-tasks.js +80 -5
  33. package/dist/bin/setup.js +128 -10
  34. package/dist/bin/shard-migrate.js +71 -3
  35. package/dist/bin/wiki-sync.js +71 -3
  36. package/dist/gateway/index.js +179 -46
  37. package/dist/hooks/bug-report-worker.js +254 -28
  38. package/dist/hooks/commit-complete.js +80 -5
  39. package/dist/hooks/error-recall.js +89 -15
  40. package/dist/hooks/exe-heartbeat-hook.js +1 -1
  41. package/dist/hooks/ingest-worker.js +185 -51
  42. package/dist/hooks/ingest.js +1 -1
  43. package/dist/hooks/instructions-loaded.js +81 -6
  44. package/dist/hooks/notification.js +81 -6
  45. package/dist/hooks/post-compact.js +81 -6
  46. package/dist/hooks/pre-compact.js +81 -6
  47. package/dist/hooks/pre-tool-use.js +423 -196
  48. package/dist/hooks/prompt-ingest-worker.js +91 -23
  49. package/dist/hooks/prompt-submit.js +159 -45
  50. package/dist/hooks/response-ingest-worker.js +96 -23
  51. package/dist/hooks/session-end.js +81 -6
  52. package/dist/hooks/session-start.js +89 -15
  53. package/dist/hooks/stop.js +81 -6
  54. package/dist/hooks/subagent-stop.js +81 -6
  55. package/dist/hooks/summary-worker.js +807 -55
  56. package/dist/index.js +198 -60
  57. package/dist/lib/cloud-sync.js +703 -18
  58. package/dist/lib/consolidation.js +4 -4
  59. package/dist/lib/database.js +64 -2
  60. package/dist/lib/device-registry.js +70 -3
  61. package/dist/lib/employee-templates.js +26 -0
  62. package/dist/lib/employees.js +34 -1
  63. package/dist/lib/exe-daemon.js +207 -74
  64. package/dist/lib/hybrid-search.js +88 -14
  65. package/dist/lib/identity-templates.js +51 -0
  66. package/dist/lib/identity.js +3 -3
  67. package/dist/lib/messaging.js +65 -17
  68. package/dist/lib/reminders.js +3 -3
  69. package/dist/lib/schedules.js +63 -3
  70. package/dist/lib/skill-learning.js +3 -3
  71. package/dist/lib/status-brief.js +63 -5
  72. package/dist/lib/store.js +73 -4
  73. package/dist/lib/task-router.js +4 -2
  74. package/dist/lib/tasks.js +95 -28
  75. package/dist/lib/tmux-routing.js +92 -23
  76. package/dist/mcp/server.js +800 -74
  77. package/dist/mcp/tools/complete-reminder.js +3 -3
  78. package/dist/mcp/tools/create-reminder.js +3 -3
  79. package/dist/mcp/tools/create-task.js +198 -31
  80. package/dist/mcp/tools/deactivate-behavior.js +4 -4
  81. package/dist/mcp/tools/list-reminders.js +3 -3
  82. package/dist/mcp/tools/list-tasks.js +19 -9
  83. package/dist/mcp/tools/send-message.js +69 -21
  84. package/dist/mcp/tools/update-task.js +28 -18
  85. package/dist/runtime/index.js +166 -28
  86. package/dist/tui/App.js +193 -40
  87. package/package.json +7 -3
  88. package/src/commands/exe/afk.md +116 -0
  89. package/src/commands/exe/rename.md +12 -0
@@ -359,6 +359,61 @@ var init_intercom_queue = __esm({
359
359
  }
360
360
  });
361
361
 
362
+ // src/lib/db-retry.ts
363
+ function isBusyError(err) {
364
+ if (err instanceof Error) {
365
+ const msg = err.message.toLowerCase();
366
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
367
+ }
368
+ return false;
369
+ }
370
+ function delay(ms) {
371
+ return new Promise((resolve) => setTimeout(resolve, ms));
372
+ }
373
+ async function retryOnBusy(fn, label) {
374
+ let lastError;
375
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
376
+ try {
377
+ return await fn();
378
+ } catch (err) {
379
+ lastError = err;
380
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
381
+ throw err;
382
+ }
383
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
384
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
385
+ process.stderr.write(
386
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
387
+ `
388
+ );
389
+ await delay(backoff + jitter);
390
+ }
391
+ }
392
+ throw lastError;
393
+ }
394
+ function wrapWithRetry(client) {
395
+ return new Proxy(client, {
396
+ get(target, prop, receiver) {
397
+ if (prop === "execute") {
398
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
399
+ }
400
+ if (prop === "batch") {
401
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
402
+ }
403
+ return Reflect.get(target, prop, receiver);
404
+ }
405
+ });
406
+ }
407
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
408
+ var init_db_retry = __esm({
409
+ "src/lib/db-retry.ts"() {
410
+ "use strict";
411
+ MAX_RETRIES = 3;
412
+ BASE_DELAY_MS = 200;
413
+ MAX_JITTER_MS = 300;
414
+ }
415
+ });
416
+
362
417
  // src/lib/database.ts
363
418
  var database_exports = {};
364
419
  __export(database_exports, {
@@ -366,6 +421,7 @@ __export(database_exports, {
366
421
  disposeTurso: () => disposeTurso,
367
422
  ensureSchema: () => ensureSchema,
368
423
  getClient: () => getClient,
424
+ getRawClient: () => getRawClient,
369
425
  initDatabase: () => initDatabase,
370
426
  initTurso: () => initTurso,
371
427
  isInitialized: () => isInitialized
@@ -375,6 +431,7 @@ async function initDatabase(config) {
375
431
  if (_client) {
376
432
  _client.close();
377
433
  _client = null;
434
+ _resilientClient = null;
378
435
  }
379
436
  const opts = {
380
437
  url: `file:${config.dbPath}`
@@ -383,20 +440,27 @@ async function initDatabase(config) {
383
440
  opts.encryptionKey = config.encryptionKey;
384
441
  }
385
442
  _client = createClient(opts);
443
+ _resilientClient = wrapWithRetry(_client);
386
444
  }
387
445
  function isInitialized() {
388
446
  return _client !== null;
389
447
  }
390
448
  function getClient() {
449
+ if (!_resilientClient) {
450
+ throw new Error("Database client not initialized. Call initDatabase() first.");
451
+ }
452
+ return _resilientClient;
453
+ }
454
+ function getRawClient() {
391
455
  if (!_client) {
392
456
  throw new Error("Database client not initialized. Call initDatabase() first.");
393
457
  }
394
458
  return _client;
395
459
  }
396
460
  async function ensureSchema() {
397
- const client = getClient();
461
+ const client = getRawClient();
398
462
  await client.execute("PRAGMA journal_mode = WAL");
399
- await client.execute("PRAGMA busy_timeout = 5000");
463
+ await client.execute("PRAGMA busy_timeout = 30000");
400
464
  try {
401
465
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
402
466
  } catch {
@@ -1189,13 +1253,16 @@ async function disposeDatabase() {
1189
1253
  if (_client) {
1190
1254
  _client.close();
1191
1255
  _client = null;
1256
+ _resilientClient = null;
1192
1257
  }
1193
1258
  }
1194
- var _client, initTurso, disposeTurso;
1259
+ var _client, _resilientClient, initTurso, disposeTurso;
1195
1260
  var init_database = __esm({
1196
1261
  "src/lib/database.ts"() {
1197
1262
  "use strict";
1263
+ init_db_retry();
1198
1264
  _client = null;
1265
+ _resilientClient = null;
1199
1266
  initTurso = initDatabase;
1200
1267
  disposeTurso = disposeDatabase;
1201
1268
  }
@@ -1392,20 +1459,38 @@ var init_config = __esm({
1392
1459
 
1393
1460
  // src/lib/employees.ts
1394
1461
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1395
- import { existsSync as existsSync4, symlinkSync, readlinkSync } from "fs";
1462
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4 } from "fs";
1396
1463
  import { execSync as execSync4 } from "child_process";
1397
1464
  import path5 from "path";
1398
- var EMPLOYEES_PATH;
1465
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
1466
+ if (!existsSync4(employeesPath)) return [];
1467
+ try {
1468
+ return JSON.parse(readFileSync4(employeesPath, "utf-8"));
1469
+ } catch {
1470
+ return [];
1471
+ }
1472
+ }
1473
+ function getEmployee(employees, name) {
1474
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
1475
+ }
1476
+ function isMultiInstance(agentName, employees) {
1477
+ const roster = employees ?? loadEmployeesSync();
1478
+ const emp = getEmployee(roster, agentName);
1479
+ if (!emp) return false;
1480
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
1481
+ }
1482
+ var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
1399
1483
  var init_employees = __esm({
1400
1484
  "src/lib/employees.ts"() {
1401
1485
  "use strict";
1402
1486
  init_config();
1403
1487
  EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
1488
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
1404
1489
  }
1405
1490
  });
1406
1491
 
1407
1492
  // src/lib/license.ts
1408
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
1493
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
1409
1494
  import { randomUUID as randomUUID2 } from "crypto";
1410
1495
  import path6 from "path";
1411
1496
  import { jwtVerify, importSPKI } from "jose";
@@ -1428,12 +1513,12 @@ var init_license = __esm({
1428
1513
  });
1429
1514
 
1430
1515
  // src/lib/plan-limits.ts
1431
- import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
1516
+ import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
1432
1517
  import path7 from "path";
1433
1518
  function getLicenseSync() {
1434
1519
  try {
1435
1520
  if (!existsSync6(CACHE_PATH2)) return freeLicense();
1436
- const raw = JSON.parse(readFileSync5(CACHE_PATH2, "utf8"));
1521
+ const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
1437
1522
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
1438
1523
  const parts = raw.token.split(".");
1439
1524
  if (parts.length !== 3) return freeLicense();
@@ -1472,7 +1557,7 @@ function assertEmployeeLimitSync(rosterPath) {
1472
1557
  let count = 0;
1473
1558
  try {
1474
1559
  if (existsSync6(filePath)) {
1475
- const raw = readFileSync5(filePath, "utf8");
1560
+ const raw = readFileSync6(filePath, "utf8");
1476
1561
  const employees = JSON.parse(raw);
1477
1562
  count = Array.isArray(employees) ? employees.length : 0;
1478
1563
  }
@@ -1510,7 +1595,7 @@ import crypto from "crypto";
1510
1595
  import path8 from "path";
1511
1596
  import os5 from "os";
1512
1597
  import {
1513
- readFileSync as readFileSync6,
1598
+ readFileSync as readFileSync7,
1514
1599
  readdirSync,
1515
1600
  unlinkSync,
1516
1601
  existsSync as existsSync7,
@@ -1596,7 +1681,7 @@ import crypto3 from "crypto";
1596
1681
  import path9 from "path";
1597
1682
  import { execSync as execSync5 } from "child_process";
1598
1683
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
1599
- import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
1684
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
1600
1685
  async function writeCheckpoint(input) {
1601
1686
  const client = getClient();
1602
1687
  const row = await resolveTask(client, input.taskId);
@@ -1973,7 +2058,7 @@ async function ensureGitignoreExe(baseDir) {
1973
2058
  const gitignorePath = path9.join(baseDir, ".gitignore");
1974
2059
  try {
1975
2060
  if (existsSync8(gitignorePath)) {
1976
- const content = readFileSync7(gitignorePath, "utf-8");
2061
+ const content = readFileSync8(gitignorePath, "utf-8");
1977
2062
  if (/^\/?exe\/?$/m.test(content)) return;
1978
2063
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
1979
2064
  } else {
@@ -2349,7 +2434,7 @@ async function dispatchTaskToEmployee(input) {
2349
2434
  } else {
2350
2435
  const projectDir = input.projectDir ?? process.cwd();
2351
2436
  const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
2352
- autoInstance: input.assignedTo === "tom" || input.assignedTo === "sasha"
2437
+ autoInstance: isMultiInstance(input.assignedTo)
2353
2438
  });
2354
2439
  if (result.status === "failed") {
2355
2440
  process.stderr.write(
@@ -2384,6 +2469,7 @@ var init_tasks_notify = __esm({
2384
2469
  init_session_key();
2385
2470
  init_notifications();
2386
2471
  init_transport();
2472
+ init_employees();
2387
2473
  }
2388
2474
  });
2389
2475
 
@@ -3199,6 +3285,7 @@ var init_capacity_monitor = __esm({
3199
3285
  // src/lib/tmux-routing.ts
3200
3286
  var tmux_routing_exports = {};
3201
3287
  __export(tmux_routing_exports, {
3288
+ acquireSpawnLock: () => acquireSpawnLock,
3202
3289
  employeeSessionName: () => employeeSessionName,
3203
3290
  ensureEmployee: () => ensureEmployee,
3204
3291
  extractRootExe: () => extractRootExe,
@@ -3213,16 +3300,53 @@ __export(tmux_routing_exports, {
3213
3300
  notifyParentExe: () => notifyParentExe,
3214
3301
  parseParentExe: () => parseParentExe,
3215
3302
  registerParentExe: () => registerParentExe,
3303
+ releaseSpawnLock: () => releaseSpawnLock,
3216
3304
  resolveExeSession: () => resolveExeSession,
3217
3305
  sendIntercom: () => sendIntercom,
3218
3306
  spawnEmployee: () => spawnEmployee,
3219
3307
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
3220
3308
  });
3221
3309
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
3222
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync } from "fs";
3310
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync } from "fs";
3223
3311
  import path14 from "path";
3224
3312
  import os6 from "os";
3225
3313
  import { fileURLToPath } from "url";
3314
+ import { unlinkSync as unlinkSync4 } from "fs";
3315
+ function spawnLockPath(sessionName) {
3316
+ return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
3317
+ }
3318
+ function isProcessAlive(pid) {
3319
+ try {
3320
+ process.kill(pid, 0);
3321
+ return true;
3322
+ } catch {
3323
+ return false;
3324
+ }
3325
+ }
3326
+ function acquireSpawnLock(sessionName) {
3327
+ if (!existsSync10(SPAWN_LOCK_DIR)) {
3328
+ mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
3329
+ }
3330
+ const lockFile = spawnLockPath(sessionName);
3331
+ if (existsSync10(lockFile)) {
3332
+ try {
3333
+ const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
3334
+ const age = Date.now() - lock.timestamp;
3335
+ if (isProcessAlive(lock.pid) && age < 6e4) {
3336
+ return false;
3337
+ }
3338
+ } catch {
3339
+ }
3340
+ }
3341
+ writeFileSync5(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
3342
+ return true;
3343
+ }
3344
+ function releaseSpawnLock(sessionName) {
3345
+ try {
3346
+ unlinkSync4(spawnLockPath(sessionName));
3347
+ } catch {
3348
+ }
3349
+ }
3226
3350
  function resolveBehaviorsExporterScript() {
3227
3351
  try {
3228
3352
  const thisFile = fileURLToPath(import.meta.url);
@@ -3286,7 +3410,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3286
3410
  }
3287
3411
  function getParentExe(sessionKey) {
3288
3412
  try {
3289
- const data = JSON.parse(readFileSync8(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
3413
+ const data = JSON.parse(readFileSync9(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
3290
3414
  return data.parentExe || null;
3291
3415
  } catch {
3292
3416
  return null;
@@ -3294,7 +3418,7 @@ function getParentExe(sessionKey) {
3294
3418
  }
3295
3419
  function getDispatchedBy(sessionKey) {
3296
3420
  try {
3297
- const data = JSON.parse(readFileSync8(
3421
+ const data = JSON.parse(readFileSync9(
3298
3422
  path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
3299
3423
  "utf8"
3300
3424
  ));
@@ -3321,10 +3445,10 @@ function isEmployeeAlive(sessionName) {
3321
3445
  }
3322
3446
  function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
3323
3447
  const base = employeeSessionName(employeeName, exeSession);
3324
- if (!isAlive(base)) return 0;
3448
+ if (!isAlive(base) && acquireSpawnLock(base)) return 0;
3325
3449
  for (let i = 2; i <= maxInstances; i++) {
3326
3450
  const candidate = employeeSessionName(employeeName, exeSession, i);
3327
- if (!isAlive(candidate)) return i;
3451
+ if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
3328
3452
  }
3329
3453
  return null;
3330
3454
  }
@@ -3357,7 +3481,7 @@ async function verifyPaneAtCapacity(sessionName) {
3357
3481
  function readDebounceState() {
3358
3482
  try {
3359
3483
  if (!existsSync10(DEBOUNCE_FILE)) return {};
3360
- return JSON.parse(readFileSync8(DEBOUNCE_FILE, "utf8"));
3484
+ return JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
3361
3485
  } catch {
3362
3486
  return {};
3363
3487
  }
@@ -3557,7 +3681,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3557
3681
  const claudeJsonPath = path14.join(os6.homedir(), ".claude.json");
3558
3682
  let claudeJson = {};
3559
3683
  try {
3560
- claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
3684
+ claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
3561
3685
  } catch {
3562
3686
  }
3563
3687
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -3575,7 +3699,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3575
3699
  const settingsPath = path14.join(projSettingsDir, "settings.json");
3576
3700
  let settings = {};
3577
3701
  try {
3578
- settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
3702
+ settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
3579
3703
  } catch {
3580
3704
  }
3581
3705
  const perms = settings.permissions ?? {};
@@ -3688,6 +3812,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3688
3812
  command: spawnCommand
3689
3813
  });
3690
3814
  if (spawnResult.error) {
3815
+ releaseSpawnLock(sessionName);
3691
3816
  return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
3692
3817
  }
3693
3818
  transport.pipeLog(sessionName, logFile);
@@ -3725,6 +3850,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3725
3850
  }
3726
3851
  }
3727
3852
  if (!booted) {
3853
+ releaseSpawnLock(sessionName);
3728
3854
  return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
3729
3855
  }
3730
3856
  if (!useExeAgent) {
@@ -3741,9 +3867,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3741
3867
  pid: 0,
3742
3868
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
3743
3869
  });
3870
+ releaseSpawnLock(sessionName);
3744
3871
  return { sessionName };
3745
3872
  }
3746
- var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
3873
+ var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
3747
3874
  var init_tmux_routing = __esm({
3748
3875
  "src/lib/tmux-routing.ts"() {
3749
3876
  "use strict";
@@ -3755,6 +3882,7 @@ var init_tmux_routing = __esm({
3755
3882
  init_provider_table();
3756
3883
  init_intercom_queue();
3757
3884
  init_plan_limits();
3885
+ SPAWN_LOCK_DIR = path14.join(os6.homedir(), ".exe-os", "spawn-locks");
3758
3886
  SESSION_CACHE = path14.join(os6.homedir(), ".exe-os", "session-cache");
3759
3887
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
3760
3888
  VERIFY_PANE_LINES = 200;
@@ -3882,7 +4010,7 @@ function listShards() {
3882
4010
  }
3883
4011
  async function ensureShardSchema(client) {
3884
4012
  await client.execute("PRAGMA journal_mode = WAL");
3885
- await client.execute("PRAGMA busy_timeout = 5000");
4013
+ await client.execute("PRAGMA busy_timeout = 30000");
3886
4014
  try {
3887
4015
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
3888
4016
  } catch {
@@ -4143,7 +4271,8 @@ async function writeMemory(record) {
4143
4271
  has_error: record.has_error ? 1 : 0,
4144
4272
  raw_text: record.raw_text,
4145
4273
  vector: record.vector,
4146
- version: _nextVersion++,
4274
+ version: 0,
4275
+ // Placeholder — assigned atomically at flush time
4147
4276
  task_id: record.task_id ?? null,
4148
4277
  importance: record.importance ?? 5,
4149
4278
  status: record.status ?? "active",
@@ -4177,6 +4306,13 @@ async function flushBatch() {
4177
4306
  _flushing = true;
4178
4307
  try {
4179
4308
  const batch = _pendingRecords.slice(0);
4309
+ const client = getClient();
4310
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
4311
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
4312
+ for (const row of batch) {
4313
+ row.version = baseVersion++;
4314
+ }
4315
+ _nextVersion = baseVersion;
4180
4316
  const buildStmt = (row) => {
4181
4317
  const hasVector = row.vector !== null;
4182
4318
  const taskId = row.task_id ?? null;
@@ -4534,8 +4670,8 @@ function findContainingChunk(filePath, snippet) {
4534
4670
  try {
4535
4671
  const ext = filePath.split(".").pop()?.toLowerCase();
4536
4672
  if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
4537
- const { readFileSync: readFileSync9 } = __require("fs");
4538
- const source = readFileSync9(filePath, "utf8");
4673
+ const { readFileSync: readFileSync10 } = __require("fs");
4674
+ const source = readFileSync10(filePath, "utf8");
4539
4675
  const lines = source.split("\n");
4540
4676
  const lowerSnippet = snippet.toLowerCase().slice(0, 80);
4541
4677
  let matchLine = -1;
@@ -6040,12 +6176,14 @@ var DEFAULT_BLOOM_CONFIG = {
6040
6176
  },
6041
6177
  tierRules: {
6042
6178
  junior: {
6043
- eligible: ["tom"],
6179
+ eligible: [],
6180
+ // resolved dynamically from roster (Principal Engineer role)
6044
6181
  reviewRequired: false,
6045
6182
  manualOnly: false
6046
6183
  },
6047
6184
  standard: {
6048
- eligible: ["tom"],
6185
+ eligible: [],
6186
+ // resolved dynamically from roster (Principal Engineer role)
6049
6187
  reviewRequired: false,
6050
6188
  manualOnly: false
6051
6189
  },