@askexenow/exe-os 0.8.82 → 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 (97) 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 +14360 -12525
  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 +1260 -323
  9. package/dist/bin/exe-call.js +10 -0
  10. package/dist/bin/exe-cloud.js +32 -9
  11. package/dist/bin/exe-dispatch.js +212 -36
  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 +553 -174
  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 +41 -11
  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 +577 -33
  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 +901 -209
  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 +906 -213
  38. package/dist/bin/setup.js +870 -271
  39. package/dist/bin/shard-migrate.js +175 -72
  40. package/dist/bin/update.js +4 -3
  41. package/dist/bin/wiki-sync.js +175 -72
  42. package/dist/gateway/index.js +550 -168
  43. package/dist/hooks/bug-report-worker.js +210 -25
  44. package/dist/hooks/commit-complete.js +899 -207
  45. package/dist/hooks/error-recall.js +988 -226
  46. package/dist/hooks/ingest-worker.js +1639 -1195
  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 +899 -207
  52. package/dist/hooks/pre-tool-use.js +742 -123
  53. package/dist/hooks/prompt-ingest-worker.js +245 -104
  54. package/dist/hooks/prompt-submit.js +995 -233
  55. package/dist/hooks/response-ingest-worker.js +245 -104
  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 +1970 -1336
  61. package/dist/index.js +1653 -1055
  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 +1957 -924
  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/license.js +3 -3
  76. package/dist/lib/messaging.js +8 -1
  77. package/dist/lib/reminders.js +91 -74
  78. package/dist/lib/schedules.js +96 -2
  79. package/dist/lib/skill-learning.js +103 -85
  80. package/dist/lib/store.js +234 -73
  81. package/dist/lib/tasks.js +113 -24
  82. package/dist/lib/tmux-routing.js +122 -33
  83. package/dist/lib/token-spend.js +273 -0
  84. package/dist/lib/ws-client.js +11 -0
  85. package/dist/mcp/server.js +10874 -5546
  86. package/dist/mcp/tools/complete-reminder.js +94 -77
  87. package/dist/mcp/tools/create-reminder.js +94 -77
  88. package/dist/mcp/tools/create-task.js +810 -27
  89. package/dist/mcp/tools/deactivate-behavior.js +95 -77
  90. package/dist/mcp/tools/list-reminders.js +94 -77
  91. package/dist/mcp/tools/list-tasks.js +31 -1
  92. package/dist/mcp/tools/send-message.js +8 -1
  93. package/dist/mcp/tools/update-task.js +39 -10
  94. package/dist/runtime/index.js +913 -221
  95. package/dist/tui/App.js +1000 -298
  96. package/package.json +6 -1
  97. package/src/commands/exe/build-adv.md +2 -2
@@ -244,6 +244,7 @@ __export(employees_exports, {
244
244
  DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
245
245
  EMPLOYEES_PATH: () => EMPLOYEES_PATH,
246
246
  addEmployee: () => addEmployee,
247
+ baseAgentName: () => baseAgentName,
247
248
  canCoordinate: () => canCoordinate,
248
249
  getCoordinatorEmployee: () => getCoordinatorEmployee,
249
250
  getCoordinatorName: () => getCoordinatorName,
@@ -340,6 +341,14 @@ function hasRole(agentName, role) {
340
341
  const emp = getEmployee(employees, agentName);
341
342
  return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
342
343
  }
344
+ function baseAgentName(name, employees) {
345
+ const match = name.match(/^([a-zA-Z]+)\d+$/);
346
+ if (!match) return name;
347
+ const base = match[1];
348
+ const roster = employees ?? loadEmployeesSync();
349
+ if (getEmployee(roster, base)) return base;
350
+ return name;
351
+ }
343
352
  function isMultiInstance(agentName, employees) {
344
353
  const roster = employees ?? loadEmployeesSync();
345
354
  const emp = getEmployee(roster, agentName);
@@ -651,6 +660,443 @@ var init_db_retry = __esm({
651
660
  }
652
661
  });
653
662
 
663
+ // src/lib/exe-daemon-client.ts
664
+ import net from "net";
665
+ import { spawn } from "child_process";
666
+ import { randomUUID } from "crypto";
667
+ import { existsSync as existsSync4, unlinkSync as unlinkSync3, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
668
+ import path6 from "path";
669
+ import { fileURLToPath } from "url";
670
+ function handleData(chunk) {
671
+ _buffer += chunk.toString();
672
+ if (_buffer.length > MAX_BUFFER) {
673
+ _buffer = "";
674
+ return;
675
+ }
676
+ let newlineIdx;
677
+ while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
678
+ const line = _buffer.slice(0, newlineIdx).trim();
679
+ _buffer = _buffer.slice(newlineIdx + 1);
680
+ if (!line) continue;
681
+ try {
682
+ const response = JSON.parse(line);
683
+ const id = response.id;
684
+ if (!id) continue;
685
+ const entry = _pending.get(id);
686
+ if (entry) {
687
+ clearTimeout(entry.timer);
688
+ _pending.delete(id);
689
+ entry.resolve(response);
690
+ }
691
+ } catch {
692
+ }
693
+ }
694
+ }
695
+ function cleanupStaleFiles() {
696
+ if (existsSync4(PID_PATH)) {
697
+ try {
698
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
699
+ if (pid > 0) {
700
+ try {
701
+ process.kill(pid, 0);
702
+ return;
703
+ } catch {
704
+ }
705
+ }
706
+ } catch {
707
+ }
708
+ try {
709
+ unlinkSync3(PID_PATH);
710
+ } catch {
711
+ }
712
+ try {
713
+ unlinkSync3(SOCKET_PATH);
714
+ } catch {
715
+ }
716
+ }
717
+ }
718
+ function findPackageRoot() {
719
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
720
+ const { root } = path6.parse(dir);
721
+ while (dir !== root) {
722
+ if (existsSync4(path6.join(dir, "package.json"))) return dir;
723
+ dir = path6.dirname(dir);
724
+ }
725
+ return null;
726
+ }
727
+ function spawnDaemon() {
728
+ const pkgRoot = findPackageRoot();
729
+ if (!pkgRoot) {
730
+ process.stderr.write("[exed-client] WARN: cannot find package root\n");
731
+ return;
732
+ }
733
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
734
+ if (!existsSync4(daemonPath)) {
735
+ process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
736
+ `);
737
+ return;
738
+ }
739
+ const resolvedPath = daemonPath;
740
+ process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
741
+ `);
742
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
743
+ let stderrFd = "ignore";
744
+ try {
745
+ stderrFd = openSync(logPath, "a");
746
+ } catch {
747
+ }
748
+ const child = spawn(process.execPath, [resolvedPath], {
749
+ detached: true,
750
+ stdio: ["ignore", "ignore", stderrFd],
751
+ env: {
752
+ ...process.env,
753
+ TMUX: void 0,
754
+ // Daemon is global — must not inherit session scope
755
+ TMUX_PANE: void 0,
756
+ // Prevents resolveExeSession() from scoping to one session
757
+ EXE_DAEMON_SOCK: SOCKET_PATH,
758
+ EXE_DAEMON_PID: PID_PATH
759
+ }
760
+ });
761
+ child.unref();
762
+ if (typeof stderrFd === "number") {
763
+ try {
764
+ closeSync(stderrFd);
765
+ } catch {
766
+ }
767
+ }
768
+ }
769
+ function acquireSpawnLock() {
770
+ try {
771
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
772
+ closeSync(fd);
773
+ return true;
774
+ } catch {
775
+ try {
776
+ const stat = statSync(SPAWN_LOCK_PATH);
777
+ if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
778
+ try {
779
+ unlinkSync3(SPAWN_LOCK_PATH);
780
+ } catch {
781
+ }
782
+ try {
783
+ const fd = openSync(SPAWN_LOCK_PATH, "wx");
784
+ closeSync(fd);
785
+ return true;
786
+ } catch {
787
+ }
788
+ }
789
+ } catch {
790
+ }
791
+ return false;
792
+ }
793
+ }
794
+ function releaseSpawnLock() {
795
+ try {
796
+ unlinkSync3(SPAWN_LOCK_PATH);
797
+ } catch {
798
+ }
799
+ }
800
+ function connectToSocket() {
801
+ return new Promise((resolve) => {
802
+ if (_socket && _connected) {
803
+ resolve(true);
804
+ return;
805
+ }
806
+ const socket = net.createConnection({ path: SOCKET_PATH });
807
+ const connectTimeout = setTimeout(() => {
808
+ socket.destroy();
809
+ resolve(false);
810
+ }, 2e3);
811
+ socket.on("connect", () => {
812
+ clearTimeout(connectTimeout);
813
+ _socket = socket;
814
+ _connected = true;
815
+ _buffer = "";
816
+ socket.on("data", handleData);
817
+ socket.on("close", () => {
818
+ _connected = false;
819
+ _socket = null;
820
+ for (const [id, entry] of _pending) {
821
+ clearTimeout(entry.timer);
822
+ _pending.delete(id);
823
+ entry.resolve({ error: "Connection closed" });
824
+ }
825
+ });
826
+ socket.on("error", () => {
827
+ _connected = false;
828
+ _socket = null;
829
+ });
830
+ resolve(true);
831
+ });
832
+ socket.on("error", () => {
833
+ clearTimeout(connectTimeout);
834
+ resolve(false);
835
+ });
836
+ });
837
+ }
838
+ async function connectEmbedDaemon() {
839
+ if (_socket && _connected) return true;
840
+ if (await connectToSocket()) return true;
841
+ if (acquireSpawnLock()) {
842
+ try {
843
+ cleanupStaleFiles();
844
+ spawnDaemon();
845
+ } finally {
846
+ releaseSpawnLock();
847
+ }
848
+ }
849
+ const start = Date.now();
850
+ let delay2 = 100;
851
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
852
+ await new Promise((r) => setTimeout(r, delay2));
853
+ if (await connectToSocket()) return true;
854
+ delay2 = Math.min(delay2 * 2, 3e3);
855
+ }
856
+ return false;
857
+ }
858
+ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
859
+ return new Promise((resolve) => {
860
+ if (!_socket || !_connected) {
861
+ resolve({ error: "Not connected" });
862
+ return;
863
+ }
864
+ const id = randomUUID();
865
+ const timer = setTimeout(() => {
866
+ _pending.delete(id);
867
+ resolve({ error: "Request timeout" });
868
+ }, timeoutMs);
869
+ _pending.set(id, { resolve, timer });
870
+ try {
871
+ _socket.write(JSON.stringify({ id, ...payload }) + "\n");
872
+ } catch {
873
+ clearTimeout(timer);
874
+ _pending.delete(id);
875
+ resolve({ error: "Write failed" });
876
+ }
877
+ });
878
+ }
879
+ function isClientConnected() {
880
+ return _connected;
881
+ }
882
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
883
+ var init_exe_daemon_client = __esm({
884
+ "src/lib/exe-daemon-client.ts"() {
885
+ "use strict";
886
+ init_config();
887
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
888
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
889
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
890
+ SPAWN_LOCK_STALE_MS = 3e4;
891
+ CONNECT_TIMEOUT_MS = 15e3;
892
+ REQUEST_TIMEOUT_MS = 3e4;
893
+ _socket = null;
894
+ _connected = false;
895
+ _buffer = "";
896
+ _pending = /* @__PURE__ */ new Map();
897
+ MAX_BUFFER = 1e7;
898
+ }
899
+ });
900
+
901
+ // src/lib/daemon-protocol.ts
902
+ function serializeValue(v) {
903
+ if (v === null || v === void 0) return null;
904
+ if (typeof v === "bigint") return Number(v);
905
+ if (typeof v === "boolean") return v ? 1 : 0;
906
+ if (v instanceof Uint8Array) {
907
+ return { __blob: Buffer.from(v).toString("base64") };
908
+ }
909
+ if (ArrayBuffer.isView(v)) {
910
+ return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
911
+ }
912
+ if (v instanceof ArrayBuffer) {
913
+ return { __blob: Buffer.from(v).toString("base64") };
914
+ }
915
+ if (typeof v === "string" || typeof v === "number") return v;
916
+ return String(v);
917
+ }
918
+ function deserializeValue(v) {
919
+ if (v === null) return null;
920
+ if (typeof v === "object" && v !== null && "__blob" in v) {
921
+ const buf = Buffer.from(v.__blob, "base64");
922
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
923
+ }
924
+ return v;
925
+ }
926
+ function deserializeResultSet(srs) {
927
+ const rows = srs.rows.map((obj) => {
928
+ const values = srs.columns.map(
929
+ (col) => deserializeValue(obj[col] ?? null)
930
+ );
931
+ const row = values;
932
+ for (let i = 0; i < srs.columns.length; i++) {
933
+ const col = srs.columns[i];
934
+ if (col !== void 0) {
935
+ row[col] = values[i] ?? null;
936
+ }
937
+ }
938
+ Object.defineProperty(row, "length", {
939
+ value: values.length,
940
+ enumerable: false
941
+ });
942
+ return row;
943
+ });
944
+ return {
945
+ columns: srs.columns,
946
+ columnTypes: srs.columnTypes ?? [],
947
+ rows,
948
+ rowsAffected: srs.rowsAffected,
949
+ lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
950
+ toJSON: () => ({
951
+ columns: srs.columns,
952
+ columnTypes: srs.columnTypes ?? [],
953
+ rows: srs.rows,
954
+ rowsAffected: srs.rowsAffected,
955
+ lastInsertRowid: srs.lastInsertRowid
956
+ })
957
+ };
958
+ }
959
+ var init_daemon_protocol = __esm({
960
+ "src/lib/daemon-protocol.ts"() {
961
+ "use strict";
962
+ }
963
+ });
964
+
965
+ // src/lib/db-daemon-client.ts
966
+ var db_daemon_client_exports = {};
967
+ __export(db_daemon_client_exports, {
968
+ createDaemonDbClient: () => createDaemonDbClient,
969
+ initDaemonDbClient: () => initDaemonDbClient
970
+ });
971
+ function normalizeStatement(stmt) {
972
+ if (typeof stmt === "string") {
973
+ return { sql: stmt, args: [] };
974
+ }
975
+ const sql = stmt.sql;
976
+ let args = [];
977
+ if (Array.isArray(stmt.args)) {
978
+ args = stmt.args.map((v) => serializeValue(v));
979
+ } else if (stmt.args && typeof stmt.args === "object") {
980
+ const named = {};
981
+ for (const [key, val] of Object.entries(stmt.args)) {
982
+ named[key] = serializeValue(val);
983
+ }
984
+ return { sql, args: named };
985
+ }
986
+ return { sql, args };
987
+ }
988
+ function createDaemonDbClient(fallbackClient) {
989
+ let _useDaemon = false;
990
+ const client = {
991
+ async execute(stmt) {
992
+ if (!_useDaemon || !isClientConnected()) {
993
+ return fallbackClient.execute(stmt);
994
+ }
995
+ const { sql, args } = normalizeStatement(stmt);
996
+ const response = await sendDaemonRequest({
997
+ type: "db-execute",
998
+ sql,
999
+ args
1000
+ });
1001
+ if (response.error) {
1002
+ const errMsg = String(response.error);
1003
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
1004
+ process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
1005
+ `);
1006
+ return fallbackClient.execute(stmt);
1007
+ }
1008
+ throw new Error(errMsg);
1009
+ }
1010
+ if (response.db) {
1011
+ return deserializeResultSet(response.db);
1012
+ }
1013
+ process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
1014
+ return fallbackClient.execute(stmt);
1015
+ },
1016
+ async batch(stmts, mode) {
1017
+ if (!_useDaemon || !isClientConnected()) {
1018
+ return fallbackClient.batch(stmts, mode);
1019
+ }
1020
+ const statements = stmts.map(normalizeStatement);
1021
+ const response = await sendDaemonRequest({
1022
+ type: "db-batch",
1023
+ statements,
1024
+ mode: mode ?? "deferred"
1025
+ });
1026
+ if (response.error) {
1027
+ const errMsg = String(response.error);
1028
+ if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
1029
+ process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
1030
+ `);
1031
+ return fallbackClient.batch(stmts, mode);
1032
+ }
1033
+ throw new Error(errMsg);
1034
+ }
1035
+ const batchResults = response["db-batch"];
1036
+ if (batchResults) {
1037
+ return batchResults.map(deserializeResultSet);
1038
+ }
1039
+ process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
1040
+ return fallbackClient.batch(stmts, mode);
1041
+ },
1042
+ // Transaction support — delegate to fallback (transactions need direct connection)
1043
+ async transaction(mode) {
1044
+ return fallbackClient.transaction(mode);
1045
+ },
1046
+ // executeMultiple — delegate to fallback (used only for schema migrations)
1047
+ async executeMultiple(sql) {
1048
+ return fallbackClient.executeMultiple(sql);
1049
+ },
1050
+ // migrate — delegate to fallback
1051
+ async migrate(stmts) {
1052
+ return fallbackClient.migrate(stmts);
1053
+ },
1054
+ // Sync mode — delegate to fallback
1055
+ sync() {
1056
+ return fallbackClient.sync();
1057
+ },
1058
+ close() {
1059
+ _useDaemon = false;
1060
+ },
1061
+ get closed() {
1062
+ return fallbackClient.closed;
1063
+ },
1064
+ get protocol() {
1065
+ return fallbackClient.protocol;
1066
+ }
1067
+ };
1068
+ return {
1069
+ ...client,
1070
+ /** Enable daemon routing (call after confirming daemon is connected) */
1071
+ _enableDaemon() {
1072
+ _useDaemon = true;
1073
+ },
1074
+ /** Check if daemon routing is active */
1075
+ _isDaemonActive() {
1076
+ return _useDaemon && isClientConnected();
1077
+ }
1078
+ };
1079
+ }
1080
+ async function initDaemonDbClient(fallbackClient) {
1081
+ if (process.env.EXE_IS_DAEMON === "1") return null;
1082
+ const connected = await connectEmbedDaemon();
1083
+ if (!connected) {
1084
+ process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
1085
+ return null;
1086
+ }
1087
+ const client = createDaemonDbClient(fallbackClient);
1088
+ client._enableDaemon();
1089
+ process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
1090
+ return client;
1091
+ }
1092
+ var init_db_daemon_client = __esm({
1093
+ "src/lib/db-daemon-client.ts"() {
1094
+ "use strict";
1095
+ init_exe_daemon_client();
1096
+ init_daemon_protocol();
1097
+ }
1098
+ });
1099
+
654
1100
  // src/lib/database.ts
655
1101
  var database_exports = {};
656
1102
  __export(database_exports, {
@@ -659,6 +1105,7 @@ __export(database_exports, {
659
1105
  ensureSchema: () => ensureSchema,
660
1106
  getClient: () => getClient,
661
1107
  getRawClient: () => getRawClient,
1108
+ initDaemonClient: () => initDaemonClient,
662
1109
  initDatabase: () => initDatabase,
663
1110
  initTurso: () => initTurso,
664
1111
  isInitialized: () => isInitialized
@@ -686,8 +1133,27 @@ function getClient() {
686
1133
  if (!_resilientClient) {
687
1134
  throw new Error("Database client not initialized. Call initDatabase() first.");
688
1135
  }
1136
+ if (process.env.EXE_IS_DAEMON === "1") {
1137
+ return _resilientClient;
1138
+ }
1139
+ if (_daemonClient && _daemonClient._isDaemonActive()) {
1140
+ return _daemonClient;
1141
+ }
689
1142
  return _resilientClient;
690
1143
  }
1144
+ async function initDaemonClient() {
1145
+ if (process.env.EXE_IS_DAEMON === "1") return;
1146
+ if (!_resilientClient) return;
1147
+ try {
1148
+ const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
1149
+ _daemonClient = await initDaemonDbClient2(_resilientClient);
1150
+ } catch (err) {
1151
+ process.stderr.write(
1152
+ `[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
1153
+ `
1154
+ );
1155
+ }
1156
+ }
691
1157
  function getRawClient() {
692
1158
  if (!_client) {
693
1159
  throw new Error("Database client not initialized. Call initDatabase() first.");
@@ -1174,6 +1640,12 @@ async function ensureSchema() {
1174
1640
  } catch {
1175
1641
  }
1176
1642
  }
1643
+ try {
1644
+ await client.execute(
1645
+ `CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
1646
+ );
1647
+ } catch {
1648
+ }
1177
1649
  await client.executeMultiple(`
1178
1650
  CREATE TABLE IF NOT EXISTS entities (
1179
1651
  id TEXT PRIMARY KEY,
@@ -1226,7 +1698,30 @@ async function ensureSchema() {
1226
1698
  entity_id TEXT NOT NULL,
1227
1699
  PRIMARY KEY (hyperedge_id, entity_id)
1228
1700
  );
1701
+
1702
+ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
1703
+ name,
1704
+ content=entities,
1705
+ content_rowid=rowid
1706
+ );
1707
+
1708
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
1709
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
1710
+ END;
1711
+
1712
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
1713
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
1714
+ END;
1715
+
1716
+ CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
1717
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
1718
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
1719
+ END;
1229
1720
  `);
1721
+ try {
1722
+ await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
1723
+ } catch {
1724
+ }
1230
1725
  await client.executeMultiple(`
1231
1726
  CREATE TABLE IF NOT EXISTS entity_aliases (
1232
1727
  alias TEXT NOT NULL PRIMARY KEY,
@@ -1407,6 +1902,33 @@ async function ensureSchema() {
1407
1902
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
1408
1903
  ON conversations(channel_id);
1409
1904
  `);
1905
+ await client.executeMultiple(`
1906
+ CREATE TABLE IF NOT EXISTS session_agent_map (
1907
+ session_uuid TEXT PRIMARY KEY,
1908
+ agent_id TEXT NOT NULL,
1909
+ session_name TEXT,
1910
+ task_id TEXT,
1911
+ project_name TEXT,
1912
+ started_at TEXT NOT NULL
1913
+ );
1914
+
1915
+ CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
1916
+ ON session_agent_map(agent_id);
1917
+ `);
1918
+ try {
1919
+ const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
1920
+ if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
1921
+ await client.execute({
1922
+ sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
1923
+ SELECT session_id, agent_id, '', MIN(timestamp)
1924
+ FROM memories
1925
+ WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
1926
+ GROUP BY session_id, agent_id`,
1927
+ args: []
1928
+ });
1929
+ }
1930
+ } catch {
1931
+ }
1410
1932
  try {
1411
1933
  await client.execute({
1412
1934
  sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
@@ -1540,15 +2062,41 @@ async function ensureSchema() {
1540
2062
  });
1541
2063
  } catch {
1542
2064
  }
2065
+ for (const col of [
2066
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
2067
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
2068
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
2069
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
2070
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
2071
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
2072
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
2073
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
2074
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
2075
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
2076
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
2077
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
2078
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
2079
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
2080
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2081
+ ]) {
2082
+ try {
2083
+ await client.execute(col);
2084
+ } catch {
2085
+ }
2086
+ }
1543
2087
  }
1544
2088
  async function disposeDatabase() {
2089
+ if (_daemonClient) {
2090
+ _daemonClient.close();
2091
+ _daemonClient = null;
2092
+ }
1545
2093
  if (_client) {
1546
2094
  _client.close();
1547
2095
  _client = null;
1548
2096
  _resilientClient = null;
1549
2097
  }
1550
2098
  }
1551
- var _client, _resilientClient, initTurso, disposeTurso;
2099
+ var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
1552
2100
  var init_database = __esm({
1553
2101
  "src/lib/database.ts"() {
1554
2102
  "use strict";
@@ -1556,30 +2104,31 @@ var init_database = __esm({
1556
2104
  init_employees();
1557
2105
  _client = null;
1558
2106
  _resilientClient = null;
2107
+ _daemonClient = null;
1559
2108
  initTurso = initDatabase;
1560
2109
  disposeTurso = disposeDatabase;
1561
2110
  }
1562
2111
  });
1563
2112
 
1564
2113
  // src/lib/license.ts
1565
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
1566
- import { randomUUID } from "crypto";
1567
- import path6 from "path";
2114
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
2115
+ import { randomUUID as randomUUID2 } from "crypto";
2116
+ import path7 from "path";
1568
2117
  import { jwtVerify, importSPKI } from "jose";
1569
2118
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
1570
2119
  var init_license = __esm({
1571
2120
  "src/lib/license.ts"() {
1572
2121
  "use strict";
1573
2122
  init_config();
1574
- LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
1575
- CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
1576
- DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
2123
+ LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
2124
+ CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
2125
+ DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
1577
2126
  }
1578
2127
  });
1579
2128
 
1580
2129
  // src/lib/plan-limits.ts
1581
- import { readFileSync as readFileSync6, existsSync as existsSync5 } from "fs";
1582
- import path7 from "path";
2130
+ import { readFileSync as readFileSync7, existsSync as existsSync6 } from "fs";
2131
+ import path8 from "path";
1583
2132
  var CACHE_PATH2;
1584
2133
  var init_plan_limits = __esm({
1585
2134
  "src/lib/plan-limits.ts"() {
@@ -1588,15 +2137,15 @@ var init_plan_limits = __esm({
1588
2137
  init_employees();
1589
2138
  init_license();
1590
2139
  init_config();
1591
- CACHE_PATH2 = path7.join(EXE_AI_DIR, "license-cache.json");
2140
+ CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
1592
2141
  }
1593
2142
  });
1594
2143
 
1595
2144
  // src/lib/tmux-routing.ts
1596
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync6, appendFileSync } from "fs";
1597
- import path8 from "path";
2145
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync7, appendFileSync } from "fs";
2146
+ import path9 from "path";
1598
2147
  import os5 from "os";
1599
- import { fileURLToPath } from "url";
2148
+ import { fileURLToPath as fileURLToPath2 } from "url";
1600
2149
  function getMySession() {
1601
2150
  return getTransport().getMySession();
1602
2151
  }
@@ -1608,7 +2157,7 @@ function extractRootExe(name) {
1608
2157
  }
1609
2158
  function getParentExe(sessionKey) {
1610
2159
  try {
1611
- const data = JSON.parse(readFileSync7(path8.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2160
+ const data = JSON.parse(readFileSync8(path9.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
1612
2161
  return data.parentExe || null;
1613
2162
  } catch {
1614
2163
  return null;
@@ -1640,10 +2189,10 @@ var init_tmux_routing = __esm({
1640
2189
  init_intercom_queue();
1641
2190
  init_plan_limits();
1642
2191
  init_employees();
1643
- SPAWN_LOCK_DIR = path8.join(os5.homedir(), ".exe-os", "spawn-locks");
1644
- SESSION_CACHE = path8.join(os5.homedir(), ".exe-os", "session-cache");
1645
- INTERCOM_LOG2 = path8.join(os5.homedir(), ".exe-os", "intercom.log");
1646
- DEBOUNCE_FILE = path8.join(SESSION_CACHE, "intercom-debounce.json");
2192
+ SPAWN_LOCK_DIR = path9.join(os5.homedir(), ".exe-os", "spawn-locks");
2193
+ SESSION_CACHE = path9.join(os5.homedir(), ".exe-os", "session-cache");
2194
+ INTERCOM_LOG2 = path9.join(os5.homedir(), ".exe-os", "intercom.log");
2195
+ DEBOUNCE_FILE = path9.join(SESSION_CACHE, "intercom-debounce.json");
1647
2196
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
1648
2197
  }
1649
2198
  });
@@ -1686,8 +2235,8 @@ __export(cto_delegation_gate_exports, {
1686
2235
  scratchpadPath: () => scratchpadPath
1687
2236
  });
1688
2237
  import os6 from "os";
1689
- import path9 from "path";
1690
- import { existsSync as existsSync7, readFileSync as readFileSync8, statSync } from "fs";
2238
+ import path10 from "path";
2239
+ import { existsSync as existsSync8, readFileSync as readFileSync9, statSync as statSync2 } from "fs";
1691
2240
  function resolveGatedAgent() {
1692
2241
  try {
1693
2242
  const employees = loadEmployeesSync();
@@ -1701,12 +2250,12 @@ function getGatedAgent() {
1701
2250
  return resolveGatedAgent() || GATED_AGENT;
1702
2251
  }
1703
2252
  function toWorkspaceRelative(filePath, cwd = process.cwd()) {
1704
- const normalized = path9.normalize(filePath);
1705
- if (normalized.startsWith(cwd + path9.sep)) {
2253
+ const normalized = path10.normalize(filePath);
2254
+ if (normalized.startsWith(cwd + path10.sep)) {
1706
2255
  return normalized.slice(cwd.length + 1);
1707
2256
  }
1708
- if (path9.isAbsolute(normalized)) {
1709
- return path9.basename(normalized);
2257
+ if (path10.isAbsolute(normalized)) {
2258
+ return path10.basename(normalized);
1710
2259
  }
1711
2260
  return normalized;
1712
2261
  }
@@ -1715,8 +2264,8 @@ function isDockerfile(basename) {
1715
2264
  }
1716
2265
  function classifyPath(filePath, cwd) {
1717
2266
  const rel = toWorkspaceRelative(filePath, cwd);
1718
- const basename = path9.basename(rel);
1719
- const ext = path9.extname(rel).toLowerCase();
2267
+ const basename = path10.basename(rel);
2268
+ const ext = path10.extname(rel).toLowerCase();
1720
2269
  if (ext === ".md") {
1721
2270
  for (const prefix of EXEMPT_MD_DIR_PREFIXES) {
1722
2271
  if (rel.startsWith(prefix)) return "exempt";
@@ -1759,7 +2308,7 @@ async function hasRecentEngineerDispatch(now = Date.now()) {
1759
2308
  }
1760
2309
  }
1761
2310
  function scratchpadPath(sessionId, homeDir = os6.homedir()) {
1762
- return path9.join(
2311
+ return path10.join(
1763
2312
  homeDir,
1764
2313
  ".exe-os",
1765
2314
  "session-cache",
@@ -1769,11 +2318,11 @@ function scratchpadPath(sessionId, homeDir = os6.homedir()) {
1769
2318
  function hasValidScratchpadEscape(sessionId, now = Date.now(), homeDir = os6.homedir()) {
1770
2319
  const paths = [scratchpadPath(sessionId, homeDir)];
1771
2320
  for (const filePath of paths) {
1772
- if (!existsSync7(filePath)) continue;
2321
+ if (!existsSync8(filePath)) continue;
1773
2322
  try {
1774
- const stat = statSync(filePath);
2323
+ const stat = statSync2(filePath);
1775
2324
  if (now - stat.mtimeMs > SCRATCHPAD_MAX_AGE_MS) continue;
1776
- const body = readFileSync8(filePath, "utf-8");
2325
+ const body = readFileSync9(filePath, "utf-8");
1777
2326
  if (SCRATCHPAD_ESCAPE_PATTERN.test(body)) return true;
1778
2327
  } catch {
1779
2328
  }
@@ -1864,14 +2413,14 @@ var init_memory = __esm({
1864
2413
 
1865
2414
  // src/lib/keychain.ts
1866
2415
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
1867
- import { existsSync as existsSync8 } from "fs";
1868
- import path10 from "path";
2416
+ import { existsSync as existsSync9 } from "fs";
2417
+ import path11 from "path";
1869
2418
  import os7 from "os";
1870
2419
  function getKeyDir() {
1871
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path10.join(os7.homedir(), ".exe-os");
2420
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path11.join(os7.homedir(), ".exe-os");
1872
2421
  }
1873
2422
  function getKeyPath() {
1874
- return path10.join(getKeyDir(), "master.key");
2423
+ return path11.join(getKeyDir(), "master.key");
1875
2424
  }
1876
2425
  async function tryKeytar() {
1877
2426
  try {
@@ -1892,13 +2441,21 @@ async function getMasterKey() {
1892
2441
  }
1893
2442
  }
1894
2443
  const keyPath = getKeyPath();
1895
- if (!existsSync8(keyPath)) {
2444
+ if (!existsSync9(keyPath)) {
2445
+ process.stderr.write(
2446
+ `[keychain] Key not found at ${keyPath} (HOME=${os7.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2447
+ `
2448
+ );
1896
2449
  return null;
1897
2450
  }
1898
2451
  try {
1899
2452
  const content = await readFile3(keyPath, "utf-8");
1900
2453
  return Buffer.from(content.trim(), "base64");
1901
- } catch {
2454
+ } catch (err) {
2455
+ process.stderr.write(
2456
+ `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
2457
+ `
2458
+ );
1902
2459
  return null;
1903
2460
  }
1904
2461
  }
@@ -1979,12 +2536,12 @@ __export(shard_manager_exports, {
1979
2536
  listShards: () => listShards,
1980
2537
  shardExists: () => shardExists
1981
2538
  });
1982
- import path11 from "path";
1983
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, readdirSync as readdirSync2 } from "fs";
2539
+ import path12 from "path";
2540
+ import { existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync2 } from "fs";
1984
2541
  import { createClient as createClient2 } from "@libsql/client";
1985
2542
  function initShardManager(encryptionKey) {
1986
2543
  _encryptionKey = encryptionKey;
1987
- if (!existsSync9(SHARDS_DIR)) {
2544
+ if (!existsSync10(SHARDS_DIR)) {
1988
2545
  mkdirSync5(SHARDS_DIR, { recursive: true });
1989
2546
  }
1990
2547
  _shardingEnabled = true;
@@ -2005,7 +2562,7 @@ function getShardClient(projectName) {
2005
2562
  }
2006
2563
  const cached = _shards.get(safeName);
2007
2564
  if (cached) return cached;
2008
- const dbPath = path11.join(SHARDS_DIR, `${safeName}.db`);
2565
+ const dbPath = path12.join(SHARDS_DIR, `${safeName}.db`);
2009
2566
  const client = createClient2({
2010
2567
  url: `file:${dbPath}`,
2011
2568
  encryptionKey: _encryptionKey
@@ -2015,10 +2572,10 @@ function getShardClient(projectName) {
2015
2572
  }
2016
2573
  function shardExists(projectName) {
2017
2574
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2018
- return existsSync9(path11.join(SHARDS_DIR, `${safeName}.db`));
2575
+ return existsSync10(path12.join(SHARDS_DIR, `${safeName}.db`));
2019
2576
  }
2020
2577
  function listShards() {
2021
- if (!existsSync9(SHARDS_DIR)) return [];
2578
+ if (!existsSync10(SHARDS_DIR)) return [];
2022
2579
  return readdirSync2(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2023
2580
  }
2024
2581
  async function ensureShardSchema(client) {
@@ -2204,7 +2761,7 @@ var init_shard_manager = __esm({
2204
2761
  "src/lib/shard-manager.ts"() {
2205
2762
  "use strict";
2206
2763
  init_config();
2207
- SHARDS_DIR = path11.join(EXE_AI_DIR, "shards");
2764
+ SHARDS_DIR = path12.join(EXE_AI_DIR, "shards");
2208
2765
  _shards = /* @__PURE__ */ new Map();
2209
2766
  _encryptionKey = null;
2210
2767
  _shardingEnabled = false;
@@ -2329,7 +2886,7 @@ __export(global_procedures_exports, {
2329
2886
  loadGlobalProcedures: () => loadGlobalProcedures,
2330
2887
  storeGlobalProcedure: () => storeGlobalProcedure
2331
2888
  });
2332
- import { randomUUID as randomUUID2 } from "crypto";
2889
+ import { randomUUID as randomUUID3 } from "crypto";
2333
2890
  async function loadGlobalProcedures() {
2334
2891
  const client = getClient();
2335
2892
  const result = await client.execute({
@@ -2358,7 +2915,7 @@ ${sections.join("\n\n")}
2358
2915
  `;
2359
2916
  }
2360
2917
  async function storeGlobalProcedure(input2) {
2361
- const id = randomUUID2();
2918
+ const id = randomUUID3();
2362
2919
  const now = (/* @__PURE__ */ new Date()).toISOString();
2363
2920
  const client = getClient();
2364
2921
  await client.execute({
@@ -2409,6 +2966,7 @@ __export(store_exports, {
2409
2966
  vectorToBlob: () => vectorToBlob,
2410
2967
  writeMemory: () => writeMemory
2411
2968
  });
2969
+ import { createHash } from "crypto";
2412
2970
  function isBusyError2(err) {
2413
2971
  if (err instanceof Error) {
2414
2972
  const msg = err.message.toLowerCase();
@@ -2482,12 +3040,52 @@ function classifyTier(record) {
2482
3040
  if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
2483
3041
  return 3;
2484
3042
  }
3043
+ function inferFilePaths(record) {
3044
+ if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
3045
+ const firstLine = record.raw_text.split("\n")[0] ?? "";
3046
+ const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
3047
+ return match ? JSON.stringify([match[1]]) : null;
3048
+ }
3049
+ function inferCommitHash(record) {
3050
+ if (record.tool_name !== "Bash") return null;
3051
+ const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
3052
+ return match ? match[1] : null;
3053
+ }
3054
+ function inferLanguageType(record) {
3055
+ const text = record.raw_text;
3056
+ if (!text || text.length < 10) return null;
3057
+ const trimmed = text.trimStart();
3058
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
3059
+ if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
3060
+ if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
3061
+ if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
3062
+ return "mixed";
3063
+ }
3064
+ function inferDomain(record) {
3065
+ const proj = (record.project_name ?? "").toLowerCase();
3066
+ if (proj.includes("marketing") || proj.includes("content")) return "marketing";
3067
+ if (proj.includes("crm") || proj.includes("customer")) return "customer";
3068
+ return null;
3069
+ }
2485
3070
  async function writeMemory(record) {
2486
3071
  if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
2487
3072
  throw new Error(
2488
3073
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
2489
3074
  );
2490
3075
  }
3076
+ const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3077
+ if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
3078
+ return;
3079
+ }
3080
+ try {
3081
+ const client = getClient();
3082
+ const existing = await client.execute({
3083
+ sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3084
+ args: [contentHash, record.agent_id]
3085
+ });
3086
+ if (existing.rows.length > 0) return;
3087
+ } catch {
3088
+ }
2491
3089
  const dbRow = {
2492
3090
  id: record.id,
2493
3091
  agent_id: record.agent_id,
@@ -2517,7 +3115,23 @@ async function writeMemory(record) {
2517
3115
  supersedes_id: record.supersedes_id ?? null,
2518
3116
  draft: record.draft ? 1 : 0,
2519
3117
  memory_type: record.memory_type ?? "raw",
2520
- trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
3118
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3119
+ content_hash: contentHash,
3120
+ intent: record.intent ?? null,
3121
+ outcome: record.outcome ?? null,
3122
+ domain: record.domain ?? inferDomain(record),
3123
+ referenced_entities: record.referenced_entities ?? null,
3124
+ retrieval_count: record.retrieval_count ?? 0,
3125
+ chain_position: record.chain_position ?? null,
3126
+ review_status: record.review_status ?? null,
3127
+ context_window_pct: record.context_window_pct ?? null,
3128
+ file_paths: record.file_paths ?? inferFilePaths(record),
3129
+ commit_hash: record.commit_hash ?? inferCommitHash(record),
3130
+ duration_ms: record.duration_ms ?? null,
3131
+ token_cost: record.token_cost ?? null,
3132
+ audience: record.audience ?? null,
3133
+ language_type: record.language_type ?? inferLanguageType(record),
3134
+ parent_memory_id: record.parent_memory_id ?? null
2521
3135
  };
2522
3136
  _pendingRecords.push(dbRow);
2523
3137
  orgBus.emit({
@@ -2575,80 +3189,85 @@ async function flushBatch() {
2575
3189
  const draft = row.draft ? 1 : 0;
2576
3190
  const memoryType = row.memory_type ?? "raw";
2577
3191
  const trajectory = row.trajectory ?? null;
2578
- return {
2579
- sql: hasVector ? `INSERT OR IGNORE INTO memories
2580
- (id, agent_id, agent_role, session_id, timestamp,
3192
+ const contentHash = row.content_hash ?? null;
3193
+ const intent = row.intent ?? null;
3194
+ const outcome = row.outcome ?? null;
3195
+ const domain = row.domain ?? null;
3196
+ const referencedEntities = row.referenced_entities ?? null;
3197
+ const retrievalCount = row.retrieval_count ?? 0;
3198
+ const chainPosition = row.chain_position ?? null;
3199
+ const reviewStatus = row.review_status ?? null;
3200
+ const contextWindowPct = row.context_window_pct ?? null;
3201
+ const filePaths = row.file_paths ?? null;
3202
+ const commitHash = row.commit_hash ?? null;
3203
+ const durationMs = row.duration_ms ?? null;
3204
+ const tokenCost = row.token_cost ?? null;
3205
+ const audience = row.audience ?? null;
3206
+ const languageType = row.language_type ?? null;
3207
+ const parentMemoryId = row.parent_memory_id ?? null;
3208
+ const cols = `id, agent_id, agent_role, session_id, timestamp,
2581
3209
  tool_name, project_name,
2582
3210
  has_error, raw_text, vector, version, task_id, importance, status,
2583
3211
  confidence, last_accessed,
2584
3212
  workspace_id, document_id, user_id, char_offset, page_number,
2585
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
2586
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
2587
- (id, agent_id, agent_role, session_id, timestamp,
2588
- tool_name, project_name,
2589
- has_error, raw_text, vector, version, task_id, importance, status,
2590
- confidence, last_accessed,
2591
- workspace_id, document_id, user_id, char_offset, page_number,
2592
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
2593
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2594
- args: hasVector ? [
2595
- row.id,
2596
- row.agent_id,
2597
- row.agent_role,
2598
- row.session_id,
2599
- row.timestamp,
2600
- row.tool_name,
2601
- row.project_name,
2602
- row.has_error,
2603
- row.raw_text,
2604
- vectorToBlob(row.vector),
2605
- row.version,
2606
- taskId,
2607
- importance,
2608
- status,
2609
- confidence,
2610
- lastAccessed,
2611
- workspaceId,
2612
- documentId,
2613
- userId,
2614
- charOffset,
2615
- pageNumber,
2616
- sourcePath,
2617
- sourceType,
2618
- tier,
2619
- supersedesId,
2620
- draft,
2621
- memoryType,
2622
- trajectory
2623
- ] : [
2624
- row.id,
2625
- row.agent_id,
2626
- row.agent_role,
2627
- row.session_id,
2628
- row.timestamp,
2629
- row.tool_name,
2630
- row.project_name,
2631
- row.has_error,
2632
- row.raw_text,
2633
- row.version,
2634
- taskId,
2635
- importance,
2636
- status,
2637
- confidence,
2638
- lastAccessed,
2639
- workspaceId,
2640
- documentId,
2641
- userId,
2642
- charOffset,
2643
- pageNumber,
2644
- sourcePath,
2645
- sourceType,
2646
- tier,
2647
- supersedesId,
2648
- draft,
2649
- memoryType,
2650
- trajectory
2651
- ]
3213
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
3214
+ intent, outcome, domain, referenced_entities, retrieval_count,
3215
+ chain_position, review_status, context_window_pct, file_paths, commit_hash,
3216
+ duration_ms, token_cost, audience, language_type, parent_memory_id`;
3217
+ const metaArgs = [
3218
+ intent,
3219
+ outcome,
3220
+ domain,
3221
+ referencedEntities,
3222
+ retrievalCount,
3223
+ chainPosition,
3224
+ reviewStatus,
3225
+ contextWindowPct,
3226
+ filePaths,
3227
+ commitHash,
3228
+ durationMs,
3229
+ tokenCost,
3230
+ audience,
3231
+ languageType,
3232
+ parentMemoryId
3233
+ ];
3234
+ const baseArgs = [
3235
+ row.id,
3236
+ row.agent_id,
3237
+ row.agent_role,
3238
+ row.session_id,
3239
+ row.timestamp,
3240
+ row.tool_name,
3241
+ row.project_name,
3242
+ row.has_error,
3243
+ row.raw_text
3244
+ ];
3245
+ const sharedArgs = [
3246
+ row.version,
3247
+ taskId,
3248
+ importance,
3249
+ status,
3250
+ confidence,
3251
+ lastAccessed,
3252
+ workspaceId,
3253
+ documentId,
3254
+ userId,
3255
+ charOffset,
3256
+ pageNumber,
3257
+ sourcePath,
3258
+ sourceType,
3259
+ tier,
3260
+ supersedesId,
3261
+ draft,
3262
+ memoryType,
3263
+ trajectory,
3264
+ contentHash
3265
+ ];
3266
+ return {
3267
+ sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
3268
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
3269
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3270
+ args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
2652
3271
  };
2653
3272
  };
2654
3273
  const globalClient = getClient();
@@ -2908,7 +3527,7 @@ __export(review_gate_exports, {
2908
3527
  runReviewGate: () => runReviewGate
2909
3528
  });
2910
3529
  import { execSync as execSync5 } from "child_process";
2911
- import { existsSync as existsSync10 } from "fs";
3530
+ import { existsSync as existsSync11 } from "fs";
2912
3531
  function checkCommitsExist(taskCreatedAt) {
2913
3532
  try {
2914
3533
  const since = new Date(taskCreatedAt).toISOString();
@@ -2971,7 +3590,7 @@ function checkTestCoverage(taskCreatedAt) {
2971
3590
  const testPath = file.replace(/^src\//, "tests/").replace(/\.ts$/, ".test.ts");
2972
3591
  const baseName = file.split("/").pop()?.replace(/\.ts$/, "") ?? "";
2973
3592
  const testDir = testPath.substring(0, testPath.lastIndexOf("/"));
2974
- const hasDirectTest = existsSync10(testPath);
3593
+ const hasDirectTest = existsSync11(testPath);
2975
3594
  let hasRelatedTest = false;
2976
3595
  try {
2977
3596
  const related = execSync5(
@@ -3025,9 +3644,9 @@ var init_review_gate = __esm({
3025
3644
  });
3026
3645
 
3027
3646
  // src/adapters/claude/hooks/pre-tool-use.ts
3028
- import { existsSync as existsSync11, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
3647
+ import { existsSync as existsSync12, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
3029
3648
  import { execSync as execSync6 } from "child_process";
3030
- import path12 from "path";
3649
+ import path13 from "path";
3031
3650
 
3032
3651
  // src/adapters/claude/active-agent.ts
3033
3652
  init_config();
@@ -3140,17 +3759,17 @@ if (!process.env.AGENT_ID) {
3140
3759
  }
3141
3760
  var DELEGATION_TASK_THRESHOLD = 3;
3142
3761
  var CTO_ROLES = ["CTO", "executive"];
3143
- var CACHE_DIR2 = path12.join(EXE_AI_DIR, "session-cache");
3762
+ var CACHE_DIR2 = path13.join(EXE_AI_DIR, "session-cache");
3144
3763
  var timeout = setTimeout(() => {
3145
3764
  process.exit(0);
3146
3765
  }, 5e3);
3147
3766
  timeout.unref();
3148
3767
  function getDelegationFlagPath() {
3149
- return path12.join(CACHE_DIR2, `delegation-checkpoint-${getSessionKey()}.json`);
3768
+ return path13.join(CACHE_DIR2, `delegation-checkpoint-${getSessionKey()}.json`);
3150
3769
  }
3151
3770
  function hasDelegationFired() {
3152
3771
  try {
3153
- return existsSync11(getDelegationFlagPath());
3772
+ return existsSync12(getDelegationFlagPath());
3154
3773
  } catch {
3155
3774
  return false;
3156
3775
  }