@askexenow/exe-os 0.8.83 → 0.8.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/bin/backfill-conversations.js +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 +97 -2
  5. package/dist/bin/cli.js +14350 -12518
  6. package/dist/bin/exe-agent.js +97 -88
  7. package/dist/bin/exe-assign.js +1003 -854
  8. package/dist/bin/exe-boot.js +1257 -320
  9. package/dist/bin/exe-call.js +10 -0
  10. package/dist/bin/exe-cloud.js +29 -6
  11. package/dist/bin/exe-dispatch.js +210 -34
  12. package/dist/bin/exe-doctor.js +403 -6
  13. package/dist/bin/exe-export-behaviors.js +175 -72
  14. package/dist/bin/exe-forget.js +97 -2
  15. package/dist/bin/exe-gateway.js +550 -171
  16. package/dist/bin/exe-healthcheck.js +1 -0
  17. package/dist/bin/exe-heartbeat.js +100 -5
  18. package/dist/bin/exe-kill.js +175 -72
  19. package/dist/bin/exe-launch-agent.js +189 -76
  20. package/dist/bin/exe-link.js +902 -80
  21. package/dist/bin/exe-new-employee.js +38 -8
  22. package/dist/bin/exe-pending-messages.js +96 -2
  23. package/dist/bin/exe-pending-notifications.js +97 -2
  24. package/dist/bin/exe-pending-reviews.js +98 -3
  25. package/dist/bin/exe-rename.js +564 -23
  26. package/dist/bin/exe-review.js +231 -73
  27. package/dist/bin/exe-search.js +989 -226
  28. package/dist/bin/exe-session-cleanup.js +4806 -1665
  29. package/dist/bin/exe-settings.js +20 -5
  30. package/dist/bin/exe-status.js +97 -2
  31. package/dist/bin/exe-team.js +97 -2
  32. package/dist/bin/git-sweep.js +899 -207
  33. package/dist/bin/graph-backfill.js +175 -72
  34. package/dist/bin/graph-export.js +175 -72
  35. package/dist/bin/install.js +38 -7
  36. package/dist/bin/list-providers.js +1 -0
  37. package/dist/bin/scan-tasks.js +904 -211
  38. package/dist/bin/setup.js +867 -268
  39. package/dist/bin/shard-migrate.js +175 -72
  40. package/dist/bin/update.js +1 -0
  41. package/dist/bin/wiki-sync.js +175 -72
  42. package/dist/gateway/index.js +548 -166
  43. package/dist/hooks/bug-report-worker.js +208 -23
  44. package/dist/hooks/commit-complete.js +897 -205
  45. package/dist/hooks/error-recall.js +988 -226
  46. package/dist/hooks/ingest-worker.js +1638 -1194
  47. package/dist/hooks/ingest.js +3 -0
  48. package/dist/hooks/instructions-loaded.js +707 -97
  49. package/dist/hooks/notification.js +699 -89
  50. package/dist/hooks/post-compact.js +714 -104
  51. package/dist/hooks/pre-compact.js +897 -205
  52. package/dist/hooks/pre-tool-use.js +742 -123
  53. package/dist/hooks/prompt-ingest-worker.js +242 -101
  54. package/dist/hooks/prompt-submit.js +995 -233
  55. package/dist/hooks/response-ingest-worker.js +242 -101
  56. package/dist/hooks/session-end.js +3941 -400
  57. package/dist/hooks/session-start.js +1001 -226
  58. package/dist/hooks/stop.js +725 -115
  59. package/dist/hooks/subagent-stop.js +714 -104
  60. package/dist/hooks/summary-worker.js +1964 -1330
  61. package/dist/index.js +1651 -1053
  62. package/dist/lib/cloud-sync.js +907 -86
  63. package/dist/lib/consolidation.js +2 -1
  64. package/dist/lib/database.js +642 -87
  65. package/dist/lib/db-daemon-client.js +503 -0
  66. package/dist/lib/device-registry.js +547 -7
  67. package/dist/lib/embedder.js +14 -28
  68. package/dist/lib/employee-templates.js +84 -74
  69. package/dist/lib/employees.js +9 -0
  70. package/dist/lib/exe-daemon-client.js +16 -29
  71. package/dist/lib/exe-daemon.js +1955 -922
  72. package/dist/lib/hybrid-search.js +988 -226
  73. package/dist/lib/identity.js +87 -67
  74. package/dist/lib/keychain.js +9 -1
  75. package/dist/lib/messaging.js +8 -1
  76. package/dist/lib/reminders.js +91 -74
  77. package/dist/lib/schedules.js +96 -2
  78. package/dist/lib/skill-learning.js +103 -85
  79. package/dist/lib/store.js +234 -73
  80. package/dist/lib/tasks.js +111 -22
  81. package/dist/lib/tmux-routing.js +120 -31
  82. package/dist/lib/token-spend.js +273 -0
  83. package/dist/lib/ws-client.js +11 -0
  84. package/dist/mcp/server.js +5222 -475
  85. package/dist/mcp/tools/complete-reminder.js +94 -77
  86. package/dist/mcp/tools/create-reminder.js +94 -77
  87. package/dist/mcp/tools/create-task.js +120 -22
  88. package/dist/mcp/tools/deactivate-behavior.js +95 -77
  89. package/dist/mcp/tools/list-reminders.js +94 -77
  90. package/dist/mcp/tools/list-tasks.js +31 -1
  91. package/dist/mcp/tools/send-message.js +8 -1
  92. package/dist/mcp/tools/update-task.js +39 -10
  93. package/dist/runtime/index.js +911 -219
  94. package/dist/tui/App.js +997 -295
  95. package/package.json +6 -1
@@ -606,6 +606,443 @@ var init_employees = __esm({
606
606
  }
607
607
  });
608
608
 
609
+ // src/lib/exe-daemon-client.ts
610
+ import net from "net";
611
+ import { spawn } from "child_process";
612
+ import { randomUUID } from "crypto";
613
+ import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
614
+ import path5 from "path";
615
+ import { fileURLToPath } from "url";
616
+ function handleData(chunk) {
617
+ _buffer += chunk.toString();
618
+ if (_buffer.length > MAX_BUFFER) {
619
+ _buffer = "";
620
+ return;
621
+ }
622
+ let newlineIdx;
623
+ while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
624
+ const line = _buffer.slice(0, newlineIdx).trim();
625
+ _buffer = _buffer.slice(newlineIdx + 1);
626
+ if (!line) continue;
627
+ try {
628
+ const response = JSON.parse(line);
629
+ const id = response.id;
630
+ if (!id) continue;
631
+ const entry = _pending.get(id);
632
+ if (entry) {
633
+ clearTimeout(entry.timer);
634
+ _pending.delete(id);
635
+ entry.resolve(response);
636
+ }
637
+ } catch {
638
+ }
639
+ }
640
+ }
641
+ function cleanupStaleFiles() {
642
+ if (existsSync5(PID_PATH)) {
643
+ try {
644
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
645
+ if (pid > 0) {
646
+ try {
647
+ process.kill(pid, 0);
648
+ return;
649
+ } catch {
650
+ }
651
+ }
652
+ } catch {
653
+ }
654
+ try {
655
+ unlinkSync2(PID_PATH);
656
+ } catch {
657
+ }
658
+ try {
659
+ unlinkSync2(SOCKET_PATH);
660
+ } catch {
661
+ }
662
+ }
663
+ }
664
+ function findPackageRoot() {
665
+ let dir = path5.dirname(fileURLToPath(import.meta.url));
666
+ const { root } = path5.parse(dir);
667
+ while (dir !== root) {
668
+ if (existsSync5(path5.join(dir, "package.json"))) return dir;
669
+ dir = path5.dirname(dir);
670
+ }
671
+ return null;
672
+ }
673
+ function spawnDaemon() {
674
+ const pkgRoot = findPackageRoot();
675
+ if (!pkgRoot) {
676
+ process.stderr.write("[exed-client] WARN: cannot find package root\n");
677
+ return;
678
+ }
679
+ const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
680
+ if (!existsSync5(daemonPath)) {
681
+ process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
682
+ `);
683
+ return;
684
+ }
685
+ const resolvedPath = daemonPath;
686
+ process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
687
+ `);
688
+ const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
689
+ let stderrFd = "ignore";
690
+ try {
691
+ stderrFd = openSync(logPath, "a");
692
+ } catch {
693
+ }
694
+ const child = spawn(process.execPath, [resolvedPath], {
695
+ detached: true,
696
+ stdio: ["ignore", "ignore", stderrFd],
697
+ env: {
698
+ ...process.env,
699
+ TMUX: void 0,
700
+ // Daemon is global — must not inherit session scope
701
+ TMUX_PANE: void 0,
702
+ // Prevents resolveExeSession() from scoping to one session
703
+ EXE_DAEMON_SOCK: SOCKET_PATH,
704
+ EXE_DAEMON_PID: PID_PATH
705
+ }
706
+ });
707
+ child.unref();
708
+ if (typeof stderrFd === "number") {
709
+ try {
710
+ closeSync(stderrFd);
711
+ } catch {
712
+ }
713
+ }
714
+ }
715
+ function acquireSpawnLock() {
716
+ try {
717
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
718
+ closeSync(fd);
719
+ return true;
720
+ } catch {
721
+ try {
722
+ const stat = statSync(SPAWN_LOCK_PATH);
723
+ if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
724
+ try {
725
+ unlinkSync2(SPAWN_LOCK_PATH);
726
+ } catch {
727
+ }
728
+ try {
729
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
730
+ closeSync(fd);
731
+ return true;
732
+ } catch {
733
+ }
734
+ }
735
+ } catch {
736
+ }
737
+ return false;
738
+ }
739
+ }
740
+ function releaseSpawnLock() {
741
+ try {
742
+ unlinkSync2(SPAWN_LOCK_PATH);
743
+ } catch {
744
+ }
745
+ }
746
+ function connectToSocket() {
747
+ return new Promise((resolve) => {
748
+ if (_socket && _connected) {
749
+ resolve(true);
750
+ return;
751
+ }
752
+ const socket = net.createConnection({ path: SOCKET_PATH });
753
+ const connectTimeout = setTimeout(() => {
754
+ socket.destroy();
755
+ resolve(false);
756
+ }, 2e3);
757
+ socket.on("connect", () => {
758
+ clearTimeout(connectTimeout);
759
+ _socket = socket;
760
+ _connected = true;
761
+ _buffer = "";
762
+ socket.on("data", handleData);
763
+ socket.on("close", () => {
764
+ _connected = false;
765
+ _socket = null;
766
+ for (const [id, entry] of _pending) {
767
+ clearTimeout(entry.timer);
768
+ _pending.delete(id);
769
+ entry.resolve({ error: "Connection closed" });
770
+ }
771
+ });
772
+ socket.on("error", () => {
773
+ _connected = false;
774
+ _socket = null;
775
+ });
776
+ resolve(true);
777
+ });
778
+ socket.on("error", () => {
779
+ clearTimeout(connectTimeout);
780
+ resolve(false);
781
+ });
782
+ });
783
+ }
784
+ async function connectEmbedDaemon() {
785
+ if (_socket && _connected) return true;
786
+ if (await connectToSocket()) return true;
787
+ if (acquireSpawnLock()) {
788
+ try {
789
+ cleanupStaleFiles();
790
+ spawnDaemon();
791
+ } finally {
792
+ releaseSpawnLock();
793
+ }
794
+ }
795
+ const start = Date.now();
796
+ let delay2 = 100;
797
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
798
+ await new Promise((r) => setTimeout(r, delay2));
799
+ if (await connectToSocket()) return true;
800
+ delay2 = Math.min(delay2 * 2, 3e3);
801
+ }
802
+ return false;
803
+ }
804
+ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
805
+ return new Promise((resolve) => {
806
+ if (!_socket || !_connected) {
807
+ resolve({ error: "Not connected" });
808
+ return;
809
+ }
810
+ const id = randomUUID();
811
+ const timer = setTimeout(() => {
812
+ _pending.delete(id);
813
+ resolve({ error: "Request timeout" });
814
+ }, timeoutMs);
815
+ _pending.set(id, { resolve, timer });
816
+ try {
817
+ _socket.write(JSON.stringify({ id, ...payload }) + "\n");
818
+ } catch {
819
+ clearTimeout(timer);
820
+ _pending.delete(id);
821
+ resolve({ error: "Write failed" });
822
+ }
823
+ });
824
+ }
825
+ function isClientConnected() {
826
+ return _connected;
827
+ }
828
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
829
+ var init_exe_daemon_client = __esm({
830
+ "src/lib/exe-daemon-client.ts"() {
831
+ "use strict";
832
+ init_config();
833
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
834
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
835
+ SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
836
+ SPAWN_LOCK_STALE_MS = 3e4;
837
+ CONNECT_TIMEOUT_MS = 15e3;
838
+ REQUEST_TIMEOUT_MS = 3e4;
839
+ _socket = null;
840
+ _connected = false;
841
+ _buffer = "";
842
+ _pending = /* @__PURE__ */ new Map();
843
+ MAX_BUFFER = 1e7;
844
+ }
845
+ });
846
+
847
+ // src/lib/daemon-protocol.ts
848
+ function serializeValue(v) {
849
+ if (v === null || v === void 0) return null;
850
+ if (typeof v === "bigint") return Number(v);
851
+ if (typeof v === "boolean") return v ? 1 : 0;
852
+ if (v instanceof Uint8Array) {
853
+ return { __blob: Buffer.from(v).toString("base64") };
854
+ }
855
+ if (ArrayBuffer.isView(v)) {
856
+ return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
857
+ }
858
+ if (v instanceof ArrayBuffer) {
859
+ return { __blob: Buffer.from(v).toString("base64") };
860
+ }
861
+ if (typeof v === "string" || typeof v === "number") return v;
862
+ return String(v);
863
+ }
864
+ function deserializeValue(v) {
865
+ if (v === null) return null;
866
+ if (typeof v === "object" && v !== null && "__blob" in v) {
867
+ const buf = Buffer.from(v.__blob, "base64");
868
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
869
+ }
870
+ return v;
871
+ }
872
+ function deserializeResultSet(srs) {
873
+ const rows = srs.rows.map((obj) => {
874
+ const values = srs.columns.map(
875
+ (col) => deserializeValue(obj[col] ?? null)
876
+ );
877
+ const row = values;
878
+ for (let i = 0; i < srs.columns.length; i++) {
879
+ const col = srs.columns[i];
880
+ if (col !== void 0) {
881
+ row[col] = values[i] ?? null;
882
+ }
883
+ }
884
+ Object.defineProperty(row, "length", {
885
+ value: values.length,
886
+ enumerable: false
887
+ });
888
+ return row;
889
+ });
890
+ return {
891
+ columns: srs.columns,
892
+ columnTypes: srs.columnTypes ?? [],
893
+ rows,
894
+ rowsAffected: srs.rowsAffected,
895
+ lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
896
+ toJSON: () => ({
897
+ columns: srs.columns,
898
+ columnTypes: srs.columnTypes ?? [],
899
+ rows: srs.rows,
900
+ rowsAffected: srs.rowsAffected,
901
+ lastInsertRowid: srs.lastInsertRowid
902
+ })
903
+ };
904
+ }
905
+ var init_daemon_protocol = __esm({
906
+ "src/lib/daemon-protocol.ts"() {
907
+ "use strict";
908
+ }
909
+ });
910
+
911
+ // src/lib/db-daemon-client.ts
912
+ var db_daemon_client_exports = {};
913
+ __export(db_daemon_client_exports, {
914
+ createDaemonDbClient: () => createDaemonDbClient,
915
+ initDaemonDbClient: () => initDaemonDbClient
916
+ });
917
+ function normalizeStatement(stmt) {
918
+ if (typeof stmt === "string") {
919
+ return { sql: stmt, args: [] };
920
+ }
921
+ const sql = stmt.sql;
922
+ let args = [];
923
+ if (Array.isArray(stmt.args)) {
924
+ args = stmt.args.map((v) => serializeValue(v));
925
+ } else if (stmt.args && typeof stmt.args === "object") {
926
+ const named = {};
927
+ for (const [key, val] of Object.entries(stmt.args)) {
928
+ named[key] = serializeValue(val);
929
+ }
930
+ return { sql, args: named };
931
+ }
932
+ return { sql, args };
933
+ }
934
+ function createDaemonDbClient(fallbackClient) {
935
+ let _useDaemon = false;
936
+ const client = {
937
+ async execute(stmt) {
938
+ if (!_useDaemon || !isClientConnected()) {
939
+ return fallbackClient.execute(stmt);
940
+ }
941
+ const { sql, args } = normalizeStatement(stmt);
942
+ const response = await sendDaemonRequest({
943
+ type: "db-execute",
944
+ sql,
945
+ args
946
+ });
947
+ if (response.error) {
948
+ const errMsg = String(response.error);
949
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
950
+ process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
951
+ `);
952
+ return fallbackClient.execute(stmt);
953
+ }
954
+ throw new Error(errMsg);
955
+ }
956
+ if (response.db) {
957
+ return deserializeResultSet(response.db);
958
+ }
959
+ process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
960
+ return fallbackClient.execute(stmt);
961
+ },
962
+ async batch(stmts, mode) {
963
+ if (!_useDaemon || !isClientConnected()) {
964
+ return fallbackClient.batch(stmts, mode);
965
+ }
966
+ const statements = stmts.map(normalizeStatement);
967
+ const response = await sendDaemonRequest({
968
+ type: "db-batch",
969
+ statements,
970
+ mode: mode ?? "deferred"
971
+ });
972
+ if (response.error) {
973
+ const errMsg = String(response.error);
974
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
975
+ process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
976
+ `);
977
+ return fallbackClient.batch(stmts, mode);
978
+ }
979
+ throw new Error(errMsg);
980
+ }
981
+ const batchResults = response["db-batch"];
982
+ if (batchResults) {
983
+ return batchResults.map(deserializeResultSet);
984
+ }
985
+ process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
986
+ return fallbackClient.batch(stmts, mode);
987
+ },
988
+ // Transaction support — delegate to fallback (transactions need direct connection)
989
+ async transaction(mode) {
990
+ return fallbackClient.transaction(mode);
991
+ },
992
+ // executeMultiple — delegate to fallback (used only for schema migrations)
993
+ async executeMultiple(sql) {
994
+ return fallbackClient.executeMultiple(sql);
995
+ },
996
+ // migrate — delegate to fallback
997
+ async migrate(stmts) {
998
+ return fallbackClient.migrate(stmts);
999
+ },
1000
+ // Sync mode — delegate to fallback
1001
+ sync() {
1002
+ return fallbackClient.sync();
1003
+ },
1004
+ close() {
1005
+ _useDaemon = false;
1006
+ },
1007
+ get closed() {
1008
+ return fallbackClient.closed;
1009
+ },
1010
+ get protocol() {
1011
+ return fallbackClient.protocol;
1012
+ }
1013
+ };
1014
+ return {
1015
+ ...client,
1016
+ /** Enable daemon routing (call after confirming daemon is connected) */
1017
+ _enableDaemon() {
1018
+ _useDaemon = true;
1019
+ },
1020
+ /** Check if daemon routing is active */
1021
+ _isDaemonActive() {
1022
+ return _useDaemon && isClientConnected();
1023
+ }
1024
+ };
1025
+ }
1026
+ async function initDaemonDbClient(fallbackClient) {
1027
+ if (process.env.EXE_IS_DAEMON === "1") return null;
1028
+ const connected = await connectEmbedDaemon();
1029
+ if (!connected) {
1030
+ process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
1031
+ return null;
1032
+ }
1033
+ const client = createDaemonDbClient(fallbackClient);
1034
+ client._enableDaemon();
1035
+ process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
1036
+ return client;
1037
+ }
1038
+ var init_db_daemon_client = __esm({
1039
+ "src/lib/db-daemon-client.ts"() {
1040
+ "use strict";
1041
+ init_exe_daemon_client();
1042
+ init_daemon_protocol();
1043
+ }
1044
+ });
1045
+
609
1046
  // src/lib/database.ts
610
1047
  var database_exports = {};
611
1048
  __export(database_exports, {
@@ -614,6 +1051,7 @@ __export(database_exports, {
614
1051
  ensureSchema: () => ensureSchema,
615
1052
  getClient: () => getClient,
616
1053
  getRawClient: () => getRawClient,
1054
+ initDaemonClient: () => initDaemonClient,
617
1055
  initDatabase: () => initDatabase,
618
1056
  initTurso: () => initTurso,
619
1057
  isInitialized: () => isInitialized
@@ -641,8 +1079,27 @@ function getClient() {
641
1079
  if (!_resilientClient) {
642
1080
  throw new Error("Database client not initialized. Call initDatabase() first.");
643
1081
  }
1082
+ if (process.env.EXE_IS_DAEMON === "1") {
1083
+ return _resilientClient;
1084
+ }
1085
+ if (_daemonClient && _daemonClient._isDaemonActive()) {
1086
+ return _daemonClient;
1087
+ }
644
1088
  return _resilientClient;
645
1089
  }
1090
+ async function initDaemonClient() {
1091
+ if (process.env.EXE_IS_DAEMON === "1") return;
1092
+ if (!_resilientClient) return;
1093
+ try {
1094
+ const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
1095
+ _daemonClient = await initDaemonDbClient2(_resilientClient);
1096
+ } catch (err) {
1097
+ process.stderr.write(
1098
+ `[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
1099
+ `
1100
+ );
1101
+ }
1102
+ }
646
1103
  function getRawClient() {
647
1104
  if (!_client) {
648
1105
  throw new Error("Database client not initialized. Call initDatabase() first.");
@@ -1129,6 +1586,12 @@ async function ensureSchema() {
1129
1586
  } catch {
1130
1587
  }
1131
1588
  }
1589
+ try {
1590
+ await client.execute(
1591
+ `CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
1592
+ );
1593
+ } catch {
1594
+ }
1132
1595
  await client.executeMultiple(`
1133
1596
  CREATE TABLE IF NOT EXISTS entities (
1134
1597
  id TEXT PRIMARY KEY,
@@ -1181,7 +1644,30 @@ async function ensureSchema() {
1181
1644
  entity_id TEXT NOT NULL,
1182
1645
  PRIMARY KEY (hyperedge_id, entity_id)
1183
1646
  );
1647
+
1648
+ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
1649
+ name,
1650
+ content=entities,
1651
+ content_rowid=rowid
1652
+ );
1653
+
1654
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
1655
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
1656
+ END;
1657
+
1658
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
1659
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
1660
+ END;
1661
+
1662
+ CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
1663
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
1664
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
1665
+ END;
1184
1666
  `);
1667
+ try {
1668
+ await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
1669
+ } catch {
1670
+ }
1185
1671
  await client.executeMultiple(`
1186
1672
  CREATE TABLE IF NOT EXISTS entity_aliases (
1187
1673
  alias TEXT NOT NULL PRIMARY KEY,
@@ -1362,6 +1848,33 @@ async function ensureSchema() {
1362
1848
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
1363
1849
  ON conversations(channel_id);
1364
1850
  `);
1851
+ await client.executeMultiple(`
1852
+ CREATE TABLE IF NOT EXISTS session_agent_map (
1853
+ session_uuid TEXT PRIMARY KEY,
1854
+ agent_id TEXT NOT NULL,
1855
+ session_name TEXT,
1856
+ task_id TEXT,
1857
+ project_name TEXT,
1858
+ started_at TEXT NOT NULL
1859
+ );
1860
+
1861
+ CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
1862
+ ON session_agent_map(agent_id);
1863
+ `);
1864
+ try {
1865
+ const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
1866
+ if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
1867
+ await client.execute({
1868
+ sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
1869
+ SELECT session_id, agent_id, '', MIN(timestamp)
1870
+ FROM memories
1871
+ WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
1872
+ GROUP BY session_id, agent_id`,
1873
+ args: []
1874
+ });
1875
+ }
1876
+ } catch {
1877
+ }
1365
1878
  try {
1366
1879
  await client.execute({
1367
1880
  sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
@@ -1495,15 +2008,41 @@ async function ensureSchema() {
1495
2008
  });
1496
2009
  } catch {
1497
2010
  }
2011
+ for (const col of [
2012
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
2013
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
2014
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
2015
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
2016
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
2017
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
2018
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
2019
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
2020
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
2021
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
2022
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
2023
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
2024
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
2025
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
2026
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2027
+ ]) {
2028
+ try {
2029
+ await client.execute(col);
2030
+ } catch {
2031
+ }
2032
+ }
1498
2033
  }
1499
2034
  async function disposeDatabase() {
2035
+ if (_daemonClient) {
2036
+ _daemonClient.close();
2037
+ _daemonClient = null;
2038
+ }
1500
2039
  if (_client) {
1501
2040
  _client.close();
1502
2041
  _client = null;
1503
2042
  _resilientClient = null;
1504
2043
  }
1505
2044
  }
1506
- var _client, _resilientClient, initTurso, disposeTurso;
2045
+ var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
1507
2046
  var init_database = __esm({
1508
2047
  "src/lib/database.ts"() {
1509
2048
  "use strict";
@@ -1511,24 +2050,25 @@ var init_database = __esm({
1511
2050
  init_employees();
1512
2051
  _client = null;
1513
2052
  _resilientClient = null;
2053
+ _daemonClient = null;
1514
2054
  initTurso = initDatabase;
1515
2055
  disposeTurso = disposeDatabase;
1516
2056
  }
1517
2057
  });
1518
2058
 
1519
2059
  // src/lib/license.ts
1520
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
1521
- import { randomUUID } from "crypto";
1522
- import path5 from "path";
2060
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
2061
+ import { randomUUID as randomUUID2 } from "crypto";
2062
+ import path6 from "path";
1523
2063
  import { jwtVerify, importSPKI } from "jose";
1524
2064
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
1525
2065
  var init_license = __esm({
1526
2066
  "src/lib/license.ts"() {
1527
2067
  "use strict";
1528
2068
  init_config();
1529
- LICENSE_PATH = path5.join(EXE_AI_DIR, "license.key");
1530
- CACHE_PATH = path5.join(EXE_AI_DIR, "license-cache.json");
1531
- DEVICE_ID_PATH = path5.join(EXE_AI_DIR, "device-id");
2069
+ LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
2070
+ CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
2071
+ DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
1532
2072
  PLAN_LIMITS = {
1533
2073
  free: { devices: 1, employees: 1, memories: 5e3 },
1534
2074
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -1540,12 +2080,12 @@ var init_license = __esm({
1540
2080
  });
1541
2081
 
1542
2082
  // src/lib/plan-limits.ts
1543
- import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
1544
- import path6 from "path";
2083
+ import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
2084
+ import path7 from "path";
1545
2085
  function getLicenseSync() {
1546
2086
  try {
1547
- if (!existsSync6(CACHE_PATH2)) return freeLicense();
1548
- const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
2087
+ if (!existsSync7(CACHE_PATH2)) return freeLicense();
2088
+ const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
1549
2089
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
1550
2090
  const parts = raw.token.split(".");
1551
2091
  if (parts.length !== 3) return freeLicense();
@@ -1583,8 +2123,8 @@ function assertEmployeeLimitSync(rosterPath) {
1583
2123
  const filePath = rosterPath ?? EMPLOYEES_PATH;
1584
2124
  let count = 0;
1585
2125
  try {
1586
- if (existsSync6(filePath)) {
1587
- const raw = readFileSync6(filePath, "utf8");
2126
+ if (existsSync7(filePath)) {
2127
+ const raw = readFileSync7(filePath, "utf8");
1588
2128
  const employees = JSON.parse(raw);
1589
2129
  count = Array.isArray(employees) ? employees.length : 0;
1590
2130
  }
@@ -1613,19 +2153,19 @@ var init_plan_limits = __esm({
1613
2153
  this.name = "PlanLimitError";
1614
2154
  }
1615
2155
  };
1616
- CACHE_PATH2 = path6.join(EXE_AI_DIR, "license-cache.json");
2156
+ CACHE_PATH2 = path7.join(EXE_AI_DIR, "license-cache.json");
1617
2157
  }
1618
2158
  });
1619
2159
 
1620
2160
  // src/lib/notifications.ts
1621
2161
  import crypto from "crypto";
1622
- import path7 from "path";
2162
+ import path8 from "path";
1623
2163
  import os5 from "os";
1624
2164
  import {
1625
- readFileSync as readFileSync7,
2165
+ readFileSync as readFileSync8,
1626
2166
  readdirSync,
1627
- unlinkSync as unlinkSync2,
1628
- existsSync as existsSync7,
2167
+ unlinkSync as unlinkSync3,
2168
+ existsSync as existsSync8,
1629
2169
  rmdirSync
1630
2170
  } from "fs";
1631
2171
  async function writeNotification(notification) {
@@ -1776,10 +2316,11 @@ __export(tasks_crud_exports, {
1776
2316
  writeCheckpoint: () => writeCheckpoint
1777
2317
  });
1778
2318
  import crypto3 from "crypto";
1779
- import path8 from "path";
2319
+ import path9 from "path";
2320
+ import os6 from "os";
1780
2321
  import { execSync as execSync4 } from "child_process";
1781
2322
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
1782
- import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
2323
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
1783
2324
  async function writeCheckpoint(input) {
1784
2325
  const client = getClient();
1785
2326
  const row = await resolveTask(client, input.taskId);
@@ -1820,6 +2361,35 @@ function extractParentFromContext(contextBody) {
1820
2361
  function slugify(title) {
1821
2362
  return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
1822
2363
  }
2364
+ function buildKeywordIndex() {
2365
+ const idx = /* @__PURE__ */ new Map();
2366
+ for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
2367
+ for (const kw of keywords) {
2368
+ const existing = idx.get(kw) ?? [];
2369
+ existing.push(role);
2370
+ idx.set(kw, existing);
2371
+ }
2372
+ }
2373
+ return idx;
2374
+ }
2375
+ function checkLaneAffinity(title, context, assigneeName) {
2376
+ const employees = loadEmployeesSync();
2377
+ const employee = employees.find((e) => e.name === assigneeName);
2378
+ if (!employee) return void 0;
2379
+ const assigneeRole = employee.role;
2380
+ const text = `${title} ${context}`.toLowerCase();
2381
+ const matchedRoles = /* @__PURE__ */ new Set();
2382
+ for (const [keyword, roles] of KEYWORD_INDEX) {
2383
+ if (text.includes(keyword)) {
2384
+ for (const role of roles) matchedRoles.add(role);
2385
+ }
2386
+ }
2387
+ if (matchedRoles.size === 0) return void 0;
2388
+ if (matchedRoles.has(assigneeRole)) return void 0;
2389
+ if (assigneeRole === "COO") return void 0;
2390
+ const expectedRoles = Array.from(matchedRoles).join(" or ");
2391
+ return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
2392
+ }
1823
2393
  async function resolveTask(client, identifier, scopeSession) {
1824
2394
  const scope = sessionScopeFilter(scopeSession);
1825
2395
  let result = await client.execute({
@@ -1869,7 +2439,14 @@ async function createTaskCore(input) {
1869
2439
  const id = crypto3.randomUUID();
1870
2440
  const now = (/* @__PURE__ */ new Date()).toISOString();
1871
2441
  const slug = slugify(input.title);
1872
- const taskFile = input.taskFile ?? `exe/${input.assignedTo}/${slug}.md`;
2442
+ let earlySessionScope = null;
2443
+ try {
2444
+ const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
2445
+ earlySessionScope = resolveExeSession2();
2446
+ } catch {
2447
+ }
2448
+ const scope = earlySessionScope ?? "default";
2449
+ const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
1873
2450
  let blockedById = null;
1874
2451
  const initialStatus = input.blockedBy ? "blocked" : "open";
1875
2452
  if (input.blockedBy) {
@@ -1909,22 +2486,24 @@ async function createTaskCore(input) {
1909
2486
  if (dupCheck.rows.length > 0) {
1910
2487
  warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
1911
2488
  }
2489
+ if (!process.env.DISABLE_LANE_AFFINITY) {
2490
+ const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
2491
+ if (laneWarning) {
2492
+ warning = warning ? `${warning}
2493
+ ${laneWarning}` : laneWarning;
2494
+ }
2495
+ }
1912
2496
  if (input.baseDir) {
1913
2497
  try {
1914
- await mkdir3(path8.join(input.baseDir, "exe", "output"), { recursive: true });
1915
- await mkdir3(path8.join(input.baseDir, "exe", "research"), { recursive: true });
2498
+ await mkdir3(path9.join(input.baseDir, "exe", "output"), { recursive: true });
2499
+ await mkdir3(path9.join(input.baseDir, "exe", "research"), { recursive: true });
1916
2500
  await ensureArchitectureDoc(input.baseDir, input.projectName);
1917
2501
  await ensureGitignoreExe(input.baseDir);
1918
2502
  } catch {
1919
2503
  }
1920
2504
  }
1921
2505
  const complexity = input.complexity ?? "standard";
1922
- let sessionScope = null;
1923
- try {
1924
- const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
1925
- sessionScope = resolveExeSession2();
1926
- } catch {
1927
- }
2506
+ const sessionScope = earlySessionScope;
1928
2507
  await client.execute({
1929
2508
  sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, created_at, updated_at)
1930
2509
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
@@ -1951,6 +2530,39 @@ async function createTaskCore(input) {
1951
2530
  now
1952
2531
  ]
1953
2532
  });
2533
+ if (input.baseDir) {
2534
+ try {
2535
+ const EXE_OS_DIR = path9.join(os6.homedir(), ".exe-os");
2536
+ const mdPath = path9.join(EXE_OS_DIR, taskFile);
2537
+ const mdDir = path9.dirname(mdPath);
2538
+ if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
2539
+ const reviewer = input.reviewer ?? input.assignedBy;
2540
+ const mdContent = `# ${input.title}
2541
+
2542
+ **ID:** ${id}
2543
+ **Status:** ${initialStatus}
2544
+ **Priority:** ${input.priority}
2545
+ **Assigned by:** ${input.assignedBy}
2546
+ **Assigned to:** ${input.assignedTo}
2547
+ **Project:** ${input.projectName}
2548
+ **Created:** ${now.split("T")[0]}${parentTaskId ? `
2549
+ **Parent task:** ${parentTaskId}` : ""}
2550
+ **Reviewer:** ${reviewer}
2551
+
2552
+ ## Context
2553
+
2554
+ ${input.context}
2555
+
2556
+ ## MANDATORY: When done
2557
+
2558
+ You MUST call update_task with status "done" and a result summary when finished.
2559
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
2560
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
2561
+ `;
2562
+ await writeFile3(mdPath, mdContent, "utf-8");
2563
+ } catch {
2564
+ }
2565
+ }
1954
2566
  return {
1955
2567
  id,
1956
2568
  title: input.title,
@@ -2143,7 +2755,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
2143
2755
  return { row, taskFile, now, taskId };
2144
2756
  }
2145
2757
  }
2146
- if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
2758
+ if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
2147
2759
  process.stderr.write(
2148
2760
  `[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
2149
2761
  `
@@ -2208,9 +2820,9 @@ async function deleteTaskCore(taskId, _baseDir) {
2208
2820
  return { taskFile, assignedTo, assignedBy, taskSlug };
2209
2821
  }
2210
2822
  async function ensureArchitectureDoc(baseDir, projectName) {
2211
- const archPath = path8.join(baseDir, "exe", "ARCHITECTURE.md");
2823
+ const archPath = path9.join(baseDir, "exe", "ARCHITECTURE.md");
2212
2824
  try {
2213
- if (existsSync8(archPath)) return;
2825
+ if (existsSync9(archPath)) return;
2214
2826
  const template = [
2215
2827
  `# ${projectName} \u2014 System Architecture`,
2216
2828
  "",
@@ -2243,10 +2855,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
2243
2855
  }
2244
2856
  }
2245
2857
  async function ensureGitignoreExe(baseDir) {
2246
- const gitignorePath = path8.join(baseDir, ".gitignore");
2858
+ const gitignorePath = path9.join(baseDir, ".gitignore");
2247
2859
  try {
2248
- if (existsSync8(gitignorePath)) {
2249
- const content = readFileSync8(gitignorePath, "utf-8");
2860
+ if (existsSync9(gitignorePath)) {
2861
+ const content = readFileSync9(gitignorePath, "utf-8");
2250
2862
  if (/^\/?exe\/?$/m.test(content)) return;
2251
2863
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
2252
2864
  } else {
@@ -2255,20 +2867,30 @@ async function ensureGitignoreExe(baseDir) {
2255
2867
  } catch {
2256
2868
  }
2257
2869
  }
2258
- var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
2870
+ var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
2259
2871
  var init_tasks_crud = __esm({
2260
2872
  "src/lib/tasks-crud.ts"() {
2261
2873
  "use strict";
2262
2874
  init_database();
2263
2875
  init_task_scope();
2876
+ init_employees();
2877
+ LANE_KEYWORDS = {
2878
+ CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
2879
+ CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
2880
+ "Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
2881
+ "Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
2882
+ "Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
2883
+ "AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
2884
+ };
2885
+ KEYWORD_INDEX = buildKeywordIndex();
2264
2886
  DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
2265
2887
  TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
2266
2888
  }
2267
2889
  });
2268
2890
 
2269
2891
  // src/lib/tasks-review.ts
2270
- import path9 from "path";
2271
- import { existsSync as existsSync9, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
2892
+ import path10 from "path";
2893
+ import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
2272
2894
  async function countPendingReviews(sessionScope) {
2273
2895
  const client = getClient();
2274
2896
  if (sessionScope) {
@@ -2290,7 +2912,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
2290
2912
  const result2 = await client.execute({
2291
2913
  sql: `SELECT COUNT(*) as cnt FROM tasks
2292
2914
  WHERE status = 'needs_review' AND updated_at > ?
2293
- AND (session_scope = ? OR session_scope IS NULL)`,
2915
+ AND session_scope = ?`,
2294
2916
  args: [sinceIso, sessionScope]
2295
2917
  });
2296
2918
  return Number(result2.rows[0]?.cnt) || 0;
@@ -2308,7 +2930,7 @@ async function listPendingReviews(limit, sessionScope) {
2308
2930
  const result2 = await client.execute({
2309
2931
  sql: `SELECT title, assigned_to, project_name FROM tasks
2310
2932
  WHERE status = 'needs_review'
2311
- AND (session_scope = ? OR session_scope IS NULL)
2933
+ AND session_scope = ?
2312
2934
  ORDER BY priority ASC, created_at DESC LIMIT ?`,
2313
2935
  args: [sessionScope, limit]
2314
2936
  });
@@ -2429,14 +3051,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
2429
3051
  if (parts.length >= 3 && parts[0] === "review") {
2430
3052
  const agent = parts[1];
2431
3053
  const slug = parts.slice(2).join("-");
2432
- const originalTaskFile = `exe/${agent}/${slug}.md`;
3054
+ const legacyTaskFile = `exe/${agent}/${slug}.md`;
2433
3055
  const result = await client.execute({
2434
- sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
2435
- args: [now, originalTaskFile]
3056
+ sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
3057
+ args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
2436
3058
  });
2437
3059
  if (result.rowsAffected > 0) {
2438
3060
  process.stderr.write(
2439
- `[review-cleanup] Cascaded original task to done (legacy path): ${originalTaskFile}
3061
+ `[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
2440
3062
  `
2441
3063
  );
2442
3064
  }
@@ -2449,11 +3071,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
2449
3071
  );
2450
3072
  }
2451
3073
  try {
2452
- const cacheDir = path9.join(EXE_AI_DIR, "session-cache");
2453
- if (existsSync9(cacheDir)) {
3074
+ const cacheDir = path10.join(EXE_AI_DIR, "session-cache");
3075
+ if (existsSync10(cacheDir)) {
2454
3076
  for (const f of readdirSync2(cacheDir)) {
2455
3077
  if (f.startsWith("review-notified-")) {
2456
- unlinkSync3(path9.join(cacheDir, f));
3078
+ unlinkSync4(path10.join(cacheDir, f));
2457
3079
  }
2458
3080
  }
2459
3081
  }
@@ -2474,7 +3096,7 @@ var init_tasks_review = __esm({
2474
3096
  });
2475
3097
 
2476
3098
  // src/lib/tasks-chain.ts
2477
- import path10 from "path";
3099
+ import path11 from "path";
2478
3100
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
2479
3101
  async function cascadeUnblock(taskId, baseDir, now) {
2480
3102
  const client = getClient();
@@ -2491,7 +3113,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
2491
3113
  });
2492
3114
  for (const ur of unblockedRows.rows) {
2493
3115
  try {
2494
- const ubFile = path10.join(baseDir, String(ur.task_file));
3116
+ const ubFile = path11.join(baseDir, String(ur.task_file));
2495
3117
  let ubContent = await readFile3(ubFile, "utf-8");
2496
3118
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
2497
3119
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -2560,7 +3182,7 @@ var init_tasks_chain = __esm({
2560
3182
 
2561
3183
  // src/lib/project-name.ts
2562
3184
  import { execSync as execSync5 } from "child_process";
2563
- import path11 from "path";
3185
+ import path12 from "path";
2564
3186
  function getProjectName(cwd) {
2565
3187
  const dir = cwd ?? process.cwd();
2566
3188
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -2573,7 +3195,7 @@ function getProjectName(cwd) {
2573
3195
  timeout: 2e3,
2574
3196
  stdio: ["pipe", "pipe", "pipe"]
2575
3197
  }).trim();
2576
- repoRoot = path11.dirname(gitCommonDir);
3198
+ repoRoot = path12.dirname(gitCommonDir);
2577
3199
  } catch {
2578
3200
  repoRoot = execSync5("git rev-parse --show-toplevel", {
2579
3201
  cwd: dir,
@@ -2582,11 +3204,11 @@ function getProjectName(cwd) {
2582
3204
  stdio: ["pipe", "pipe", "pipe"]
2583
3205
  }).trim();
2584
3206
  }
2585
- _cached2 = path11.basename(repoRoot);
3207
+ _cached2 = path12.basename(repoRoot);
2586
3208
  _cachedCwd = dir;
2587
3209
  return _cached2;
2588
3210
  } catch {
2589
- _cached2 = path11.basename(dir);
3211
+ _cached2 = path12.basename(dir);
2590
3212
  _cachedCwd = dir;
2591
3213
  return _cached2;
2592
3214
  }
@@ -2618,7 +3240,7 @@ function findSessionForProject(projectName) {
2618
3240
  const sessions = listSessions();
2619
3241
  for (const s of sessions) {
2620
3242
  const proj = s.projectDir.split("/").filter(Boolean).pop();
2621
- if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
3243
+ if (proj === projectName && isCoordinatorName(s.agentId)) return s;
2622
3244
  }
2623
3245
  return null;
2624
3246
  }
@@ -2664,7 +3286,7 @@ var init_session_scope = __esm({
2664
3286
 
2665
3287
  // src/lib/tasks-notify.ts
2666
3288
  async function dispatchTaskToEmployee(input) {
2667
- if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
3289
+ if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
2668
3290
  let crossProject = false;
2669
3291
  if (input.projectName) {
2670
3292
  try {
@@ -3059,8 +3681,8 @@ __export(tasks_exports, {
3059
3681
  updateTaskStatus: () => updateTaskStatus,
3060
3682
  writeCheckpoint: () => writeCheckpoint
3061
3683
  });
3062
- import path12 from "path";
3063
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync4 } from "fs";
3684
+ import path13 from "path";
3685
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync5 } from "fs";
3064
3686
  async function createTask(input) {
3065
3687
  const result = await createTaskCore(input);
3066
3688
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -3079,14 +3701,14 @@ async function updateTask(input) {
3079
3701
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
3080
3702
  try {
3081
3703
  const agent = String(row.assigned_to);
3082
- const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
3083
- const cachePath = path12.join(cacheDir, `current-task-${agent}.json`);
3704
+ const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
3705
+ const cachePath = path13.join(cacheDir, `current-task-${agent}.json`);
3084
3706
  if (input.status === "in_progress") {
3085
3707
  mkdirSync4(cacheDir, { recursive: true });
3086
3708
  writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3087
3709
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
3088
3710
  try {
3089
- unlinkSync4(cachePath);
3711
+ unlinkSync5(cachePath);
3090
3712
  } catch {
3091
3713
  }
3092
3714
  }
@@ -3143,7 +3765,7 @@ async function updateTask(input) {
3143
3765
  }
3144
3766
  const isTerminal = input.status === "done" || input.status === "needs_review";
3145
3767
  if (isTerminal) {
3146
- const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
3768
+ const isCoordinator = isCoordinatorName(String(row.assigned_to));
3147
3769
  if (!isCoordinator) {
3148
3770
  notifyTaskDone();
3149
3771
  }
@@ -3168,7 +3790,7 @@ async function updateTask(input) {
3168
3790
  }
3169
3791
  }
3170
3792
  }
3171
- if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
3793
+ if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
3172
3794
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
3173
3795
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
3174
3796
  taskId,
@@ -3184,7 +3806,7 @@ async function updateTask(input) {
3184
3806
  });
3185
3807
  }
3186
3808
  let nextTask;
3187
- if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
3809
+ if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
3188
3810
  try {
3189
3811
  nextTask = await findNextTask(String(row.assigned_to));
3190
3812
  } catch {
@@ -3528,7 +4150,7 @@ var init_capacity_monitor = __esm({
3528
4150
  // src/lib/tmux-routing.ts
3529
4151
  var tmux_routing_exports = {};
3530
4152
  __export(tmux_routing_exports, {
3531
- acquireSpawnLock: () => acquireSpawnLock,
4153
+ acquireSpawnLock: () => acquireSpawnLock2,
3532
4154
  employeeSessionName: () => employeeSessionName,
3533
4155
  ensureEmployee: () => ensureEmployee,
3534
4156
  extractRootExe: () => extractRootExe,
@@ -3543,20 +4165,20 @@ __export(tmux_routing_exports, {
3543
4165
  notifyParentExe: () => notifyParentExe,
3544
4166
  parseParentExe: () => parseParentExe,
3545
4167
  registerParentExe: () => registerParentExe,
3546
- releaseSpawnLock: () => releaseSpawnLock,
4168
+ releaseSpawnLock: () => releaseSpawnLock2,
3547
4169
  resolveExeSession: () => resolveExeSession,
3548
4170
  sendIntercom: () => sendIntercom,
3549
4171
  spawnEmployee: () => spawnEmployee,
3550
4172
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
3551
4173
  });
3552
4174
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
3553
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync } from "fs";
3554
- import path13 from "path";
3555
- import os6 from "os";
3556
- import { fileURLToPath } from "url";
3557
- import { unlinkSync as unlinkSync5 } from "fs";
4175
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
4176
+ import path14 from "path";
4177
+ import os7 from "os";
4178
+ import { fileURLToPath as fileURLToPath2 } from "url";
4179
+ import { unlinkSync as unlinkSync6 } from "fs";
3558
4180
  function spawnLockPath(sessionName) {
3559
- return path13.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4181
+ return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
3560
4182
  }
3561
4183
  function isProcessAlive(pid) {
3562
4184
  try {
@@ -3566,14 +4188,14 @@ function isProcessAlive(pid) {
3566
4188
  return false;
3567
4189
  }
3568
4190
  }
3569
- function acquireSpawnLock(sessionName) {
3570
- if (!existsSync10(SPAWN_LOCK_DIR)) {
4191
+ function acquireSpawnLock2(sessionName) {
4192
+ if (!existsSync11(SPAWN_LOCK_DIR)) {
3571
4193
  mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
3572
4194
  }
3573
4195
  const lockFile = spawnLockPath(sessionName);
3574
- if (existsSync10(lockFile)) {
4196
+ if (existsSync11(lockFile)) {
3575
4197
  try {
3576
- const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
4198
+ const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
3577
4199
  const age = Date.now() - lock.timestamp;
3578
4200
  if (isProcessAlive(lock.pid) && age < 6e4) {
3579
4201
  return false;
@@ -3584,22 +4206,22 @@ function acquireSpawnLock(sessionName) {
3584
4206
  writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
3585
4207
  return true;
3586
4208
  }
3587
- function releaseSpawnLock(sessionName) {
4209
+ function releaseSpawnLock2(sessionName) {
3588
4210
  try {
3589
- unlinkSync5(spawnLockPath(sessionName));
4211
+ unlinkSync6(spawnLockPath(sessionName));
3590
4212
  } catch {
3591
4213
  }
3592
4214
  }
3593
4215
  function resolveBehaviorsExporterScript() {
3594
4216
  try {
3595
- const thisFile = fileURLToPath(import.meta.url);
3596
- const scriptPath = path13.join(
3597
- path13.dirname(thisFile),
4217
+ const thisFile = fileURLToPath2(import.meta.url);
4218
+ const scriptPath = path14.join(
4219
+ path14.dirname(thisFile),
3598
4220
  "..",
3599
4221
  "bin",
3600
4222
  "exe-export-behaviors.js"
3601
4223
  );
3602
- return existsSync10(scriptPath) ? scriptPath : null;
4224
+ return existsSync11(scriptPath) ? scriptPath : null;
3603
4225
  } catch {
3604
4226
  return null;
3605
4227
  }
@@ -3665,11 +4287,11 @@ function extractRootExe(name) {
3665
4287
  return parts.length > 0 ? parts[parts.length - 1] : null;
3666
4288
  }
3667
4289
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3668
- if (!existsSync10(SESSION_CACHE)) {
4290
+ if (!existsSync11(SESSION_CACHE)) {
3669
4291
  mkdirSync5(SESSION_CACHE, { recursive: true });
3670
4292
  }
3671
4293
  const rootExe = extractRootExe(parentExe) ?? parentExe;
3672
- const filePath = path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4294
+ const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
3673
4295
  writeFileSync6(filePath, JSON.stringify({
3674
4296
  parentExe: rootExe,
3675
4297
  dispatchedBy: dispatchedBy || rootExe,
@@ -3678,7 +4300,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3678
4300
  }
3679
4301
  function getParentExe(sessionKey) {
3680
4302
  try {
3681
- const data = JSON.parse(readFileSync9(path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4303
+ const data = JSON.parse(readFileSync10(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
3682
4304
  return data.parentExe || null;
3683
4305
  } catch {
3684
4306
  return null;
@@ -3686,8 +4308,8 @@ function getParentExe(sessionKey) {
3686
4308
  }
3687
4309
  function getDispatchedBy(sessionKey) {
3688
4310
  try {
3689
- const data = JSON.parse(readFileSync9(
3690
- path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4311
+ const data = JSON.parse(readFileSync10(
4312
+ path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
3691
4313
  "utf8"
3692
4314
  ));
3693
4315
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -3713,10 +4335,10 @@ function isEmployeeAlive(sessionName) {
3713
4335
  }
3714
4336
  function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
3715
4337
  const base = employeeSessionName(employeeName, exeSession);
3716
- if (!isAlive(base) && acquireSpawnLock(base)) return 0;
4338
+ if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
3717
4339
  for (let i = 2; i <= maxInstances; i++) {
3718
4340
  const candidate = employeeSessionName(employeeName, exeSession, i);
3719
- if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
4341
+ if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
3720
4342
  }
3721
4343
  return null;
3722
4344
  }
@@ -3748,15 +4370,15 @@ async function verifyPaneAtCapacity(sessionName) {
3748
4370
  }
3749
4371
  function readDebounceState() {
3750
4372
  try {
3751
- if (!existsSync10(DEBOUNCE_FILE)) return {};
3752
- return JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
4373
+ if (!existsSync11(DEBOUNCE_FILE)) return {};
4374
+ return JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
3753
4375
  } catch {
3754
4376
  return {};
3755
4377
  }
3756
4378
  }
3757
4379
  function writeDebounceState(state) {
3758
4380
  try {
3759
- if (!existsSync10(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
4381
+ if (!existsSync11(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
3760
4382
  writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
3761
4383
  } catch {
3762
4384
  }
@@ -3876,7 +4498,7 @@ function notifyParentExe(sessionKey) {
3876
4498
  return true;
3877
4499
  }
3878
4500
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
3879
- if (employeeName === "exe" || isCoordinatorName(employeeName)) {
4501
+ if (isCoordinatorName(employeeName)) {
3880
4502
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
3881
4503
  }
3882
4504
  try {
@@ -3948,26 +4570,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3948
4570
  const transport = getTransport();
3949
4571
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
3950
4572
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
3951
- const logDir = path13.join(os6.homedir(), ".exe-os", "session-logs");
3952
- const logFile = path13.join(logDir, `${instanceLabel}-${Date.now()}.log`);
3953
- if (!existsSync10(logDir)) {
4573
+ const logDir = path14.join(os7.homedir(), ".exe-os", "session-logs");
4574
+ const logFile = path14.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4575
+ if (!existsSync11(logDir)) {
3954
4576
  mkdirSync5(logDir, { recursive: true });
3955
4577
  }
3956
4578
  transport.kill(sessionName);
3957
4579
  let cleanupSuffix = "";
3958
4580
  try {
3959
- const thisFile = fileURLToPath(import.meta.url);
3960
- const cleanupScript = path13.join(path13.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
3961
- if (existsSync10(cleanupScript)) {
4581
+ const thisFile = fileURLToPath2(import.meta.url);
4582
+ const cleanupScript = path14.join(path14.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4583
+ if (existsSync11(cleanupScript)) {
3962
4584
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
3963
4585
  }
3964
4586
  } catch {
3965
4587
  }
3966
4588
  try {
3967
- const claudeJsonPath = path13.join(os6.homedir(), ".claude.json");
4589
+ const claudeJsonPath = path14.join(os7.homedir(), ".claude.json");
3968
4590
  let claudeJson = {};
3969
4591
  try {
3970
- claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
4592
+ claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
3971
4593
  } catch {
3972
4594
  }
3973
4595
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -3979,13 +4601,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3979
4601
  } catch {
3980
4602
  }
3981
4603
  try {
3982
- const settingsDir = path13.join(os6.homedir(), ".claude", "projects");
4604
+ const settingsDir = path14.join(os7.homedir(), ".claude", "projects");
3983
4605
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
3984
- const projSettingsDir = path13.join(settingsDir, normalizedKey);
3985
- const settingsPath = path13.join(projSettingsDir, "settings.json");
4606
+ const projSettingsDir = path14.join(settingsDir, normalizedKey);
4607
+ const settingsPath = path14.join(projSettingsDir, "settings.json");
3986
4608
  let settings = {};
3987
4609
  try {
3988
- settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
4610
+ settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
3989
4611
  } catch {
3990
4612
  }
3991
4613
  const perms = settings.permissions ?? {};
@@ -4026,8 +4648,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4026
4648
  let behaviorsFlag = "";
4027
4649
  let legacyFallbackWarned = false;
4028
4650
  if (!useExeAgent && !useBinSymlink) {
4029
- const identityPath = path13.join(
4030
- os6.homedir(),
4651
+ const identityPath = path14.join(
4652
+ os7.homedir(),
4031
4653
  ".exe-os",
4032
4654
  "identity",
4033
4655
  `${employeeName}.md`
@@ -4036,13 +4658,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4036
4658
  const hasAgentFlag = claudeSupportsAgentFlag();
4037
4659
  if (hasAgentFlag) {
4038
4660
  identityFlag = ` --agent ${employeeName}`;
4039
- } else if (existsSync10(identityPath)) {
4661
+ } else if (existsSync11(identityPath)) {
4040
4662
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
4041
4663
  legacyFallbackWarned = true;
4042
4664
  }
4043
4665
  const behaviorsFile = exportBehaviorsSync(
4044
4666
  employeeName,
4045
- path13.basename(spawnCwd),
4667
+ path14.basename(spawnCwd),
4046
4668
  sessionName
4047
4669
  );
4048
4670
  if (behaviorsFile) {
@@ -4057,9 +4679,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4057
4679
  }
4058
4680
  let sessionContextFlag = "";
4059
4681
  try {
4060
- const ctxDir = path13.join(os6.homedir(), ".exe-os", "session-cache");
4682
+ const ctxDir = path14.join(os7.homedir(), ".exe-os", "session-cache");
4061
4683
  mkdirSync5(ctxDir, { recursive: true });
4062
- const ctxFile = path13.join(ctxDir, `session-context-${sessionName}.md`);
4684
+ const ctxFile = path14.join(ctxDir, `session-context-${sessionName}.md`);
4063
4685
  const ctxContent = [
4064
4686
  `## Session Context`,
4065
4687
  `You are running in tmux session: ${sessionName}.`,
@@ -4098,13 +4720,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4098
4720
  command: spawnCommand
4099
4721
  });
4100
4722
  if (spawnResult.error) {
4101
- releaseSpawnLock(sessionName);
4723
+ releaseSpawnLock2(sessionName);
4102
4724
  return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
4103
4725
  }
4104
4726
  transport.pipeLog(sessionName, logFile);
4105
4727
  try {
4106
4728
  const mySession = getMySession();
4107
- const dispatchInfo = path13.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4729
+ const dispatchInfo = path14.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4108
4730
  writeFileSync6(dispatchInfo, JSON.stringify({
4109
4731
  dispatchedBy: mySession,
4110
4732
  rootExe: exeSession,
@@ -4136,7 +4758,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4136
4758
  }
4137
4759
  }
4138
4760
  if (!booted) {
4139
- releaseSpawnLock(sessionName);
4761
+ releaseSpawnLock2(sessionName);
4140
4762
  return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
4141
4763
  }
4142
4764
  if (!useExeAgent) {
@@ -4153,7 +4775,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4153
4775
  pid: 0,
4154
4776
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
4155
4777
  });
4156
- releaseSpawnLock(sessionName);
4778
+ releaseSpawnLock2(sessionName);
4157
4779
  return { sessionName };
4158
4780
  }
4159
4781
  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;
@@ -4169,14 +4791,14 @@ var init_tmux_routing = __esm({
4169
4791
  init_intercom_queue();
4170
4792
  init_plan_limits();
4171
4793
  init_employees();
4172
- SPAWN_LOCK_DIR = path13.join(os6.homedir(), ".exe-os", "spawn-locks");
4173
- SESSION_CACHE = path13.join(os6.homedir(), ".exe-os", "session-cache");
4794
+ SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
4795
+ SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
4174
4796
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
4175
4797
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
4176
4798
  VERIFY_PANE_LINES = 200;
4177
4799
  INTERCOM_DEBOUNCE_MS = 3e4;
4178
- INTERCOM_LOG2 = path13.join(os6.homedir(), ".exe-os", "intercom.log");
4179
- DEBOUNCE_FILE = path13.join(SESSION_CACHE, "intercom-debounce.json");
4800
+ INTERCOM_LOG2 = path14.join(os7.homedir(), ".exe-os", "intercom.log");
4801
+ DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
4180
4802
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
4181
4803
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
4182
4804
  }
@@ -4217,14 +4839,14 @@ var init_memory = __esm({
4217
4839
 
4218
4840
  // src/lib/keychain.ts
4219
4841
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
4220
- import { existsSync as existsSync11 } from "fs";
4221
- import path14 from "path";
4222
- import os7 from "os";
4842
+ import { existsSync as existsSync12 } from "fs";
4843
+ import path15 from "path";
4844
+ import os8 from "os";
4223
4845
  function getKeyDir() {
4224
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path14.join(os7.homedir(), ".exe-os");
4846
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path15.join(os8.homedir(), ".exe-os");
4225
4847
  }
4226
4848
  function getKeyPath() {
4227
- return path14.join(getKeyDir(), "master.key");
4849
+ return path15.join(getKeyDir(), "master.key");
4228
4850
  }
4229
4851
  async function tryKeytar() {
4230
4852
  try {
@@ -4245,13 +4867,21 @@ async function getMasterKey() {
4245
4867
  }
4246
4868
  }
4247
4869
  const keyPath = getKeyPath();
4248
- if (!existsSync11(keyPath)) {
4870
+ if (!existsSync12(keyPath)) {
4871
+ process.stderr.write(
4872
+ `[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
4873
+ `
4874
+ );
4249
4875
  return null;
4250
4876
  }
4251
4877
  try {
4252
4878
  const content = await readFile4(keyPath, "utf-8");
4253
4879
  return Buffer.from(content.trim(), "base64");
4254
- } catch {
4880
+ } catch (err) {
4881
+ process.stderr.write(
4882
+ `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
4883
+ `
4884
+ );
4255
4885
  return null;
4256
4886
  }
4257
4887
  }
@@ -4277,12 +4907,12 @@ __export(shard_manager_exports, {
4277
4907
  listShards: () => listShards,
4278
4908
  shardExists: () => shardExists
4279
4909
  });
4280
- import path15 from "path";
4281
- import { existsSync as existsSync12, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
4910
+ import path16 from "path";
4911
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
4282
4912
  import { createClient as createClient2 } from "@libsql/client";
4283
4913
  function initShardManager(encryptionKey) {
4284
4914
  _encryptionKey = encryptionKey;
4285
- if (!existsSync12(SHARDS_DIR)) {
4915
+ if (!existsSync13(SHARDS_DIR)) {
4286
4916
  mkdirSync6(SHARDS_DIR, { recursive: true });
4287
4917
  }
4288
4918
  _shardingEnabled = true;
@@ -4303,7 +4933,7 @@ function getShardClient(projectName) {
4303
4933
  }
4304
4934
  const cached = _shards.get(safeName);
4305
4935
  if (cached) return cached;
4306
- const dbPath = path15.join(SHARDS_DIR, `${safeName}.db`);
4936
+ const dbPath = path16.join(SHARDS_DIR, `${safeName}.db`);
4307
4937
  const client = createClient2({
4308
4938
  url: `file:${dbPath}`,
4309
4939
  encryptionKey: _encryptionKey
@@ -4313,10 +4943,10 @@ function getShardClient(projectName) {
4313
4943
  }
4314
4944
  function shardExists(projectName) {
4315
4945
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
4316
- return existsSync12(path15.join(SHARDS_DIR, `${safeName}.db`));
4946
+ return existsSync13(path16.join(SHARDS_DIR, `${safeName}.db`));
4317
4947
  }
4318
4948
  function listShards() {
4319
- if (!existsSync12(SHARDS_DIR)) return [];
4949
+ if (!existsSync13(SHARDS_DIR)) return [];
4320
4950
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
4321
4951
  }
4322
4952
  async function ensureShardSchema(client) {
@@ -4502,7 +5132,7 @@ var init_shard_manager = __esm({
4502
5132
  "src/lib/shard-manager.ts"() {
4503
5133
  "use strict";
4504
5134
  init_config();
4505
- SHARDS_DIR = path15.join(EXE_AI_DIR, "shards");
5135
+ SHARDS_DIR = path16.join(EXE_AI_DIR, "shards");
4506
5136
  _shards = /* @__PURE__ */ new Map();
4507
5137
  _encryptionKey = null;
4508
5138
  _shardingEnabled = false;
@@ -4627,7 +5257,7 @@ __export(global_procedures_exports, {
4627
5257
  loadGlobalProcedures: () => loadGlobalProcedures,
4628
5258
  storeGlobalProcedure: () => storeGlobalProcedure
4629
5259
  });
4630
- import { randomUUID as randomUUID2 } from "crypto";
5260
+ import { randomUUID as randomUUID3 } from "crypto";
4631
5261
  async function loadGlobalProcedures() {
4632
5262
  const client = getClient();
4633
5263
  const result = await client.execute({
@@ -4656,7 +5286,7 @@ ${sections.join("\n\n")}
4656
5286
  `;
4657
5287
  }
4658
5288
  async function storeGlobalProcedure(input) {
4659
- const id = randomUUID2();
5289
+ const id = randomUUID3();
4660
5290
  const now = (/* @__PURE__ */ new Date()).toISOString();
4661
5291
  const client = getClient();
4662
5292
  await client.execute({
@@ -4707,6 +5337,7 @@ __export(store_exports, {
4707
5337
  vectorToBlob: () => vectorToBlob,
4708
5338
  writeMemory: () => writeMemory
4709
5339
  });
5340
+ import { createHash } from "crypto";
4710
5341
  function isBusyError2(err) {
4711
5342
  if (err instanceof Error) {
4712
5343
  const msg = err.message.toLowerCase();
@@ -4780,12 +5411,52 @@ function classifyTier(record) {
4780
5411
  if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
4781
5412
  return 3;
4782
5413
  }
5414
+ function inferFilePaths(record) {
5415
+ if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
5416
+ const firstLine = record.raw_text.split("\n")[0] ?? "";
5417
+ const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
5418
+ return match ? JSON.stringify([match[1]]) : null;
5419
+ }
5420
+ function inferCommitHash(record) {
5421
+ if (record.tool_name !== "Bash") return null;
5422
+ const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
5423
+ return match ? match[1] : null;
5424
+ }
5425
+ function inferLanguageType(record) {
5426
+ const text = record.raw_text;
5427
+ if (!text || text.length < 10) return null;
5428
+ const trimmed = text.trimStart();
5429
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
5430
+ if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
5431
+ if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
5432
+ if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
5433
+ return "mixed";
5434
+ }
5435
+ function inferDomain(record) {
5436
+ const proj = (record.project_name ?? "").toLowerCase();
5437
+ if (proj.includes("marketing") || proj.includes("content")) return "marketing";
5438
+ if (proj.includes("crm") || proj.includes("customer")) return "customer";
5439
+ return null;
5440
+ }
4783
5441
  async function writeMemory(record) {
4784
5442
  if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
4785
5443
  throw new Error(
4786
5444
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
4787
5445
  );
4788
5446
  }
5447
+ const contentHash = createHash("md5").update(record.raw_text).digest("hex");
5448
+ if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
5449
+ return;
5450
+ }
5451
+ try {
5452
+ const client = getClient();
5453
+ const existing = await client.execute({
5454
+ sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
5455
+ args: [contentHash, record.agent_id]
5456
+ });
5457
+ if (existing.rows.length > 0) return;
5458
+ } catch {
5459
+ }
4789
5460
  const dbRow = {
4790
5461
  id: record.id,
4791
5462
  agent_id: record.agent_id,
@@ -4815,7 +5486,23 @@ async function writeMemory(record) {
4815
5486
  supersedes_id: record.supersedes_id ?? null,
4816
5487
  draft: record.draft ? 1 : 0,
4817
5488
  memory_type: record.memory_type ?? "raw",
4818
- trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
5489
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
5490
+ content_hash: contentHash,
5491
+ intent: record.intent ?? null,
5492
+ outcome: record.outcome ?? null,
5493
+ domain: record.domain ?? inferDomain(record),
5494
+ referenced_entities: record.referenced_entities ?? null,
5495
+ retrieval_count: record.retrieval_count ?? 0,
5496
+ chain_position: record.chain_position ?? null,
5497
+ review_status: record.review_status ?? null,
5498
+ context_window_pct: record.context_window_pct ?? null,
5499
+ file_paths: record.file_paths ?? inferFilePaths(record),
5500
+ commit_hash: record.commit_hash ?? inferCommitHash(record),
5501
+ duration_ms: record.duration_ms ?? null,
5502
+ token_cost: record.token_cost ?? null,
5503
+ audience: record.audience ?? null,
5504
+ language_type: record.language_type ?? inferLanguageType(record),
5505
+ parent_memory_id: record.parent_memory_id ?? null
4819
5506
  };
4820
5507
  _pendingRecords.push(dbRow);
4821
5508
  orgBus.emit({
@@ -4873,80 +5560,85 @@ async function flushBatch() {
4873
5560
  const draft = row.draft ? 1 : 0;
4874
5561
  const memoryType = row.memory_type ?? "raw";
4875
5562
  const trajectory = row.trajectory ?? null;
4876
- return {
4877
- sql: hasVector ? `INSERT OR IGNORE INTO memories
4878
- (id, agent_id, agent_role, session_id, timestamp,
4879
- tool_name, project_name,
4880
- has_error, raw_text, vector, version, task_id, importance, status,
4881
- confidence, last_accessed,
4882
- workspace_id, document_id, user_id, char_offset, page_number,
4883
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
4884
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
4885
- (id, agent_id, agent_role, session_id, timestamp,
5563
+ const contentHash = row.content_hash ?? null;
5564
+ const intent = row.intent ?? null;
5565
+ const outcome = row.outcome ?? null;
5566
+ const domain = row.domain ?? null;
5567
+ const referencedEntities = row.referenced_entities ?? null;
5568
+ const retrievalCount = row.retrieval_count ?? 0;
5569
+ const chainPosition = row.chain_position ?? null;
5570
+ const reviewStatus = row.review_status ?? null;
5571
+ const contextWindowPct = row.context_window_pct ?? null;
5572
+ const filePaths = row.file_paths ?? null;
5573
+ const commitHash = row.commit_hash ?? null;
5574
+ const durationMs = row.duration_ms ?? null;
5575
+ const tokenCost = row.token_cost ?? null;
5576
+ const audience = row.audience ?? null;
5577
+ const languageType = row.language_type ?? null;
5578
+ const parentMemoryId = row.parent_memory_id ?? null;
5579
+ const cols = `id, agent_id, agent_role, session_id, timestamp,
4886
5580
  tool_name, project_name,
4887
5581
  has_error, raw_text, vector, version, task_id, importance, status,
4888
5582
  confidence, last_accessed,
4889
5583
  workspace_id, document_id, user_id, char_offset, page_number,
4890
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
4891
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4892
- args: hasVector ? [
4893
- row.id,
4894
- row.agent_id,
4895
- row.agent_role,
4896
- row.session_id,
4897
- row.timestamp,
4898
- row.tool_name,
4899
- row.project_name,
4900
- row.has_error,
4901
- row.raw_text,
4902
- vectorToBlob(row.vector),
4903
- row.version,
4904
- taskId,
4905
- importance,
4906
- status,
4907
- confidence,
4908
- lastAccessed,
4909
- workspaceId,
4910
- documentId,
4911
- userId,
4912
- charOffset,
4913
- pageNumber,
4914
- sourcePath,
4915
- sourceType,
4916
- tier,
4917
- supersedesId,
4918
- draft,
4919
- memoryType,
4920
- trajectory
4921
- ] : [
4922
- row.id,
4923
- row.agent_id,
4924
- row.agent_role,
4925
- row.session_id,
4926
- row.timestamp,
4927
- row.tool_name,
4928
- row.project_name,
4929
- row.has_error,
4930
- row.raw_text,
4931
- row.version,
4932
- taskId,
4933
- importance,
4934
- status,
4935
- confidence,
4936
- lastAccessed,
4937
- workspaceId,
4938
- documentId,
4939
- userId,
4940
- charOffset,
4941
- pageNumber,
4942
- sourcePath,
4943
- sourceType,
4944
- tier,
4945
- supersedesId,
4946
- draft,
4947
- memoryType,
4948
- trajectory
4949
- ]
5584
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
5585
+ intent, outcome, domain, referenced_entities, retrieval_count,
5586
+ chain_position, review_status, context_window_pct, file_paths, commit_hash,
5587
+ duration_ms, token_cost, audience, language_type, parent_memory_id`;
5588
+ const metaArgs = [
5589
+ intent,
5590
+ outcome,
5591
+ domain,
5592
+ referencedEntities,
5593
+ retrievalCount,
5594
+ chainPosition,
5595
+ reviewStatus,
5596
+ contextWindowPct,
5597
+ filePaths,
5598
+ commitHash,
5599
+ durationMs,
5600
+ tokenCost,
5601
+ audience,
5602
+ languageType,
5603
+ parentMemoryId
5604
+ ];
5605
+ const baseArgs = [
5606
+ row.id,
5607
+ row.agent_id,
5608
+ row.agent_role,
5609
+ row.session_id,
5610
+ row.timestamp,
5611
+ row.tool_name,
5612
+ row.project_name,
5613
+ row.has_error,
5614
+ row.raw_text
5615
+ ];
5616
+ const sharedArgs = [
5617
+ row.version,
5618
+ taskId,
5619
+ importance,
5620
+ status,
5621
+ confidence,
5622
+ lastAccessed,
5623
+ workspaceId,
5624
+ documentId,
5625
+ userId,
5626
+ charOffset,
5627
+ pageNumber,
5628
+ sourcePath,
5629
+ sourceType,
5630
+ tier,
5631
+ supersedesId,
5632
+ draft,
5633
+ memoryType,
5634
+ trajectory,
5635
+ contentHash
5636
+ ];
5637
+ return {
5638
+ sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
5639
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
5640
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5641
+ args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
4950
5642
  };
4951
5643
  };
4952
5644
  const globalClient = getClient();