@askexenow/exe-os 0.8.83 → 0.8.86

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 (103) hide show
  1. package/dist/bin/backfill-conversations.js +746 -595
  2. package/dist/bin/backfill-responses.js +745 -594
  3. package/dist/bin/backfill-vectors.js +312 -226
  4. package/dist/bin/cleanup-stale-review-tasks.js +154 -21
  5. package/dist/bin/cli.js +14678 -12676
  6. package/dist/bin/exe-agent-config.js +242 -0
  7. package/dist/bin/exe-agent.js +100 -91
  8. package/dist/bin/exe-assign.js +1003 -854
  9. package/dist/bin/exe-boot.js +1420 -485
  10. package/dist/bin/exe-call.js +10 -0
  11. package/dist/bin/exe-cloud.js +29 -6
  12. package/dist/bin/exe-dispatch.js +572 -271
  13. package/dist/bin/exe-doctor.js +403 -6
  14. package/dist/bin/exe-export-behaviors.js +175 -72
  15. package/dist/bin/exe-forget.js +102 -3
  16. package/dist/bin/exe-gateway.js +796 -292
  17. package/dist/bin/exe-healthcheck.js +134 -1
  18. package/dist/bin/exe-heartbeat.js +172 -36
  19. package/dist/bin/exe-kill.js +175 -72
  20. package/dist/bin/exe-launch-agent.js +189 -76
  21. package/dist/bin/exe-link.js +927 -82
  22. package/dist/bin/exe-new-employee.js +60 -8
  23. package/dist/bin/exe-pending-messages.js +151 -19
  24. package/dist/bin/exe-pending-notifications.js +97 -2
  25. package/dist/bin/exe-pending-reviews.js +155 -22
  26. package/dist/bin/exe-rename.js +564 -23
  27. package/dist/bin/exe-review.js +231 -73
  28. package/dist/bin/exe-search.js +995 -228
  29. package/dist/bin/exe-session-cleanup.js +4930 -1664
  30. package/dist/bin/exe-settings.js +20 -5
  31. package/dist/bin/exe-start-codex.js +2598 -0
  32. package/dist/bin/exe-start.sh +15 -3
  33. package/dist/bin/exe-status.js +154 -21
  34. package/dist/bin/exe-team.js +97 -2
  35. package/dist/bin/git-sweep.js +1180 -363
  36. package/dist/bin/graph-backfill.js +175 -72
  37. package/dist/bin/graph-export.js +175 -72
  38. package/dist/bin/install.js +60 -7
  39. package/dist/bin/list-providers.js +1 -0
  40. package/dist/bin/scan-tasks.js +1185 -367
  41. package/dist/bin/setup.js +914 -270
  42. package/dist/bin/shard-migrate.js +175 -72
  43. package/dist/bin/update.js +1 -0
  44. package/dist/bin/wiki-sync.js +175 -72
  45. package/dist/gateway/index.js +792 -285
  46. package/dist/hooks/bug-report-worker.js +445 -135
  47. package/dist/hooks/commit-complete.js +1178 -361
  48. package/dist/hooks/error-recall.js +994 -228
  49. package/dist/hooks/ingest-worker.js +1799 -1234
  50. package/dist/hooks/ingest.js +3 -0
  51. package/dist/hooks/instructions-loaded.js +707 -97
  52. package/dist/hooks/notification.js +699 -89
  53. package/dist/hooks/post-compact.js +757 -109
  54. package/dist/hooks/pre-compact.js +1061 -244
  55. package/dist/hooks/pre-tool-use.js +787 -130
  56. package/dist/hooks/prompt-ingest-worker.js +242 -101
  57. package/dist/hooks/prompt-submit.js +1121 -299
  58. package/dist/hooks/response-ingest-worker.js +242 -101
  59. package/dist/hooks/session-end.js +4063 -397
  60. package/dist/hooks/session-start.js +1071 -254
  61. package/dist/hooks/stop.js +768 -120
  62. package/dist/hooks/subagent-stop.js +757 -109
  63. package/dist/hooks/summary-worker.js +1706 -1011
  64. package/dist/index.js +1821 -1098
  65. package/dist/lib/agent-config.js +167 -0
  66. package/dist/lib/cloud-sync.js +932 -88
  67. package/dist/lib/consolidation.js +2 -1
  68. package/dist/lib/database.js +642 -87
  69. package/dist/lib/db-daemon-client.js +503 -0
  70. package/dist/lib/device-registry.js +547 -7
  71. package/dist/lib/embedder.js +14 -28
  72. package/dist/lib/employee-templates.js +84 -74
  73. package/dist/lib/employees.js +9 -0
  74. package/dist/lib/exe-daemon-client.js +16 -29
  75. package/dist/lib/exe-daemon.js +2733 -1575
  76. package/dist/lib/hybrid-search.js +995 -228
  77. package/dist/lib/identity.js +87 -67
  78. package/dist/lib/keychain.js +9 -1
  79. package/dist/lib/messaging.js +103 -40
  80. package/dist/lib/reminders.js +91 -74
  81. package/dist/lib/runtime-table.js +16 -0
  82. package/dist/lib/schedules.js +96 -2
  83. package/dist/lib/session-wrappers.js +22 -0
  84. package/dist/lib/skill-learning.js +103 -85
  85. package/dist/lib/store.js +234 -73
  86. package/dist/lib/tasks.js +348 -134
  87. package/dist/lib/tmux-routing.js +422 -208
  88. package/dist/lib/token-spend.js +273 -0
  89. package/dist/lib/ws-client.js +11 -0
  90. package/dist/mcp/server.js +5742 -696
  91. package/dist/mcp/tools/complete-reminder.js +94 -77
  92. package/dist/mcp/tools/create-reminder.js +94 -77
  93. package/dist/mcp/tools/create-task.js +375 -152
  94. package/dist/mcp/tools/deactivate-behavior.js +95 -77
  95. package/dist/mcp/tools/list-reminders.js +94 -77
  96. package/dist/mcp/tools/list-tasks.js +99 -31
  97. package/dist/mcp/tools/send-message.js +108 -45
  98. package/dist/mcp/tools/update-task.js +162 -77
  99. package/dist/runtime/index.js +1075 -258
  100. package/dist/tui/App.js +1333 -506
  101. package/package.json +6 -1
  102. package/src/commands/exe/agent-config.md +27 -0
  103. package/src/commands/exe/cc-doctor.md +10 -0
@@ -268,123 +268,19 @@ var init_provider_table = __esm({
268
268
  }
269
269
  });
270
270
 
271
- // src/lib/intercom-queue.ts
272
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
273
- import path2 from "path";
274
- import os2 from "os";
275
- function ensureDir() {
276
- const dir = path2.dirname(QUEUE_PATH);
277
- if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
278
- }
279
- function readQueue() {
280
- try {
281
- if (!existsSync2(QUEUE_PATH)) return [];
282
- return JSON.parse(readFileSync2(QUEUE_PATH, "utf8"));
283
- } catch {
284
- return [];
285
- }
286
- }
287
- function writeQueue(queue) {
288
- ensureDir();
289
- const tmp = `${QUEUE_PATH}.tmp`;
290
- writeFileSync2(tmp, JSON.stringify(queue, null, 2));
291
- renameSync(tmp, QUEUE_PATH);
292
- }
293
- function queueIntercom(targetSession, reason) {
294
- const queue = readQueue();
295
- const existing = queue.find((q) => q.targetSession === targetSession);
296
- if (existing) {
297
- existing.attempts++;
298
- existing.queuedAt = (/* @__PURE__ */ new Date()).toISOString();
299
- existing.reason = reason;
300
- } else {
301
- queue.push({
302
- targetSession,
303
- queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
304
- attempts: 0,
305
- reason
306
- });
307
- }
308
- writeQueue(queue);
309
- }
310
- var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
311
- var init_intercom_queue = __esm({
312
- "src/lib/intercom-queue.ts"() {
313
- "use strict";
314
- QUEUE_PATH = path2.join(os2.homedir(), ".exe-os", "intercom-queue.json");
315
- TTL_MS = 60 * 60 * 1e3;
316
- INTERCOM_LOG = path2.join(os2.homedir(), ".exe-os", "intercom.log");
317
- }
318
- });
319
-
320
- // src/lib/db-retry.ts
321
- function isBusyError(err) {
322
- if (err instanceof Error) {
323
- const msg = err.message.toLowerCase();
324
- return msg.includes("sqlite_busy") || msg.includes("database is locked");
325
- }
326
- return false;
327
- }
328
- function delay(ms) {
329
- return new Promise((resolve) => setTimeout(resolve, ms));
330
- }
331
- async function retryOnBusy(fn, label) {
332
- let lastError;
333
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
334
- try {
335
- return await fn();
336
- } catch (err) {
337
- lastError = err;
338
- if (!isBusyError(err) || attempt === MAX_RETRIES) {
339
- throw err;
340
- }
341
- const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
342
- const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
343
- process.stderr.write(
344
- `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
345
- `
346
- );
347
- await delay(backoff + jitter);
348
- }
349
- }
350
- throw lastError;
351
- }
352
- function wrapWithRetry(client) {
353
- return new Proxy(client, {
354
- get(target, prop, receiver) {
355
- if (prop === "execute") {
356
- return (sql) => retryOnBusy(() => target.execute(sql), "execute");
357
- }
358
- if (prop === "batch") {
359
- return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
360
- }
361
- return Reflect.get(target, prop, receiver);
362
- }
363
- });
364
- }
365
- var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
366
- var init_db_retry = __esm({
367
- "src/lib/db-retry.ts"() {
368
- "use strict";
369
- MAX_RETRIES = 3;
370
- BASE_DELAY_MS = 200;
371
- MAX_JITTER_MS = 300;
372
- }
373
- });
374
-
375
271
  // src/lib/config.ts
376
272
  import { readFile, writeFile, mkdir, chmod } from "fs/promises";
377
- import { readFileSync as readFileSync3, existsSync as existsSync3, renameSync as renameSync2 } from "fs";
378
- import path3 from "path";
379
- import os3 from "os";
273
+ import { readFileSync as readFileSync2, existsSync as existsSync2, renameSync } from "fs";
274
+ import path2 from "path";
275
+ import os2 from "os";
380
276
  function resolveDataDir() {
381
277
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
382
278
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
383
- const newDir = path3.join(os3.homedir(), ".exe-os");
384
- const legacyDir = path3.join(os3.homedir(), ".exe-mem");
385
- if (!existsSync3(newDir) && existsSync3(legacyDir)) {
279
+ const newDir = path2.join(os2.homedir(), ".exe-os");
280
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
281
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
386
282
  try {
387
- renameSync2(legacyDir, newDir);
283
+ renameSync(legacyDir, newDir);
388
284
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
389
285
  `);
390
286
  } catch {
@@ -446,9 +342,9 @@ function normalizeAutoUpdate(raw) {
446
342
  async function loadConfig() {
447
343
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
448
344
  await mkdir(dir, { recursive: true });
449
- const configPath = path3.join(dir, "config.json");
450
- if (!existsSync3(configPath)) {
451
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
345
+ const configPath = path2.join(dir, "config.json");
346
+ if (!existsSync2(configPath)) {
347
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
452
348
  }
453
349
  const raw = await readFile(configPath, "utf-8");
454
350
  try {
@@ -466,13 +362,13 @@ async function loadConfig() {
466
362
  normalizeScalingRoadmap(migratedCfg);
467
363
  normalizeSessionLifecycle(migratedCfg);
468
364
  normalizeAutoUpdate(migratedCfg);
469
- const config = { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
365
+ const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
470
366
  if (config.dbPath.startsWith("~")) {
471
- config.dbPath = config.dbPath.replace(/^~/, os3.homedir());
367
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
472
368
  }
473
369
  return config;
474
370
  } catch {
475
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
371
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
476
372
  }
477
373
  }
478
374
  var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
@@ -480,10 +376,10 @@ var init_config = __esm({
480
376
  "src/lib/config.ts"() {
481
377
  "use strict";
482
378
  EXE_AI_DIR = resolveDataDir();
483
- DB_PATH = path3.join(EXE_AI_DIR, "memories.db");
484
- MODELS_DIR = path3.join(EXE_AI_DIR, "models");
485
- CONFIG_PATH = path3.join(EXE_AI_DIR, "config.json");
486
- LEGACY_LANCE_PATH = path3.join(EXE_AI_DIR, "local.lance");
379
+ DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
380
+ MODELS_DIR = path2.join(EXE_AI_DIR, "models");
381
+ CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
382
+ LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
487
383
  CURRENT_CONFIG_VERSION = 1;
488
384
  DEFAULT_CONFIG = {
489
385
  config_version: CURRENT_CONFIG_VERSION,
@@ -555,11 +451,166 @@ var init_config = __esm({
555
451
  }
556
452
  });
557
453
 
454
+ // src/lib/runtime-table.ts
455
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
456
+ var init_runtime_table = __esm({
457
+ "src/lib/runtime-table.ts"() {
458
+ "use strict";
459
+ RUNTIME_TABLE = {
460
+ codex: {
461
+ binary: "codex",
462
+ launchMode: "exec",
463
+ autoApproveFlag: "--full-auto",
464
+ inlineFlag: "--no-alt-screen",
465
+ apiKeyEnv: "OPENAI_API_KEY",
466
+ defaultModel: "gpt-5.4"
467
+ }
468
+ };
469
+ DEFAULT_RUNTIME = "claude";
470
+ }
471
+ });
472
+
473
+ // src/lib/agent-config.ts
474
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
475
+ import path3 from "path";
476
+ function loadAgentConfig() {
477
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
478
+ try {
479
+ return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
480
+ } catch {
481
+ return {};
482
+ }
483
+ }
484
+ function getAgentRuntime(agentId) {
485
+ const config = loadAgentConfig();
486
+ const entry = config[agentId];
487
+ if (entry) return entry;
488
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
489
+ }
490
+ var AGENT_CONFIG_PATH, DEFAULT_MODELS;
491
+ var init_agent_config = __esm({
492
+ "src/lib/agent-config.ts"() {
493
+ "use strict";
494
+ init_config();
495
+ init_runtime_table();
496
+ AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
497
+ DEFAULT_MODELS = {
498
+ claude: "claude-opus-4",
499
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
500
+ opencode: "minimax-m2.7"
501
+ };
502
+ }
503
+ });
504
+
505
+ // src/lib/intercom-queue.ts
506
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
507
+ import path4 from "path";
508
+ import os3 from "os";
509
+ function ensureDir() {
510
+ const dir = path4.dirname(QUEUE_PATH);
511
+ if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
512
+ }
513
+ function readQueue() {
514
+ try {
515
+ if (!existsSync4(QUEUE_PATH)) return [];
516
+ return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
517
+ } catch {
518
+ return [];
519
+ }
520
+ }
521
+ function writeQueue(queue) {
522
+ ensureDir();
523
+ const tmp = `${QUEUE_PATH}.tmp`;
524
+ writeFileSync3(tmp, JSON.stringify(queue, null, 2));
525
+ renameSync2(tmp, QUEUE_PATH);
526
+ }
527
+ function queueIntercom(targetSession, reason) {
528
+ const queue = readQueue();
529
+ const existing = queue.find((q) => q.targetSession === targetSession);
530
+ if (existing) {
531
+ existing.attempts++;
532
+ existing.queuedAt = (/* @__PURE__ */ new Date()).toISOString();
533
+ existing.reason = reason;
534
+ } else {
535
+ queue.push({
536
+ targetSession,
537
+ queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
538
+ attempts: 0,
539
+ reason
540
+ });
541
+ }
542
+ writeQueue(queue);
543
+ }
544
+ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
545
+ var init_intercom_queue = __esm({
546
+ "src/lib/intercom-queue.ts"() {
547
+ "use strict";
548
+ QUEUE_PATH = path4.join(os3.homedir(), ".exe-os", "intercom-queue.json");
549
+ TTL_MS = 60 * 60 * 1e3;
550
+ INTERCOM_LOG = path4.join(os3.homedir(), ".exe-os", "intercom.log");
551
+ }
552
+ });
553
+
554
+ // src/lib/db-retry.ts
555
+ function isBusyError(err) {
556
+ if (err instanceof Error) {
557
+ const msg = err.message.toLowerCase();
558
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
559
+ }
560
+ return false;
561
+ }
562
+ function delay(ms) {
563
+ return new Promise((resolve) => setTimeout(resolve, ms));
564
+ }
565
+ async function retryOnBusy(fn, label) {
566
+ let lastError;
567
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
568
+ try {
569
+ return await fn();
570
+ } catch (err) {
571
+ lastError = err;
572
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
573
+ throw err;
574
+ }
575
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
576
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
577
+ process.stderr.write(
578
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
579
+ `
580
+ );
581
+ await delay(backoff + jitter);
582
+ }
583
+ }
584
+ throw lastError;
585
+ }
586
+ function wrapWithRetry(client) {
587
+ return new Proxy(client, {
588
+ get(target, prop, receiver) {
589
+ if (prop === "execute") {
590
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
591
+ }
592
+ if (prop === "batch") {
593
+ return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
594
+ }
595
+ return Reflect.get(target, prop, receiver);
596
+ }
597
+ });
598
+ }
599
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
600
+ var init_db_retry = __esm({
601
+ "src/lib/db-retry.ts"() {
602
+ "use strict";
603
+ MAX_RETRIES = 3;
604
+ BASE_DELAY_MS = 200;
605
+ MAX_JITTER_MS = 300;
606
+ }
607
+ });
608
+
558
609
  // src/lib/employees.ts
559
610
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
560
- import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
611
+ import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
561
612
  import { execSync as execSync3 } from "child_process";
562
- import path4 from "path";
613
+ import path5 from "path";
563
614
  import os4 from "os";
564
615
  function normalizeRole(role) {
565
616
  return (role ?? "").trim().toLowerCase();
@@ -578,9 +629,9 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
578
629
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
579
630
  }
580
631
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
581
- if (!existsSync4(employeesPath)) return [];
632
+ if (!existsSync5(employeesPath)) return [];
582
633
  try {
583
- return JSON.parse(readFileSync4(employeesPath, "utf-8"));
634
+ return JSON.parse(readFileSync5(employeesPath, "utf-8"));
584
635
  } catch {
585
636
  return [];
586
637
  }
@@ -599,13 +650,450 @@ var init_employees = __esm({
599
650
  "src/lib/employees.ts"() {
600
651
  "use strict";
601
652
  init_config();
602
- EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
653
+ EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
603
654
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
604
655
  COORDINATOR_ROLE = "COO";
605
656
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
606
657
  }
607
658
  });
608
659
 
660
+ // src/lib/exe-daemon-client.ts
661
+ import net from "net";
662
+ import { spawn } from "child_process";
663
+ import { randomUUID } from "crypto";
664
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
665
+ import path6 from "path";
666
+ import { fileURLToPath } from "url";
667
+ function handleData(chunk) {
668
+ _buffer += chunk.toString();
669
+ if (_buffer.length > MAX_BUFFER) {
670
+ _buffer = "";
671
+ return;
672
+ }
673
+ let newlineIdx;
674
+ while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
675
+ const line = _buffer.slice(0, newlineIdx).trim();
676
+ _buffer = _buffer.slice(newlineIdx + 1);
677
+ if (!line) continue;
678
+ try {
679
+ const response = JSON.parse(line);
680
+ const id = response.id;
681
+ if (!id) continue;
682
+ const entry = _pending.get(id);
683
+ if (entry) {
684
+ clearTimeout(entry.timer);
685
+ _pending.delete(id);
686
+ entry.resolve(response);
687
+ }
688
+ } catch {
689
+ }
690
+ }
691
+ }
692
+ function cleanupStaleFiles() {
693
+ if (existsSync6(PID_PATH)) {
694
+ try {
695
+ const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
696
+ if (pid > 0) {
697
+ try {
698
+ process.kill(pid, 0);
699
+ return;
700
+ } catch {
701
+ }
702
+ }
703
+ } catch {
704
+ }
705
+ try {
706
+ unlinkSync2(PID_PATH);
707
+ } catch {
708
+ }
709
+ try {
710
+ unlinkSync2(SOCKET_PATH);
711
+ } catch {
712
+ }
713
+ }
714
+ }
715
+ function findPackageRoot() {
716
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
717
+ const { root } = path6.parse(dir);
718
+ while (dir !== root) {
719
+ if (existsSync6(path6.join(dir, "package.json"))) return dir;
720
+ dir = path6.dirname(dir);
721
+ }
722
+ return null;
723
+ }
724
+ function spawnDaemon() {
725
+ const pkgRoot = findPackageRoot();
726
+ if (!pkgRoot) {
727
+ process.stderr.write("[exed-client] WARN: cannot find package root\n");
728
+ return;
729
+ }
730
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
731
+ if (!existsSync6(daemonPath)) {
732
+ process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
733
+ `);
734
+ return;
735
+ }
736
+ const resolvedPath = daemonPath;
737
+ process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
738
+ `);
739
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
740
+ let stderrFd = "ignore";
741
+ try {
742
+ stderrFd = openSync(logPath, "a");
743
+ } catch {
744
+ }
745
+ const child = spawn(process.execPath, [resolvedPath], {
746
+ detached: true,
747
+ stdio: ["ignore", "ignore", stderrFd],
748
+ env: {
749
+ ...process.env,
750
+ TMUX: void 0,
751
+ // Daemon is global — must not inherit session scope
752
+ TMUX_PANE: void 0,
753
+ // Prevents resolveExeSession() from scoping to one session
754
+ EXE_DAEMON_SOCK: SOCKET_PATH,
755
+ EXE_DAEMON_PID: PID_PATH
756
+ }
757
+ });
758
+ child.unref();
759
+ if (typeof stderrFd === "number") {
760
+ try {
761
+ closeSync(stderrFd);
762
+ } catch {
763
+ }
764
+ }
765
+ }
766
+ function acquireSpawnLock() {
767
+ try {
768
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
769
+ closeSync(fd);
770
+ return true;
771
+ } catch {
772
+ try {
773
+ const stat = statSync(SPAWN_LOCK_PATH);
774
+ if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
775
+ try {
776
+ unlinkSync2(SPAWN_LOCK_PATH);
777
+ } catch {
778
+ }
779
+ try {
780
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
781
+ closeSync(fd);
782
+ return true;
783
+ } catch {
784
+ }
785
+ }
786
+ } catch {
787
+ }
788
+ return false;
789
+ }
790
+ }
791
+ function releaseSpawnLock() {
792
+ try {
793
+ unlinkSync2(SPAWN_LOCK_PATH);
794
+ } catch {
795
+ }
796
+ }
797
+ function connectToSocket() {
798
+ return new Promise((resolve) => {
799
+ if (_socket && _connected) {
800
+ resolve(true);
801
+ return;
802
+ }
803
+ const socket = net.createConnection({ path: SOCKET_PATH });
804
+ const connectTimeout = setTimeout(() => {
805
+ socket.destroy();
806
+ resolve(false);
807
+ }, 2e3);
808
+ socket.on("connect", () => {
809
+ clearTimeout(connectTimeout);
810
+ _socket = socket;
811
+ _connected = true;
812
+ _buffer = "";
813
+ socket.on("data", handleData);
814
+ socket.on("close", () => {
815
+ _connected = false;
816
+ _socket = null;
817
+ for (const [id, entry] of _pending) {
818
+ clearTimeout(entry.timer);
819
+ _pending.delete(id);
820
+ entry.resolve({ error: "Connection closed" });
821
+ }
822
+ });
823
+ socket.on("error", () => {
824
+ _connected = false;
825
+ _socket = null;
826
+ });
827
+ resolve(true);
828
+ });
829
+ socket.on("error", () => {
830
+ clearTimeout(connectTimeout);
831
+ resolve(false);
832
+ });
833
+ });
834
+ }
835
+ async function connectEmbedDaemon() {
836
+ if (_socket && _connected) return true;
837
+ if (await connectToSocket()) return true;
838
+ if (acquireSpawnLock()) {
839
+ try {
840
+ cleanupStaleFiles();
841
+ spawnDaemon();
842
+ } finally {
843
+ releaseSpawnLock();
844
+ }
845
+ }
846
+ const start = Date.now();
847
+ let delay2 = 100;
848
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
849
+ await new Promise((r) => setTimeout(r, delay2));
850
+ if (await connectToSocket()) return true;
851
+ delay2 = Math.min(delay2 * 2, 3e3);
852
+ }
853
+ return false;
854
+ }
855
+ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
856
+ return new Promise((resolve) => {
857
+ if (!_socket || !_connected) {
858
+ resolve({ error: "Not connected" });
859
+ return;
860
+ }
861
+ const id = randomUUID();
862
+ const timer = setTimeout(() => {
863
+ _pending.delete(id);
864
+ resolve({ error: "Request timeout" });
865
+ }, timeoutMs);
866
+ _pending.set(id, { resolve, timer });
867
+ try {
868
+ _socket.write(JSON.stringify({ id, ...payload }) + "\n");
869
+ } catch {
870
+ clearTimeout(timer);
871
+ _pending.delete(id);
872
+ resolve({ error: "Write failed" });
873
+ }
874
+ });
875
+ }
876
+ function isClientConnected() {
877
+ return _connected;
878
+ }
879
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
880
+ var init_exe_daemon_client = __esm({
881
+ "src/lib/exe-daemon-client.ts"() {
882
+ "use strict";
883
+ init_config();
884
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
885
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
886
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
887
+ SPAWN_LOCK_STALE_MS = 3e4;
888
+ CONNECT_TIMEOUT_MS = 15e3;
889
+ REQUEST_TIMEOUT_MS = 3e4;
890
+ _socket = null;
891
+ _connected = false;
892
+ _buffer = "";
893
+ _pending = /* @__PURE__ */ new Map();
894
+ MAX_BUFFER = 1e7;
895
+ }
896
+ });
897
+
898
+ // src/lib/daemon-protocol.ts
899
+ function serializeValue(v) {
900
+ if (v === null || v === void 0) return null;
901
+ if (typeof v === "bigint") return Number(v);
902
+ if (typeof v === "boolean") return v ? 1 : 0;
903
+ if (v instanceof Uint8Array) {
904
+ return { __blob: Buffer.from(v).toString("base64") };
905
+ }
906
+ if (ArrayBuffer.isView(v)) {
907
+ return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
908
+ }
909
+ if (v instanceof ArrayBuffer) {
910
+ return { __blob: Buffer.from(v).toString("base64") };
911
+ }
912
+ if (typeof v === "string" || typeof v === "number") return v;
913
+ return String(v);
914
+ }
915
+ function deserializeValue(v) {
916
+ if (v === null) return null;
917
+ if (typeof v === "object" && v !== null && "__blob" in v) {
918
+ const buf = Buffer.from(v.__blob, "base64");
919
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
920
+ }
921
+ return v;
922
+ }
923
+ function deserializeResultSet(srs) {
924
+ const rows = srs.rows.map((obj) => {
925
+ const values = srs.columns.map(
926
+ (col) => deserializeValue(obj[col] ?? null)
927
+ );
928
+ const row = values;
929
+ for (let i = 0; i < srs.columns.length; i++) {
930
+ const col = srs.columns[i];
931
+ if (col !== void 0) {
932
+ row[col] = values[i] ?? null;
933
+ }
934
+ }
935
+ Object.defineProperty(row, "length", {
936
+ value: values.length,
937
+ enumerable: false
938
+ });
939
+ return row;
940
+ });
941
+ return {
942
+ columns: srs.columns,
943
+ columnTypes: srs.columnTypes ?? [],
944
+ rows,
945
+ rowsAffected: srs.rowsAffected,
946
+ lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
947
+ toJSON: () => ({
948
+ columns: srs.columns,
949
+ columnTypes: srs.columnTypes ?? [],
950
+ rows: srs.rows,
951
+ rowsAffected: srs.rowsAffected,
952
+ lastInsertRowid: srs.lastInsertRowid
953
+ })
954
+ };
955
+ }
956
+ var init_daemon_protocol = __esm({
957
+ "src/lib/daemon-protocol.ts"() {
958
+ "use strict";
959
+ }
960
+ });
961
+
962
+ // src/lib/db-daemon-client.ts
963
+ var db_daemon_client_exports = {};
964
+ __export(db_daemon_client_exports, {
965
+ createDaemonDbClient: () => createDaemonDbClient,
966
+ initDaemonDbClient: () => initDaemonDbClient
967
+ });
968
+ function normalizeStatement(stmt) {
969
+ if (typeof stmt === "string") {
970
+ return { sql: stmt, args: [] };
971
+ }
972
+ const sql = stmt.sql;
973
+ let args = [];
974
+ if (Array.isArray(stmt.args)) {
975
+ args = stmt.args.map((v) => serializeValue(v));
976
+ } else if (stmt.args && typeof stmt.args === "object") {
977
+ const named = {};
978
+ for (const [key, val] of Object.entries(stmt.args)) {
979
+ named[key] = serializeValue(val);
980
+ }
981
+ return { sql, args: named };
982
+ }
983
+ return { sql, args };
984
+ }
985
+ function createDaemonDbClient(fallbackClient) {
986
+ let _useDaemon = false;
987
+ const client = {
988
+ async execute(stmt) {
989
+ if (!_useDaemon || !isClientConnected()) {
990
+ return fallbackClient.execute(stmt);
991
+ }
992
+ const { sql, args } = normalizeStatement(stmt);
993
+ const response = await sendDaemonRequest({
994
+ type: "db-execute",
995
+ sql,
996
+ args
997
+ });
998
+ if (response.error) {
999
+ const errMsg = String(response.error);
1000
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
1001
+ process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
1002
+ `);
1003
+ return fallbackClient.execute(stmt);
1004
+ }
1005
+ throw new Error(errMsg);
1006
+ }
1007
+ if (response.db) {
1008
+ return deserializeResultSet(response.db);
1009
+ }
1010
+ process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
1011
+ return fallbackClient.execute(stmt);
1012
+ },
1013
+ async batch(stmts, mode) {
1014
+ if (!_useDaemon || !isClientConnected()) {
1015
+ return fallbackClient.batch(stmts, mode);
1016
+ }
1017
+ const statements = stmts.map(normalizeStatement);
1018
+ const response = await sendDaemonRequest({
1019
+ type: "db-batch",
1020
+ statements,
1021
+ mode: mode ?? "deferred"
1022
+ });
1023
+ if (response.error) {
1024
+ const errMsg = String(response.error);
1025
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
1026
+ process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
1027
+ `);
1028
+ return fallbackClient.batch(stmts, mode);
1029
+ }
1030
+ throw new Error(errMsg);
1031
+ }
1032
+ const batchResults = response["db-batch"];
1033
+ if (batchResults) {
1034
+ return batchResults.map(deserializeResultSet);
1035
+ }
1036
+ process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
1037
+ return fallbackClient.batch(stmts, mode);
1038
+ },
1039
+ // Transaction support — delegate to fallback (transactions need direct connection)
1040
+ async transaction(mode) {
1041
+ return fallbackClient.transaction(mode);
1042
+ },
1043
+ // executeMultiple — delegate to fallback (used only for schema migrations)
1044
+ async executeMultiple(sql) {
1045
+ return fallbackClient.executeMultiple(sql);
1046
+ },
1047
+ // migrate — delegate to fallback
1048
+ async migrate(stmts) {
1049
+ return fallbackClient.migrate(stmts);
1050
+ },
1051
+ // Sync mode — delegate to fallback
1052
+ sync() {
1053
+ return fallbackClient.sync();
1054
+ },
1055
+ close() {
1056
+ _useDaemon = false;
1057
+ },
1058
+ get closed() {
1059
+ return fallbackClient.closed;
1060
+ },
1061
+ get protocol() {
1062
+ return fallbackClient.protocol;
1063
+ }
1064
+ };
1065
+ return {
1066
+ ...client,
1067
+ /** Enable daemon routing (call after confirming daemon is connected) */
1068
+ _enableDaemon() {
1069
+ _useDaemon = true;
1070
+ },
1071
+ /** Check if daemon routing is active */
1072
+ _isDaemonActive() {
1073
+ return _useDaemon && isClientConnected();
1074
+ }
1075
+ };
1076
+ }
1077
+ async function initDaemonDbClient(fallbackClient) {
1078
+ if (process.env.EXE_IS_DAEMON === "1") return null;
1079
+ const connected = await connectEmbedDaemon();
1080
+ if (!connected) {
1081
+ process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
1082
+ return null;
1083
+ }
1084
+ const client = createDaemonDbClient(fallbackClient);
1085
+ client._enableDaemon();
1086
+ process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
1087
+ return client;
1088
+ }
1089
+ var init_db_daemon_client = __esm({
1090
+ "src/lib/db-daemon-client.ts"() {
1091
+ "use strict";
1092
+ init_exe_daemon_client();
1093
+ init_daemon_protocol();
1094
+ }
1095
+ });
1096
+
609
1097
  // src/lib/database.ts
610
1098
  var database_exports = {};
611
1099
  __export(database_exports, {
@@ -614,6 +1102,7 @@ __export(database_exports, {
614
1102
  ensureSchema: () => ensureSchema,
615
1103
  getClient: () => getClient,
616
1104
  getRawClient: () => getRawClient,
1105
+ initDaemonClient: () => initDaemonClient,
617
1106
  initDatabase: () => initDatabase,
618
1107
  initTurso: () => initTurso,
619
1108
  isInitialized: () => isInitialized
@@ -641,8 +1130,27 @@ function getClient() {
641
1130
  if (!_resilientClient) {
642
1131
  throw new Error("Database client not initialized. Call initDatabase() first.");
643
1132
  }
1133
+ if (process.env.EXE_IS_DAEMON === "1") {
1134
+ return _resilientClient;
1135
+ }
1136
+ if (_daemonClient && _daemonClient._isDaemonActive()) {
1137
+ return _daemonClient;
1138
+ }
644
1139
  return _resilientClient;
645
1140
  }
1141
+ async function initDaemonClient() {
1142
+ if (process.env.EXE_IS_DAEMON === "1") return;
1143
+ if (!_resilientClient) return;
1144
+ try {
1145
+ const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
1146
+ _daemonClient = await initDaemonDbClient2(_resilientClient);
1147
+ } catch (err) {
1148
+ process.stderr.write(
1149
+ `[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
1150
+ `
1151
+ );
1152
+ }
1153
+ }
646
1154
  function getRawClient() {
647
1155
  if (!_client) {
648
1156
  throw new Error("Database client not initialized. Call initDatabase() first.");
@@ -1129,6 +1637,12 @@ async function ensureSchema() {
1129
1637
  } catch {
1130
1638
  }
1131
1639
  }
1640
+ try {
1641
+ await client.execute(
1642
+ `CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
1643
+ );
1644
+ } catch {
1645
+ }
1132
1646
  await client.executeMultiple(`
1133
1647
  CREATE TABLE IF NOT EXISTS entities (
1134
1648
  id TEXT PRIMARY KEY,
@@ -1181,7 +1695,30 @@ async function ensureSchema() {
1181
1695
  entity_id TEXT NOT NULL,
1182
1696
  PRIMARY KEY (hyperedge_id, entity_id)
1183
1697
  );
1698
+
1699
+ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
1700
+ name,
1701
+ content=entities,
1702
+ content_rowid=rowid
1703
+ );
1704
+
1705
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
1706
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
1707
+ END;
1708
+
1709
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
1710
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
1711
+ END;
1712
+
1713
+ CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
1714
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
1715
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
1716
+ END;
1184
1717
  `);
1718
+ try {
1719
+ await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
1720
+ } catch {
1721
+ }
1185
1722
  await client.executeMultiple(`
1186
1723
  CREATE TABLE IF NOT EXISTS entity_aliases (
1187
1724
  alias TEXT NOT NULL PRIMARY KEY,
@@ -1362,6 +1899,33 @@ async function ensureSchema() {
1362
1899
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
1363
1900
  ON conversations(channel_id);
1364
1901
  `);
1902
+ await client.executeMultiple(`
1903
+ CREATE TABLE IF NOT EXISTS session_agent_map (
1904
+ session_uuid TEXT PRIMARY KEY,
1905
+ agent_id TEXT NOT NULL,
1906
+ session_name TEXT,
1907
+ task_id TEXT,
1908
+ project_name TEXT,
1909
+ started_at TEXT NOT NULL
1910
+ );
1911
+
1912
+ CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
1913
+ ON session_agent_map(agent_id);
1914
+ `);
1915
+ try {
1916
+ const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
1917
+ if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
1918
+ await client.execute({
1919
+ sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
1920
+ SELECT session_id, agent_id, '', MIN(timestamp)
1921
+ FROM memories
1922
+ WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
1923
+ GROUP BY session_id, agent_id`,
1924
+ args: []
1925
+ });
1926
+ }
1927
+ } catch {
1928
+ }
1365
1929
  try {
1366
1930
  await client.execute({
1367
1931
  sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
@@ -1495,15 +2059,41 @@ async function ensureSchema() {
1495
2059
  });
1496
2060
  } catch {
1497
2061
  }
2062
+ for (const col of [
2063
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
2064
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
2065
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
2066
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
2067
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
2068
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
2069
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
2070
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
2071
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
2072
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
2073
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
2074
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
2075
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
2076
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
2077
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2078
+ ]) {
2079
+ try {
2080
+ await client.execute(col);
2081
+ } catch {
2082
+ }
2083
+ }
1498
2084
  }
1499
2085
  async function disposeDatabase() {
2086
+ if (_daemonClient) {
2087
+ _daemonClient.close();
2088
+ _daemonClient = null;
2089
+ }
1500
2090
  if (_client) {
1501
2091
  _client.close();
1502
2092
  _client = null;
1503
2093
  _resilientClient = null;
1504
2094
  }
1505
2095
  }
1506
- var _client, _resilientClient, initTurso, disposeTurso;
2096
+ var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
1507
2097
  var init_database = __esm({
1508
2098
  "src/lib/database.ts"() {
1509
2099
  "use strict";
@@ -1511,24 +2101,25 @@ var init_database = __esm({
1511
2101
  init_employees();
1512
2102
  _client = null;
1513
2103
  _resilientClient = null;
2104
+ _daemonClient = null;
1514
2105
  initTurso = initDatabase;
1515
2106
  disposeTurso = disposeDatabase;
1516
2107
  }
1517
2108
  });
1518
2109
 
1519
2110
  // src/lib/license.ts
1520
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
1521
- import { randomUUID } from "crypto";
1522
- import path5 from "path";
2111
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2112
+ import { randomUUID as randomUUID2 } from "crypto";
2113
+ import path7 from "path";
1523
2114
  import { jwtVerify, importSPKI } from "jose";
1524
2115
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
1525
2116
  var init_license = __esm({
1526
2117
  "src/lib/license.ts"() {
1527
2118
  "use strict";
1528
2119
  init_config();
1529
- LICENSE_PATH = path5.join(EXE_AI_DIR, "license.key");
1530
- CACHE_PATH = path5.join(EXE_AI_DIR, "license-cache.json");
1531
- DEVICE_ID_PATH = path5.join(EXE_AI_DIR, "device-id");
2120
+ LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
2121
+ CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
2122
+ DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
1532
2123
  PLAN_LIMITS = {
1533
2124
  free: { devices: 1, employees: 1, memories: 5e3 },
1534
2125
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -1540,12 +2131,12 @@ var init_license = __esm({
1540
2131
  });
1541
2132
 
1542
2133
  // src/lib/plan-limits.ts
1543
- import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
1544
- import path6 from "path";
2134
+ import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
2135
+ import path8 from "path";
1545
2136
  function getLicenseSync() {
1546
2137
  try {
1547
- if (!existsSync6(CACHE_PATH2)) return freeLicense();
1548
- const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
2138
+ if (!existsSync8(CACHE_PATH2)) return freeLicense();
2139
+ const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
1549
2140
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
1550
2141
  const parts = raw.token.split(".");
1551
2142
  if (parts.length !== 3) return freeLicense();
@@ -1583,8 +2174,8 @@ function assertEmployeeLimitSync(rosterPath) {
1583
2174
  const filePath = rosterPath ?? EMPLOYEES_PATH;
1584
2175
  let count = 0;
1585
2176
  try {
1586
- if (existsSync6(filePath)) {
1587
- const raw = readFileSync6(filePath, "utf8");
2177
+ if (existsSync8(filePath)) {
2178
+ const raw = readFileSync8(filePath, "utf8");
1588
2179
  const employees = JSON.parse(raw);
1589
2180
  count = Array.isArray(employees) ? employees.length : 0;
1590
2181
  }
@@ -1613,19 +2204,19 @@ var init_plan_limits = __esm({
1613
2204
  this.name = "PlanLimitError";
1614
2205
  }
1615
2206
  };
1616
- CACHE_PATH2 = path6.join(EXE_AI_DIR, "license-cache.json");
2207
+ CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
1617
2208
  }
1618
2209
  });
1619
2210
 
1620
2211
  // src/lib/notifications.ts
1621
2212
  import crypto from "crypto";
1622
- import path7 from "path";
2213
+ import path9 from "path";
1623
2214
  import os5 from "os";
1624
2215
  import {
1625
- readFileSync as readFileSync7,
2216
+ readFileSync as readFileSync9,
1626
2217
  readdirSync,
1627
- unlinkSync as unlinkSync2,
1628
- existsSync as existsSync7,
2218
+ unlinkSync as unlinkSync3,
2219
+ existsSync as existsSync9,
1629
2220
  rmdirSync
1630
2221
  } from "fs";
1631
2222
  async function writeNotification(notification) {
@@ -1776,10 +2367,11 @@ __export(tasks_crud_exports, {
1776
2367
  writeCheckpoint: () => writeCheckpoint
1777
2368
  });
1778
2369
  import crypto3 from "crypto";
1779
- import path8 from "path";
2370
+ import path10 from "path";
2371
+ import os6 from "os";
1780
2372
  import { execSync as execSync4 } from "child_process";
1781
2373
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
1782
- import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
2374
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
1783
2375
  async function writeCheckpoint(input) {
1784
2376
  const client = getClient();
1785
2377
  const row = await resolveTask(client, input.taskId);
@@ -1820,6 +2412,35 @@ function extractParentFromContext(contextBody) {
1820
2412
  function slugify(title) {
1821
2413
  return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
1822
2414
  }
2415
+ function buildKeywordIndex() {
2416
+ const idx = /* @__PURE__ */ new Map();
2417
+ for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
2418
+ for (const kw of keywords) {
2419
+ const existing = idx.get(kw) ?? [];
2420
+ existing.push(role);
2421
+ idx.set(kw, existing);
2422
+ }
2423
+ }
2424
+ return idx;
2425
+ }
2426
+ function checkLaneAffinity(title, context, assigneeName) {
2427
+ const employees = loadEmployeesSync();
2428
+ const employee = employees.find((e) => e.name === assigneeName);
2429
+ if (!employee) return void 0;
2430
+ const assigneeRole = employee.role;
2431
+ const text = `${title} ${context}`.toLowerCase();
2432
+ const matchedRoles = /* @__PURE__ */ new Set();
2433
+ for (const [keyword, roles] of KEYWORD_INDEX) {
2434
+ if (text.includes(keyword)) {
2435
+ for (const role of roles) matchedRoles.add(role);
2436
+ }
2437
+ }
2438
+ if (matchedRoles.size === 0) return void 0;
2439
+ if (matchedRoles.has(assigneeRole)) return void 0;
2440
+ if (assigneeRole === "COO") return void 0;
2441
+ const expectedRoles = Array.from(matchedRoles).join(" or ");
2442
+ return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
2443
+ }
1823
2444
  async function resolveTask(client, identifier, scopeSession) {
1824
2445
  const scope = sessionScopeFilter(scopeSession);
1825
2446
  let result = await client.execute({
@@ -1869,7 +2490,14 @@ async function createTaskCore(input) {
1869
2490
  const id = crypto3.randomUUID();
1870
2491
  const now = (/* @__PURE__ */ new Date()).toISOString();
1871
2492
  const slug = slugify(input.title);
1872
- const taskFile = input.taskFile ?? `exe/${input.assignedTo}/${slug}.md`;
2493
+ let earlySessionScope = null;
2494
+ try {
2495
+ const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
2496
+ earlySessionScope = resolveExeSession2();
2497
+ } catch {
2498
+ }
2499
+ const scope = earlySessionScope ?? "default";
2500
+ const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
1873
2501
  let blockedById = null;
1874
2502
  const initialStatus = input.blockedBy ? "blocked" : "open";
1875
2503
  if (input.blockedBy) {
@@ -1909,22 +2537,24 @@ async function createTaskCore(input) {
1909
2537
  if (dupCheck.rows.length > 0) {
1910
2538
  warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
1911
2539
  }
2540
+ if (!process.env.DISABLE_LANE_AFFINITY) {
2541
+ const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
2542
+ if (laneWarning) {
2543
+ warning = warning ? `${warning}
2544
+ ${laneWarning}` : laneWarning;
2545
+ }
2546
+ }
1912
2547
  if (input.baseDir) {
1913
2548
  try {
1914
- await mkdir3(path8.join(input.baseDir, "exe", "output"), { recursive: true });
1915
- await mkdir3(path8.join(input.baseDir, "exe", "research"), { recursive: true });
2549
+ await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
2550
+ await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
1916
2551
  await ensureArchitectureDoc(input.baseDir, input.projectName);
1917
2552
  await ensureGitignoreExe(input.baseDir);
1918
2553
  } catch {
1919
2554
  }
1920
2555
  }
1921
2556
  const complexity = input.complexity ?? "standard";
1922
- let sessionScope = null;
1923
- try {
1924
- const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
1925
- sessionScope = resolveExeSession2();
1926
- } catch {
1927
- }
2557
+ const sessionScope = earlySessionScope;
1928
2558
  await client.execute({
1929
2559
  sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, created_at, updated_at)
1930
2560
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
@@ -1951,6 +2581,43 @@ async function createTaskCore(input) {
1951
2581
  now
1952
2582
  ]
1953
2583
  });
2584
+ if (input.baseDir) {
2585
+ try {
2586
+ const EXE_OS_DIR = path10.join(os6.homedir(), ".exe-os");
2587
+ const mdPath = path10.join(EXE_OS_DIR, taskFile);
2588
+ const mdDir = path10.dirname(mdPath);
2589
+ if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
2590
+ const reviewer = input.reviewer ?? input.assignedBy;
2591
+ const mdContent = `# ${input.title}
2592
+
2593
+ **ID:** ${id}
2594
+ **Status:** ${initialStatus}
2595
+ **Priority:** ${input.priority}
2596
+ **Assigned by:** ${input.assignedBy}
2597
+ **Assigned to:** ${input.assignedTo}
2598
+ **Project:** ${input.projectName}
2599
+ **Created:** ${now.split("T")[0]}${parentTaskId ? `
2600
+ **Parent task:** ${parentTaskId}` : ""}
2601
+ **Reviewer:** ${reviewer}
2602
+
2603
+ ## Context
2604
+
2605
+ ${input.context}
2606
+
2607
+ ## MANDATORY: When done
2608
+
2609
+ You MUST call update_task with status "done" and a result summary when finished.
2610
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
2611
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
2612
+ `;
2613
+ await writeFile3(mdPath, mdContent, "utf-8");
2614
+ } catch (err) {
2615
+ process.stderr.write(
2616
+ `[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
2617
+ `
2618
+ );
2619
+ }
2620
+ }
1954
2621
  return {
1955
2622
  id,
1956
2623
  title: input.title,
@@ -2143,7 +2810,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
2143
2810
  return { row, taskFile, now, taskId };
2144
2811
  }
2145
2812
  }
2146
- if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
2813
+ if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
2147
2814
  process.stderr.write(
2148
2815
  `[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
2149
2816
  `
@@ -2208,9 +2875,9 @@ async function deleteTaskCore(taskId, _baseDir) {
2208
2875
  return { taskFile, assignedTo, assignedBy, taskSlug };
2209
2876
  }
2210
2877
  async function ensureArchitectureDoc(baseDir, projectName) {
2211
- const archPath = path8.join(baseDir, "exe", "ARCHITECTURE.md");
2878
+ const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
2212
2879
  try {
2213
- if (existsSync8(archPath)) return;
2880
+ if (existsSync10(archPath)) return;
2214
2881
  const template = [
2215
2882
  `# ${projectName} \u2014 System Architecture`,
2216
2883
  "",
@@ -2243,10 +2910,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
2243
2910
  }
2244
2911
  }
2245
2912
  async function ensureGitignoreExe(baseDir) {
2246
- const gitignorePath = path8.join(baseDir, ".gitignore");
2913
+ const gitignorePath = path10.join(baseDir, ".gitignore");
2247
2914
  try {
2248
- if (existsSync8(gitignorePath)) {
2249
- const content = readFileSync8(gitignorePath, "utf-8");
2915
+ if (existsSync10(gitignorePath)) {
2916
+ const content = readFileSync10(gitignorePath, "utf-8");
2250
2917
  if (/^\/?exe\/?$/m.test(content)) return;
2251
2918
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
2252
2919
  } else {
@@ -2255,20 +2922,30 @@ async function ensureGitignoreExe(baseDir) {
2255
2922
  } catch {
2256
2923
  }
2257
2924
  }
2258
- var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
2925
+ var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
2259
2926
  var init_tasks_crud = __esm({
2260
2927
  "src/lib/tasks-crud.ts"() {
2261
2928
  "use strict";
2262
2929
  init_database();
2263
2930
  init_task_scope();
2931
+ init_employees();
2932
+ LANE_KEYWORDS = {
2933
+ CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
2934
+ CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
2935
+ "Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
2936
+ "Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
2937
+ "Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
2938
+ "AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
2939
+ };
2940
+ KEYWORD_INDEX = buildKeywordIndex();
2264
2941
  DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
2265
2942
  TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
2266
2943
  }
2267
2944
  });
2268
2945
 
2269
2946
  // src/lib/tasks-review.ts
2270
- import path9 from "path";
2271
- import { existsSync as existsSync9, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
2947
+ import path11 from "path";
2948
+ import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
2272
2949
  async function countPendingReviews(sessionScope) {
2273
2950
  const client = getClient();
2274
2951
  if (sessionScope) {
@@ -2290,7 +2967,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
2290
2967
  const result2 = await client.execute({
2291
2968
  sql: `SELECT COUNT(*) as cnt FROM tasks
2292
2969
  WHERE status = 'needs_review' AND updated_at > ?
2293
- AND (session_scope = ? OR session_scope IS NULL)`,
2970
+ AND session_scope = ?`,
2294
2971
  args: [sinceIso, sessionScope]
2295
2972
  });
2296
2973
  return Number(result2.rows[0]?.cnt) || 0;
@@ -2308,7 +2985,7 @@ async function listPendingReviews(limit, sessionScope) {
2308
2985
  const result2 = await client.execute({
2309
2986
  sql: `SELECT title, assigned_to, project_name FROM tasks
2310
2987
  WHERE status = 'needs_review'
2311
- AND (session_scope = ? OR session_scope IS NULL)
2988
+ AND session_scope = ?
2312
2989
  ORDER BY priority ASC, created_at DESC LIMIT ?`,
2313
2990
  args: [sessionScope, limit]
2314
2991
  });
@@ -2429,14 +3106,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
2429
3106
  if (parts.length >= 3 && parts[0] === "review") {
2430
3107
  const agent = parts[1];
2431
3108
  const slug = parts.slice(2).join("-");
2432
- const originalTaskFile = `exe/${agent}/${slug}.md`;
3109
+ const legacyTaskFile = `exe/${agent}/${slug}.md`;
2433
3110
  const result = await client.execute({
2434
- sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
2435
- args: [now, originalTaskFile]
3111
+ sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
3112
+ args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
2436
3113
  });
2437
3114
  if (result.rowsAffected > 0) {
2438
3115
  process.stderr.write(
2439
- `[review-cleanup] Cascaded original task to done (legacy path): ${originalTaskFile}
3116
+ `[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
2440
3117
  `
2441
3118
  );
2442
3119
  }
@@ -2449,11 +3126,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
2449
3126
  );
2450
3127
  }
2451
3128
  try {
2452
- const cacheDir = path9.join(EXE_AI_DIR, "session-cache");
2453
- if (existsSync9(cacheDir)) {
3129
+ const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
3130
+ if (existsSync11(cacheDir)) {
2454
3131
  for (const f of readdirSync2(cacheDir)) {
2455
3132
  if (f.startsWith("review-notified-")) {
2456
- unlinkSync3(path9.join(cacheDir, f));
3133
+ unlinkSync4(path11.join(cacheDir, f));
2457
3134
  }
2458
3135
  }
2459
3136
  }
@@ -2474,7 +3151,7 @@ var init_tasks_review = __esm({
2474
3151
  });
2475
3152
 
2476
3153
  // src/lib/tasks-chain.ts
2477
- import path10 from "path";
3154
+ import path12 from "path";
2478
3155
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
2479
3156
  async function cascadeUnblock(taskId, baseDir, now) {
2480
3157
  const client = getClient();
@@ -2491,7 +3168,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
2491
3168
  });
2492
3169
  for (const ur of unblockedRows.rows) {
2493
3170
  try {
2494
- const ubFile = path10.join(baseDir, String(ur.task_file));
3171
+ const ubFile = path12.join(baseDir, String(ur.task_file));
2495
3172
  let ubContent = await readFile3(ubFile, "utf-8");
2496
3173
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
2497
3174
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -2560,7 +3237,7 @@ var init_tasks_chain = __esm({
2560
3237
 
2561
3238
  // src/lib/project-name.ts
2562
3239
  import { execSync as execSync5 } from "child_process";
2563
- import path11 from "path";
3240
+ import path13 from "path";
2564
3241
  function getProjectName(cwd) {
2565
3242
  const dir = cwd ?? process.cwd();
2566
3243
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -2573,7 +3250,7 @@ function getProjectName(cwd) {
2573
3250
  timeout: 2e3,
2574
3251
  stdio: ["pipe", "pipe", "pipe"]
2575
3252
  }).trim();
2576
- repoRoot = path11.dirname(gitCommonDir);
3253
+ repoRoot = path13.dirname(gitCommonDir);
2577
3254
  } catch {
2578
3255
  repoRoot = execSync5("git rev-parse --show-toplevel", {
2579
3256
  cwd: dir,
@@ -2582,11 +3259,11 @@ function getProjectName(cwd) {
2582
3259
  stdio: ["pipe", "pipe", "pipe"]
2583
3260
  }).trim();
2584
3261
  }
2585
- _cached2 = path11.basename(repoRoot);
3262
+ _cached2 = path13.basename(repoRoot);
2586
3263
  _cachedCwd = dir;
2587
3264
  return _cached2;
2588
3265
  } catch {
2589
- _cached2 = path11.basename(dir);
3266
+ _cached2 = path13.basename(dir);
2590
3267
  _cachedCwd = dir;
2591
3268
  return _cached2;
2592
3269
  }
@@ -2618,7 +3295,7 @@ function findSessionForProject(projectName) {
2618
3295
  const sessions = listSessions();
2619
3296
  for (const s of sessions) {
2620
3297
  const proj = s.projectDir.split("/").filter(Boolean).pop();
2621
- if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
3298
+ if (proj === projectName && isCoordinatorName(s.agentId)) return s;
2622
3299
  }
2623
3300
  return null;
2624
3301
  }
@@ -2664,7 +3341,7 @@ var init_session_scope = __esm({
2664
3341
 
2665
3342
  // src/lib/tasks-notify.ts
2666
3343
  async function dispatchTaskToEmployee(input) {
2667
- if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
3344
+ if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
2668
3345
  let crossProject = false;
2669
3346
  if (input.projectName) {
2670
3347
  try {
@@ -3059,8 +3736,8 @@ __export(tasks_exports, {
3059
3736
  updateTaskStatus: () => updateTaskStatus,
3060
3737
  writeCheckpoint: () => writeCheckpoint
3061
3738
  });
3062
- import path12 from "path";
3063
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync4 } from "fs";
3739
+ import path14 from "path";
3740
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
3064
3741
  async function createTask(input) {
3065
3742
  const result = await createTaskCore(input);
3066
3743
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -3079,14 +3756,14 @@ async function updateTask(input) {
3079
3756
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
3080
3757
  try {
3081
3758
  const agent = String(row.assigned_to);
3082
- const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
3083
- const cachePath = path12.join(cacheDir, `current-task-${agent}.json`);
3759
+ const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
3760
+ const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
3084
3761
  if (input.status === "in_progress") {
3085
- mkdirSync4(cacheDir, { recursive: true });
3086
- writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3762
+ mkdirSync5(cacheDir, { recursive: true });
3763
+ writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3087
3764
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
3088
3765
  try {
3089
- unlinkSync4(cachePath);
3766
+ unlinkSync5(cachePath);
3090
3767
  } catch {
3091
3768
  }
3092
3769
  }
@@ -3143,7 +3820,7 @@ async function updateTask(input) {
3143
3820
  }
3144
3821
  const isTerminal = input.status === "done" || input.status === "needs_review";
3145
3822
  if (isTerminal) {
3146
- const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
3823
+ const isCoordinator = isCoordinatorName(String(row.assigned_to));
3147
3824
  if (!isCoordinator) {
3148
3825
  notifyTaskDone();
3149
3826
  }
@@ -3168,7 +3845,7 @@ async function updateTask(input) {
3168
3845
  }
3169
3846
  }
3170
3847
  }
3171
- if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
3848
+ if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
3172
3849
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
3173
3850
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
3174
3851
  taskId,
@@ -3184,7 +3861,7 @@ async function updateTask(input) {
3184
3861
  });
3185
3862
  }
3186
3863
  let nextTask;
3187
- if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
3864
+ if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
3188
3865
  try {
3189
3866
  nextTask = await findNextTask(String(row.assigned_to));
3190
3867
  } catch {
@@ -3528,7 +4205,7 @@ var init_capacity_monitor = __esm({
3528
4205
  // src/lib/tmux-routing.ts
3529
4206
  var tmux_routing_exports = {};
3530
4207
  __export(tmux_routing_exports, {
3531
- acquireSpawnLock: () => acquireSpawnLock,
4208
+ acquireSpawnLock: () => acquireSpawnLock2,
3532
4209
  employeeSessionName: () => employeeSessionName,
3533
4210
  ensureEmployee: () => ensureEmployee,
3534
4211
  extractRootExe: () => extractRootExe,
@@ -3543,20 +4220,20 @@ __export(tmux_routing_exports, {
3543
4220
  notifyParentExe: () => notifyParentExe,
3544
4221
  parseParentExe: () => parseParentExe,
3545
4222
  registerParentExe: () => registerParentExe,
3546
- releaseSpawnLock: () => releaseSpawnLock,
4223
+ releaseSpawnLock: () => releaseSpawnLock2,
3547
4224
  resolveExeSession: () => resolveExeSession,
3548
4225
  sendIntercom: () => sendIntercom,
3549
4226
  spawnEmployee: () => spawnEmployee,
3550
4227
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
3551
4228
  });
3552
4229
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
3553
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync } from "fs";
3554
- import path13 from "path";
3555
- import os6 from "os";
3556
- import { fileURLToPath } from "url";
3557
- import { unlinkSync as unlinkSync5 } from "fs";
4230
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync } from "fs";
4231
+ import path15 from "path";
4232
+ import os7 from "os";
4233
+ import { fileURLToPath as fileURLToPath2 } from "url";
4234
+ import { unlinkSync as unlinkSync6 } from "fs";
3558
4235
  function spawnLockPath(sessionName) {
3559
- return path13.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4236
+ return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
3560
4237
  }
3561
4238
  function isProcessAlive(pid) {
3562
4239
  try {
@@ -3566,14 +4243,14 @@ function isProcessAlive(pid) {
3566
4243
  return false;
3567
4244
  }
3568
4245
  }
3569
- function acquireSpawnLock(sessionName) {
3570
- if (!existsSync10(SPAWN_LOCK_DIR)) {
3571
- mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
4246
+ function acquireSpawnLock2(sessionName) {
4247
+ if (!existsSync12(SPAWN_LOCK_DIR)) {
4248
+ mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
3572
4249
  }
3573
4250
  const lockFile = spawnLockPath(sessionName);
3574
- if (existsSync10(lockFile)) {
4251
+ if (existsSync12(lockFile)) {
3575
4252
  try {
3576
- const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
4253
+ const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
3577
4254
  const age = Date.now() - lock.timestamp;
3578
4255
  if (isProcessAlive(lock.pid) && age < 6e4) {
3579
4256
  return false;
@@ -3581,25 +4258,25 @@ function acquireSpawnLock(sessionName) {
3581
4258
  } catch {
3582
4259
  }
3583
4260
  }
3584
- writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4261
+ writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
3585
4262
  return true;
3586
4263
  }
3587
- function releaseSpawnLock(sessionName) {
4264
+ function releaseSpawnLock2(sessionName) {
3588
4265
  try {
3589
- unlinkSync5(spawnLockPath(sessionName));
4266
+ unlinkSync6(spawnLockPath(sessionName));
3590
4267
  } catch {
3591
4268
  }
3592
4269
  }
3593
4270
  function resolveBehaviorsExporterScript() {
3594
4271
  try {
3595
- const thisFile = fileURLToPath(import.meta.url);
3596
- const scriptPath = path13.join(
3597
- path13.dirname(thisFile),
4272
+ const thisFile = fileURLToPath2(import.meta.url);
4273
+ const scriptPath = path15.join(
4274
+ path15.dirname(thisFile),
3598
4275
  "..",
3599
4276
  "bin",
3600
4277
  "exe-export-behaviors.js"
3601
4278
  );
3602
- return existsSync10(scriptPath) ? scriptPath : null;
4279
+ return existsSync12(scriptPath) ? scriptPath : null;
3603
4280
  } catch {
3604
4281
  return null;
3605
4282
  }
@@ -3665,12 +4342,12 @@ function extractRootExe(name) {
3665
4342
  return parts.length > 0 ? parts[parts.length - 1] : null;
3666
4343
  }
3667
4344
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3668
- if (!existsSync10(SESSION_CACHE)) {
3669
- mkdirSync5(SESSION_CACHE, { recursive: true });
4345
+ if (!existsSync12(SESSION_CACHE)) {
4346
+ mkdirSync6(SESSION_CACHE, { recursive: true });
3670
4347
  }
3671
4348
  const rootExe = extractRootExe(parentExe) ?? parentExe;
3672
- const filePath = path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
3673
- writeFileSync6(filePath, JSON.stringify({
4349
+ const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4350
+ writeFileSync7(filePath, JSON.stringify({
3674
4351
  parentExe: rootExe,
3675
4352
  dispatchedBy: dispatchedBy || rootExe,
3676
4353
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -3678,7 +4355,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3678
4355
  }
3679
4356
  function getParentExe(sessionKey) {
3680
4357
  try {
3681
- const data = JSON.parse(readFileSync9(path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4358
+ const data = JSON.parse(readFileSync11(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
3682
4359
  return data.parentExe || null;
3683
4360
  } catch {
3684
4361
  return null;
@@ -3686,8 +4363,8 @@ function getParentExe(sessionKey) {
3686
4363
  }
3687
4364
  function getDispatchedBy(sessionKey) {
3688
4365
  try {
3689
- const data = JSON.parse(readFileSync9(
3690
- path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4366
+ const data = JSON.parse(readFileSync11(
4367
+ path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
3691
4368
  "utf8"
3692
4369
  ));
3693
4370
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -3713,10 +4390,10 @@ function isEmployeeAlive(sessionName) {
3713
4390
  }
3714
4391
  function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
3715
4392
  const base = employeeSessionName(employeeName, exeSession);
3716
- if (!isAlive(base) && acquireSpawnLock(base)) return 0;
4393
+ if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
3717
4394
  for (let i = 2; i <= maxInstances; i++) {
3718
4395
  const candidate = employeeSessionName(employeeName, exeSession, i);
3719
- if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
4396
+ if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
3720
4397
  }
3721
4398
  return null;
3722
4399
  }
@@ -3748,32 +4425,50 @@ async function verifyPaneAtCapacity(sessionName) {
3748
4425
  }
3749
4426
  function readDebounceState() {
3750
4427
  try {
3751
- if (!existsSync10(DEBOUNCE_FILE)) return {};
3752
- return JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
4428
+ if (!existsSync12(DEBOUNCE_FILE)) return {};
4429
+ const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
4430
+ const state = {};
4431
+ for (const [key, val] of Object.entries(raw)) {
4432
+ if (typeof val === "number") {
4433
+ state[key] = { lastSent: val, pending: 0 };
4434
+ } else if (val && typeof val === "object" && "lastSent" in val) {
4435
+ state[key] = val;
4436
+ }
4437
+ }
4438
+ return state;
3753
4439
  } catch {
3754
4440
  return {};
3755
4441
  }
3756
4442
  }
3757
4443
  function writeDebounceState(state) {
3758
4444
  try {
3759
- if (!existsSync10(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
3760
- writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
4445
+ if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
4446
+ writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
3761
4447
  } catch {
3762
4448
  }
3763
4449
  }
3764
4450
  function isDebounced(targetSession) {
3765
4451
  const state = readDebounceState();
3766
- const lastSent = state[targetSession] ?? 0;
3767
- return Date.now() - lastSent < INTERCOM_DEBOUNCE_MS;
4452
+ const entry = state[targetSession];
4453
+ const lastSent = entry?.lastSent ?? 0;
4454
+ if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
4455
+ if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
4456
+ state[targetSession].pending++;
4457
+ writeDebounceState(state);
4458
+ return true;
4459
+ }
4460
+ return false;
3768
4461
  }
3769
4462
  function recordDebounce(targetSession) {
3770
4463
  const state = readDebounceState();
3771
- state[targetSession] = Date.now();
4464
+ const batched = state[targetSession]?.pending ?? 0;
4465
+ state[targetSession] = { lastSent: Date.now(), pending: 0 };
3772
4466
  const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
3773
4467
  for (const key of Object.keys(state)) {
3774
- if ((state[key] ?? 0) < cutoff) delete state[key];
4468
+ if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
3775
4469
  }
3776
4470
  writeDebounceState(state);
4471
+ return batched;
3777
4472
  }
3778
4473
  function logIntercom(msg) {
3779
4474
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
@@ -3818,7 +4513,7 @@ function sendIntercom(targetSession) {
3818
4513
  return "skipped_exe";
3819
4514
  }
3820
4515
  if (isDebounced(targetSession)) {
3821
- logIntercom(`DEBOUNCE \u2192 ${targetSession} (cross-process file debounce)`);
4516
+ logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
3822
4517
  return "debounced";
3823
4518
  }
3824
4519
  try {
@@ -3830,14 +4525,14 @@ function sendIntercom(targetSession) {
3830
4525
  const sessionState = getSessionState(targetSession);
3831
4526
  if (sessionState === "no_claude") {
3832
4527
  queueIntercom(targetSession, "claude not running in session");
3833
- recordDebounce(targetSession);
3834
- logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
4528
+ const batched2 = recordDebounce(targetSession);
4529
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
3835
4530
  return "queued";
3836
4531
  }
3837
4532
  if (sessionState === "thinking" || sessionState === "tool") {
3838
4533
  queueIntercom(targetSession, "session busy at send time");
3839
- recordDebounce(targetSession);
3840
- logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
4534
+ const batched2 = recordDebounce(targetSession);
4535
+ logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
3841
4536
  return "queued";
3842
4537
  }
3843
4538
  if (transport.isPaneInCopyMode(targetSession)) {
@@ -3845,8 +4540,8 @@ function sendIntercom(targetSession) {
3845
4540
  transport.sendKeys(targetSession, "q");
3846
4541
  }
3847
4542
  transport.sendKeys(targetSession, "/exe-intercom");
3848
- recordDebounce(targetSession);
3849
- logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
4543
+ const batched = recordDebounce(targetSession);
4544
+ logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
3850
4545
  return "delivered";
3851
4546
  } catch {
3852
4547
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -3876,7 +4571,7 @@ function notifyParentExe(sessionKey) {
3876
4571
  return true;
3877
4572
  }
3878
4573
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
3879
- if (employeeName === "exe" || isCoordinatorName(employeeName)) {
4574
+ if (isCoordinatorName(employeeName)) {
3880
4575
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
3881
4576
  }
3882
4577
  try {
@@ -3948,26 +4643,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3948
4643
  const transport = getTransport();
3949
4644
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
3950
4645
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
3951
- const logDir = path13.join(os6.homedir(), ".exe-os", "session-logs");
3952
- const logFile = path13.join(logDir, `${instanceLabel}-${Date.now()}.log`);
3953
- if (!existsSync10(logDir)) {
3954
- mkdirSync5(logDir, { recursive: true });
4646
+ const logDir = path15.join(os7.homedir(), ".exe-os", "session-logs");
4647
+ const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4648
+ if (!existsSync12(logDir)) {
4649
+ mkdirSync6(logDir, { recursive: true });
3955
4650
  }
3956
4651
  transport.kill(sessionName);
3957
4652
  let cleanupSuffix = "";
3958
4653
  try {
3959
- const thisFile = fileURLToPath(import.meta.url);
3960
- const cleanupScript = path13.join(path13.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
3961
- if (existsSync10(cleanupScript)) {
4654
+ const thisFile = fileURLToPath2(import.meta.url);
4655
+ const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4656
+ if (existsSync12(cleanupScript)) {
3962
4657
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
3963
4658
  }
3964
4659
  } catch {
3965
4660
  }
3966
4661
  try {
3967
- const claudeJsonPath = path13.join(os6.homedir(), ".claude.json");
4662
+ const claudeJsonPath = path15.join(os7.homedir(), ".claude.json");
3968
4663
  let claudeJson = {};
3969
4664
  try {
3970
- claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
4665
+ claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
3971
4666
  } catch {
3972
4667
  }
3973
4668
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -3975,17 +4670,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3975
4670
  const trustDir = opts?.cwd ?? projectDir;
3976
4671
  if (!projects[trustDir]) projects[trustDir] = {};
3977
4672
  projects[trustDir].hasTrustDialogAccepted = true;
3978
- writeFileSync6(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4673
+ writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
3979
4674
  } catch {
3980
4675
  }
3981
4676
  try {
3982
- const settingsDir = path13.join(os6.homedir(), ".claude", "projects");
4677
+ const settingsDir = path15.join(os7.homedir(), ".claude", "projects");
3983
4678
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
3984
- const projSettingsDir = path13.join(settingsDir, normalizedKey);
3985
- const settingsPath = path13.join(projSettingsDir, "settings.json");
4679
+ const projSettingsDir = path15.join(settingsDir, normalizedKey);
4680
+ const settingsPath = path15.join(projSettingsDir, "settings.json");
3986
4681
  let settings = {};
3987
4682
  try {
3988
- settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
4683
+ settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
3989
4684
  } catch {
3990
4685
  }
3991
4686
  const perms = settings.permissions ?? {};
@@ -4013,21 +4708,24 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4013
4708
  if (changed) {
4014
4709
  perms.allow = allow;
4015
4710
  settings.permissions = perms;
4016
- mkdirSync5(projSettingsDir, { recursive: true });
4017
- writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4711
+ mkdirSync6(projSettingsDir, { recursive: true });
4712
+ writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4018
4713
  }
4019
4714
  } catch {
4020
4715
  }
4021
4716
  const spawnCwd = opts?.cwd ?? projectDir;
4022
4717
  const useExeAgent = !!(opts?.model && opts?.provider);
4023
- const ccProvider = useExeAgent ? DEFAULT_PROVIDER : detectActiveProvider();
4718
+ const agentRtConfig = getAgentRuntime(employeeName);
4719
+ const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
4720
+ const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
4721
+ const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
4024
4722
  const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
4025
4723
  let identityFlag = "";
4026
4724
  let behaviorsFlag = "";
4027
4725
  let legacyFallbackWarned = false;
4028
4726
  if (!useExeAgent && !useBinSymlink) {
4029
- const identityPath = path13.join(
4030
- os6.homedir(),
4727
+ const identityPath = path15.join(
4728
+ os7.homedir(),
4031
4729
  ".exe-os",
4032
4730
  "identity",
4033
4731
  `${employeeName}.md`
@@ -4036,13 +4734,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4036
4734
  const hasAgentFlag = claudeSupportsAgentFlag();
4037
4735
  if (hasAgentFlag) {
4038
4736
  identityFlag = ` --agent ${employeeName}`;
4039
- } else if (existsSync10(identityPath)) {
4737
+ } else if (existsSync12(identityPath)) {
4040
4738
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
4041
4739
  legacyFallbackWarned = true;
4042
4740
  }
4043
4741
  const behaviorsFile = exportBehaviorsSync(
4044
4742
  employeeName,
4045
- path13.basename(spawnCwd),
4743
+ path15.basename(spawnCwd),
4046
4744
  sessionName
4047
4745
  );
4048
4746
  if (behaviorsFile) {
@@ -4057,16 +4755,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4057
4755
  }
4058
4756
  let sessionContextFlag = "";
4059
4757
  try {
4060
- const ctxDir = path13.join(os6.homedir(), ".exe-os", "session-cache");
4061
- mkdirSync5(ctxDir, { recursive: true });
4062
- const ctxFile = path13.join(ctxDir, `session-context-${sessionName}.md`);
4758
+ const ctxDir = path15.join(os7.homedir(), ".exe-os", "session-cache");
4759
+ mkdirSync6(ctxDir, { recursive: true });
4760
+ const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
4063
4761
  const ctxContent = [
4064
4762
  `## Session Context`,
4065
4763
  `You are running in tmux session: ${sessionName}.`,
4066
4764
  `Your parent coordinator session is ${exeSession}.`,
4067
4765
  `Your employees (if any) use the -${exeSession} suffix.`
4068
4766
  ].join("\n");
4069
- writeFileSync6(ctxFile, ctxContent);
4767
+ writeFileSync7(ctxFile, ctxContent);
4070
4768
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
4071
4769
  } catch {
4072
4770
  }
@@ -4080,9 +4778,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4080
4778
  }
4081
4779
  }
4082
4780
  }
4781
+ if (useCodex) {
4782
+ const codexCfg = RUNTIME_TABLE.codex;
4783
+ if (codexCfg?.apiKeyEnv) {
4784
+ const keyVal = process.env[codexCfg.apiKeyEnv];
4785
+ if (keyVal) {
4786
+ envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
4787
+ }
4788
+ }
4789
+ envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
4790
+ }
4791
+ if (useOpencode) {
4792
+ const ocCfg = PROVIDER_TABLE.opencode;
4793
+ if (ocCfg?.apiKeyEnv) {
4794
+ const keyVal = process.env[ocCfg.apiKeyEnv];
4795
+ if (keyVal) {
4796
+ envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
4797
+ }
4798
+ }
4799
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
4800
+ }
4801
+ if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
4802
+ const defaultClaudeModel = DEFAULT_MODELS.claude;
4803
+ if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
4804
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
4805
+ }
4806
+ }
4083
4807
  let spawnCommand;
4084
4808
  if (useExeAgent) {
4085
4809
  spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
4810
+ } else if (useCodex) {
4811
+ process.stderr.write(
4812
+ `[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
4813
+ `
4814
+ );
4815
+ spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
4816
+ } else if (useOpencode) {
4817
+ const binName = `${employeeName}-opencode`;
4818
+ process.stderr.write(
4819
+ `[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
4820
+ `
4821
+ );
4822
+ spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
4086
4823
  } else if (useBinSymlink) {
4087
4824
  const binName = `${employeeName}-${ccProvider}`;
4088
4825
  process.stderr.write(
@@ -4098,17 +4835,19 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4098
4835
  command: spawnCommand
4099
4836
  });
4100
4837
  if (spawnResult.error) {
4101
- releaseSpawnLock(sessionName);
4838
+ releaseSpawnLock2(sessionName);
4102
4839
  return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
4103
4840
  }
4104
4841
  transport.pipeLog(sessionName, logFile);
4105
4842
  try {
4106
4843
  const mySession = getMySession();
4107
- const dispatchInfo = path13.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4108
- writeFileSync6(dispatchInfo, JSON.stringify({
4844
+ const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4845
+ writeFileSync7(dispatchInfo, JSON.stringify({
4109
4846
  dispatchedBy: mySession,
4110
4847
  rootExe: exeSession,
4111
- provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
4848
+ provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
4849
+ runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
4850
+ model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
4112
4851
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
4113
4852
  }));
4114
4853
  } catch {
@@ -4126,6 +4865,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4126
4865
  booted = true;
4127
4866
  break;
4128
4867
  }
4868
+ } else if (useCodex) {
4869
+ if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
4870
+ booted = true;
4871
+ break;
4872
+ }
4129
4873
  } else {
4130
4874
  if (pane.includes("Claude Code") || pane.includes("\u276F")) {
4131
4875
  booted = true;
@@ -4136,10 +4880,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4136
4880
  }
4137
4881
  }
4138
4882
  if (!booted) {
4139
- releaseSpawnLock(sessionName);
4140
- return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
4883
+ releaseSpawnLock2(sessionName);
4884
+ const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
4885
+ return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
4141
4886
  }
4142
- if (!useExeAgent) {
4887
+ if (!useExeAgent && !useCodex) {
4143
4888
  try {
4144
4889
  transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
4145
4890
  } catch {
@@ -4153,7 +4898,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4153
4898
  pid: 0,
4154
4899
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
4155
4900
  });
4156
- releaseSpawnLock(sessionName);
4901
+ releaseSpawnLock2(sessionName);
4157
4902
  return { sessionName };
4158
4903
  }
4159
4904
  var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VALID_SESSION_NAME, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
@@ -4166,17 +4911,19 @@ var init_tmux_routing = __esm({
4166
4911
  init_cc_agent_support();
4167
4912
  init_mcp_prefix();
4168
4913
  init_provider_table();
4914
+ init_agent_config();
4915
+ init_runtime_table();
4169
4916
  init_intercom_queue();
4170
4917
  init_plan_limits();
4171
4918
  init_employees();
4172
- SPAWN_LOCK_DIR = path13.join(os6.homedir(), ".exe-os", "spawn-locks");
4173
- SESSION_CACHE = path13.join(os6.homedir(), ".exe-os", "session-cache");
4919
+ SPAWN_LOCK_DIR = path15.join(os7.homedir(), ".exe-os", "spawn-locks");
4920
+ SESSION_CACHE = path15.join(os7.homedir(), ".exe-os", "session-cache");
4174
4921
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
4175
4922
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
4176
4923
  VERIFY_PANE_LINES = 200;
4177
4924
  INTERCOM_DEBOUNCE_MS = 3e4;
4178
- INTERCOM_LOG2 = path13.join(os6.homedir(), ".exe-os", "intercom.log");
4179
- DEBOUNCE_FILE = path13.join(SESSION_CACHE, "intercom-debounce.json");
4925
+ INTERCOM_LOG2 = path15.join(os7.homedir(), ".exe-os", "intercom.log");
4926
+ DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
4180
4927
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
4181
4928
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
4182
4929
  }
@@ -4217,14 +4964,14 @@ var init_memory = __esm({
4217
4964
 
4218
4965
  // src/lib/keychain.ts
4219
4966
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
4220
- import { existsSync as existsSync11 } from "fs";
4221
- import path14 from "path";
4222
- import os7 from "os";
4967
+ import { existsSync as existsSync13 } from "fs";
4968
+ import path16 from "path";
4969
+ import os8 from "os";
4223
4970
  function getKeyDir() {
4224
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path14.join(os7.homedir(), ".exe-os");
4971
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os8.homedir(), ".exe-os");
4225
4972
  }
4226
4973
  function getKeyPath() {
4227
- return path14.join(getKeyDir(), "master.key");
4974
+ return path16.join(getKeyDir(), "master.key");
4228
4975
  }
4229
4976
  async function tryKeytar() {
4230
4977
  try {
@@ -4245,13 +4992,21 @@ async function getMasterKey() {
4245
4992
  }
4246
4993
  }
4247
4994
  const keyPath = getKeyPath();
4248
- if (!existsSync11(keyPath)) {
4995
+ if (!existsSync13(keyPath)) {
4996
+ process.stderr.write(
4997
+ `[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
4998
+ `
4999
+ );
4249
5000
  return null;
4250
5001
  }
4251
5002
  try {
4252
5003
  const content = await readFile4(keyPath, "utf-8");
4253
5004
  return Buffer.from(content.trim(), "base64");
4254
- } catch {
5005
+ } catch (err) {
5006
+ process.stderr.write(
5007
+ `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
5008
+ `
5009
+ );
4255
5010
  return null;
4256
5011
  }
4257
5012
  }
@@ -4277,13 +5032,13 @@ __export(shard_manager_exports, {
4277
5032
  listShards: () => listShards,
4278
5033
  shardExists: () => shardExists
4279
5034
  });
4280
- import path15 from "path";
4281
- import { existsSync as existsSync12, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
5035
+ import path17 from "path";
5036
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync3 } from "fs";
4282
5037
  import { createClient as createClient2 } from "@libsql/client";
4283
5038
  function initShardManager(encryptionKey) {
4284
5039
  _encryptionKey = encryptionKey;
4285
- if (!existsSync12(SHARDS_DIR)) {
4286
- mkdirSync6(SHARDS_DIR, { recursive: true });
5040
+ if (!existsSync14(SHARDS_DIR)) {
5041
+ mkdirSync7(SHARDS_DIR, { recursive: true });
4287
5042
  }
4288
5043
  _shardingEnabled = true;
4289
5044
  }
@@ -4303,7 +5058,7 @@ function getShardClient(projectName) {
4303
5058
  }
4304
5059
  const cached = _shards.get(safeName);
4305
5060
  if (cached) return cached;
4306
- const dbPath = path15.join(SHARDS_DIR, `${safeName}.db`);
5061
+ const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
4307
5062
  const client = createClient2({
4308
5063
  url: `file:${dbPath}`,
4309
5064
  encryptionKey: _encryptionKey
@@ -4313,10 +5068,10 @@ function getShardClient(projectName) {
4313
5068
  }
4314
5069
  function shardExists(projectName) {
4315
5070
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
4316
- return existsSync12(path15.join(SHARDS_DIR, `${safeName}.db`));
5071
+ return existsSync14(path17.join(SHARDS_DIR, `${safeName}.db`));
4317
5072
  }
4318
5073
  function listShards() {
4319
- if (!existsSync12(SHARDS_DIR)) return [];
5074
+ if (!existsSync14(SHARDS_DIR)) return [];
4320
5075
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
4321
5076
  }
4322
5077
  async function ensureShardSchema(client) {
@@ -4502,7 +5257,7 @@ var init_shard_manager = __esm({
4502
5257
  "src/lib/shard-manager.ts"() {
4503
5258
  "use strict";
4504
5259
  init_config();
4505
- SHARDS_DIR = path15.join(EXE_AI_DIR, "shards");
5260
+ SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
4506
5261
  _shards = /* @__PURE__ */ new Map();
4507
5262
  _encryptionKey = null;
4508
5263
  _shardingEnabled = false;
@@ -4627,7 +5382,7 @@ __export(global_procedures_exports, {
4627
5382
  loadGlobalProcedures: () => loadGlobalProcedures,
4628
5383
  storeGlobalProcedure: () => storeGlobalProcedure
4629
5384
  });
4630
- import { randomUUID as randomUUID2 } from "crypto";
5385
+ import { randomUUID as randomUUID3 } from "crypto";
4631
5386
  async function loadGlobalProcedures() {
4632
5387
  const client = getClient();
4633
5388
  const result = await client.execute({
@@ -4656,7 +5411,7 @@ ${sections.join("\n\n")}
4656
5411
  `;
4657
5412
  }
4658
5413
  async function storeGlobalProcedure(input) {
4659
- const id = randomUUID2();
5414
+ const id = randomUUID3();
4660
5415
  const now = (/* @__PURE__ */ new Date()).toISOString();
4661
5416
  const client = getClient();
4662
5417
  await client.execute({
@@ -4707,6 +5462,7 @@ __export(store_exports, {
4707
5462
  vectorToBlob: () => vectorToBlob,
4708
5463
  writeMemory: () => writeMemory
4709
5464
  });
5465
+ import { createHash } from "crypto";
4710
5466
  function isBusyError2(err) {
4711
5467
  if (err instanceof Error) {
4712
5468
  const msg = err.message.toLowerCase();
@@ -4780,12 +5536,52 @@ function classifyTier(record) {
4780
5536
  if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
4781
5537
  return 3;
4782
5538
  }
5539
+ function inferFilePaths(record) {
5540
+ if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
5541
+ const firstLine = record.raw_text.split("\n")[0] ?? "";
5542
+ const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
5543
+ return match ? JSON.stringify([match[1]]) : null;
5544
+ }
5545
+ function inferCommitHash(record) {
5546
+ if (record.tool_name !== "Bash") return null;
5547
+ const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
5548
+ return match ? match[1] : null;
5549
+ }
5550
+ function inferLanguageType(record) {
5551
+ const text = record.raw_text;
5552
+ if (!text || text.length < 10) return null;
5553
+ const trimmed = text.trimStart();
5554
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
5555
+ if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
5556
+ if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
5557
+ if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
5558
+ return "mixed";
5559
+ }
5560
+ function inferDomain(record) {
5561
+ const proj = (record.project_name ?? "").toLowerCase();
5562
+ if (proj.includes("marketing") || proj.includes("content")) return "marketing";
5563
+ if (proj.includes("crm") || proj.includes("customer")) return "customer";
5564
+ return null;
5565
+ }
4783
5566
  async function writeMemory(record) {
4784
5567
  if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
4785
5568
  throw new Error(
4786
5569
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
4787
5570
  );
4788
5571
  }
5572
+ const contentHash = createHash("md5").update(record.raw_text).digest("hex");
5573
+ if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
5574
+ return;
5575
+ }
5576
+ try {
5577
+ const client = getClient();
5578
+ const existing = await client.execute({
5579
+ sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
5580
+ args: [contentHash, record.agent_id]
5581
+ });
5582
+ if (existing.rows.length > 0) return;
5583
+ } catch {
5584
+ }
4789
5585
  const dbRow = {
4790
5586
  id: record.id,
4791
5587
  agent_id: record.agent_id,
@@ -4815,7 +5611,23 @@ async function writeMemory(record) {
4815
5611
  supersedes_id: record.supersedes_id ?? null,
4816
5612
  draft: record.draft ? 1 : 0,
4817
5613
  memory_type: record.memory_type ?? "raw",
4818
- trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
5614
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
5615
+ content_hash: contentHash,
5616
+ intent: record.intent ?? null,
5617
+ outcome: record.outcome ?? null,
5618
+ domain: record.domain ?? inferDomain(record),
5619
+ referenced_entities: record.referenced_entities ?? null,
5620
+ retrieval_count: record.retrieval_count ?? 0,
5621
+ chain_position: record.chain_position ?? null,
5622
+ review_status: record.review_status ?? null,
5623
+ context_window_pct: record.context_window_pct ?? null,
5624
+ file_paths: record.file_paths ?? inferFilePaths(record),
5625
+ commit_hash: record.commit_hash ?? inferCommitHash(record),
5626
+ duration_ms: record.duration_ms ?? null,
5627
+ token_cost: record.token_cost ?? null,
5628
+ audience: record.audience ?? null,
5629
+ language_type: record.language_type ?? inferLanguageType(record),
5630
+ parent_memory_id: record.parent_memory_id ?? null
4819
5631
  };
4820
5632
  _pendingRecords.push(dbRow);
4821
5633
  orgBus.emit({
@@ -4873,80 +5685,85 @@ async function flushBatch() {
4873
5685
  const draft = row.draft ? 1 : 0;
4874
5686
  const memoryType = row.memory_type ?? "raw";
4875
5687
  const trajectory = row.trajectory ?? null;
4876
- return {
4877
- sql: hasVector ? `INSERT OR IGNORE INTO memories
4878
- (id, agent_id, agent_role, session_id, timestamp,
5688
+ const contentHash = row.content_hash ?? null;
5689
+ const intent = row.intent ?? null;
5690
+ const outcome = row.outcome ?? null;
5691
+ const domain = row.domain ?? null;
5692
+ const referencedEntities = row.referenced_entities ?? null;
5693
+ const retrievalCount = row.retrieval_count ?? 0;
5694
+ const chainPosition = row.chain_position ?? null;
5695
+ const reviewStatus = row.review_status ?? null;
5696
+ const contextWindowPct = row.context_window_pct ?? null;
5697
+ const filePaths = row.file_paths ?? null;
5698
+ const commitHash = row.commit_hash ?? null;
5699
+ const durationMs = row.duration_ms ?? null;
5700
+ const tokenCost = row.token_cost ?? null;
5701
+ const audience = row.audience ?? null;
5702
+ const languageType = row.language_type ?? null;
5703
+ const parentMemoryId = row.parent_memory_id ?? null;
5704
+ const cols = `id, agent_id, agent_role, session_id, timestamp,
4879
5705
  tool_name, project_name,
4880
5706
  has_error, raw_text, vector, version, task_id, importance, status,
4881
5707
  confidence, last_accessed,
4882
5708
  workspace_id, document_id, user_id, char_offset, page_number,
4883
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
4884
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
4885
- (id, agent_id, agent_role, session_id, timestamp,
4886
- tool_name, project_name,
4887
- has_error, raw_text, vector, version, task_id, importance, status,
4888
- confidence, last_accessed,
4889
- workspace_id, document_id, user_id, char_offset, page_number,
4890
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
4891
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4892
- args: hasVector ? [
4893
- row.id,
4894
- row.agent_id,
4895
- row.agent_role,
4896
- row.session_id,
4897
- row.timestamp,
4898
- row.tool_name,
4899
- row.project_name,
4900
- row.has_error,
4901
- row.raw_text,
4902
- vectorToBlob(row.vector),
4903
- row.version,
4904
- taskId,
4905
- importance,
4906
- status,
4907
- confidence,
4908
- lastAccessed,
4909
- workspaceId,
4910
- documentId,
4911
- userId,
4912
- charOffset,
4913
- pageNumber,
4914
- sourcePath,
4915
- sourceType,
4916
- tier,
4917
- supersedesId,
4918
- draft,
4919
- memoryType,
4920
- trajectory
4921
- ] : [
4922
- row.id,
4923
- row.agent_id,
4924
- row.agent_role,
4925
- row.session_id,
4926
- row.timestamp,
4927
- row.tool_name,
4928
- row.project_name,
4929
- row.has_error,
4930
- row.raw_text,
4931
- row.version,
4932
- taskId,
4933
- importance,
4934
- status,
4935
- confidence,
4936
- lastAccessed,
4937
- workspaceId,
4938
- documentId,
4939
- userId,
4940
- charOffset,
4941
- pageNumber,
4942
- sourcePath,
4943
- sourceType,
4944
- tier,
4945
- supersedesId,
4946
- draft,
4947
- memoryType,
4948
- trajectory
4949
- ]
5709
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
5710
+ intent, outcome, domain, referenced_entities, retrieval_count,
5711
+ chain_position, review_status, context_window_pct, file_paths, commit_hash,
5712
+ duration_ms, token_cost, audience, language_type, parent_memory_id`;
5713
+ const metaArgs = [
5714
+ intent,
5715
+ outcome,
5716
+ domain,
5717
+ referencedEntities,
5718
+ retrievalCount,
5719
+ chainPosition,
5720
+ reviewStatus,
5721
+ contextWindowPct,
5722
+ filePaths,
5723
+ commitHash,
5724
+ durationMs,
5725
+ tokenCost,
5726
+ audience,
5727
+ languageType,
5728
+ parentMemoryId
5729
+ ];
5730
+ const baseArgs = [
5731
+ row.id,
5732
+ row.agent_id,
5733
+ row.agent_role,
5734
+ row.session_id,
5735
+ row.timestamp,
5736
+ row.tool_name,
5737
+ row.project_name,
5738
+ row.has_error,
5739
+ row.raw_text
5740
+ ];
5741
+ const sharedArgs = [
5742
+ row.version,
5743
+ taskId,
5744
+ importance,
5745
+ status,
5746
+ confidence,
5747
+ lastAccessed,
5748
+ workspaceId,
5749
+ documentId,
5750
+ userId,
5751
+ charOffset,
5752
+ pageNumber,
5753
+ sourcePath,
5754
+ sourceType,
5755
+ tier,
5756
+ supersedesId,
5757
+ draft,
5758
+ memoryType,
5759
+ trajectory,
5760
+ contentHash
5761
+ ];
5762
+ return {
5763
+ sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
5764
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
5765
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5766
+ args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
4950
5767
  };
4951
5768
  };
4952
5769
  const globalClient = getClient();