@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
@@ -544,18 +544,69 @@ var init_provider_table = __esm({
544
544
  }
545
545
  });
546
546
 
547
- // src/lib/intercom-queue.ts
548
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
547
+ // src/lib/runtime-table.ts
548
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
549
+ var init_runtime_table = __esm({
550
+ "src/lib/runtime-table.ts"() {
551
+ "use strict";
552
+ RUNTIME_TABLE = {
553
+ codex: {
554
+ binary: "codex",
555
+ launchMode: "exec",
556
+ autoApproveFlag: "--full-auto",
557
+ inlineFlag: "--no-alt-screen",
558
+ apiKeyEnv: "OPENAI_API_KEY",
559
+ defaultModel: "gpt-5.4"
560
+ }
561
+ };
562
+ DEFAULT_RUNTIME = "claude";
563
+ }
564
+ });
565
+
566
+ // src/lib/agent-config.ts
567
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
549
568
  import path5 from "path";
569
+ function loadAgentConfig() {
570
+ if (!existsSync4(AGENT_CONFIG_PATH)) return {};
571
+ try {
572
+ return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
573
+ } catch {
574
+ return {};
575
+ }
576
+ }
577
+ function getAgentRuntime(agentId) {
578
+ const config = loadAgentConfig();
579
+ const entry = config[agentId];
580
+ if (entry) return entry;
581
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
582
+ }
583
+ var AGENT_CONFIG_PATH, DEFAULT_MODELS;
584
+ var init_agent_config = __esm({
585
+ "src/lib/agent-config.ts"() {
586
+ "use strict";
587
+ init_config();
588
+ init_runtime_table();
589
+ AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
590
+ DEFAULT_MODELS = {
591
+ claude: "claude-opus-4",
592
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
593
+ opencode: "minimax-m2.7"
594
+ };
595
+ }
596
+ });
597
+
598
+ // src/lib/intercom-queue.ts
599
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
600
+ import path6 from "path";
550
601
  import os5 from "os";
551
602
  function ensureDir() {
552
- const dir = path5.dirname(QUEUE_PATH);
553
- if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
603
+ const dir = path6.dirname(QUEUE_PATH);
604
+ if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
554
605
  }
555
606
  function readQueue() {
556
607
  try {
557
- if (!existsSync4(QUEUE_PATH)) return [];
558
- return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
608
+ if (!existsSync5(QUEUE_PATH)) return [];
609
+ return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
559
610
  } catch {
560
611
  return [];
561
612
  }
@@ -563,7 +614,7 @@ function readQueue() {
563
614
  function writeQueue(queue) {
564
615
  ensureDir();
565
616
  const tmp = `${QUEUE_PATH}.tmp`;
566
- writeFileSync3(tmp, JSON.stringify(queue, null, 2));
617
+ writeFileSync4(tmp, JSON.stringify(queue, null, 2));
567
618
  renameSync3(tmp, QUEUE_PATH);
568
619
  }
569
620
  function queueIntercom(targetSession, reason) {
@@ -587,9 +638,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
587
638
  var init_intercom_queue = __esm({
588
639
  "src/lib/intercom-queue.ts"() {
589
640
  "use strict";
590
- QUEUE_PATH = path5.join(os5.homedir(), ".exe-os", "intercom-queue.json");
641
+ QUEUE_PATH = path6.join(os5.homedir(), ".exe-os", "intercom-queue.json");
591
642
  TTL_MS = 60 * 60 * 1e3;
592
- INTERCOM_LOG = path5.join(os5.homedir(), ".exe-os", "intercom.log");
643
+ INTERCOM_LOG = path6.join(os5.homedir(), ".exe-os", "intercom.log");
593
644
  }
594
645
  });
595
646
 
@@ -648,6 +699,443 @@ var init_db_retry = __esm({
648
699
  }
649
700
  });
650
701
 
702
+ // src/lib/exe-daemon-client.ts
703
+ import net from "net";
704
+ import { spawn } from "child_process";
705
+ import { randomUUID as randomUUID2 } from "crypto";
706
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
707
+ import path7 from "path";
708
+ import { fileURLToPath } from "url";
709
+ function handleData(chunk) {
710
+ _buffer += chunk.toString();
711
+ if (_buffer.length > MAX_BUFFER) {
712
+ _buffer = "";
713
+ return;
714
+ }
715
+ let newlineIdx;
716
+ while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
717
+ const line = _buffer.slice(0, newlineIdx).trim();
718
+ _buffer = _buffer.slice(newlineIdx + 1);
719
+ if (!line) continue;
720
+ try {
721
+ const response = JSON.parse(line);
722
+ const id = response.id;
723
+ if (!id) continue;
724
+ const entry = _pending.get(id);
725
+ if (entry) {
726
+ clearTimeout(entry.timer);
727
+ _pending.delete(id);
728
+ entry.resolve(response);
729
+ }
730
+ } catch {
731
+ }
732
+ }
733
+ }
734
+ function cleanupStaleFiles() {
735
+ if (existsSync6(PID_PATH)) {
736
+ try {
737
+ const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
738
+ if (pid > 0) {
739
+ try {
740
+ process.kill(pid, 0);
741
+ return;
742
+ } catch {
743
+ }
744
+ }
745
+ } catch {
746
+ }
747
+ try {
748
+ unlinkSync2(PID_PATH);
749
+ } catch {
750
+ }
751
+ try {
752
+ unlinkSync2(SOCKET_PATH);
753
+ } catch {
754
+ }
755
+ }
756
+ }
757
+ function findPackageRoot() {
758
+ let dir = path7.dirname(fileURLToPath(import.meta.url));
759
+ const { root } = path7.parse(dir);
760
+ while (dir !== root) {
761
+ if (existsSync6(path7.join(dir, "package.json"))) return dir;
762
+ dir = path7.dirname(dir);
763
+ }
764
+ return null;
765
+ }
766
+ function spawnDaemon() {
767
+ const pkgRoot = findPackageRoot();
768
+ if (!pkgRoot) {
769
+ process.stderr.write("[exed-client] WARN: cannot find package root\n");
770
+ return;
771
+ }
772
+ const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
773
+ if (!existsSync6(daemonPath)) {
774
+ process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
775
+ `);
776
+ return;
777
+ }
778
+ const resolvedPath = daemonPath;
779
+ process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
780
+ `);
781
+ const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
782
+ let stderrFd = "ignore";
783
+ try {
784
+ stderrFd = openSync(logPath, "a");
785
+ } catch {
786
+ }
787
+ const child = spawn(process.execPath, [resolvedPath], {
788
+ detached: true,
789
+ stdio: ["ignore", "ignore", stderrFd],
790
+ env: {
791
+ ...process.env,
792
+ TMUX: void 0,
793
+ // Daemon is global — must not inherit session scope
794
+ TMUX_PANE: void 0,
795
+ // Prevents resolveExeSession() from scoping to one session
796
+ EXE_DAEMON_SOCK: SOCKET_PATH,
797
+ EXE_DAEMON_PID: PID_PATH
798
+ }
799
+ });
800
+ child.unref();
801
+ if (typeof stderrFd === "number") {
802
+ try {
803
+ closeSync(stderrFd);
804
+ } catch {
805
+ }
806
+ }
807
+ }
808
+ function acquireSpawnLock() {
809
+ try {
810
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
811
+ closeSync(fd);
812
+ return true;
813
+ } catch {
814
+ try {
815
+ const stat = statSync(SPAWN_LOCK_PATH);
816
+ if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
817
+ try {
818
+ unlinkSync2(SPAWN_LOCK_PATH);
819
+ } catch {
820
+ }
821
+ try {
822
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
823
+ closeSync(fd);
824
+ return true;
825
+ } catch {
826
+ }
827
+ }
828
+ } catch {
829
+ }
830
+ return false;
831
+ }
832
+ }
833
+ function releaseSpawnLock() {
834
+ try {
835
+ unlinkSync2(SPAWN_LOCK_PATH);
836
+ } catch {
837
+ }
838
+ }
839
+ function connectToSocket() {
840
+ return new Promise((resolve) => {
841
+ if (_socket && _connected) {
842
+ resolve(true);
843
+ return;
844
+ }
845
+ const socket = net.createConnection({ path: SOCKET_PATH });
846
+ const connectTimeout = setTimeout(() => {
847
+ socket.destroy();
848
+ resolve(false);
849
+ }, 2e3);
850
+ socket.on("connect", () => {
851
+ clearTimeout(connectTimeout);
852
+ _socket = socket;
853
+ _connected = true;
854
+ _buffer = "";
855
+ socket.on("data", handleData);
856
+ socket.on("close", () => {
857
+ _connected = false;
858
+ _socket = null;
859
+ for (const [id, entry] of _pending) {
860
+ clearTimeout(entry.timer);
861
+ _pending.delete(id);
862
+ entry.resolve({ error: "Connection closed" });
863
+ }
864
+ });
865
+ socket.on("error", () => {
866
+ _connected = false;
867
+ _socket = null;
868
+ });
869
+ resolve(true);
870
+ });
871
+ socket.on("error", () => {
872
+ clearTimeout(connectTimeout);
873
+ resolve(false);
874
+ });
875
+ });
876
+ }
877
+ async function connectEmbedDaemon() {
878
+ if (_socket && _connected) return true;
879
+ if (await connectToSocket()) return true;
880
+ if (acquireSpawnLock()) {
881
+ try {
882
+ cleanupStaleFiles();
883
+ spawnDaemon();
884
+ } finally {
885
+ releaseSpawnLock();
886
+ }
887
+ }
888
+ const start = Date.now();
889
+ let delay2 = 100;
890
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
891
+ await new Promise((r) => setTimeout(r, delay2));
892
+ if (await connectToSocket()) return true;
893
+ delay2 = Math.min(delay2 * 2, 3e3);
894
+ }
895
+ return false;
896
+ }
897
+ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
898
+ return new Promise((resolve) => {
899
+ if (!_socket || !_connected) {
900
+ resolve({ error: "Not connected" });
901
+ return;
902
+ }
903
+ const id = randomUUID2();
904
+ const timer = setTimeout(() => {
905
+ _pending.delete(id);
906
+ resolve({ error: "Request timeout" });
907
+ }, timeoutMs);
908
+ _pending.set(id, { resolve, timer });
909
+ try {
910
+ _socket.write(JSON.stringify({ id, ...payload }) + "\n");
911
+ } catch {
912
+ clearTimeout(timer);
913
+ _pending.delete(id);
914
+ resolve({ error: "Write failed" });
915
+ }
916
+ });
917
+ }
918
+ function isClientConnected() {
919
+ return _connected;
920
+ }
921
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
922
+ var init_exe_daemon_client = __esm({
923
+ "src/lib/exe-daemon-client.ts"() {
924
+ "use strict";
925
+ init_config();
926
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
927
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
928
+ SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
929
+ SPAWN_LOCK_STALE_MS = 3e4;
930
+ CONNECT_TIMEOUT_MS = 15e3;
931
+ REQUEST_TIMEOUT_MS = 3e4;
932
+ _socket = null;
933
+ _connected = false;
934
+ _buffer = "";
935
+ _pending = /* @__PURE__ */ new Map();
936
+ MAX_BUFFER = 1e7;
937
+ }
938
+ });
939
+
940
+ // src/lib/daemon-protocol.ts
941
+ function serializeValue(v) {
942
+ if (v === null || v === void 0) return null;
943
+ if (typeof v === "bigint") return Number(v);
944
+ if (typeof v === "boolean") return v ? 1 : 0;
945
+ if (v instanceof Uint8Array) {
946
+ return { __blob: Buffer.from(v).toString("base64") };
947
+ }
948
+ if (ArrayBuffer.isView(v)) {
949
+ return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
950
+ }
951
+ if (v instanceof ArrayBuffer) {
952
+ return { __blob: Buffer.from(v).toString("base64") };
953
+ }
954
+ if (typeof v === "string" || typeof v === "number") return v;
955
+ return String(v);
956
+ }
957
+ function deserializeValue(v) {
958
+ if (v === null) return null;
959
+ if (typeof v === "object" && v !== null && "__blob" in v) {
960
+ const buf = Buffer.from(v.__blob, "base64");
961
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
962
+ }
963
+ return v;
964
+ }
965
+ function deserializeResultSet(srs) {
966
+ const rows = srs.rows.map((obj) => {
967
+ const values = srs.columns.map(
968
+ (col) => deserializeValue(obj[col] ?? null)
969
+ );
970
+ const row = values;
971
+ for (let i = 0; i < srs.columns.length; i++) {
972
+ const col = srs.columns[i];
973
+ if (col !== void 0) {
974
+ row[col] = values[i] ?? null;
975
+ }
976
+ }
977
+ Object.defineProperty(row, "length", {
978
+ value: values.length,
979
+ enumerable: false
980
+ });
981
+ return row;
982
+ });
983
+ return {
984
+ columns: srs.columns,
985
+ columnTypes: srs.columnTypes ?? [],
986
+ rows,
987
+ rowsAffected: srs.rowsAffected,
988
+ lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
989
+ toJSON: () => ({
990
+ columns: srs.columns,
991
+ columnTypes: srs.columnTypes ?? [],
992
+ rows: srs.rows,
993
+ rowsAffected: srs.rowsAffected,
994
+ lastInsertRowid: srs.lastInsertRowid
995
+ })
996
+ };
997
+ }
998
+ var init_daemon_protocol = __esm({
999
+ "src/lib/daemon-protocol.ts"() {
1000
+ "use strict";
1001
+ }
1002
+ });
1003
+
1004
+ // src/lib/db-daemon-client.ts
1005
+ var db_daemon_client_exports = {};
1006
+ __export(db_daemon_client_exports, {
1007
+ createDaemonDbClient: () => createDaemonDbClient,
1008
+ initDaemonDbClient: () => initDaemonDbClient
1009
+ });
1010
+ function normalizeStatement(stmt) {
1011
+ if (typeof stmt === "string") {
1012
+ return { sql: stmt, args: [] };
1013
+ }
1014
+ const sql = stmt.sql;
1015
+ let args = [];
1016
+ if (Array.isArray(stmt.args)) {
1017
+ args = stmt.args.map((v) => serializeValue(v));
1018
+ } else if (stmt.args && typeof stmt.args === "object") {
1019
+ const named = {};
1020
+ for (const [key, val] of Object.entries(stmt.args)) {
1021
+ named[key] = serializeValue(val);
1022
+ }
1023
+ return { sql, args: named };
1024
+ }
1025
+ return { sql, args };
1026
+ }
1027
+ function createDaemonDbClient(fallbackClient) {
1028
+ let _useDaemon = false;
1029
+ const client = {
1030
+ async execute(stmt) {
1031
+ if (!_useDaemon || !isClientConnected()) {
1032
+ return fallbackClient.execute(stmt);
1033
+ }
1034
+ const { sql, args } = normalizeStatement(stmt);
1035
+ const response = await sendDaemonRequest({
1036
+ type: "db-execute",
1037
+ sql,
1038
+ args
1039
+ });
1040
+ if (response.error) {
1041
+ const errMsg = String(response.error);
1042
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
1043
+ process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
1044
+ `);
1045
+ return fallbackClient.execute(stmt);
1046
+ }
1047
+ throw new Error(errMsg);
1048
+ }
1049
+ if (response.db) {
1050
+ return deserializeResultSet(response.db);
1051
+ }
1052
+ process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
1053
+ return fallbackClient.execute(stmt);
1054
+ },
1055
+ async batch(stmts, mode) {
1056
+ if (!_useDaemon || !isClientConnected()) {
1057
+ return fallbackClient.batch(stmts, mode);
1058
+ }
1059
+ const statements = stmts.map(normalizeStatement);
1060
+ const response = await sendDaemonRequest({
1061
+ type: "db-batch",
1062
+ statements,
1063
+ mode: mode ?? "deferred"
1064
+ });
1065
+ if (response.error) {
1066
+ const errMsg = String(response.error);
1067
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
1068
+ process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
1069
+ `);
1070
+ return fallbackClient.batch(stmts, mode);
1071
+ }
1072
+ throw new Error(errMsg);
1073
+ }
1074
+ const batchResults = response["db-batch"];
1075
+ if (batchResults) {
1076
+ return batchResults.map(deserializeResultSet);
1077
+ }
1078
+ process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
1079
+ return fallbackClient.batch(stmts, mode);
1080
+ },
1081
+ // Transaction support — delegate to fallback (transactions need direct connection)
1082
+ async transaction(mode) {
1083
+ return fallbackClient.transaction(mode);
1084
+ },
1085
+ // executeMultiple — delegate to fallback (used only for schema migrations)
1086
+ async executeMultiple(sql) {
1087
+ return fallbackClient.executeMultiple(sql);
1088
+ },
1089
+ // migrate — delegate to fallback
1090
+ async migrate(stmts) {
1091
+ return fallbackClient.migrate(stmts);
1092
+ },
1093
+ // Sync mode — delegate to fallback
1094
+ sync() {
1095
+ return fallbackClient.sync();
1096
+ },
1097
+ close() {
1098
+ _useDaemon = false;
1099
+ },
1100
+ get closed() {
1101
+ return fallbackClient.closed;
1102
+ },
1103
+ get protocol() {
1104
+ return fallbackClient.protocol;
1105
+ }
1106
+ };
1107
+ return {
1108
+ ...client,
1109
+ /** Enable daemon routing (call after confirming daemon is connected) */
1110
+ _enableDaemon() {
1111
+ _useDaemon = true;
1112
+ },
1113
+ /** Check if daemon routing is active */
1114
+ _isDaemonActive() {
1115
+ return _useDaemon && isClientConnected();
1116
+ }
1117
+ };
1118
+ }
1119
+ async function initDaemonDbClient(fallbackClient) {
1120
+ if (process.env.EXE_IS_DAEMON === "1") return null;
1121
+ const connected = await connectEmbedDaemon();
1122
+ if (!connected) {
1123
+ process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
1124
+ return null;
1125
+ }
1126
+ const client = createDaemonDbClient(fallbackClient);
1127
+ client._enableDaemon();
1128
+ process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
1129
+ return client;
1130
+ }
1131
+ var init_db_daemon_client = __esm({
1132
+ "src/lib/db-daemon-client.ts"() {
1133
+ "use strict";
1134
+ init_exe_daemon_client();
1135
+ init_daemon_protocol();
1136
+ }
1137
+ });
1138
+
651
1139
  // src/lib/database.ts
652
1140
  var database_exports = {};
653
1141
  __export(database_exports, {
@@ -656,6 +1144,7 @@ __export(database_exports, {
656
1144
  ensureSchema: () => ensureSchema,
657
1145
  getClient: () => getClient,
658
1146
  getRawClient: () => getRawClient,
1147
+ initDaemonClient: () => initDaemonClient,
659
1148
  initDatabase: () => initDatabase,
660
1149
  initTurso: () => initTurso,
661
1150
  isInitialized: () => isInitialized
@@ -683,8 +1172,27 @@ function getClient() {
683
1172
  if (!_resilientClient) {
684
1173
  throw new Error("Database client not initialized. Call initDatabase() first.");
685
1174
  }
1175
+ if (process.env.EXE_IS_DAEMON === "1") {
1176
+ return _resilientClient;
1177
+ }
1178
+ if (_daemonClient && _daemonClient._isDaemonActive()) {
1179
+ return _daemonClient;
1180
+ }
686
1181
  return _resilientClient;
687
1182
  }
1183
+ async function initDaemonClient() {
1184
+ if (process.env.EXE_IS_DAEMON === "1") return;
1185
+ if (!_resilientClient) return;
1186
+ try {
1187
+ const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
1188
+ _daemonClient = await initDaemonDbClient2(_resilientClient);
1189
+ } catch (err) {
1190
+ process.stderr.write(
1191
+ `[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
1192
+ `
1193
+ );
1194
+ }
1195
+ }
688
1196
  function getRawClient() {
689
1197
  if (!_client) {
690
1198
  throw new Error("Database client not initialized. Call initDatabase() first.");
@@ -1171,6 +1679,12 @@ async function ensureSchema() {
1171
1679
  } catch {
1172
1680
  }
1173
1681
  }
1682
+ try {
1683
+ await client.execute(
1684
+ `CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
1685
+ );
1686
+ } catch {
1687
+ }
1174
1688
  await client.executeMultiple(`
1175
1689
  CREATE TABLE IF NOT EXISTS entities (
1176
1690
  id TEXT PRIMARY KEY,
@@ -1223,7 +1737,30 @@ async function ensureSchema() {
1223
1737
  entity_id TEXT NOT NULL,
1224
1738
  PRIMARY KEY (hyperedge_id, entity_id)
1225
1739
  );
1740
+
1741
+ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
1742
+ name,
1743
+ content=entities,
1744
+ content_rowid=rowid
1745
+ );
1746
+
1747
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
1748
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
1749
+ END;
1750
+
1751
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
1752
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
1753
+ END;
1754
+
1755
+ CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
1756
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
1757
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
1758
+ END;
1226
1759
  `);
1760
+ try {
1761
+ await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
1762
+ } catch {
1763
+ }
1227
1764
  await client.executeMultiple(`
1228
1765
  CREATE TABLE IF NOT EXISTS entity_aliases (
1229
1766
  alias TEXT NOT NULL PRIMARY KEY,
@@ -1404,6 +1941,33 @@ async function ensureSchema() {
1404
1941
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
1405
1942
  ON conversations(channel_id);
1406
1943
  `);
1944
+ await client.executeMultiple(`
1945
+ CREATE TABLE IF NOT EXISTS session_agent_map (
1946
+ session_uuid TEXT PRIMARY KEY,
1947
+ agent_id TEXT NOT NULL,
1948
+ session_name TEXT,
1949
+ task_id TEXT,
1950
+ project_name TEXT,
1951
+ started_at TEXT NOT NULL
1952
+ );
1953
+
1954
+ CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
1955
+ ON session_agent_map(agent_id);
1956
+ `);
1957
+ try {
1958
+ const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
1959
+ if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
1960
+ await client.execute({
1961
+ sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
1962
+ SELECT session_id, agent_id, '', MIN(timestamp)
1963
+ FROM memories
1964
+ WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
1965
+ GROUP BY session_id, agent_id`,
1966
+ args: []
1967
+ });
1968
+ }
1969
+ } catch {
1970
+ }
1407
1971
  try {
1408
1972
  await client.execute({
1409
1973
  sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
@@ -1537,15 +2101,41 @@ async function ensureSchema() {
1537
2101
  });
1538
2102
  } catch {
1539
2103
  }
2104
+ for (const col of [
2105
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
2106
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
2107
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
2108
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
2109
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
2110
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
2111
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
2112
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
2113
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
2114
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
2115
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
2116
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
2117
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
2118
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
2119
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2120
+ ]) {
2121
+ try {
2122
+ await client.execute(col);
2123
+ } catch {
2124
+ }
2125
+ }
1540
2126
  }
1541
2127
  async function disposeDatabase() {
2128
+ if (_daemonClient) {
2129
+ _daemonClient.close();
2130
+ _daemonClient = null;
2131
+ }
1542
2132
  if (_client) {
1543
2133
  _client.close();
1544
2134
  _client = null;
1545
2135
  _resilientClient = null;
1546
2136
  }
1547
2137
  }
1548
- var _client, _resilientClient, initTurso, disposeTurso;
2138
+ var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
1549
2139
  var init_database = __esm({
1550
2140
  "src/lib/database.ts"() {
1551
2141
  "use strict";
@@ -1553,24 +2143,25 @@ var init_database = __esm({
1553
2143
  init_employees();
1554
2144
  _client = null;
1555
2145
  _resilientClient = null;
2146
+ _daemonClient = null;
1556
2147
  initTurso = initDatabase;
1557
2148
  disposeTurso = disposeDatabase;
1558
2149
  }
1559
2150
  });
1560
2151
 
1561
2152
  // src/lib/license.ts
1562
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
1563
- import { randomUUID as randomUUID2 } from "crypto";
1564
- import path6 from "path";
2153
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2154
+ import { randomUUID as randomUUID3 } from "crypto";
2155
+ import path8 from "path";
1565
2156
  import { jwtVerify, importSPKI } from "jose";
1566
2157
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
1567
2158
  var init_license = __esm({
1568
2159
  "src/lib/license.ts"() {
1569
2160
  "use strict";
1570
2161
  init_config();
1571
- LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
1572
- CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
1573
- DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
2162
+ LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
2163
+ CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
2164
+ DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
1574
2165
  PLAN_LIMITS = {
1575
2166
  free: { devices: 1, employees: 1, memories: 5e3 },
1576
2167
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -1582,12 +2173,12 @@ var init_license = __esm({
1582
2173
  });
1583
2174
 
1584
2175
  // src/lib/plan-limits.ts
1585
- import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
1586
- import path7 from "path";
2176
+ import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
2177
+ import path9 from "path";
1587
2178
  function getLicenseSync() {
1588
2179
  try {
1589
- if (!existsSync6(CACHE_PATH2)) return freeLicense();
1590
- const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
2180
+ if (!existsSync8(CACHE_PATH2)) return freeLicense();
2181
+ const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
1591
2182
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
1592
2183
  const parts = raw.token.split(".");
1593
2184
  if (parts.length !== 3) return freeLicense();
@@ -1625,8 +2216,8 @@ function assertEmployeeLimitSync(rosterPath) {
1625
2216
  const filePath = rosterPath ?? EMPLOYEES_PATH;
1626
2217
  let count = 0;
1627
2218
  try {
1628
- if (existsSync6(filePath)) {
1629
- const raw = readFileSync6(filePath, "utf8");
2219
+ if (existsSync8(filePath)) {
2220
+ const raw = readFileSync8(filePath, "utf8");
1630
2221
  const employees = JSON.parse(raw);
1631
2222
  count = Array.isArray(employees) ? employees.length : 0;
1632
2223
  }
@@ -1655,19 +2246,19 @@ var init_plan_limits = __esm({
1655
2246
  this.name = "PlanLimitError";
1656
2247
  }
1657
2248
  };
1658
- CACHE_PATH2 = path7.join(EXE_AI_DIR, "license-cache.json");
2249
+ CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
1659
2250
  }
1660
2251
  });
1661
2252
 
1662
2253
  // src/lib/notifications.ts
1663
2254
  import crypto from "crypto";
1664
- import path8 from "path";
2255
+ import path10 from "path";
1665
2256
  import os6 from "os";
1666
2257
  import {
1667
- readFileSync as readFileSync7,
2258
+ readFileSync as readFileSync9,
1668
2259
  readdirSync,
1669
- unlinkSync as unlinkSync2,
1670
- existsSync as existsSync7,
2260
+ unlinkSync as unlinkSync3,
2261
+ existsSync as existsSync9,
1671
2262
  rmdirSync
1672
2263
  } from "fs";
1673
2264
  async function writeNotification(notification) {
@@ -1831,10 +2422,11 @@ var init_state_bus = __esm({
1831
2422
 
1832
2423
  // src/lib/tasks-crud.ts
1833
2424
  import crypto3 from "crypto";
1834
- import path9 from "path";
2425
+ import path11 from "path";
2426
+ import os7 from "os";
1835
2427
  import { execSync as execSync5 } from "child_process";
1836
2428
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
1837
- import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
2429
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
1838
2430
  async function writeCheckpoint(input) {
1839
2431
  const client = getClient();
1840
2432
  const row = await resolveTask(client, input.taskId);
@@ -1875,6 +2467,35 @@ function extractParentFromContext(contextBody) {
1875
2467
  function slugify(title) {
1876
2468
  return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
1877
2469
  }
2470
+ function buildKeywordIndex() {
2471
+ const idx = /* @__PURE__ */ new Map();
2472
+ for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
2473
+ for (const kw of keywords) {
2474
+ const existing = idx.get(kw) ?? [];
2475
+ existing.push(role);
2476
+ idx.set(kw, existing);
2477
+ }
2478
+ }
2479
+ return idx;
2480
+ }
2481
+ function checkLaneAffinity(title, context, assigneeName) {
2482
+ const employees = loadEmployeesSync();
2483
+ const employee = employees.find((e) => e.name === assigneeName);
2484
+ if (!employee) return void 0;
2485
+ const assigneeRole = employee.role;
2486
+ const text = `${title} ${context}`.toLowerCase();
2487
+ const matchedRoles = /* @__PURE__ */ new Set();
2488
+ for (const [keyword, roles] of KEYWORD_INDEX) {
2489
+ if (text.includes(keyword)) {
2490
+ for (const role of roles) matchedRoles.add(role);
2491
+ }
2492
+ }
2493
+ if (matchedRoles.size === 0) return void 0;
2494
+ if (matchedRoles.has(assigneeRole)) return void 0;
2495
+ if (assigneeRole === "COO") return void 0;
2496
+ const expectedRoles = Array.from(matchedRoles).join(" or ");
2497
+ return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
2498
+ }
1878
2499
  async function resolveTask(client, identifier, scopeSession) {
1879
2500
  const scope = sessionScopeFilter(scopeSession);
1880
2501
  let result = await client.execute({
@@ -1924,7 +2545,14 @@ async function createTaskCore(input) {
1924
2545
  const id = crypto3.randomUUID();
1925
2546
  const now = (/* @__PURE__ */ new Date()).toISOString();
1926
2547
  const slug = slugify(input.title);
1927
- const taskFile = input.taskFile ?? `exe/${input.assignedTo}/${slug}.md`;
2548
+ let earlySessionScope = null;
2549
+ try {
2550
+ const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
2551
+ earlySessionScope = resolveExeSession2();
2552
+ } catch {
2553
+ }
2554
+ const scope = earlySessionScope ?? "default";
2555
+ const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
1928
2556
  let blockedById = null;
1929
2557
  const initialStatus = input.blockedBy ? "blocked" : "open";
1930
2558
  if (input.blockedBy) {
@@ -1964,22 +2592,24 @@ async function createTaskCore(input) {
1964
2592
  if (dupCheck.rows.length > 0) {
1965
2593
  warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
1966
2594
  }
2595
+ if (!process.env.DISABLE_LANE_AFFINITY) {
2596
+ const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
2597
+ if (laneWarning) {
2598
+ warning = warning ? `${warning}
2599
+ ${laneWarning}` : laneWarning;
2600
+ }
2601
+ }
1967
2602
  if (input.baseDir) {
1968
2603
  try {
1969
- await mkdir3(path9.join(input.baseDir, "exe", "output"), { recursive: true });
1970
- await mkdir3(path9.join(input.baseDir, "exe", "research"), { recursive: true });
2604
+ await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
2605
+ await mkdir3(path11.join(input.baseDir, "exe", "research"), { recursive: true });
1971
2606
  await ensureArchitectureDoc(input.baseDir, input.projectName);
1972
2607
  await ensureGitignoreExe(input.baseDir);
1973
2608
  } catch {
1974
2609
  }
1975
2610
  }
1976
2611
  const complexity = input.complexity ?? "standard";
1977
- let sessionScope = null;
1978
- try {
1979
- const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
1980
- sessionScope = resolveExeSession2();
1981
- } catch {
1982
- }
2612
+ const sessionScope = earlySessionScope;
1983
2613
  await client.execute({
1984
2614
  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)
1985
2615
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
@@ -2006,6 +2636,43 @@ async function createTaskCore(input) {
2006
2636
  now
2007
2637
  ]
2008
2638
  });
2639
+ if (input.baseDir) {
2640
+ try {
2641
+ const EXE_OS_DIR = path11.join(os7.homedir(), ".exe-os");
2642
+ const mdPath = path11.join(EXE_OS_DIR, taskFile);
2643
+ const mdDir = path11.dirname(mdPath);
2644
+ if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
2645
+ const reviewer = input.reviewer ?? input.assignedBy;
2646
+ const mdContent = `# ${input.title}
2647
+
2648
+ **ID:** ${id}
2649
+ **Status:** ${initialStatus}
2650
+ **Priority:** ${input.priority}
2651
+ **Assigned by:** ${input.assignedBy}
2652
+ **Assigned to:** ${input.assignedTo}
2653
+ **Project:** ${input.projectName}
2654
+ **Created:** ${now.split("T")[0]}${parentTaskId ? `
2655
+ **Parent task:** ${parentTaskId}` : ""}
2656
+ **Reviewer:** ${reviewer}
2657
+
2658
+ ## Context
2659
+
2660
+ ${input.context}
2661
+
2662
+ ## MANDATORY: When done
2663
+
2664
+ You MUST call update_task with status "done" and a result summary when finished.
2665
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
2666
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
2667
+ `;
2668
+ await writeFile3(mdPath, mdContent, "utf-8");
2669
+ } catch (err) {
2670
+ process.stderr.write(
2671
+ `[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
2672
+ `
2673
+ );
2674
+ }
2675
+ }
2009
2676
  return {
2010
2677
  id,
2011
2678
  title: input.title,
@@ -2198,7 +2865,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
2198
2865
  return { row, taskFile, now, taskId };
2199
2866
  }
2200
2867
  }
2201
- if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
2868
+ if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
2202
2869
  process.stderr.write(
2203
2870
  `[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
2204
2871
  `
@@ -2263,9 +2930,9 @@ async function deleteTaskCore(taskId, _baseDir) {
2263
2930
  return { taskFile, assignedTo, assignedBy, taskSlug };
2264
2931
  }
2265
2932
  async function ensureArchitectureDoc(baseDir, projectName) {
2266
- const archPath = path9.join(baseDir, "exe", "ARCHITECTURE.md");
2933
+ const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
2267
2934
  try {
2268
- if (existsSync8(archPath)) return;
2935
+ if (existsSync10(archPath)) return;
2269
2936
  const template = [
2270
2937
  `# ${projectName} \u2014 System Architecture`,
2271
2938
  "",
@@ -2298,10 +2965,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
2298
2965
  }
2299
2966
  }
2300
2967
  async function ensureGitignoreExe(baseDir) {
2301
- const gitignorePath = path9.join(baseDir, ".gitignore");
2968
+ const gitignorePath = path11.join(baseDir, ".gitignore");
2302
2969
  try {
2303
- if (existsSync8(gitignorePath)) {
2304
- const content = readFileSync8(gitignorePath, "utf-8");
2970
+ if (existsSync10(gitignorePath)) {
2971
+ const content = readFileSync10(gitignorePath, "utf-8");
2305
2972
  if (/^\/?exe\/?$/m.test(content)) return;
2306
2973
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
2307
2974
  } else {
@@ -2310,20 +2977,30 @@ async function ensureGitignoreExe(baseDir) {
2310
2977
  } catch {
2311
2978
  }
2312
2979
  }
2313
- var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
2980
+ var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
2314
2981
  var init_tasks_crud = __esm({
2315
2982
  "src/lib/tasks-crud.ts"() {
2316
2983
  "use strict";
2317
2984
  init_database();
2318
2985
  init_task_scope();
2986
+ init_employees();
2987
+ LANE_KEYWORDS = {
2988
+ CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
2989
+ CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
2990
+ "Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
2991
+ "Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
2992
+ "Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
2993
+ "AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
2994
+ };
2995
+ KEYWORD_INDEX = buildKeywordIndex();
2319
2996
  DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
2320
2997
  TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
2321
2998
  }
2322
2999
  });
2323
3000
 
2324
3001
  // src/lib/tasks-review.ts
2325
- import path10 from "path";
2326
- import { existsSync as existsSync9, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
3002
+ import path12 from "path";
3003
+ import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
2327
3004
  async function countPendingReviews(sessionScope) {
2328
3005
  const client = getClient();
2329
3006
  if (sessionScope) {
@@ -2345,7 +3022,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
2345
3022
  const result2 = await client.execute({
2346
3023
  sql: `SELECT COUNT(*) as cnt FROM tasks
2347
3024
  WHERE status = 'needs_review' AND updated_at > ?
2348
- AND (session_scope = ? OR session_scope IS NULL)`,
3025
+ AND session_scope = ?`,
2349
3026
  args: [sinceIso, sessionScope]
2350
3027
  });
2351
3028
  return Number(result2.rows[0]?.cnt) || 0;
@@ -2363,7 +3040,7 @@ async function listPendingReviews(limit, sessionScope) {
2363
3040
  const result2 = await client.execute({
2364
3041
  sql: `SELECT title, assigned_to, project_name FROM tasks
2365
3042
  WHERE status = 'needs_review'
2366
- AND (session_scope = ? OR session_scope IS NULL)
3043
+ AND session_scope = ?
2367
3044
  ORDER BY priority ASC, created_at DESC LIMIT ?`,
2368
3045
  args: [sessionScope, limit]
2369
3046
  });
@@ -2484,14 +3161,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
2484
3161
  if (parts.length >= 3 && parts[0] === "review") {
2485
3162
  const agent = parts[1];
2486
3163
  const slug = parts.slice(2).join("-");
2487
- const originalTaskFile = `exe/${agent}/${slug}.md`;
3164
+ const legacyTaskFile = `exe/${agent}/${slug}.md`;
2488
3165
  const result = await client.execute({
2489
- sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
2490
- args: [now, originalTaskFile]
3166
+ sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
3167
+ args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
2491
3168
  });
2492
3169
  if (result.rowsAffected > 0) {
2493
3170
  process.stderr.write(
2494
- `[review-cleanup] Cascaded original task to done (legacy path): ${originalTaskFile}
3171
+ `[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
2495
3172
  `
2496
3173
  );
2497
3174
  }
@@ -2504,11 +3181,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
2504
3181
  );
2505
3182
  }
2506
3183
  try {
2507
- const cacheDir = path10.join(EXE_AI_DIR, "session-cache");
2508
- if (existsSync9(cacheDir)) {
3184
+ const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
3185
+ if (existsSync11(cacheDir)) {
2509
3186
  for (const f of readdirSync2(cacheDir)) {
2510
3187
  if (f.startsWith("review-notified-")) {
2511
- unlinkSync3(path10.join(cacheDir, f));
3188
+ unlinkSync4(path12.join(cacheDir, f));
2512
3189
  }
2513
3190
  }
2514
3191
  }
@@ -2529,7 +3206,7 @@ var init_tasks_review = __esm({
2529
3206
  });
2530
3207
 
2531
3208
  // src/lib/tasks-chain.ts
2532
- import path11 from "path";
3209
+ import path13 from "path";
2533
3210
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
2534
3211
  async function cascadeUnblock(taskId, baseDir, now) {
2535
3212
  const client = getClient();
@@ -2546,7 +3223,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
2546
3223
  });
2547
3224
  for (const ur of unblockedRows.rows) {
2548
3225
  try {
2549
- const ubFile = path11.join(baseDir, String(ur.task_file));
3226
+ const ubFile = path13.join(baseDir, String(ur.task_file));
2550
3227
  let ubContent = await readFile3(ubFile, "utf-8");
2551
3228
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
2552
3229
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -2615,7 +3292,7 @@ var init_tasks_chain = __esm({
2615
3292
 
2616
3293
  // src/lib/project-name.ts
2617
3294
  import { execSync as execSync6 } from "child_process";
2618
- import path12 from "path";
3295
+ import path14 from "path";
2619
3296
  function getProjectName(cwd) {
2620
3297
  const dir = cwd ?? process.cwd();
2621
3298
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -2628,7 +3305,7 @@ function getProjectName(cwd) {
2628
3305
  timeout: 2e3,
2629
3306
  stdio: ["pipe", "pipe", "pipe"]
2630
3307
  }).trim();
2631
- repoRoot = path12.dirname(gitCommonDir);
3308
+ repoRoot = path14.dirname(gitCommonDir);
2632
3309
  } catch {
2633
3310
  repoRoot = execSync6("git rev-parse --show-toplevel", {
2634
3311
  cwd: dir,
@@ -2637,11 +3314,11 @@ function getProjectName(cwd) {
2637
3314
  stdio: ["pipe", "pipe", "pipe"]
2638
3315
  }).trim();
2639
3316
  }
2640
- _cached2 = path12.basename(repoRoot);
3317
+ _cached2 = path14.basename(repoRoot);
2641
3318
  _cachedCwd = dir;
2642
3319
  return _cached2;
2643
3320
  } catch {
2644
- _cached2 = path12.basename(dir);
3321
+ _cached2 = path14.basename(dir);
2645
3322
  _cachedCwd = dir;
2646
3323
  return _cached2;
2647
3324
  }
@@ -2673,7 +3350,7 @@ function findSessionForProject(projectName) {
2673
3350
  const sessions = listSessions();
2674
3351
  for (const s of sessions) {
2675
3352
  const proj = s.projectDir.split("/").filter(Boolean).pop();
2676
- if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
3353
+ if (proj === projectName && isCoordinatorName(s.agentId)) return s;
2677
3354
  }
2678
3355
  return null;
2679
3356
  }
@@ -2719,7 +3396,7 @@ var init_session_scope = __esm({
2719
3396
 
2720
3397
  // src/lib/tasks-notify.ts
2721
3398
  async function dispatchTaskToEmployee(input) {
2722
- if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
3399
+ if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
2723
3400
  let crossProject = false;
2724
3401
  if (input.projectName) {
2725
3402
  try {
@@ -3176,8 +3853,8 @@ __export(tasks_exports, {
3176
3853
  updateTaskStatus: () => updateTaskStatus,
3177
3854
  writeCheckpoint: () => writeCheckpoint
3178
3855
  });
3179
- import path13 from "path";
3180
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync4 } from "fs";
3856
+ import path15 from "path";
3857
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
3181
3858
  async function createTask(input) {
3182
3859
  const result = await createTaskCore(input);
3183
3860
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -3196,14 +3873,14 @@ async function updateTask(input) {
3196
3873
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
3197
3874
  try {
3198
3875
  const agent = String(row.assigned_to);
3199
- const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
3200
- const cachePath = path13.join(cacheDir, `current-task-${agent}.json`);
3876
+ const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
3877
+ const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
3201
3878
  if (input.status === "in_progress") {
3202
- mkdirSync4(cacheDir, { recursive: true });
3203
- writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3879
+ mkdirSync5(cacheDir, { recursive: true });
3880
+ writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3204
3881
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
3205
3882
  try {
3206
- unlinkSync4(cachePath);
3883
+ unlinkSync5(cachePath);
3207
3884
  } catch {
3208
3885
  }
3209
3886
  }
@@ -3260,7 +3937,7 @@ async function updateTask(input) {
3260
3937
  }
3261
3938
  const isTerminal = input.status === "done" || input.status === "needs_review";
3262
3939
  if (isTerminal) {
3263
- const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
3940
+ const isCoordinator = isCoordinatorName(String(row.assigned_to));
3264
3941
  if (!isCoordinator) {
3265
3942
  notifyTaskDone();
3266
3943
  }
@@ -3285,7 +3962,7 @@ async function updateTask(input) {
3285
3962
  }
3286
3963
  }
3287
3964
  }
3288
- if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
3965
+ if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
3289
3966
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
3290
3967
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
3291
3968
  taskId,
@@ -3301,7 +3978,7 @@ async function updateTask(input) {
3301
3978
  });
3302
3979
  }
3303
3980
  let nextTask;
3304
- if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
3981
+ if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
3305
3982
  try {
3306
3983
  nextTask = await findNextTask(String(row.assigned_to));
3307
3984
  } catch {
@@ -3645,7 +4322,7 @@ var init_capacity_monitor = __esm({
3645
4322
  // src/lib/tmux-routing.ts
3646
4323
  var tmux_routing_exports = {};
3647
4324
  __export(tmux_routing_exports, {
3648
- acquireSpawnLock: () => acquireSpawnLock,
4325
+ acquireSpawnLock: () => acquireSpawnLock2,
3649
4326
  employeeSessionName: () => employeeSessionName,
3650
4327
  ensureEmployee: () => ensureEmployee,
3651
4328
  extractRootExe: () => extractRootExe,
@@ -3660,20 +4337,20 @@ __export(tmux_routing_exports, {
3660
4337
  notifyParentExe: () => notifyParentExe,
3661
4338
  parseParentExe: () => parseParentExe,
3662
4339
  registerParentExe: () => registerParentExe,
3663
- releaseSpawnLock: () => releaseSpawnLock,
4340
+ releaseSpawnLock: () => releaseSpawnLock2,
3664
4341
  resolveExeSession: () => resolveExeSession,
3665
4342
  sendIntercom: () => sendIntercom,
3666
4343
  spawnEmployee: () => spawnEmployee,
3667
4344
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
3668
4345
  });
3669
4346
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
3670
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync } from "fs";
3671
- import path14 from "path";
3672
- import os7 from "os";
3673
- import { fileURLToPath } from "url";
3674
- import { unlinkSync as unlinkSync5 } from "fs";
4347
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync } from "fs";
4348
+ import path16 from "path";
4349
+ import os8 from "os";
4350
+ import { fileURLToPath as fileURLToPath2 } from "url";
4351
+ import { unlinkSync as unlinkSync6 } from "fs";
3675
4352
  function spawnLockPath(sessionName) {
3676
- return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4353
+ return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
3677
4354
  }
3678
4355
  function isProcessAlive(pid) {
3679
4356
  try {
@@ -3683,14 +4360,14 @@ function isProcessAlive(pid) {
3683
4360
  return false;
3684
4361
  }
3685
4362
  }
3686
- function acquireSpawnLock(sessionName) {
3687
- if (!existsSync10(SPAWN_LOCK_DIR)) {
3688
- mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
4363
+ function acquireSpawnLock2(sessionName) {
4364
+ if (!existsSync12(SPAWN_LOCK_DIR)) {
4365
+ mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
3689
4366
  }
3690
4367
  const lockFile = spawnLockPath(sessionName);
3691
- if (existsSync10(lockFile)) {
4368
+ if (existsSync12(lockFile)) {
3692
4369
  try {
3693
- const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
4370
+ const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
3694
4371
  const age = Date.now() - lock.timestamp;
3695
4372
  if (isProcessAlive(lock.pid) && age < 6e4) {
3696
4373
  return false;
@@ -3698,25 +4375,25 @@ function acquireSpawnLock(sessionName) {
3698
4375
  } catch {
3699
4376
  }
3700
4377
  }
3701
- writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4378
+ writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
3702
4379
  return true;
3703
4380
  }
3704
- function releaseSpawnLock(sessionName) {
4381
+ function releaseSpawnLock2(sessionName) {
3705
4382
  try {
3706
- unlinkSync5(spawnLockPath(sessionName));
4383
+ unlinkSync6(spawnLockPath(sessionName));
3707
4384
  } catch {
3708
4385
  }
3709
4386
  }
3710
4387
  function resolveBehaviorsExporterScript() {
3711
4388
  try {
3712
- const thisFile = fileURLToPath(import.meta.url);
3713
- const scriptPath = path14.join(
3714
- path14.dirname(thisFile),
4389
+ const thisFile = fileURLToPath2(import.meta.url);
4390
+ const scriptPath = path16.join(
4391
+ path16.dirname(thisFile),
3715
4392
  "..",
3716
4393
  "bin",
3717
4394
  "exe-export-behaviors.js"
3718
4395
  );
3719
- return existsSync10(scriptPath) ? scriptPath : null;
4396
+ return existsSync12(scriptPath) ? scriptPath : null;
3720
4397
  } catch {
3721
4398
  return null;
3722
4399
  }
@@ -3782,12 +4459,12 @@ function extractRootExe(name) {
3782
4459
  return parts.length > 0 ? parts[parts.length - 1] : null;
3783
4460
  }
3784
4461
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3785
- if (!existsSync10(SESSION_CACHE)) {
3786
- mkdirSync5(SESSION_CACHE, { recursive: true });
4462
+ if (!existsSync12(SESSION_CACHE)) {
4463
+ mkdirSync6(SESSION_CACHE, { recursive: true });
3787
4464
  }
3788
4465
  const rootExe = extractRootExe(parentExe) ?? parentExe;
3789
- const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
3790
- writeFileSync6(filePath, JSON.stringify({
4466
+ const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4467
+ writeFileSync7(filePath, JSON.stringify({
3791
4468
  parentExe: rootExe,
3792
4469
  dispatchedBy: dispatchedBy || rootExe,
3793
4470
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -3795,7 +4472,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3795
4472
  }
3796
4473
  function getParentExe(sessionKey) {
3797
4474
  try {
3798
- const data = JSON.parse(readFileSync9(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4475
+ const data = JSON.parse(readFileSync11(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
3799
4476
  return data.parentExe || null;
3800
4477
  } catch {
3801
4478
  return null;
@@ -3803,8 +4480,8 @@ function getParentExe(sessionKey) {
3803
4480
  }
3804
4481
  function getDispatchedBy(sessionKey) {
3805
4482
  try {
3806
- const data = JSON.parse(readFileSync9(
3807
- path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4483
+ const data = JSON.parse(readFileSync11(
4484
+ path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
3808
4485
  "utf8"
3809
4486
  ));
3810
4487
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -3830,10 +4507,10 @@ function isEmployeeAlive(sessionName) {
3830
4507
  }
3831
4508
  function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
3832
4509
  const base = employeeSessionName(employeeName, exeSession);
3833
- if (!isAlive(base) && acquireSpawnLock(base)) return 0;
4510
+ if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
3834
4511
  for (let i = 2; i <= maxInstances; i++) {
3835
4512
  const candidate = employeeSessionName(employeeName, exeSession, i);
3836
- if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
4513
+ if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
3837
4514
  }
3838
4515
  return null;
3839
4516
  }
@@ -3865,32 +4542,50 @@ async function verifyPaneAtCapacity(sessionName) {
3865
4542
  }
3866
4543
  function readDebounceState() {
3867
4544
  try {
3868
- if (!existsSync10(DEBOUNCE_FILE)) return {};
3869
- return JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
4545
+ if (!existsSync12(DEBOUNCE_FILE)) return {};
4546
+ const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
4547
+ const state = {};
4548
+ for (const [key, val] of Object.entries(raw)) {
4549
+ if (typeof val === "number") {
4550
+ state[key] = { lastSent: val, pending: 0 };
4551
+ } else if (val && typeof val === "object" && "lastSent" in val) {
4552
+ state[key] = val;
4553
+ }
4554
+ }
4555
+ return state;
3870
4556
  } catch {
3871
4557
  return {};
3872
4558
  }
3873
4559
  }
3874
4560
  function writeDebounceState(state) {
3875
4561
  try {
3876
- if (!existsSync10(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
3877
- writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
4562
+ if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
4563
+ writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
3878
4564
  } catch {
3879
4565
  }
3880
4566
  }
3881
4567
  function isDebounced(targetSession) {
3882
4568
  const state = readDebounceState();
3883
- const lastSent = state[targetSession] ?? 0;
3884
- return Date.now() - lastSent < INTERCOM_DEBOUNCE_MS;
4569
+ const entry = state[targetSession];
4570
+ const lastSent = entry?.lastSent ?? 0;
4571
+ if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
4572
+ if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
4573
+ state[targetSession].pending++;
4574
+ writeDebounceState(state);
4575
+ return true;
4576
+ }
4577
+ return false;
3885
4578
  }
3886
4579
  function recordDebounce(targetSession) {
3887
4580
  const state = readDebounceState();
3888
- state[targetSession] = Date.now();
4581
+ const batched = state[targetSession]?.pending ?? 0;
4582
+ state[targetSession] = { lastSent: Date.now(), pending: 0 };
3889
4583
  const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
3890
4584
  for (const key of Object.keys(state)) {
3891
- if ((state[key] ?? 0) < cutoff) delete state[key];
4585
+ if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
3892
4586
  }
3893
4587
  writeDebounceState(state);
4588
+ return batched;
3894
4589
  }
3895
4590
  function logIntercom(msg) {
3896
4591
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
@@ -3935,7 +4630,7 @@ function sendIntercom(targetSession) {
3935
4630
  return "skipped_exe";
3936
4631
  }
3937
4632
  if (isDebounced(targetSession)) {
3938
- logIntercom(`DEBOUNCE \u2192 ${targetSession} (cross-process file debounce)`);
4633
+ logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
3939
4634
  return "debounced";
3940
4635
  }
3941
4636
  try {
@@ -3947,14 +4642,14 @@ function sendIntercom(targetSession) {
3947
4642
  const sessionState = getSessionState(targetSession);
3948
4643
  if (sessionState === "no_claude") {
3949
4644
  queueIntercom(targetSession, "claude not running in session");
3950
- recordDebounce(targetSession);
3951
- logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
4645
+ const batched2 = recordDebounce(targetSession);
4646
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
3952
4647
  return "queued";
3953
4648
  }
3954
4649
  if (sessionState === "thinking" || sessionState === "tool") {
3955
4650
  queueIntercom(targetSession, "session busy at send time");
3956
- recordDebounce(targetSession);
3957
- logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
4651
+ const batched2 = recordDebounce(targetSession);
4652
+ logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
3958
4653
  return "queued";
3959
4654
  }
3960
4655
  if (transport.isPaneInCopyMode(targetSession)) {
@@ -3962,8 +4657,8 @@ function sendIntercom(targetSession) {
3962
4657
  transport.sendKeys(targetSession, "q");
3963
4658
  }
3964
4659
  transport.sendKeys(targetSession, "/exe-intercom");
3965
- recordDebounce(targetSession);
3966
- logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
4660
+ const batched = recordDebounce(targetSession);
4661
+ logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
3967
4662
  return "delivered";
3968
4663
  } catch {
3969
4664
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -3993,7 +4688,7 @@ function notifyParentExe(sessionKey) {
3993
4688
  return true;
3994
4689
  }
3995
4690
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
3996
- if (employeeName === "exe" || isCoordinatorName(employeeName)) {
4691
+ if (isCoordinatorName(employeeName)) {
3997
4692
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
3998
4693
  }
3999
4694
  try {
@@ -4065,26 +4760,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4065
4760
  const transport = getTransport();
4066
4761
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
4067
4762
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
4068
- const logDir = path14.join(os7.homedir(), ".exe-os", "session-logs");
4069
- const logFile = path14.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4070
- if (!existsSync10(logDir)) {
4071
- mkdirSync5(logDir, { recursive: true });
4763
+ const logDir = path16.join(os8.homedir(), ".exe-os", "session-logs");
4764
+ const logFile = path16.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4765
+ if (!existsSync12(logDir)) {
4766
+ mkdirSync6(logDir, { recursive: true });
4072
4767
  }
4073
4768
  transport.kill(sessionName);
4074
4769
  let cleanupSuffix = "";
4075
4770
  try {
4076
- const thisFile = fileURLToPath(import.meta.url);
4077
- const cleanupScript = path14.join(path14.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4078
- if (existsSync10(cleanupScript)) {
4771
+ const thisFile = fileURLToPath2(import.meta.url);
4772
+ const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4773
+ if (existsSync12(cleanupScript)) {
4079
4774
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
4080
4775
  }
4081
4776
  } catch {
4082
4777
  }
4083
4778
  try {
4084
- const claudeJsonPath = path14.join(os7.homedir(), ".claude.json");
4779
+ const claudeJsonPath = path16.join(os8.homedir(), ".claude.json");
4085
4780
  let claudeJson = {};
4086
4781
  try {
4087
- claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
4782
+ claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
4088
4783
  } catch {
4089
4784
  }
4090
4785
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -4092,17 +4787,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4092
4787
  const trustDir = opts?.cwd ?? projectDir;
4093
4788
  if (!projects[trustDir]) projects[trustDir] = {};
4094
4789
  projects[trustDir].hasTrustDialogAccepted = true;
4095
- writeFileSync6(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4790
+ writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4096
4791
  } catch {
4097
4792
  }
4098
4793
  try {
4099
- const settingsDir = path14.join(os7.homedir(), ".claude", "projects");
4794
+ const settingsDir = path16.join(os8.homedir(), ".claude", "projects");
4100
4795
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
4101
- const projSettingsDir = path14.join(settingsDir, normalizedKey);
4102
- const settingsPath = path14.join(projSettingsDir, "settings.json");
4796
+ const projSettingsDir = path16.join(settingsDir, normalizedKey);
4797
+ const settingsPath = path16.join(projSettingsDir, "settings.json");
4103
4798
  let settings = {};
4104
4799
  try {
4105
- settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
4800
+ settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
4106
4801
  } catch {
4107
4802
  }
4108
4803
  const perms = settings.permissions ?? {};
@@ -4130,21 +4825,24 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4130
4825
  if (changed) {
4131
4826
  perms.allow = allow;
4132
4827
  settings.permissions = perms;
4133
- mkdirSync5(projSettingsDir, { recursive: true });
4134
- writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4828
+ mkdirSync6(projSettingsDir, { recursive: true });
4829
+ writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4135
4830
  }
4136
4831
  } catch {
4137
4832
  }
4138
4833
  const spawnCwd = opts?.cwd ?? projectDir;
4139
4834
  const useExeAgent = !!(opts?.model && opts?.provider);
4140
- const ccProvider = useExeAgent ? DEFAULT_PROVIDER : detectActiveProvider();
4835
+ const agentRtConfig = getAgentRuntime(employeeName);
4836
+ const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
4837
+ const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
4838
+ const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
4141
4839
  const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
4142
4840
  let identityFlag = "";
4143
4841
  let behaviorsFlag = "";
4144
4842
  let legacyFallbackWarned = false;
4145
4843
  if (!useExeAgent && !useBinSymlink) {
4146
- const identityPath = path14.join(
4147
- os7.homedir(),
4844
+ const identityPath = path16.join(
4845
+ os8.homedir(),
4148
4846
  ".exe-os",
4149
4847
  "identity",
4150
4848
  `${employeeName}.md`
@@ -4153,13 +4851,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4153
4851
  const hasAgentFlag = claudeSupportsAgentFlag();
4154
4852
  if (hasAgentFlag) {
4155
4853
  identityFlag = ` --agent ${employeeName}`;
4156
- } else if (existsSync10(identityPath)) {
4854
+ } else if (existsSync12(identityPath)) {
4157
4855
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
4158
4856
  legacyFallbackWarned = true;
4159
4857
  }
4160
4858
  const behaviorsFile = exportBehaviorsSync(
4161
4859
  employeeName,
4162
- path14.basename(spawnCwd),
4860
+ path16.basename(spawnCwd),
4163
4861
  sessionName
4164
4862
  );
4165
4863
  if (behaviorsFile) {
@@ -4174,16 +4872,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4174
4872
  }
4175
4873
  let sessionContextFlag = "";
4176
4874
  try {
4177
- const ctxDir = path14.join(os7.homedir(), ".exe-os", "session-cache");
4178
- mkdirSync5(ctxDir, { recursive: true });
4179
- const ctxFile = path14.join(ctxDir, `session-context-${sessionName}.md`);
4875
+ const ctxDir = path16.join(os8.homedir(), ".exe-os", "session-cache");
4876
+ mkdirSync6(ctxDir, { recursive: true });
4877
+ const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
4180
4878
  const ctxContent = [
4181
4879
  `## Session Context`,
4182
4880
  `You are running in tmux session: ${sessionName}.`,
4183
4881
  `Your parent coordinator session is ${exeSession}.`,
4184
4882
  `Your employees (if any) use the -${exeSession} suffix.`
4185
4883
  ].join("\n");
4186
- writeFileSync6(ctxFile, ctxContent);
4884
+ writeFileSync7(ctxFile, ctxContent);
4187
4885
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
4188
4886
  } catch {
4189
4887
  }
@@ -4197,9 +4895,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4197
4895
  }
4198
4896
  }
4199
4897
  }
4898
+ if (useCodex) {
4899
+ const codexCfg = RUNTIME_TABLE.codex;
4900
+ if (codexCfg?.apiKeyEnv) {
4901
+ const keyVal = process.env[codexCfg.apiKeyEnv];
4902
+ if (keyVal) {
4903
+ envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
4904
+ }
4905
+ }
4906
+ envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
4907
+ }
4908
+ if (useOpencode) {
4909
+ const ocCfg = PROVIDER_TABLE.opencode;
4910
+ if (ocCfg?.apiKeyEnv) {
4911
+ const keyVal = process.env[ocCfg.apiKeyEnv];
4912
+ if (keyVal) {
4913
+ envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
4914
+ }
4915
+ }
4916
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
4917
+ }
4918
+ if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
4919
+ const defaultClaudeModel = DEFAULT_MODELS.claude;
4920
+ if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
4921
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
4922
+ }
4923
+ }
4200
4924
  let spawnCommand;
4201
4925
  if (useExeAgent) {
4202
4926
  spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
4927
+ } else if (useCodex) {
4928
+ process.stderr.write(
4929
+ `[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
4930
+ `
4931
+ );
4932
+ spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
4933
+ } else if (useOpencode) {
4934
+ const binName = `${employeeName}-opencode`;
4935
+ process.stderr.write(
4936
+ `[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
4937
+ `
4938
+ );
4939
+ spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
4203
4940
  } else if (useBinSymlink) {
4204
4941
  const binName = `${employeeName}-${ccProvider}`;
4205
4942
  process.stderr.write(
@@ -4215,17 +4952,19 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4215
4952
  command: spawnCommand
4216
4953
  });
4217
4954
  if (spawnResult.error) {
4218
- releaseSpawnLock(sessionName);
4955
+ releaseSpawnLock2(sessionName);
4219
4956
  return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
4220
4957
  }
4221
4958
  transport.pipeLog(sessionName, logFile);
4222
4959
  try {
4223
4960
  const mySession = getMySession();
4224
- const dispatchInfo = path14.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4225
- writeFileSync6(dispatchInfo, JSON.stringify({
4961
+ const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4962
+ writeFileSync7(dispatchInfo, JSON.stringify({
4226
4963
  dispatchedBy: mySession,
4227
4964
  rootExe: exeSession,
4228
- provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
4965
+ provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
4966
+ runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
4967
+ model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
4229
4968
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
4230
4969
  }));
4231
4970
  } catch {
@@ -4243,6 +4982,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4243
4982
  booted = true;
4244
4983
  break;
4245
4984
  }
4985
+ } else if (useCodex) {
4986
+ if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
4987
+ booted = true;
4988
+ break;
4989
+ }
4246
4990
  } else {
4247
4991
  if (pane.includes("Claude Code") || pane.includes("\u276F")) {
4248
4992
  booted = true;
@@ -4253,10 +4997,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4253
4997
  }
4254
4998
  }
4255
4999
  if (!booted) {
4256
- releaseSpawnLock(sessionName);
4257
- return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
5000
+ releaseSpawnLock2(sessionName);
5001
+ const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
5002
+ return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
4258
5003
  }
4259
- if (!useExeAgent) {
5004
+ if (!useExeAgent && !useCodex) {
4260
5005
  try {
4261
5006
  transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
4262
5007
  } catch {
@@ -4270,7 +5015,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4270
5015
  pid: 0,
4271
5016
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
4272
5017
  });
4273
- releaseSpawnLock(sessionName);
5018
+ releaseSpawnLock2(sessionName);
4274
5019
  return { sessionName };
4275
5020
  }
4276
5021
  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;
@@ -4283,17 +5028,19 @@ var init_tmux_routing = __esm({
4283
5028
  init_cc_agent_support();
4284
5029
  init_mcp_prefix();
4285
5030
  init_provider_table();
5031
+ init_agent_config();
5032
+ init_runtime_table();
4286
5033
  init_intercom_queue();
4287
5034
  init_plan_limits();
4288
5035
  init_employees();
4289
- SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
4290
- SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
5036
+ SPAWN_LOCK_DIR = path16.join(os8.homedir(), ".exe-os", "spawn-locks");
5037
+ SESSION_CACHE = path16.join(os8.homedir(), ".exe-os", "session-cache");
4291
5038
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
4292
5039
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
4293
5040
  VERIFY_PANE_LINES = 200;
4294
5041
  INTERCOM_DEBOUNCE_MS = 3e4;
4295
- INTERCOM_LOG2 = path14.join(os7.homedir(), ".exe-os", "intercom.log");
4296
- DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
5042
+ INTERCOM_LOG2 = path16.join(os8.homedir(), ".exe-os", "intercom.log");
5043
+ DEBOUNCE_FILE = path16.join(SESSION_CACHE, "intercom-debounce.json");
4297
5044
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
4298
5045
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
4299
5046
  }
@@ -4310,14 +5057,14 @@ var init_memory = __esm({
4310
5057
 
4311
5058
  // src/lib/keychain.ts
4312
5059
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
4313
- import { existsSync as existsSync11 } from "fs";
4314
- import path15 from "path";
4315
- import os8 from "os";
5060
+ import { existsSync as existsSync13 } from "fs";
5061
+ import path17 from "path";
5062
+ import os9 from "os";
4316
5063
  function getKeyDir() {
4317
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path15.join(os8.homedir(), ".exe-os");
5064
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path17.join(os9.homedir(), ".exe-os");
4318
5065
  }
4319
5066
  function getKeyPath() {
4320
- return path15.join(getKeyDir(), "master.key");
5067
+ return path17.join(getKeyDir(), "master.key");
4321
5068
  }
4322
5069
  async function tryKeytar() {
4323
5070
  try {
@@ -4338,13 +5085,21 @@ async function getMasterKey() {
4338
5085
  }
4339
5086
  }
4340
5087
  const keyPath = getKeyPath();
4341
- if (!existsSync11(keyPath)) {
5088
+ if (!existsSync13(keyPath)) {
5089
+ process.stderr.write(
5090
+ `[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5091
+ `
5092
+ );
4342
5093
  return null;
4343
5094
  }
4344
5095
  try {
4345
5096
  const content = await readFile4(keyPath, "utf-8");
4346
5097
  return Buffer.from(content.trim(), "base64");
4347
- } catch {
5098
+ } catch (err) {
5099
+ process.stderr.write(
5100
+ `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
5101
+ `
5102
+ );
4348
5103
  return null;
4349
5104
  }
4350
5105
  }
@@ -4370,13 +5125,13 @@ __export(shard_manager_exports, {
4370
5125
  listShards: () => listShards,
4371
5126
  shardExists: () => shardExists
4372
5127
  });
4373
- import path16 from "path";
4374
- import { existsSync as existsSync12, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
5128
+ import path18 from "path";
5129
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync3 } from "fs";
4375
5130
  import { createClient as createClient2 } from "@libsql/client";
4376
5131
  function initShardManager(encryptionKey) {
4377
5132
  _encryptionKey = encryptionKey;
4378
- if (!existsSync12(SHARDS_DIR)) {
4379
- mkdirSync6(SHARDS_DIR, { recursive: true });
5133
+ if (!existsSync14(SHARDS_DIR)) {
5134
+ mkdirSync7(SHARDS_DIR, { recursive: true });
4380
5135
  }
4381
5136
  _shardingEnabled = true;
4382
5137
  }
@@ -4396,7 +5151,7 @@ function getShardClient(projectName) {
4396
5151
  }
4397
5152
  const cached = _shards.get(safeName);
4398
5153
  if (cached) return cached;
4399
- const dbPath = path16.join(SHARDS_DIR, `${safeName}.db`);
5154
+ const dbPath = path18.join(SHARDS_DIR, `${safeName}.db`);
4400
5155
  const client = createClient2({
4401
5156
  url: `file:${dbPath}`,
4402
5157
  encryptionKey: _encryptionKey
@@ -4406,10 +5161,10 @@ function getShardClient(projectName) {
4406
5161
  }
4407
5162
  function shardExists(projectName) {
4408
5163
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
4409
- return existsSync12(path16.join(SHARDS_DIR, `${safeName}.db`));
5164
+ return existsSync14(path18.join(SHARDS_DIR, `${safeName}.db`));
4410
5165
  }
4411
5166
  function listShards() {
4412
- if (!existsSync12(SHARDS_DIR)) return [];
5167
+ if (!existsSync14(SHARDS_DIR)) return [];
4413
5168
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
4414
5169
  }
4415
5170
  async function ensureShardSchema(client) {
@@ -4595,7 +5350,7 @@ var init_shard_manager = __esm({
4595
5350
  "src/lib/shard-manager.ts"() {
4596
5351
  "use strict";
4597
5352
  init_config();
4598
- SHARDS_DIR = path16.join(EXE_AI_DIR, "shards");
5353
+ SHARDS_DIR = path18.join(EXE_AI_DIR, "shards");
4599
5354
  _shards = /* @__PURE__ */ new Map();
4600
5355
  _encryptionKey = null;
4601
5356
  _shardingEnabled = false;
@@ -4720,7 +5475,7 @@ __export(global_procedures_exports, {
4720
5475
  loadGlobalProcedures: () => loadGlobalProcedures,
4721
5476
  storeGlobalProcedure: () => storeGlobalProcedure
4722
5477
  });
4723
- import { randomUUID as randomUUID3 } from "crypto";
5478
+ import { randomUUID as randomUUID4 } from "crypto";
4724
5479
  async function loadGlobalProcedures() {
4725
5480
  const client = getClient();
4726
5481
  const result = await client.execute({
@@ -4749,7 +5504,7 @@ ${sections.join("\n\n")}
4749
5504
  `;
4750
5505
  }
4751
5506
  async function storeGlobalProcedure(input) {
4752
- const id = randomUUID3();
5507
+ const id = randomUUID4();
4753
5508
  const now = (/* @__PURE__ */ new Date()).toISOString();
4754
5509
  const client = getClient();
4755
5510
  await client.execute({
@@ -4800,6 +5555,7 @@ __export(store_exports, {
4800
5555
  vectorToBlob: () => vectorToBlob,
4801
5556
  writeMemory: () => writeMemory
4802
5557
  });
5558
+ import { createHash } from "crypto";
4803
5559
  function isBusyError2(err) {
4804
5560
  if (err instanceof Error) {
4805
5561
  const msg = err.message.toLowerCase();
@@ -4873,12 +5629,52 @@ function classifyTier(record) {
4873
5629
  if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
4874
5630
  return 3;
4875
5631
  }
5632
+ function inferFilePaths(record) {
5633
+ if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
5634
+ const firstLine = record.raw_text.split("\n")[0] ?? "";
5635
+ const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
5636
+ return match ? JSON.stringify([match[1]]) : null;
5637
+ }
5638
+ function inferCommitHash(record) {
5639
+ if (record.tool_name !== "Bash") return null;
5640
+ const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
5641
+ return match ? match[1] : null;
5642
+ }
5643
+ function inferLanguageType(record) {
5644
+ const text = record.raw_text;
5645
+ if (!text || text.length < 10) return null;
5646
+ const trimmed = text.trimStart();
5647
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
5648
+ if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
5649
+ if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
5650
+ if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
5651
+ return "mixed";
5652
+ }
5653
+ function inferDomain(record) {
5654
+ const proj = (record.project_name ?? "").toLowerCase();
5655
+ if (proj.includes("marketing") || proj.includes("content")) return "marketing";
5656
+ if (proj.includes("crm") || proj.includes("customer")) return "customer";
5657
+ return null;
5658
+ }
4876
5659
  async function writeMemory(record) {
4877
5660
  if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
4878
5661
  throw new Error(
4879
5662
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
4880
5663
  );
4881
5664
  }
5665
+ const contentHash = createHash("md5").update(record.raw_text).digest("hex");
5666
+ if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
5667
+ return;
5668
+ }
5669
+ try {
5670
+ const client = getClient();
5671
+ const existing = await client.execute({
5672
+ sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
5673
+ args: [contentHash, record.agent_id]
5674
+ });
5675
+ if (existing.rows.length > 0) return;
5676
+ } catch {
5677
+ }
4882
5678
  const dbRow = {
4883
5679
  id: record.id,
4884
5680
  agent_id: record.agent_id,
@@ -4908,7 +5704,23 @@ async function writeMemory(record) {
4908
5704
  supersedes_id: record.supersedes_id ?? null,
4909
5705
  draft: record.draft ? 1 : 0,
4910
5706
  memory_type: record.memory_type ?? "raw",
4911
- trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
5707
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
5708
+ content_hash: contentHash,
5709
+ intent: record.intent ?? null,
5710
+ outcome: record.outcome ?? null,
5711
+ domain: record.domain ?? inferDomain(record),
5712
+ referenced_entities: record.referenced_entities ?? null,
5713
+ retrieval_count: record.retrieval_count ?? 0,
5714
+ chain_position: record.chain_position ?? null,
5715
+ review_status: record.review_status ?? null,
5716
+ context_window_pct: record.context_window_pct ?? null,
5717
+ file_paths: record.file_paths ?? inferFilePaths(record),
5718
+ commit_hash: record.commit_hash ?? inferCommitHash(record),
5719
+ duration_ms: record.duration_ms ?? null,
5720
+ token_cost: record.token_cost ?? null,
5721
+ audience: record.audience ?? null,
5722
+ language_type: record.language_type ?? inferLanguageType(record),
5723
+ parent_memory_id: record.parent_memory_id ?? null
4912
5724
  };
4913
5725
  _pendingRecords.push(dbRow);
4914
5726
  orgBus.emit({
@@ -4966,80 +5778,85 @@ async function flushBatch() {
4966
5778
  const draft = row.draft ? 1 : 0;
4967
5779
  const memoryType = row.memory_type ?? "raw";
4968
5780
  const trajectory = row.trajectory ?? null;
4969
- return {
4970
- sql: hasVector ? `INSERT OR IGNORE INTO memories
4971
- (id, agent_id, agent_role, session_id, timestamp,
5781
+ const contentHash = row.content_hash ?? null;
5782
+ const intent = row.intent ?? null;
5783
+ const outcome = row.outcome ?? null;
5784
+ const domain = row.domain ?? null;
5785
+ const referencedEntities = row.referenced_entities ?? null;
5786
+ const retrievalCount = row.retrieval_count ?? 0;
5787
+ const chainPosition = row.chain_position ?? null;
5788
+ const reviewStatus = row.review_status ?? null;
5789
+ const contextWindowPct = row.context_window_pct ?? null;
5790
+ const filePaths = row.file_paths ?? null;
5791
+ const commitHash = row.commit_hash ?? null;
5792
+ const durationMs = row.duration_ms ?? null;
5793
+ const tokenCost = row.token_cost ?? null;
5794
+ const audience = row.audience ?? null;
5795
+ const languageType = row.language_type ?? null;
5796
+ const parentMemoryId = row.parent_memory_id ?? null;
5797
+ const cols = `id, agent_id, agent_role, session_id, timestamp,
4972
5798
  tool_name, project_name,
4973
5799
  has_error, raw_text, vector, version, task_id, importance, status,
4974
5800
  confidence, last_accessed,
4975
5801
  workspace_id, document_id, user_id, char_offset, page_number,
4976
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
4977
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
4978
- (id, agent_id, agent_role, session_id, timestamp,
4979
- tool_name, project_name,
4980
- has_error, raw_text, vector, version, task_id, importance, status,
4981
- confidence, last_accessed,
4982
- workspace_id, document_id, user_id, char_offset, page_number,
4983
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
4984
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4985
- args: hasVector ? [
4986
- row.id,
4987
- row.agent_id,
4988
- row.agent_role,
4989
- row.session_id,
4990
- row.timestamp,
4991
- row.tool_name,
4992
- row.project_name,
4993
- row.has_error,
4994
- row.raw_text,
4995
- vectorToBlob(row.vector),
4996
- row.version,
4997
- taskId,
4998
- importance,
4999
- status,
5000
- confidence,
5001
- lastAccessed,
5002
- workspaceId,
5003
- documentId,
5004
- userId,
5005
- charOffset,
5006
- pageNumber,
5007
- sourcePath,
5008
- sourceType,
5009
- tier,
5010
- supersedesId,
5011
- draft,
5012
- memoryType,
5013
- trajectory
5014
- ] : [
5015
- row.id,
5016
- row.agent_id,
5017
- row.agent_role,
5018
- row.session_id,
5019
- row.timestamp,
5020
- row.tool_name,
5021
- row.project_name,
5022
- row.has_error,
5023
- row.raw_text,
5024
- row.version,
5025
- taskId,
5026
- importance,
5027
- status,
5028
- confidence,
5029
- lastAccessed,
5030
- workspaceId,
5031
- documentId,
5032
- userId,
5033
- charOffset,
5034
- pageNumber,
5035
- sourcePath,
5036
- sourceType,
5037
- tier,
5038
- supersedesId,
5039
- draft,
5040
- memoryType,
5041
- trajectory
5042
- ]
5802
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
5803
+ intent, outcome, domain, referenced_entities, retrieval_count,
5804
+ chain_position, review_status, context_window_pct, file_paths, commit_hash,
5805
+ duration_ms, token_cost, audience, language_type, parent_memory_id`;
5806
+ const metaArgs = [
5807
+ intent,
5808
+ outcome,
5809
+ domain,
5810
+ referencedEntities,
5811
+ retrievalCount,
5812
+ chainPosition,
5813
+ reviewStatus,
5814
+ contextWindowPct,
5815
+ filePaths,
5816
+ commitHash,
5817
+ durationMs,
5818
+ tokenCost,
5819
+ audience,
5820
+ languageType,
5821
+ parentMemoryId
5822
+ ];
5823
+ const baseArgs = [
5824
+ row.id,
5825
+ row.agent_id,
5826
+ row.agent_role,
5827
+ row.session_id,
5828
+ row.timestamp,
5829
+ row.tool_name,
5830
+ row.project_name,
5831
+ row.has_error,
5832
+ row.raw_text
5833
+ ];
5834
+ const sharedArgs = [
5835
+ row.version,
5836
+ taskId,
5837
+ importance,
5838
+ status,
5839
+ confidence,
5840
+ lastAccessed,
5841
+ workspaceId,
5842
+ documentId,
5843
+ userId,
5844
+ charOffset,
5845
+ pageNumber,
5846
+ sourcePath,
5847
+ sourceType,
5848
+ tier,
5849
+ supersedesId,
5850
+ draft,
5851
+ memoryType,
5852
+ trajectory,
5853
+ contentHash
5854
+ ];
5855
+ return {
5856
+ sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
5857
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
5858
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5859
+ args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
5043
5860
  };
5044
5861
  };
5045
5862
  const globalClient = getClient();
@@ -5322,8 +6139,8 @@ function findContainingChunk(filePath, snippet) {
5322
6139
  try {
5323
6140
  const ext = filePath.split(".").pop()?.toLowerCase();
5324
6141
  if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
5325
- const { readFileSync: readFileSync10 } = __require("fs");
5326
- const source = readFileSync10(filePath, "utf8");
6142
+ const { readFileSync: readFileSync12 } = __require("fs");
6143
+ const source = readFileSync12(filePath, "utf8");
5327
6144
  const lines = source.split("\n");
5328
6145
  const lowerSnippet = snippet.toLowerCase().slice(0, 80);
5329
6146
  let matchLine = -1;
@@ -5389,9 +6206,9 @@ function extractBash(input, response) {
5389
6206
  }
5390
6207
  function extractGrep(input, response) {
5391
6208
  const pattern = String(input.pattern ?? "");
5392
- const path17 = input.path ? String(input.path) : "";
6209
+ const path19 = input.path ? String(input.path) : "";
5393
6210
  const output = String(response.text ?? response.content ?? JSON.stringify(response).slice(0, MAX_OUTPUT));
5394
- return `Searched for "${pattern}"${path17 ? ` in ${path17}` : ""}
6211
+ return `Searched for "${pattern}"${path19 ? ` in ${path19}` : ""}
5395
6212
  ${output.slice(0, MAX_OUTPUT)}`;
5396
6213
  }
5397
6214
  function extractGlob(input, response) {
@@ -7276,11 +8093,11 @@ function createQuietRenderer() {
7276
8093
  init_state_bus();
7277
8094
 
7278
8095
  // src/runtime/session-manager.ts
7279
- import { randomUUID as randomUUID5 } from "crypto";
8096
+ import { randomUUID as randomUUID6 } from "crypto";
7280
8097
  init_state_bus();
7281
8098
 
7282
8099
  // src/runtime/exe-hooks.ts
7283
- import { randomUUID as randomUUID4 } from "crypto";
8100
+ import { randomUUID as randomUUID5 } from "crypto";
7284
8101
  function createExeOSHooks(config) {
7285
8102
  let sessionRegistered = false;
7286
8103
  return {
@@ -7339,7 +8156,7 @@ function createExeOSHooks(config) {
7339
8156
  const toolResponse = result.isError ? { error: result.content } : { output: result.content };
7340
8157
  const rawText = extractSemanticText2(toolName, toolInput, toolResponse);
7341
8158
  await writeMemory2({
7342
- id: randomUUID4(),
8159
+ id: randomUUID5(),
7343
8160
  agent_id: config.agentId,
7344
8161
  agent_role: "employee",
7345
8162
  session_id: `api-${config.agentId}`,
@@ -7362,7 +8179,7 @@ function createExeOSHooks(config) {
7362
8179
  const { writeMemory: writeMemory2 } = await Promise.resolve().then(() => (init_store(), store_exports));
7363
8180
  if (summary) {
7364
8181
  await writeMemory2({
7365
- id: randomUUID4(),
8182
+ id: randomUUID5(),
7366
8183
  agent_id: config.agentId,
7367
8184
  agent_role: "employee",
7368
8185
  session_id: `api-${config.agentId}`,
@@ -7435,7 +8252,7 @@ function createExeOSHooks(config) {
7435
8252
  await client.execute({
7436
8253
  sql: `INSERT OR IGNORE INTO notifications (id, type, source_agent, message, task_file, created_at, read)
7437
8254
  VALUES (?, 'system', ?, ?, NULL, ?, 0)`,
7438
- args: [randomUUID4(), config.agentId, message.slice(0, 500), (/* @__PURE__ */ new Date()).toISOString()]
8255
+ args: [randomUUID5(), config.agentId, message.slice(0, 500), (/* @__PURE__ */ new Date()).toISOString()]
7439
8256
  });
7440
8257
  } catch {
7441
8258
  }
@@ -7451,7 +8268,7 @@ function createExeOSHooks(config) {
7451
8268
  try {
7452
8269
  const { writeMemory: writeMemory2 } = await Promise.resolve().then(() => (init_store(), store_exports));
7453
8270
  await writeMemory2({
7454
- id: randomUUID4(),
8271
+ id: randomUUID5(),
7455
8272
  agent_id: config.agentId,
7456
8273
  agent_role: "employee",
7457
8274
  session_id: `api-${config.agentId}`,
@@ -7473,7 +8290,7 @@ function createExeOSHooks(config) {
7473
8290
  try {
7474
8291
  const { writeMemory: writeMemory2 } = await Promise.resolve().then(() => (init_store(), store_exports));
7475
8292
  await writeMemory2({
7476
- id: randomUUID4(),
8293
+ id: randomUUID5(),
7477
8294
  agent_id: config.agentId,
7478
8295
  agent_role: "employee",
7479
8296
  session_id: `api-${config.agentId}`,
@@ -7512,7 +8329,7 @@ function createExeOSHooks(config) {
7512
8329
  if (tasks.rows.length > 0) {
7513
8330
  const taskList = tasks.rows.map((r) => `- [${String(r.status)}] ${String(r.title)} (${String(r.task_file)})`).join("\n");
7514
8331
  await writeMemory2({
7515
- id: randomUUID4(),
8332
+ id: randomUUID5(),
7516
8333
  agent_id: config.agentId,
7517
8334
  agent_role: "employee",
7518
8335
  session_id: `api-${config.agentId}`,
@@ -7538,7 +8355,7 @@ ${taskList}`,
7538
8355
  try {
7539
8356
  const { writeMemory: writeMemory2 } = await Promise.resolve().then(() => (init_store(), store_exports));
7540
8357
  await writeMemory2({
7541
- id: randomUUID4(),
8358
+ id: randomUUID5(),
7542
8359
  agent_id: config.agentId,
7543
8360
  agent_role: "employee",
7544
8361
  session_id: `api-${config.agentId}`,
@@ -7565,7 +8382,7 @@ var SessionManager = class {
7565
8382
  eventHandlers = /* @__PURE__ */ new Set();
7566
8383
  /** Start a new agent session for an employee */
7567
8384
  startSession(employeeId, config) {
7568
- const sessionId = randomUUID5();
8385
+ const sessionId = randomUUID6();
7569
8386
  const abortController = new AbortController();
7570
8387
  const session = {
7571
8388
  info: {