@askexenow/exe-os 0.9.66 → 0.9.67

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 (99) hide show
  1. package/deploy/stack-manifests/v0.9.json +54 -5
  2. package/dist/bin/age-ontology-load.js +61 -0
  3. package/dist/bin/agentic-ontology-backfill.js +4708 -0
  4. package/dist/bin/agentic-reflection-backfill.js +4144 -0
  5. package/dist/bin/{exe-link.js → agentic-semantic-label.js} +1523 -2275
  6. package/dist/bin/backfill-conversations.js +506 -20
  7. package/dist/bin/backfill-responses.js +506 -20
  8. package/dist/bin/backfill-vectors.js +233 -20
  9. package/dist/bin/bulk-sync-postgres.js +4876 -0
  10. package/dist/bin/cleanup-stale-review-tasks.js +507 -21
  11. package/dist/bin/cli.js +2450 -1530
  12. package/dist/bin/exe-assign.js +506 -20
  13. package/dist/bin/exe-boot.js +410 -60
  14. package/dist/bin/exe-cloud.js +795 -105
  15. package/dist/bin/exe-dispatch.js +516 -22
  16. package/dist/bin/exe-doctor.js +587 -30
  17. package/dist/bin/exe-export-behaviors.js +518 -24
  18. package/dist/bin/exe-forget.js +507 -21
  19. package/dist/bin/exe-gateway.js +571 -25
  20. package/dist/bin/exe-heartbeat.js +518 -24
  21. package/dist/bin/exe-kill.js +507 -21
  22. package/dist/bin/exe-launch-agent.js +2312 -1069
  23. package/dist/bin/exe-new-employee.js +197 -165
  24. package/dist/bin/exe-pending-messages.js +507 -21
  25. package/dist/bin/exe-pending-notifications.js +507 -21
  26. package/dist/bin/exe-pending-reviews.js +507 -21
  27. package/dist/bin/exe-rename.js +507 -21
  28. package/dist/bin/exe-review.js +507 -21
  29. package/dist/bin/exe-search.js +518 -24
  30. package/dist/bin/exe-session-cleanup.js +516 -22
  31. package/dist/bin/exe-settings.js +4 -0
  32. package/dist/bin/exe-start-codex.js +682 -143
  33. package/dist/bin/exe-start-opencode.js +627 -79
  34. package/dist/bin/exe-status.js +507 -21
  35. package/dist/bin/exe-team.js +507 -21
  36. package/dist/bin/git-sweep.js +516 -22
  37. package/dist/bin/graph-backfill.js +558 -21
  38. package/dist/bin/graph-export.js +507 -21
  39. package/dist/bin/graph-layer-benchmark.js +109 -0
  40. package/dist/bin/install.js +305 -288
  41. package/dist/bin/intercom-check.js +516 -22
  42. package/dist/bin/postgres-agentic-reflection-backfill.js +187 -0
  43. package/dist/bin/postgres-agentic-semantic-backfill.js +237 -0
  44. package/dist/bin/scan-tasks.js +516 -22
  45. package/dist/bin/setup.js +412 -62
  46. package/dist/bin/shard-migrate.js +506 -20
  47. package/dist/gateway/index.js +569 -23
  48. package/dist/hooks/bug-report-worker.js +519 -25
  49. package/dist/hooks/codex-stop-task-finalizer.js +516 -22
  50. package/dist/hooks/commit-complete.js +516 -22
  51. package/dist/hooks/error-recall.js +518 -24
  52. package/dist/hooks/ingest.js +516 -22
  53. package/dist/hooks/instructions-loaded.js +507 -21
  54. package/dist/hooks/notification.js +507 -21
  55. package/dist/hooks/post-compact.js +507 -21
  56. package/dist/hooks/post-tool-combined.js +519 -25
  57. package/dist/hooks/pre-compact.js +516 -22
  58. package/dist/hooks/pre-tool-use.js +507 -21
  59. package/dist/hooks/prompt-submit.js +519 -25
  60. package/dist/hooks/session-end.js +516 -22
  61. package/dist/hooks/session-start.js +520 -26
  62. package/dist/hooks/stop.js +517 -23
  63. package/dist/hooks/subagent-stop.js +507 -21
  64. package/dist/hooks/summary-worker.js +411 -61
  65. package/dist/index.js +569 -23
  66. package/dist/lib/cloud-sync.js +391 -53
  67. package/dist/lib/config.js +13 -1
  68. package/dist/lib/consolidation.js +1 -1
  69. package/dist/lib/database.js +124 -0
  70. package/dist/lib/db.js +124 -0
  71. package/dist/lib/device-registry.js +124 -0
  72. package/dist/lib/embedder.js +13 -1
  73. package/dist/lib/exe-daemon.js +2184 -561
  74. package/dist/lib/hybrid-search.js +518 -24
  75. package/dist/lib/identity.js +3 -0
  76. package/dist/lib/keychain.js +178 -22
  77. package/dist/lib/messaging.js +3 -0
  78. package/dist/lib/reminders.js +3 -0
  79. package/dist/lib/schedules.js +233 -20
  80. package/dist/lib/skill-learning.js +16 -1
  81. package/dist/lib/store.js +506 -20
  82. package/dist/lib/tasks.js +16 -1
  83. package/dist/lib/tmux-routing.js +16 -1
  84. package/dist/lib/token-spend.js +3 -0
  85. package/dist/mcp/server.js +1757 -428
  86. package/dist/mcp/tools/complete-reminder.js +3 -0
  87. package/dist/mcp/tools/create-reminder.js +3 -0
  88. package/dist/mcp/tools/create-task.js +16 -1
  89. package/dist/mcp/tools/deactivate-behavior.js +3 -0
  90. package/dist/mcp/tools/list-reminders.js +3 -0
  91. package/dist/mcp/tools/list-tasks.js +3 -0
  92. package/dist/mcp/tools/send-message.js +3 -0
  93. package/dist/mcp/tools/update-task.js +16 -1
  94. package/dist/runtime/index.js +516 -22
  95. package/dist/tui/App.js +594 -29
  96. package/package.json +8 -5
  97. package/src/commands/exe/cloud.md +6 -10
  98. package/stack.release.json +3 -3
  99. package/src/commands/exe/link.md +0 -18
@@ -15,334 +15,77 @@ var __export = (target, all) => {
15
15
  __defProp(target, name, { get: all[name], enumerable: true });
16
16
  };
17
17
 
18
- // src/lib/keychain.ts
19
- var keychain_exports = {};
20
- __export(keychain_exports, {
21
- deleteMasterKey: () => deleteMasterKey,
22
- exportMnemonic: () => exportMnemonic,
23
- getMasterKey: () => getMasterKey,
24
- importMnemonic: () => importMnemonic,
25
- setMasterKey: () => setMasterKey
26
- });
27
- import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
28
- import { existsSync } from "fs";
29
- import { execSync } from "child_process";
30
- import path from "path";
31
- import os from "os";
32
- function getKeyDir() {
33
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
34
- }
35
- function getKeyPath() {
36
- return path.join(getKeyDir(), "master.key");
37
- }
38
- function macKeychainGet() {
39
- if (process.platform !== "darwin") return null;
40
- try {
41
- return execSync(
42
- `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
43
- { encoding: "utf-8", timeout: 5e3 }
44
- ).trim();
45
- } catch {
46
- return null;
47
- }
48
- }
49
- function macKeychainSet(value) {
50
- if (process.platform !== "darwin") return false;
51
- try {
52
- try {
53
- execSync(
54
- `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
55
- { timeout: 5e3 }
56
- );
57
- } catch {
58
- }
59
- execSync(
60
- `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
61
- { timeout: 5e3 }
62
- );
63
- return true;
64
- } catch {
65
- return false;
66
- }
67
- }
68
- function macKeychainDelete() {
69
- if (process.platform !== "darwin") return false;
70
- try {
71
- execSync(
72
- `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
73
- { timeout: 5e3 }
74
- );
75
- return true;
76
- } catch {
77
- return false;
78
- }
79
- }
80
- function linuxSecretGet() {
81
- if (process.platform !== "linux") return null;
82
- try {
83
- return execSync(
84
- `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
85
- { encoding: "utf-8", timeout: 5e3 }
86
- ).trim();
87
- } catch {
88
- return null;
89
- }
90
- }
91
- function linuxSecretSet(value) {
92
- if (process.platform !== "linux") return false;
93
- try {
94
- execSync(
95
- `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
96
- { timeout: 5e3 }
97
- );
98
- return true;
99
- } catch {
100
- return false;
101
- }
102
- }
103
- function linuxSecretDelete() {
104
- if (process.platform !== "linux") return false;
105
- try {
106
- execSync(
107
- `secret-tool clear service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
108
- { timeout: 5e3 }
109
- );
110
- return true;
111
- } catch {
112
- return false;
113
- }
114
- }
115
- async function tryKeytar() {
116
- try {
117
- return await import("keytar");
118
- } catch {
119
- return null;
120
- }
121
- }
122
- function deriveMachineKey() {
123
- try {
124
- const crypto4 = __require("crypto");
125
- const material = [
126
- os.hostname(),
127
- os.userInfo().username,
128
- os.arch(),
129
- os.platform(),
130
- // Machine ID on Linux (stable across reboots)
131
- process.platform === "linux" ? readMachineId() : ""
132
- ].join("|");
133
- return crypto4.createHash("sha256").update(material).digest();
134
- } catch {
135
- return null;
136
- }
137
- }
138
- function readMachineId() {
139
- try {
140
- const { readFileSync: readFileSync9 } = __require("fs");
141
- return readFileSync9("/etc/machine-id", "utf-8").trim();
142
- } catch {
143
- return "";
18
+ // src/types/memory.ts
19
+ var EMBEDDING_DIM;
20
+ var init_memory = __esm({
21
+ "src/types/memory.ts"() {
22
+ "use strict";
23
+ EMBEDDING_DIM = 1024;
144
24
  }
145
- }
146
- function encryptWithMachineKey(plaintext, machineKey) {
147
- const crypto4 = __require("crypto");
148
- const iv = crypto4.randomBytes(12);
149
- const cipher = crypto4.createCipheriv("aes-256-gcm", machineKey, iv);
150
- let encrypted = cipher.update(plaintext, "utf-8", "base64");
151
- encrypted += cipher.final("base64");
152
- const authTag = cipher.getAuthTag().toString("base64");
153
- return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
154
- }
155
- function decryptWithMachineKey(encrypted, machineKey) {
156
- if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
157
- try {
158
- const crypto4 = __require("crypto");
159
- const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
160
- if (parts.length !== 3) return null;
161
- const [ivB64, tagB64, cipherB64] = parts;
162
- const iv = Buffer.from(ivB64, "base64");
163
- const authTag = Buffer.from(tagB64, "base64");
164
- const decipher = crypto4.createDecipheriv("aes-256-gcm", machineKey, iv);
165
- decipher.setAuthTag(authTag);
166
- let decrypted = decipher.update(cipherB64, "base64", "utf-8");
167
- decrypted += decipher.final("utf-8");
168
- return decrypted;
169
- } catch {
170
- return null;
25
+ });
26
+
27
+ // src/lib/db-retry.ts
28
+ function isBusyError(err) {
29
+ if (err instanceof Error) {
30
+ const msg = err.message.toLowerCase();
31
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
171
32
  }
33
+ return false;
172
34
  }
173
- async function writeMachineBoundFileFallback(b64) {
174
- const dir = getKeyDir();
175
- await mkdir(dir, { recursive: true });
176
- const keyPath = getKeyPath();
177
- const machineKey = deriveMachineKey();
178
- if (machineKey) {
179
- const encrypted = encryptWithMachineKey(b64, machineKey);
180
- await writeFile(keyPath, encrypted + "\n", "utf-8");
181
- await chmod(keyPath, 384);
182
- return "encrypted";
183
- }
184
- await writeFile(keyPath, b64 + "\n", "utf-8");
185
- await chmod(keyPath, 384);
186
- return "plaintext";
35
+ function delay(ms) {
36
+ return new Promise((resolve) => setTimeout(resolve, ms));
187
37
  }
188
- async function getMasterKey() {
189
- const nativeValue = macKeychainGet() ?? linuxSecretGet();
190
- if (nativeValue) {
191
- return Buffer.from(nativeValue, "base64");
192
- }
193
- const keytar = await tryKeytar();
194
- if (keytar) {
38
+ async function retryOnBusy(fn, label) {
39
+ let lastError;
40
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
195
41
  try {
196
- const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
197
- if (keytarValue) {
198
- const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
199
- if (migrated) {
200
- process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
201
- }
202
- return Buffer.from(keytarValue, "base64");
203
- }
204
- } catch {
205
- }
206
- }
207
- const keyPath = getKeyPath();
208
- if (!existsSync(keyPath)) {
209
- process.stderr.write(
210
- `[keychain] Key not found at ${keyPath} (HOME=${os.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
211
- `
212
- );
213
- return null;
214
- }
215
- try {
216
- const content = (await readFile(keyPath, "utf-8")).trim();
217
- let b64Value;
218
- if (content.startsWith(ENCRYPTED_PREFIX)) {
219
- const machineKey = deriveMachineKey();
220
- if (!machineKey) {
221
- process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
222
- return null;
223
- }
224
- const decrypted = decryptWithMachineKey(content, machineKey);
225
- if (!decrypted) {
226
- process.stderr.write(
227
- "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
228
- );
229
- return null;
230
- }
231
- b64Value = decrypted;
232
- } else {
233
- b64Value = content;
234
- }
235
- const key = Buffer.from(b64Value, "base64");
236
- const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
237
- if (migrated) {
238
- process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
239
- try {
240
- await unlink(keyPath);
241
- process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
242
- } catch {
243
- }
244
- } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
245
- const fallback = await writeMachineBoundFileFallback(b64Value);
246
- if (fallback === "encrypted") {
247
- process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
248
- } else {
249
- process.stderr.write(
250
- "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
251
- );
42
+ return await fn();
43
+ } catch (err) {
44
+ lastError = err;
45
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
46
+ throw err;
252
47
  }
253
- }
254
- return key;
255
- } catch (err) {
256
- process.stderr.write(
257
- `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
48
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
49
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
50
+ process.stderr.write(
51
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
258
52
  `
259
- );
260
- return null;
261
- }
262
- }
263
- async function setMasterKey(key) {
264
- const b64 = key.toString("base64");
265
- if (macKeychainSet(b64) || linuxSecretSet(b64)) {
266
- return;
267
- }
268
- const keytar = await tryKeytar();
269
- if (keytar) {
270
- try {
271
- await keytar.setPassword(SERVICE, ACCOUNT, b64);
272
- return;
273
- } catch {
53
+ );
54
+ await delay(backoff + jitter);
274
55
  }
275
56
  }
276
- const fallback = await writeMachineBoundFileFallback(b64);
277
- if (fallback === "encrypted") {
278
- process.stderr.write("[keychain] Key stored encrypted (machine-bound).\n");
279
- } else {
280
- process.stderr.write(
281
- "[keychain] WARNING: Key stored in plaintext file \u2014 no OS keychain available.\n"
282
- );
283
- }
57
+ throw lastError;
284
58
  }
285
- async function deleteMasterKey() {
286
- macKeychainDelete();
287
- linuxSecretDelete();
288
- const keytar = await tryKeytar();
289
- if (keytar) {
290
- try {
291
- await keytar.deletePassword(SERVICE, ACCOUNT);
292
- } catch {
59
+ function wrapWithRetry(client) {
60
+ return new Proxy(client, {
61
+ get(target, prop, receiver) {
62
+ if (prop === "execute") {
63
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
64
+ }
65
+ if (prop === "batch") {
66
+ return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
67
+ }
68
+ return Reflect.get(target, prop, receiver);
293
69
  }
294
- }
295
- const keyPath = getKeyPath();
296
- if (existsSync(keyPath)) {
297
- await unlink(keyPath);
298
- }
299
- }
300
- async function loadBip39() {
301
- try {
302
- return await import("bip39");
303
- } catch {
304
- throw new Error(
305
- "bip39 package not found. Run: npm install -g bip39\nOr reinstall exe-os: npm install -g @askexenow/exe-os"
306
- );
307
- }
308
- }
309
- async function exportMnemonic(key) {
310
- if (key.length !== 32) {
311
- throw new Error(`Key must be 32 bytes, got ${key.length}`);
312
- }
313
- const { entropyToMnemonic } = await loadBip39();
314
- return entropyToMnemonic(key.toString("hex"));
315
- }
316
- async function importMnemonic(mnemonic) {
317
- const trimmed = mnemonic.trim();
318
- const words = trimmed.split(/\s+/);
319
- if (words.length !== 24) {
320
- throw new Error(`Expected 24 words, got ${words.length}`);
321
- }
322
- const { validateMnemonic, mnemonicToEntropy } = await loadBip39();
323
- if (!validateMnemonic(trimmed)) {
324
- throw new Error("Invalid mnemonic \u2014 check for typos or missing words");
325
- }
326
- const entropy = mnemonicToEntropy(trimmed);
327
- return Buffer.from(entropy, "hex");
70
+ });
328
71
  }
329
- var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
330
- var init_keychain = __esm({
331
- "src/lib/keychain.ts"() {
72
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
73
+ var init_db_retry = __esm({
74
+ "src/lib/db-retry.ts"() {
332
75
  "use strict";
333
- SERVICE = "exe-mem";
334
- ACCOUNT = "master-key";
335
- ENCRYPTED_PREFIX = "enc:";
76
+ MAX_RETRIES = 5;
77
+ BASE_DELAY_MS = 250;
78
+ MAX_JITTER_MS = 400;
336
79
  }
337
80
  });
338
81
 
339
82
  // src/lib/secure-files.ts
340
- import { chmodSync, existsSync as existsSync2, mkdirSync } from "fs";
341
- import { chmod as chmod2, mkdir as mkdir2 } from "fs/promises";
83
+ import { chmodSync, existsSync, mkdirSync } from "fs";
84
+ import { chmod, mkdir } from "fs/promises";
342
85
  async function ensurePrivateDir(dirPath) {
343
- await mkdir2(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
86
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
344
87
  try {
345
- await chmod2(dirPath, PRIVATE_DIR_MODE);
88
+ await chmod(dirPath, PRIVATE_DIR_MODE);
346
89
  } catch {
347
90
  }
348
91
  }
@@ -355,13 +98,13 @@ function ensurePrivateDirSync(dirPath) {
355
98
  }
356
99
  async function enforcePrivateFile(filePath) {
357
100
  try {
358
- await chmod2(filePath, PRIVATE_FILE_MODE);
101
+ await chmod(filePath, PRIVATE_FILE_MODE);
359
102
  } catch {
360
103
  }
361
104
  }
362
105
  function enforcePrivateFileSync(filePath) {
363
106
  try {
364
- if (existsSync2(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
107
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
365
108
  } catch {
366
109
  }
367
110
  }
@@ -375,31 +118,16 @@ var init_secure_files = __esm({
375
118
  });
376
119
 
377
120
  // src/lib/config.ts
378
- var config_exports = {};
379
- __export(config_exports, {
380
- CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
381
- CONFIG_PATH: () => CONFIG_PATH,
382
- CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
383
- DB_PATH: () => DB_PATH,
384
- EXE_AI_DIR: () => EXE_AI_DIR,
385
- LEGACY_LANCE_PATH: () => LEGACY_LANCE_PATH,
386
- MODELS_DIR: () => MODELS_DIR,
387
- loadConfig: () => loadConfig,
388
- loadConfigFrom: () => loadConfigFrom,
389
- loadConfigSync: () => loadConfigSync,
390
- migrateConfig: () => migrateConfig,
391
- saveConfig: () => saveConfig
392
- });
393
- import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
394
- import { readFileSync, existsSync as existsSync3, renameSync } from "fs";
395
- import path2 from "path";
396
- import os2 from "os";
121
+ import { readFile, writeFile } from "fs/promises";
122
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
123
+ import path from "path";
124
+ import os from "os";
397
125
  function resolveDataDir() {
398
126
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
399
127
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
400
- const newDir = path2.join(os2.homedir(), ".exe-os");
401
- const legacyDir = path2.join(os2.homedir(), ".exe-mem");
402
- if (!existsSync3(newDir) && existsSync3(legacyDir)) {
128
+ const newDir = path.join(os.homedir(), ".exe-os");
129
+ const legacyDir = path.join(os.homedir(), ".exe-mem");
130
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
403
131
  try {
404
132
  renameSync(legacyDir, newDir);
405
133
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -468,11 +196,11 @@ function normalizeOrchestration(raw) {
468
196
  async function loadConfig() {
469
197
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
470
198
  await ensurePrivateDir(dir);
471
- const configPath = path2.join(dir, "config.json");
472
- if (!existsSync3(configPath)) {
473
- return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
199
+ const configPath = path.join(dir, "config.json");
200
+ if (!existsSync2(configPath)) {
201
+ return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
474
202
  }
475
- const raw = await readFile2(configPath, "utf-8");
203
+ const raw = await readFile(configPath, "utf-8");
476
204
  try {
477
205
  let parsed = JSON.parse(raw);
478
206
  parsed = migrateLegacyConfig(parsed);
@@ -481,7 +209,7 @@ async function loadConfig() {
481
209
  process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
482
210
  `);
483
211
  try {
484
- await writeFile2(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
212
+ await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
485
213
  await enforcePrivateFile(configPath);
486
214
  } catch {
487
215
  }
@@ -490,55 +218,17 @@ async function loadConfig() {
490
218
  normalizeSessionLifecycle(migratedCfg);
491
219
  normalizeAutoUpdate(migratedCfg);
492
220
  normalizeOrchestration(migratedCfg);
493
- const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
221
+ const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
494
222
  if (config.dbPath.startsWith("~")) {
495
- config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
223
+ config.dbPath = config.dbPath.replace(/^~/, os.homedir());
224
+ }
225
+ const envDbPath = path.join(dir, "memories.db");
226
+ if (process.env.EXE_OS_DIR && config.dbPath !== envDbPath && !existsSync2(config.dbPath) && existsSync2(envDbPath)) {
227
+ config.dbPath = envDbPath;
496
228
  }
497
229
  return config;
498
230
  } catch {
499
- return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
500
- }
501
- }
502
- function loadConfigSync() {
503
- const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
504
- const configPath = path2.join(dir, "config.json");
505
- if (!existsSync3(configPath)) {
506
- return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
507
- }
508
- try {
509
- const raw = readFileSync(configPath, "utf-8");
510
- let parsed = JSON.parse(raw);
511
- parsed = migrateLegacyConfig(parsed);
512
- const { config: migratedCfg } = migrateConfig(parsed);
513
- normalizeScalingRoadmap(migratedCfg);
514
- normalizeSessionLifecycle(migratedCfg);
515
- normalizeAutoUpdate(migratedCfg);
516
- normalizeOrchestration(migratedCfg);
517
- return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
518
- } catch {
519
- return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
520
- }
521
- }
522
- async function saveConfig(config) {
523
- const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
524
- await ensurePrivateDir(dir);
525
- const configPath = path2.join(dir, "config.json");
526
- await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n");
527
- await enforcePrivateFile(configPath);
528
- }
529
- async function loadConfigFrom(configPath) {
530
- const raw = await readFile2(configPath, "utf-8");
531
- try {
532
- let parsed = JSON.parse(raw);
533
- parsed = migrateLegacyConfig(parsed);
534
- const { config: migratedCfg } = migrateConfig(parsed);
535
- normalizeScalingRoadmap(migratedCfg);
536
- normalizeSessionLifecycle(migratedCfg);
537
- normalizeAutoUpdate(migratedCfg);
538
- normalizeOrchestration(migratedCfg);
539
- return { ...DEFAULT_CONFIG, ...migratedCfg };
540
- } catch {
541
- return { ...DEFAULT_CONFIG };
231
+ return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
542
232
  }
543
233
  }
544
234
  var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
@@ -547,10 +237,10 @@ var init_config = __esm({
547
237
  "use strict";
548
238
  init_secure_files();
549
239
  EXE_AI_DIR = resolveDataDir();
550
- DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
551
- MODELS_DIR = path2.join(EXE_AI_DIR, "models");
552
- CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
553
- LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
240
+ DB_PATH = path.join(EXE_AI_DIR, "memories.db");
241
+ MODELS_DIR = path.join(EXE_AI_DIR, "models");
242
+ CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
243
+ LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
554
244
  CURRENT_CONFIG_VERSION = 1;
555
245
  DEFAULT_CONFIG = {
556
246
  config_version: CURRENT_CONFIG_VERSION,
@@ -626,167 +316,12 @@ var init_config = __esm({
626
316
  }
627
317
  });
628
318
 
629
- // src/lib/key-backup-status.ts
630
- var key_backup_status_exports = {};
631
- __export(key_backup_status_exports, {
632
- getKeyBackupStatus: () => getKeyBackupStatus,
633
- keyBackupMarkerPath: () => keyBackupMarkerPath,
634
- markKeyBackupConfirmed: () => markKeyBackupConfirmed
635
- });
636
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
637
- import path3 from "path";
638
- function keyBackupMarkerPath() {
639
- return path3.join(EXE_AI_DIR, "key-backup-confirmed.json");
640
- }
641
- function getKeyBackupStatus() {
642
- const marker = keyBackupMarkerPath();
643
- if (!existsSync4(marker)) return { exists: false };
644
- try {
645
- const parsed = JSON.parse(readFileSync2(marker, "utf8"));
646
- return {
647
- exists: true,
648
- confirmedAt: parsed.confirmedAt,
649
- source: parsed.source
650
- };
651
- } catch {
652
- return { exists: true };
653
- }
654
- }
655
- function markKeyBackupConfirmed(source) {
656
- mkdirSync2(EXE_AI_DIR, { recursive: true, mode: 448 });
657
- writeFileSync(
658
- keyBackupMarkerPath(),
659
- JSON.stringify({ confirmedAt: (/* @__PURE__ */ new Date()).toISOString(), source }, null, 2) + "\n",
660
- { mode: 384 }
661
- );
662
- }
663
- var init_key_backup_status = __esm({
664
- "src/lib/key-backup-status.ts"() {
665
- "use strict";
666
- init_config();
667
- }
668
- });
669
-
670
- // src/lib/crypto.ts
671
- var crypto_exports = {};
672
- __export(crypto_exports, {
673
- decryptSyncBlob: () => decryptSyncBlob,
674
- encryptSyncBlob: () => encryptSyncBlob,
675
- initSyncCrypto: () => initSyncCrypto,
676
- isSyncCryptoInitialized: () => isSyncCryptoInitialized
677
- });
678
- import crypto from "crypto";
679
- function initSyncCrypto(masterKey) {
680
- if (masterKey.length !== 32) {
681
- throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
682
- }
683
- _syncKey = Buffer.from(
684
- crypto.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
685
- );
686
- }
687
- function isSyncCryptoInitialized() {
688
- return _syncKey !== null;
689
- }
690
- function requireSyncKey() {
691
- if (!_syncKey) {
692
- throw new Error("Sync crypto not initialized. Call initSyncCrypto(masterKey) first.");
693
- }
694
- return _syncKey;
695
- }
696
- function encryptSyncBlob(data) {
697
- const key = requireSyncKey();
698
- const iv = crypto.randomBytes(IV_LENGTH);
699
- const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
700
- const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
701
- const tag = cipher.getAuthTag();
702
- return Buffer.concat([iv, encrypted, tag]).toString("base64");
703
- }
704
- function decryptSyncBlob(ciphertext) {
705
- const key = requireSyncKey();
706
- const combined = Buffer.from(ciphertext, "base64");
707
- if (combined.length < IV_LENGTH + TAG_LENGTH) {
708
- throw new Error("Sync blob too short to contain IV + tag");
709
- }
710
- const iv = combined.subarray(0, IV_LENGTH);
711
- const tag = combined.subarray(combined.length - TAG_LENGTH);
712
- const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
713
- const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
714
- decipher.setAuthTag(tag);
715
- return Buffer.concat([decipher.update(encrypted), decipher.final()]);
716
- }
717
- var ALGORITHM, IV_LENGTH, TAG_LENGTH, SYNC_HKDF_INFO, _syncKey;
718
- var init_crypto = __esm({
719
- "src/lib/crypto.ts"() {
720
- "use strict";
721
- ALGORITHM = "aes-256-gcm";
722
- IV_LENGTH = 12;
723
- TAG_LENGTH = 16;
724
- SYNC_HKDF_INFO = "exe-mem-sync-v2";
725
- _syncKey = null;
726
- }
727
- });
728
-
729
- // src/lib/db-retry.ts
730
- function isBusyError(err) {
731
- if (err instanceof Error) {
732
- const msg = err.message.toLowerCase();
733
- return msg.includes("sqlite_busy") || msg.includes("database is locked");
734
- }
735
- return false;
736
- }
737
- function delay(ms) {
738
- return new Promise((resolve) => setTimeout(resolve, ms));
739
- }
740
- async function retryOnBusy(fn, label) {
741
- let lastError;
742
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
743
- try {
744
- return await fn();
745
- } catch (err) {
746
- lastError = err;
747
- if (!isBusyError(err) || attempt === MAX_RETRIES) {
748
- throw err;
749
- }
750
- const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
751
- const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
752
- process.stderr.write(
753
- `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
754
- `
755
- );
756
- await delay(backoff + jitter);
757
- }
758
- }
759
- throw lastError;
760
- }
761
- function wrapWithRetry(client) {
762
- return new Proxy(client, {
763
- get(target, prop, receiver) {
764
- if (prop === "execute") {
765
- return (sql) => retryOnBusy(() => target.execute(sql), "execute");
766
- }
767
- if (prop === "batch") {
768
- return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
769
- }
770
- return Reflect.get(target, prop, receiver);
771
- }
772
- });
773
- }
774
- var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
775
- var init_db_retry = __esm({
776
- "src/lib/db-retry.ts"() {
777
- "use strict";
778
- MAX_RETRIES = 5;
779
- BASE_DELAY_MS = 250;
780
- MAX_JITTER_MS = 400;
781
- }
782
- });
783
-
784
319
  // src/lib/employees.ts
785
- import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
786
- import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
787
- import { execSync as execSync2 } from "child_process";
788
- import path4 from "path";
789
- import os3 from "os";
320
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
321
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
322
+ import { execSync } from "child_process";
323
+ import path2 from "path";
324
+ import os2 from "os";
790
325
  function normalizeRole(role) {
791
326
  return (role ?? "").trim().toLowerCase();
792
327
  }
@@ -799,84 +334,29 @@ function getCoordinatorEmployee(employees) {
799
334
  function getCoordinatorName(employees = loadEmployeesSync()) {
800
335
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
801
336
  }
802
- async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
803
- if (!existsSync5(employeesPath)) {
804
- return [];
805
- }
806
- const raw = await readFile3(employeesPath, "utf-8");
807
- try {
808
- return JSON.parse(raw);
809
- } catch {
810
- return [];
811
- }
812
- }
813
- async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
814
- await mkdir3(path4.dirname(employeesPath), { recursive: true });
815
- await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
816
- }
817
337
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
818
- if (!existsSync5(employeesPath)) return [];
338
+ if (!existsSync3(employeesPath)) return [];
819
339
  try {
820
- return JSON.parse(readFileSync3(employeesPath, "utf-8"));
340
+ return JSON.parse(readFileSync2(employeesPath, "utf-8"));
821
341
  } catch {
822
342
  return [];
823
343
  }
824
344
  }
825
- function findExeBin() {
826
- try {
827
- return execSync2(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
828
- } catch {
829
- return null;
830
- }
831
- }
832
- function registerBinSymlinks(name) {
833
- const created = [];
834
- const skipped = [];
835
- const errors = [];
836
- const exeBinPath = findExeBin();
837
- if (!exeBinPath) {
838
- errors.push("Could not find 'exe-os' in PATH");
839
- return { created, skipped, errors };
840
- }
841
- const binDir = path4.dirname(exeBinPath);
842
- let target;
843
- try {
844
- target = readlinkSync(exeBinPath);
845
- } catch {
846
- errors.push("Could not read 'exe' symlink");
847
- return { created, skipped, errors };
848
- }
849
- for (const suffix of ["", "-opencode"]) {
850
- const linkName = `${name}${suffix}`;
851
- const linkPath = path4.join(binDir, linkName);
852
- if (existsSync5(linkPath)) {
853
- skipped.push(linkName);
854
- continue;
855
- }
856
- try {
857
- symlinkSync(target, linkPath);
858
- created.push(linkName);
859
- } catch (err) {
860
- errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
861
- }
862
- }
863
- return { created, skipped, errors };
864
- }
865
345
  var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
866
346
  var init_employees = __esm({
867
347
  "src/lib/employees.ts"() {
868
348
  "use strict";
869
349
  init_config();
870
- EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
350
+ EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
871
351
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
872
352
  COORDINATOR_ROLE = "COO";
873
- IDENTITY_DIR = path4.join(EXE_AI_DIR, "identity");
353
+ IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
874
354
  }
875
355
  });
876
356
 
877
357
  // src/lib/database-adapter.ts
878
- import os4 from "os";
879
- import path5 from "path";
358
+ import os3 from "os";
359
+ import path3 from "path";
880
360
  import { createRequire } from "module";
881
361
  import { pathToFileURL } from "url";
882
362
  function quotedIdentifier(identifier) {
@@ -1187,8 +667,8 @@ async function loadPrismaClient() {
1187
667
  }
1188
668
  return new PrismaClient2();
1189
669
  }
1190
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path5.join(os4.homedir(), "exe-db");
1191
- const requireFromExeDb = createRequire(path5.join(exeDbRoot, "package.json"));
670
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
671
+ const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
1192
672
  const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1193
673
  const module = await import(pathToFileURL(prismaEntry).href);
1194
674
  const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
@@ -1458,19 +938,10 @@ var init_database_adapter = __esm({
1458
938
  }
1459
939
  });
1460
940
 
1461
- // src/types/memory.ts
1462
- var EMBEDDING_DIM;
1463
- var init_memory = __esm({
1464
- "src/types/memory.ts"() {
1465
- "use strict";
1466
- EMBEDDING_DIM = 1024;
1467
- }
1468
- });
1469
-
1470
941
  // src/lib/daemon-auth.ts
1471
- import crypto2 from "crypto";
1472
- import path6 from "path";
1473
- import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
942
+ import crypto from "crypto";
943
+ import path4 from "path";
944
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1474
945
  function normalizeToken(token) {
1475
946
  if (!token) return null;
1476
947
  const trimmed = token.trim();
@@ -1478,8 +949,8 @@ function normalizeToken(token) {
1478
949
  }
1479
950
  function readDaemonToken() {
1480
951
  try {
1481
- if (!existsSync6(DAEMON_TOKEN_PATH)) return null;
1482
- return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
952
+ if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
953
+ return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
1483
954
  } catch {
1484
955
  return null;
1485
956
  }
@@ -1487,9 +958,9 @@ function readDaemonToken() {
1487
958
  function ensureDaemonToken(seed) {
1488
959
  const existing = readDaemonToken();
1489
960
  if (existing) return existing;
1490
- const token = normalizeToken(seed) ?? crypto2.randomBytes(32).toString("hex");
961
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1491
962
  ensurePrivateDirSync(EXE_AI_DIR);
1492
- writeFileSync3(DAEMON_TOKEN_PATH, `${token}
963
+ writeFileSync2(DAEMON_TOKEN_PATH, `${token}
1493
964
  `, "utf8");
1494
965
  enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1495
966
  return token;
@@ -1500,18 +971,18 @@ var init_daemon_auth = __esm({
1500
971
  "use strict";
1501
972
  init_config();
1502
973
  init_secure_files();
1503
- DAEMON_TOKEN_PATH = path6.join(EXE_AI_DIR, "exed.token");
974
+ DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
1504
975
  }
1505
976
  });
1506
977
 
1507
978
  // src/lib/exe-daemon-client.ts
1508
979
  import net from "net";
1509
- import os5 from "os";
980
+ import os4 from "os";
1510
981
  import { spawn } from "child_process";
1511
982
  import { randomUUID } from "crypto";
1512
- import { existsSync as existsSync7, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
1513
- import path7 from "path";
1514
- import { fileURLToPath as fileURLToPath2 } from "url";
983
+ import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
984
+ import path5 from "path";
985
+ import { fileURLToPath } from "url";
1515
986
  function handleData(chunk) {
1516
987
  _buffer += chunk.toString();
1517
988
  if (_buffer.length > MAX_BUFFER) {
@@ -1538,9 +1009,9 @@ function handleData(chunk) {
1538
1009
  }
1539
1010
  }
1540
1011
  function cleanupStaleFiles() {
1541
- if (existsSync7(PID_PATH)) {
1012
+ if (existsSync5(PID_PATH)) {
1542
1013
  try {
1543
- const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1014
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1544
1015
  if (pid > 0) {
1545
1016
  try {
1546
1017
  process.kill(pid, 0);
@@ -1561,11 +1032,11 @@ function cleanupStaleFiles() {
1561
1032
  }
1562
1033
  }
1563
1034
  function findPackageRoot() {
1564
- let dir = path7.dirname(fileURLToPath2(import.meta.url));
1565
- const { root } = path7.parse(dir);
1035
+ let dir = path5.dirname(fileURLToPath(import.meta.url));
1036
+ const { root } = path5.parse(dir);
1566
1037
  while (dir !== root) {
1567
- if (existsSync7(path7.join(dir, "package.json"))) return dir;
1568
- dir = path7.dirname(dir);
1038
+ if (existsSync5(path5.join(dir, "package.json"))) return dir;
1039
+ dir = path5.dirname(dir);
1569
1040
  }
1570
1041
  return null;
1571
1042
  }
@@ -1585,14 +1056,14 @@ function getAvailableMemoryGB() {
1585
1056
  const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
1586
1057
  return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
1587
1058
  } catch {
1588
- return os5.freemem() / (1024 * 1024 * 1024);
1059
+ return os4.freemem() / (1024 * 1024 * 1024);
1589
1060
  }
1590
1061
  }
1591
- return os5.freemem() / (1024 * 1024 * 1024);
1062
+ return os4.freemem() / (1024 * 1024 * 1024);
1592
1063
  }
1593
1064
  function spawnDaemon() {
1594
1065
  const freeGB = getAvailableMemoryGB();
1595
- const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
1066
+ const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
1596
1067
  if (totalGB <= 8) {
1597
1068
  process.stderr.write(
1598
1069
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -1612,8 +1083,8 @@ function spawnDaemon() {
1612
1083
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1613
1084
  return;
1614
1085
  }
1615
- const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1616
- if (!existsSync7(daemonPath)) {
1086
+ const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1087
+ if (!existsSync5(daemonPath)) {
1617
1088
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1618
1089
  `);
1619
1090
  return;
@@ -1622,7 +1093,7 @@ function spawnDaemon() {
1622
1093
  const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1623
1094
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1624
1095
  `);
1625
- const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
1096
+ const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
1626
1097
  let stderrFd = "ignore";
1627
1098
  try {
1628
1099
  stderrFd = openSync(logPath, "a");
@@ -1772,9 +1243,9 @@ var init_exe_daemon_client = __esm({
1772
1243
  "use strict";
1773
1244
  init_config();
1774
1245
  init_daemon_auth();
1775
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
1776
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
1777
- SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
1246
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1247
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1248
+ SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
1778
1249
  SPAWN_LOCK_STALE_MS = 3e4;
1779
1250
  CONNECT_TIMEOUT_MS = 15e3;
1780
1251
  REQUEST_TIMEOUT_MS = 3e4;
@@ -2062,6 +1533,9 @@ function getClient() {
2062
1533
  if (_daemonClient && _daemonClient._isDaemonActive()) {
2063
1534
  return _daemonClient;
2064
1535
  }
1536
+ if (!_resilientClient) {
1537
+ return _adapterClient;
1538
+ }
2065
1539
  return _resilientClient;
2066
1540
  }
2067
1541
  async function initDaemonClient() {
@@ -3094,6 +2568,127 @@ async function ensureSchema() {
3094
2568
  VALUES (new.rowid, new.content, new.subject, new.predicate, new.object);
3095
2569
  END;
3096
2570
  `);
2571
+ await client.executeMultiple(`
2572
+ CREATE TABLE IF NOT EXISTS agent_sessions (
2573
+ id TEXT PRIMARY KEY,
2574
+ agent_id TEXT NOT NULL,
2575
+ project_name TEXT,
2576
+ started_at TEXT NOT NULL,
2577
+ last_event_at TEXT NOT NULL,
2578
+ event_count INTEGER NOT NULL DEFAULT 0,
2579
+ properties TEXT DEFAULT '{}'
2580
+ );
2581
+
2582
+ CREATE INDEX IF NOT EXISTS idx_agent_sessions_agent_time
2583
+ ON agent_sessions(agent_id, started_at);
2584
+
2585
+ CREATE TABLE IF NOT EXISTS agent_goals (
2586
+ id TEXT PRIMARY KEY,
2587
+ statement TEXT NOT NULL,
2588
+ owner_agent_id TEXT,
2589
+ project_name TEXT,
2590
+ status TEXT NOT NULL DEFAULT 'open',
2591
+ priority INTEGER NOT NULL DEFAULT 5,
2592
+ success_criteria TEXT,
2593
+ parent_goal_id TEXT,
2594
+ due_at TEXT,
2595
+ achieved_at TEXT,
2596
+ supersedes_id TEXT,
2597
+ created_at TEXT NOT NULL,
2598
+ updated_at TEXT NOT NULL,
2599
+ source_memory_id TEXT
2600
+ );
2601
+
2602
+ CREATE INDEX IF NOT EXISTS idx_agent_goals_project_status
2603
+ ON agent_goals(project_name, status, priority);
2604
+
2605
+ CREATE TABLE IF NOT EXISTS agent_events (
2606
+ id TEXT PRIMARY KEY,
2607
+ event_type TEXT NOT NULL,
2608
+ occurred_at TEXT NOT NULL,
2609
+ sequence_index INTEGER NOT NULL,
2610
+ actor_agent_id TEXT,
2611
+ agent_role TEXT,
2612
+ project_name TEXT,
2613
+ session_id TEXT,
2614
+ task_id TEXT,
2615
+ goal_id TEXT,
2616
+ parent_event_id TEXT,
2617
+ intention TEXT,
2618
+ outcome TEXT,
2619
+ evidence_memory_id TEXT,
2620
+ impact TEXT,
2621
+ payload TEXT DEFAULT '{}',
2622
+ created_at TEXT NOT NULL
2623
+ );
2624
+
2625
+ CREATE INDEX IF NOT EXISTS idx_agent_events_time
2626
+ ON agent_events(occurred_at, sequence_index);
2627
+
2628
+ CREATE INDEX IF NOT EXISTS idx_agent_events_session_seq
2629
+ ON agent_events(session_id, sequence_index);
2630
+
2631
+ CREATE INDEX IF NOT EXISTS idx_agent_events_goal_time
2632
+ ON agent_events(goal_id, occurred_at);
2633
+
2634
+ CREATE INDEX IF NOT EXISTS idx_agent_events_memory
2635
+ ON agent_events(evidence_memory_id);
2636
+
2637
+ CREATE TABLE IF NOT EXISTS agent_goal_links (
2638
+ id TEXT PRIMARY KEY,
2639
+ goal_id TEXT NOT NULL,
2640
+ link_type TEXT NOT NULL,
2641
+ target_id TEXT NOT NULL,
2642
+ target_type TEXT NOT NULL,
2643
+ created_at TEXT NOT NULL
2644
+ );
2645
+
2646
+ CREATE INDEX IF NOT EXISTS idx_agent_goal_links_goal
2647
+ ON agent_goal_links(goal_id, target_type);
2648
+
2649
+ CREATE TABLE IF NOT EXISTS agent_semantic_labels (
2650
+ id TEXT PRIMARY KEY,
2651
+ source_memory_id TEXT NOT NULL,
2652
+ event_id TEXT,
2653
+ labeler TEXT NOT NULL,
2654
+ schema_version INTEGER NOT NULL DEFAULT 1,
2655
+ confidence REAL NOT NULL DEFAULT 0,
2656
+ labels TEXT NOT NULL,
2657
+ created_at TEXT NOT NULL,
2658
+ updated_at TEXT NOT NULL
2659
+ );
2660
+
2661
+ CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_memory
2662
+ ON agent_semantic_labels(source_memory_id, labeler);
2663
+
2664
+ CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_event
2665
+ ON agent_semantic_labels(event_id);
2666
+
2667
+ CREATE TABLE IF NOT EXISTS agent_reflection_checkpoints (
2668
+ id TEXT PRIMARY KEY,
2669
+ project_name TEXT,
2670
+ session_id TEXT,
2671
+ window_start_at TEXT NOT NULL,
2672
+ window_end_at TEXT NOT NULL,
2673
+ event_count INTEGER NOT NULL DEFAULT 0,
2674
+ goal_count INTEGER NOT NULL DEFAULT 0,
2675
+ success_count INTEGER NOT NULL DEFAULT 0,
2676
+ failure_count INTEGER NOT NULL DEFAULT 0,
2677
+ risk_count INTEGER NOT NULL DEFAULT 0,
2678
+ summary TEXT NOT NULL,
2679
+ learnings TEXT NOT NULL DEFAULT '[]',
2680
+ next_actions TEXT NOT NULL DEFAULT '[]',
2681
+ evidence_event_ids TEXT NOT NULL DEFAULT '[]',
2682
+ confidence REAL NOT NULL DEFAULT 0,
2683
+ created_at TEXT NOT NULL
2684
+ );
2685
+
2686
+ CREATE INDEX IF NOT EXISTS idx_agent_reflection_project_time
2687
+ ON agent_reflection_checkpoints(project_name, window_end_at);
2688
+
2689
+ CREATE INDEX IF NOT EXISTS idx_agent_reflection_session_time
2690
+ ON agent_reflection_checkpoints(session_id, window_end_at);
2691
+ `);
3097
2692
  try {
3098
2693
  await client.execute({
3099
2694
  sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
@@ -3241,1777 +2836,1430 @@ var init_database = __esm({
3241
2836
  }
3242
2837
  });
3243
2838
 
3244
- // src/lib/compress.ts
3245
- import { brotliCompressSync, brotliDecompressSync, constants } from "zlib";
3246
- function compress(input) {
3247
- if (input.length === 0) return Buffer.alloc(0);
3248
- return brotliCompressSync(input, {
3249
- params: {
3250
- [constants.BROTLI_PARAM_QUALITY]: 4
3251
- }
2839
+ // src/lib/shard-manager.ts
2840
+ var shard_manager_exports = {};
2841
+ __export(shard_manager_exports, {
2842
+ auditShardHealth: () => auditShardHealth,
2843
+ disposeShards: () => disposeShards,
2844
+ ensureShardSchema: () => ensureShardSchema,
2845
+ getOpenShardCount: () => getOpenShardCount,
2846
+ getReadyShardClient: () => getReadyShardClient,
2847
+ getShardClient: () => getShardClient,
2848
+ getShardsDir: () => getShardsDir,
2849
+ initShardManager: () => initShardManager,
2850
+ isShardingEnabled: () => isShardingEnabled,
2851
+ listShards: () => listShards,
2852
+ shardExists: () => shardExists
2853
+ });
2854
+ import path7 from "path";
2855
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync, renameSync as renameSync3, statSync as statSync3 } from "fs";
2856
+ import { createClient as createClient2 } from "@libsql/client";
2857
+ function initShardManager(encryptionKey) {
2858
+ _encryptionKey = encryptionKey;
2859
+ if (!existsSync7(SHARDS_DIR)) {
2860
+ mkdirSync2(SHARDS_DIR, { recursive: true });
2861
+ }
2862
+ _shardingEnabled = true;
2863
+ if (_evictionTimer) clearInterval(_evictionTimer);
2864
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
2865
+ _evictionTimer.unref();
2866
+ }
2867
+ function isShardingEnabled() {
2868
+ return _shardingEnabled;
2869
+ }
2870
+ function getShardsDir() {
2871
+ return SHARDS_DIR;
2872
+ }
2873
+ function getShardClient(projectName) {
2874
+ if (!_encryptionKey) {
2875
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
2876
+ }
2877
+ const safeName = safeShardName(projectName);
2878
+ if (!safeName || safeName === "unknown") {
2879
+ throw new Error(`Invalid project name for shard: "${projectName}" (resolved to "${safeName}")`);
2880
+ }
2881
+ const cached = _shards.get(safeName);
2882
+ if (cached) {
2883
+ _shardLastAccess.set(safeName, Date.now());
2884
+ return cached;
2885
+ }
2886
+ while (_shards.size >= MAX_OPEN_SHARDS) {
2887
+ evictLRU();
2888
+ }
2889
+ const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
2890
+ const client = createClient2({
2891
+ url: `file:${dbPath}`,
2892
+ encryptionKey: _encryptionKey
3252
2893
  });
2894
+ _shards.set(safeName, client);
2895
+ _shardLastAccess.set(safeName, Date.now());
2896
+ return client;
3253
2897
  }
3254
- function decompress(input) {
3255
- if (input.length === 0) return Buffer.alloc(0);
3256
- return brotliDecompressSync(input);
2898
+ function shardExists(projectName) {
2899
+ const safeName = safeShardName(projectName);
2900
+ return existsSync7(path7.join(SHARDS_DIR, `${safeName}.db`));
2901
+ }
2902
+ function safeShardName(projectName) {
2903
+ return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2904
+ }
2905
+ function listShards() {
2906
+ if (!existsSync7(SHARDS_DIR)) return [];
2907
+ return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2908
+ }
2909
+ async function auditShardHealth(options = {}) {
2910
+ if (!_encryptionKey) {
2911
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
2912
+ }
2913
+ const repair = options.repair === true;
2914
+ const dryRun = options.dryRun === true;
2915
+ const names = listShards();
2916
+ const shards = [];
2917
+ for (const name of names) {
2918
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
2919
+ const stat = statSync3(dbPath);
2920
+ const item = {
2921
+ name,
2922
+ path: dbPath,
2923
+ ok: false,
2924
+ unreadable: false,
2925
+ error: null,
2926
+ size: stat.size,
2927
+ mtime: stat.mtime.toISOString(),
2928
+ memoryCount: null
2929
+ };
2930
+ const client = createClient2({
2931
+ url: `file:${dbPath}`,
2932
+ encryptionKey: _encryptionKey
2933
+ });
2934
+ try {
2935
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
2936
+ const hasMemories = await client.execute(
2937
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
2938
+ );
2939
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
2940
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
2941
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
2942
+ }
2943
+ item.ok = true;
2944
+ } catch (err) {
2945
+ const message = err instanceof Error ? err.message : String(err);
2946
+ item.error = message;
2947
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
2948
+ if (item.unreadable && repair && !dryRun) {
2949
+ client.close();
2950
+ _shards.delete(name);
2951
+ _shardLastAccess.delete(name);
2952
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2953
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
2954
+ renameSync3(dbPath, archivedPath);
2955
+ item.archivedPath = archivedPath;
2956
+ }
2957
+ } finally {
2958
+ try {
2959
+ client.close();
2960
+ } catch {
2961
+ }
2962
+ }
2963
+ shards.push(item);
2964
+ }
2965
+ return {
2966
+ total: shards.length,
2967
+ ok: shards.filter((s) => s.ok).length,
2968
+ unreadable: shards.filter((s) => s.unreadable).length,
2969
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
2970
+ shards
2971
+ };
3257
2972
  }
3258
- var init_compress = __esm({
3259
- "src/lib/compress.ts"() {
3260
- "use strict";
2973
+ async function ensureShardSchema(client) {
2974
+ await client.execute("PRAGMA journal_mode = WAL");
2975
+ await client.execute("PRAGMA busy_timeout = 30000");
2976
+ try {
2977
+ await client.execute("PRAGMA libsql_vector_search_ef = 128");
2978
+ } catch {
3261
2979
  }
3262
- });
2980
+ await client.executeMultiple(`
2981
+ CREATE TABLE IF NOT EXISTS memories (
2982
+ id TEXT PRIMARY KEY,
2983
+ agent_id TEXT NOT NULL,
2984
+ agent_role TEXT NOT NULL,
2985
+ session_id TEXT NOT NULL,
2986
+ timestamp TEXT NOT NULL,
2987
+ tool_name TEXT NOT NULL,
2988
+ project_name TEXT NOT NULL,
2989
+ has_error INTEGER NOT NULL DEFAULT 0,
2990
+ raw_text TEXT NOT NULL,
2991
+ vector F32_BLOB(1024),
2992
+ version INTEGER NOT NULL DEFAULT 0
2993
+ );
3263
2994
 
3264
- // src/lib/license.ts
3265
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
3266
- import { randomUUID as randomUUID2 } from "crypto";
3267
- import { createRequire as createRequire2 } from "module";
3268
- import { pathToFileURL as pathToFileURL2 } from "url";
3269
- import os6 from "os";
3270
- import path8 from "path";
3271
- import { jwtVerify, importSPKI } from "jose";
3272
- function loadDeviceId() {
3273
- const deviceJsonPath = path8.join(EXE_AI_DIR, "device.json");
3274
- try {
3275
- if (existsSync8(deviceJsonPath)) {
3276
- const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
3277
- if (data.deviceId) return data.deviceId;
2995
+ CREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);
2996
+ CREATE INDEX IF NOT EXISTS idx_memories_timestamp ON memories(timestamp);
2997
+ CREATE INDEX IF NOT EXISTS idx_memories_agent_project ON memories(agent_id, project_name);
2998
+ `);
2999
+ await client.executeMultiple(`
3000
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
3001
+ raw_text,
3002
+ content='memories',
3003
+ content_rowid='rowid'
3004
+ );
3005
+
3006
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
3007
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
3008
+ END;
3009
+
3010
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
3011
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
3012
+ END;
3013
+
3014
+ CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
3015
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
3016
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
3017
+ END;
3018
+ `);
3019
+ for (const col of [
3020
+ "ALTER TABLE memories ADD COLUMN task_id TEXT",
3021
+ "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
3022
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
3023
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
3024
+ "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
3025
+ "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
3026
+ "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
3027
+ "ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0",
3028
+ "ALTER TABLE memories ADD COLUMN content_hash TEXT",
3029
+ "ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT",
3030
+ "ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7",
3031
+ "ALTER TABLE memories ADD COLUMN last_accessed TEXT",
3032
+ // Wiki linkage columns (must match database.ts)
3033
+ "ALTER TABLE memories ADD COLUMN workspace_id TEXT",
3034
+ "ALTER TABLE memories ADD COLUMN document_id TEXT",
3035
+ "ALTER TABLE memories ADD COLUMN user_id TEXT",
3036
+ "ALTER TABLE memories ADD COLUMN char_offset INTEGER",
3037
+ "ALTER TABLE memories ADD COLUMN page_number INTEGER",
3038
+ // Source provenance columns (must match database.ts)
3039
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
3040
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
3041
+ "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
3042
+ "ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
3043
+ // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
3044
+ "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
3045
+ "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
3046
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
3047
+ // Metadata enrichment columns (must match database.ts)
3048
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
3049
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
3050
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
3051
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
3052
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
3053
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
3054
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
3055
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
3056
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
3057
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
3058
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
3059
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
3060
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
3061
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
3062
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT",
3063
+ "ALTER TABLE memories ADD COLUMN deleted_at TEXT"
3064
+ ]) {
3065
+ try {
3066
+ await client.execute(col);
3067
+ } catch {
3278
3068
  }
3279
- } catch {
3280
3069
  }
3281
- try {
3282
- if (existsSync8(DEVICE_ID_PATH)) {
3283
- const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
3284
- if (id2) return id2;
3070
+ for (const idx of [
3071
+ "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3072
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3073
+ "CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash ON memories(content_hash, agent_id, project_name, memory_type) WHERE content_hash IS NOT NULL"
3074
+ ]) {
3075
+ try {
3076
+ await client.execute(idx);
3077
+ } catch {
3285
3078
  }
3079
+ }
3080
+ try {
3081
+ await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
3286
3082
  } catch {
3287
3083
  }
3288
- const id = randomUUID2();
3289
- mkdirSync3(EXE_AI_DIR, { recursive: true });
3290
- writeFileSync4(DEVICE_ID_PATH, id, "utf8");
3291
- return id;
3292
- }
3293
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
3294
- var init_license = __esm({
3295
- "src/lib/license.ts"() {
3296
- "use strict";
3297
- init_config();
3298
- LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
3299
- CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
3300
- DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
3084
+ for (const idx of [
3085
+ "CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace_id)",
3086
+ "CREATE INDEX IF NOT EXISTS idx_memories_document ON memories(document_id)",
3087
+ "CREATE INDEX IF NOT EXISTS idx_memories_user ON memories(user_id)"
3088
+ ]) {
3089
+ try {
3090
+ await client.execute(idx);
3091
+ } catch {
3092
+ }
3301
3093
  }
3302
- });
3094
+ await client.executeMultiple(`
3095
+ CREATE TABLE IF NOT EXISTS entities (
3096
+ id TEXT PRIMARY KEY,
3097
+ name TEXT NOT NULL,
3098
+ type TEXT NOT NULL,
3099
+ first_seen TEXT NOT NULL,
3100
+ last_seen TEXT NOT NULL,
3101
+ properties TEXT DEFAULT '{}',
3102
+ UNIQUE(name, type)
3103
+ );
3303
3104
 
3304
- // src/lib/crdt-sync.ts
3305
- var crdt_sync_exports = {};
3306
- __export(crdt_sync_exports, {
3307
- _setStatePath: () => _setStatePath,
3308
- applyRemoteUpdate: () => applyRemoteUpdate,
3309
- destroyCrdtDoc: () => destroyCrdtDoc,
3310
- getDiffUpdate: () => getDiffUpdate,
3311
- getFullState: () => getFullState,
3312
- getStateVector: () => getStateVector,
3313
- importExistingBehaviors: () => importExistingBehaviors,
3314
- importExistingMemories: () => importExistingMemories,
3315
- initCrdtDoc: () => initCrdtDoc,
3316
- isCrdtSyncEnabled: () => isCrdtSyncEnabled,
3317
- onUpdate: () => onUpdate,
3318
- readAllBehaviors: () => readAllBehaviors,
3319
- readAllMemories: () => readAllMemories,
3320
- rebuildFromDb: () => rebuildFromDb
3321
- });
3322
- import * as Y from "yjs";
3323
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync9, mkdirSync as mkdirSync4, unlinkSync as unlinkSync3 } from "fs";
3324
- import path9 from "path";
3325
- import { homedir } from "os";
3326
- function getStatePath() {
3327
- return _statePathOverride ?? DEFAULT_STATE_PATH;
3328
- }
3329
- function _setStatePath(p) {
3330
- _statePathOverride = p;
3331
- }
3332
- function initCrdtDoc() {
3333
- if (doc) return doc;
3334
- doc = new Y.Doc();
3335
- const sp = getStatePath();
3336
- if (existsSync9(sp)) {
3105
+ CREATE TABLE IF NOT EXISTS relationships (
3106
+ id TEXT PRIMARY KEY,
3107
+ source_entity_id TEXT NOT NULL,
3108
+ target_entity_id TEXT NOT NULL,
3109
+ type TEXT NOT NULL,
3110
+ weight REAL DEFAULT 1.0,
3111
+ timestamp TEXT NOT NULL,
3112
+ properties TEXT DEFAULT '{}',
3113
+ UNIQUE(source_entity_id, target_entity_id, type)
3114
+ );
3115
+
3116
+ CREATE TABLE IF NOT EXISTS entity_memories (
3117
+ entity_id TEXT NOT NULL,
3118
+ memory_id TEXT NOT NULL,
3119
+ PRIMARY KEY (entity_id, memory_id)
3120
+ );
3121
+
3122
+ CREATE TABLE IF NOT EXISTS relationship_memories (
3123
+ relationship_id TEXT NOT NULL,
3124
+ memory_id TEXT NOT NULL,
3125
+ PRIMARY KEY (relationship_id, memory_id)
3126
+ );
3127
+
3128
+ CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
3129
+ CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
3130
+ CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
3131
+ CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
3132
+ CREATE INDEX IF NOT EXISTS idx_relationships_type ON relationships(type);
3133
+
3134
+ CREATE TABLE IF NOT EXISTS hyperedges (
3135
+ id TEXT PRIMARY KEY,
3136
+ label TEXT NOT NULL,
3137
+ relation TEXT NOT NULL,
3138
+ confidence REAL DEFAULT 1.0,
3139
+ timestamp TEXT NOT NULL
3140
+ );
3141
+
3142
+ CREATE TABLE IF NOT EXISTS hyperedge_nodes (
3143
+ hyperedge_id TEXT NOT NULL,
3144
+ entity_id TEXT NOT NULL,
3145
+ PRIMARY KEY (hyperedge_id, entity_id)
3146
+ );
3147
+ `);
3148
+ for (const col of [
3149
+ "ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
3150
+ "ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
3151
+ ]) {
3337
3152
  try {
3338
- const state = readFileSync7(sp);
3339
- Y.applyUpdate(doc, new Uint8Array(state));
3153
+ await client.execute(col);
3340
3154
  } catch {
3341
- console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
3342
- try {
3343
- unlinkSync3(sp);
3344
- } catch {
3345
- }
3346
- rebuildFromDb().catch((err) => {
3347
- console.warn("[crdt-sync] rebuild from DB failed:", err);
3348
- });
3349
3155
  }
3350
3156
  }
3351
- doc.on("update", () => {
3352
- persistState();
3353
- });
3354
- return doc;
3355
- }
3356
- function getMemoriesMap() {
3357
- const d = initCrdtDoc();
3358
- return d.getMap("memories");
3359
- }
3360
- function getBehaviorsMap() {
3361
- const d = initCrdtDoc();
3362
- return d.getMap("behaviors");
3363
- }
3364
- function applyRemoteUpdate(update) {
3365
- const d = initCrdtDoc();
3366
- Y.applyUpdate(d, update);
3367
- }
3368
- function getFullState() {
3369
- const d = initCrdtDoc();
3370
- return Y.encodeStateAsUpdate(d);
3371
- }
3372
- function getDiffUpdate(remoteStateVector) {
3373
- const d = initCrdtDoc();
3374
- return Y.encodeStateAsUpdate(d, remoteStateVector);
3375
- }
3376
- function getStateVector() {
3377
- const d = initCrdtDoc();
3378
- return Y.encodeStateVector(d);
3379
- }
3380
- function importExistingMemories(memories) {
3381
- const map = getMemoriesMap();
3382
- const d = initCrdtDoc();
3383
- let imported = 0;
3384
- d.transact(() => {
3385
- for (const mem of memories) {
3386
- if (!mem.id) continue;
3387
- if (map.has(mem.id)) continue;
3388
- const entry = new Y.Map();
3389
- entry.set("id", mem.id);
3390
- entry.set("agent_id", mem.agent_id ?? null);
3391
- entry.set("agent_role", mem.agent_role ?? null);
3392
- entry.set("session_id", mem.session_id ?? null);
3393
- entry.set("timestamp", mem.timestamp ?? null);
3394
- entry.set("tool_name", mem.tool_name ?? null);
3395
- entry.set("project_name", mem.project_name ?? null);
3396
- entry.set("has_error", mem.has_error ?? 0);
3397
- entry.set("raw_text", mem.raw_text ?? "");
3398
- entry.set("version", mem.version ?? 0);
3399
- entry.set("author_device_id", mem.author_device_id ?? null);
3400
- entry.set("scope", mem.scope ?? "business");
3401
- map.set(mem.id, entry);
3402
- imported++;
3157
+ }
3158
+ async function getReadyShardClient(projectName) {
3159
+ const safeName = safeShardName(projectName);
3160
+ let client = getShardClient(projectName);
3161
+ try {
3162
+ await ensureShardSchema(client);
3163
+ return client;
3164
+ } catch (err) {
3165
+ const message = err instanceof Error ? err.message : String(err);
3166
+ if (!/SQLITE_NOTADB|file is not a database/i.test(message)) throw err;
3167
+ client.close();
3168
+ _shards.delete(safeName);
3169
+ _shardLastAccess.delete(safeName);
3170
+ const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
3171
+ if (existsSync7(dbPath)) {
3172
+ const stat = statSync3(dbPath);
3173
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3174
+ const archivedPath = path7.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
3175
+ renameSync3(dbPath, archivedPath);
3176
+ process.stderr.write(
3177
+ `[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat.size} bytes, mtime ${stat.mtime.toISOString()})
3178
+ `
3179
+ );
3403
3180
  }
3404
- });
3405
- return imported;
3406
- }
3407
- function importExistingBehaviors(behaviors) {
3408
- const map = getBehaviorsMap();
3409
- const d = initCrdtDoc();
3410
- let imported = 0;
3411
- d.transact(() => {
3412
- for (const beh of behaviors) {
3413
- if (!beh.id) continue;
3414
- if (map.has(beh.id)) continue;
3415
- const entry = new Y.Map();
3416
- entry.set("id", beh.id);
3417
- entry.set("agent_id", beh.agent_id ?? null);
3418
- entry.set("project_name", beh.project_name ?? null);
3419
- entry.set("domain", beh.domain ?? null);
3420
- entry.set("content", beh.content ?? null);
3421
- entry.set("active", beh.active ?? 1);
3422
- entry.set("priority", beh.priority ?? "p1");
3423
- entry.set("created_at", beh.created_at ?? null);
3424
- entry.set("updated_at", beh.updated_at ?? null);
3425
- map.set(beh.id, entry);
3426
- imported++;
3181
+ client = getShardClient(projectName);
3182
+ await ensureShardSchema(client);
3183
+ return client;
3184
+ }
3185
+ }
3186
+ function evictLRU() {
3187
+ let oldest = null;
3188
+ let oldestTime = Infinity;
3189
+ for (const [name, time] of _shardLastAccess) {
3190
+ if (time < oldestTime) {
3191
+ oldestTime = time;
3192
+ oldest = name;
3427
3193
  }
3194
+ }
3195
+ if (oldest) {
3196
+ const client = _shards.get(oldest);
3197
+ if (client) {
3198
+ client.close();
3199
+ }
3200
+ _shards.delete(oldest);
3201
+ _shardLastAccess.delete(oldest);
3202
+ }
3203
+ }
3204
+ function evictIdleShards() {
3205
+ const now = Date.now();
3206
+ const toEvict = [];
3207
+ for (const [name, lastAccess] of _shardLastAccess) {
3208
+ if (now - lastAccess > SHARD_IDLE_MS) {
3209
+ toEvict.push(name);
3210
+ }
3211
+ }
3212
+ for (const name of toEvict) {
3213
+ const client = _shards.get(name);
3214
+ if (client) {
3215
+ client.close();
3216
+ }
3217
+ _shards.delete(name);
3218
+ _shardLastAccess.delete(name);
3219
+ }
3220
+ }
3221
+ function getOpenShardCount() {
3222
+ return _shards.size;
3223
+ }
3224
+ function disposeShards() {
3225
+ if (_evictionTimer) {
3226
+ clearInterval(_evictionTimer);
3227
+ _evictionTimer = null;
3228
+ }
3229
+ for (const [, client] of _shards) {
3230
+ client.close();
3231
+ }
3232
+ _shards.clear();
3233
+ _shardLastAccess.clear();
3234
+ _shardingEnabled = false;
3235
+ _encryptionKey = null;
3236
+ }
3237
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
3238
+ var init_shard_manager = __esm({
3239
+ "src/lib/shard-manager.ts"() {
3240
+ "use strict";
3241
+ init_config();
3242
+ SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
3243
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
3244
+ MAX_OPEN_SHARDS = 10;
3245
+ EVICTION_INTERVAL_MS = 60 * 1e3;
3246
+ _shards = /* @__PURE__ */ new Map();
3247
+ _shardLastAccess = /* @__PURE__ */ new Map();
3248
+ _evictionTimer = null;
3249
+ _encryptionKey = null;
3250
+ _shardingEnabled = false;
3251
+ }
3252
+ });
3253
+
3254
+ // src/lib/platform-procedures.ts
3255
+ var PLATFORM_PROCEDURES, PLATFORM_PROCEDURE_TITLES;
3256
+ var init_platform_procedures = __esm({
3257
+ "src/lib/platform-procedures.ts"() {
3258
+ "use strict";
3259
+ PLATFORM_PROCEDURES = [
3260
+ // --- Foundation: what is exe-os ---
3261
+ {
3262
+ title: "What is exe-os \u2014 the operating model every agent must understand",
3263
+ domain: "architecture",
3264
+ priority: "p0",
3265
+ content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO, CTO, CMO, engineers, and content production specialists. Each agent has identity, expertise, and experience layers \u2014 persistent memory that makes them better over time. All data is local-first, E2EE, owned by the user. The MCP server is the ONLY data interface \u2014 never access the DB directly."
3266
+ },
3267
+ {
3268
+ title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
3269
+ domain: "architecture",
3270
+ priority: "p0",
3271
+ content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code, Codex, or OpenCode. The founder picks their default tool at setup. The COO manages employees in tmux sessions. Each coordinator session is a separate window/project. Employees run in their own tmux panes via create_task auto-spawn. The founder talks to the COO; the COO orchestrates the team. The tool is the shell, exe-os is the brain."
3272
+ },
3273
+ {
3274
+ title: "Sessions explained \u2014 coordinator session names and projects",
3275
+ domain: "architecture",
3276
+ priority: "p0",
3277
+ content: "Each coordinator session is an isolated project session. One might be exe-os development, another might be exe-wiki. Each session spawns its own employees using {employee}-{coordinatorSession}. Sessions share the same memory DB but tasks are scoped to the session that created them. A founder can run multiple projects simultaneously. Sessions never interfere with each other."
3278
+ },
3279
+ {
3280
+ title: "Runtime settings \u2014 COO can view and change tools per agent",
3281
+ domain: "workflow",
3282
+ priority: "p1",
3283
+ content: "exe-os supports three tools: Claude Code (Anthropic), Codex (OpenAI), and OpenCode (open source, 75+ providers). Each agent can use a different tool and model. COO uses set_agent_config MCP tool to view or change settings. Call with no args to show all agents. Call with agent_id + runtime + model to change. Users can also run `exe-os settings` from terminal for interactive arrow-key selection."
3284
+ },
3285
+ // --- Hierarchy and dispatch ---
3286
+ {
3287
+ title: "Chain of command \u2014 who talks to whom",
3288
+ domain: "workflow",
3289
+ priority: "p0",
3290
+ content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
3291
+ },
3292
+ {
3293
+ title: "Customer orchestration maturity \u2014 recommend, never trap",
3294
+ domain: "workflow",
3295
+ priority: "p1",
3296
+ content: "New customers start best in Phase 1: founder \u2194 coordinator/Chief of Staff, building company context. Suggest Phase 2 executives when domain work repeats; suggest Phase 3 parallel execution only when review/permission gates are ready. This is guidance, not a blocker: users may jump phases anytime. Never overwrite their phase, role titles, identities, or custom org design."
3297
+ },
3298
+ {
3299
+ title: "Single dispatch path \u2014 create_task only",
3300
+ domain: "workflow",
3301
+ priority: "p0",
3302
+ content: "create_task is the ONLY way to dispatch work to another agent. No direct ensureEmployee calls, no manual tmux spawns, no send_message for actionable work. create_task \u2192 system auto-spawns \u2192 session correctly named. ONE PATH. No backdoors. No exceptions."
3303
+ },
3304
+ // --- Session isolation ---
3305
+ {
3306
+ title: "Session scoping \u2014 stay in your coordinator boundary",
3307
+ domain: "security",
3308
+ priority: "p0",
3309
+ content: "Session scoping is mandatory. Managers dispatch to workers within their own coordinator session ONLY. Employee sessions use {employee}-{coordinatorSession}. Cross-session dispatch is blocked by the system. Verify session names before dispatch. Tasks are scoped to the creating coordinator session."
3310
+ },
3311
+ {
3312
+ title: "Session isolation \u2014 never touch another session's work",
3313
+ domain: "workflow",
3314
+ priority: "p0",
3315
+ content: "Sessions are isolated. A coordinator session owns ONLY tasks it dispatched. (1) Never close/update/cancel tasks from another coordinator session. (2) Never review work from a different session \u2014 report that it belongs to another session and skip. (3) Ignore other sessions' items in list_tasks results. (4) Employees inherit session: employee sessions work ONLY on their parent coordinator session's tasks. Cross-session work is a system violation."
3316
+ },
3317
+ // --- Engineering: session scoping in code ---
3318
+ {
3319
+ title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
3320
+ domain: "architecture",
3321
+ priority: "p0",
3322
+ content: "Every DB query, notification, review count, and task operation MUST be scoped on 3 dimensions: (1) Session \u2014 filter by session_scope matching the current coordinator session. (2) Project \u2014 filter by project_name. (3) Role \u2014 agents only see data at their hierarchy level. When writing ANY function that touches tasks, reviews, messages, or notifications: always accept a sessionScope parameter and pass it to the SQL WHERE clause. Unscoped queries are bugs. Test by running 2+ coordinator sessions simultaneously."
3323
+ },
3324
+ // --- Hard constraints ---
3325
+ {
3326
+ title: "What you CANNOT do in exe-os \u2014 hard constraints",
3327
+ domain: "security",
3328
+ priority: "p0",
3329
+ content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 the COO reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
3330
+ },
3331
+ {
3332
+ title: "Customer patch triage \u2014 upstream bug vs customization",
3333
+ domain: "support",
3334
+ priority: "p0",
3335
+ content: "When an agent encounters a suspected Exe OS bug, update breakage, MCP/tool failure, installer issue, memory/orchestration defect, or customer-local patch need, it MUST use create_bug_report. Do this before or alongside any local workaround so the report reaches AskExe support directly via the customer's license. Classify first: upstream_bug = reproducible exe-os/platform defect; customer_customization = identity, behavior, procedure, config, branding, workflow preference that belongs in customer-owned layers; emergency_hotfix = temporary local patch. For upstream bugs/emergency hotfixes include version, repro steps, expected/actual, files changed, workaround, and local diff summary. Avoid permanent platform-code patches unless founder approves; if a hotfix is unavoidable, document it in the bug report and re-check after npm update."
3336
+ },
3337
+ // --- Operations ---
3338
+ {
3339
+ title: "Managers must supervise deployed workers",
3340
+ domain: "workflow",
3341
+ priority: "p0",
3342
+ content: `Every manager (COO/CTO/CMO) who dispatches work to a worker MUST actively monitor them. Check tmux capture-pane every 10 minutes. Verify they're working, not stuck. If idle at prompt with in_progress task \u2192 send intercom. If stuck \u2192 unblock or escalate. "Standing by" without checking is negligence.`
3343
+ },
3344
+ {
3345
+ title: "COO boot health check \u2014 memory, cloud sync, daemon on every launch",
3346
+ domain: "workflow",
3347
+ priority: "p0",
3348
+ content: "On every /exe boot, COO MUST check system health BEFORE other work: (1) daemon \u2014 is exed PID alive, (2) cloud sync \u2014 grep workers.log for recent cloud-sync errors, (3) memory count \u2014 total in DB, (4) sync delta \u2014 local vs cloud storage_bytes. Report as 4-line status table. If ANY check fails, surface to founder immediately. Do not proceed to tasks until health confirmed."
3349
+ },
3350
+ {
3351
+ title: "exe-build-adv mandatory for 3+ files",
3352
+ domain: "workflow",
3353
+ priority: "p0",
3354
+ content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
3355
+ },
3356
+ {
3357
+ title: "Commit discipline \u2014 never leave verified work floating",
3358
+ domain: "workflow",
3359
+ priority: "p1",
3360
+ content: "After any code-change batch passes typecheck/tests/build, run git status, summarize changed files, and commit with a clear message before ending the session. If work must remain uncommitted for review/dogfood, explicitly say so, list the files, and state the blocker. Never imply work is complete while verified changes are still floating locally."
3361
+ },
3362
+ {
3363
+ title: "Desktop and TUI are the same product",
3364
+ domain: "architecture",
3365
+ priority: "p0",
3366
+ content: "Desktop and TUI are the SAME product in different renderers. Same data contracts, same interactions, same acceptance criteria. Desktop tab specs in ARCHITECTURE.md ARE the TUI specs. When building TUI, cross-reference Desktop spec. Different tab names, identical behavior. Never treat them as separate products."
3367
+ },
3368
+ // --- Orchestration golden path ---
3369
+ {
3370
+ title: "Task lifecycle \u2014 the golden path every agent follows",
3371
+ domain: "workflow",
3372
+ priority: "p0",
3373
+ content: "create_task is dispatch + delivery. Task lifecycle: open \u2192 in_progress (you start) \u2192 done (update_task when finished) \u2192 needs_review (reviewer nudged) \u2192 closed (COO only via close_task). DB is the reliable delivery \u2014 intercom is just a speedup nudge. If you finish a task, self-chain: check for next task immediately (step 7). Never wait for a nudge. Never say 'standing by.'"
3374
+ },
3375
+ {
3376
+ title: "Intercom is a speedup, not delivery \u2014 DB is the source of truth",
3377
+ domain: "architecture",
3378
+ priority: "p0",
3379
+ content: "Tasks live in the DB. Intercom (tmux send-keys) is fire-and-forget \u2014 it may fail, get garbled, or arrive mid-work. Never rely on intercom for task delivery. The UserPromptSubmit hook checks the DB for new tasks on every prompt. Your operating procedures step 7 says check for next work. The daemon nudges idle agents as a speedup. If you have no tasks, you found them all."
3380
+ },
3381
+ // --- MCP is the ONLY data interface ---
3382
+ {
3383
+ title: "MCP disconnect \u2014 ask the user, never work around it",
3384
+ domain: "workflow",
3385
+ priority: "p0",
3386
+ content: "If MCP tools are unavailable, disconnected, or returning connection errors: STOP. Tell the user clearly: 'MCP server is disconnected. Please run /mcp to reconnect.' Do NOT attempt workarounds \u2014 no raw Node imports, no direct DB access, no CLI hacks, no daemon socket calls. MCP is the ONLY data interface. Working around it wastes time, hits bundling issues, and bypasses the contract boundary. Ask once, wait, proceed when reconnected."
3387
+ },
3388
+ // --- MCP Tool Catalog (Layer 0 — every agent knows what tools exist) ---
3389
+ {
3390
+ title: "MCP tools \u2014 memory and search",
3391
+ domain: "tool-use",
3392
+ priority: "p1",
3393
+ content: "recall_my_memory: search your own memories (semantic + FTS). ask_team_memory: search a colleague's memories by agent name. store_memory: persist a memory (decisions, summaries, context). commit_memory: high-importance memory that survives consolidation. search_everything: unified search across memories, tasks, entities, conversations. get_session_context: temporal window of memories around a timestamp. consolidate_memories: merge duplicate/related memories into insights. get_memory_cardinality: count memories per agent (health check)."
3394
+ },
3395
+ {
3396
+ title: "MCP tools \u2014 task orchestration",
3397
+ domain: "tool-use",
3398
+ priority: "p1",
3399
+ content: "create_task: dispatch work to an employee (auto-spawns session). The ONLY dispatch path. list_tasks: query tasks by status, assignee, project. get_task: fetch full task details by ID. update_task: change status (in_progress, done, blocked, cancelled) + add result summary. close_task: finalize a reviewed task (COO only). checkpoint_task: save progress state for crash recovery. resume_employee: re-spawn an employee session for an existing task."
3400
+ },
3401
+ {
3402
+ title: "MCP tools \u2014 knowledge graph (GraphRAG)",
3403
+ domain: "tool-use",
3404
+ priority: "p1",
3405
+ content: "query_relationships: find connections between entities in the knowledge graph. get_entity_neighbors: explore an entity's direct connections. get_hot_entities: find most-referenced entities (trending topics). get_graph_stats: graph health \u2014 entity/relationship counts, density. export_graph: export graph data for visualization. merge_entities: deduplicate entities (alias resolution). find_similar_trajectories: match tool-call patterns to past task solutions."
3406
+ },
3407
+ {
3408
+ title: "MCP tools \u2014 identity, behavior, and decisions",
3409
+ domain: "tool-use",
3410
+ priority: "p1",
3411
+ content: "get_identity: read an agent's exe.md (Layer 1 identity). update_identity: write an agent's exe.md. Identity > behavior \u2014 use for permanent rules. store_behavior: record a correction or pattern for an agent (Layer 2 expertise). list_behaviors: view an agent's active behaviors. deactivate_behavior: soft-delete a stale or conflicting behavior. store_decision: record an ADR (architectural decision record). get_decision: retrieve a past decision by query. create_bug_report: customer-facing bug/support intake; use whenever an Exe OS bug or emergency hotfix is encountered so the report reaches AskExe directly. Customers only get report access; internal list/get/triage support tools are AskExe-only."
3412
+ },
3413
+ {
3414
+ title: "MCP tools \u2014 communication and messaging",
3415
+ domain: "tool-use",
3416
+ priority: "p1",
3417
+ content: "send_message: send supplementary context to another agent (NOT for actionable work \u2014 use create_task). acknowledge_messages: mark messages as read. send_whatsapp: send WhatsApp message via gateway (customer-facing alerts). query_conversations: search ingested conversations across all channels (WhatsApp, email, etc.)."
3418
+ },
3419
+ {
3420
+ title: "MCP tools \u2014 wiki, documents, and content",
3421
+ domain: "tool-use",
3422
+ priority: "p1",
3423
+ content: "wiki: read/list wiki pages only. Direct wiki write tools are removed; wiki updates flow through raw-data ingestion/projection into the curated wiki store. Legacy aliases: list_wiki_pages/get_wiki_page. crm: read/list/get CRM records from exe-db. raw_data: read capped raw landing-pad events from exe-db with payload opt-in. ingest_document: import a file (PDF, MD, etc.) into memory as chunks. list_documents: browse ingested documents by workspace. purge_document: remove a document and its memory chunks. set_document_importance: adjust chunk importance scores. rerank_documents: re-score document relevance for a query."
3424
+ },
3425
+ {
3426
+ title: "MCP tools \u2014 system, operations, and admin",
3427
+ domain: "tool-use",
3428
+ priority: "p1",
3429
+ content: "get_agent_spend: token usage per agent/session (cost tracking). list_agent_sessions: view agent session history. get_session_kills: audit log of killed sessions. get_daemon_health: check exed daemon status. get_auto_wake_status: daemon auto-wake configuration. get_worker_gate: check worker deployment gates. run_memory_audit: health check \u2014 duplicates, null vectors, orphaned rows. run_consolidation: trigger sleep-time memory consolidation. cloud_sync: force a cloud sync cycle. backup_vps: trigger VPS backup."
3430
+ },
3431
+ {
3432
+ title: "MCP tools \u2014 config, licensing, and team",
3433
+ domain: "tool-use",
3434
+ priority: "p1",
3435
+ content: "set_agent_config: view/change per-agent runtime and model settings. list_employees: view the employee roster. add_person: add a person to the CRM contacts roster. list_people: browse CRM contacts. get_person: fetch contact details. get_license_status: check license validity. create_license: generate a new license key (admin). list_licenses: view all issued licenses. activate_license: activate a license on a device."
3436
+ },
3437
+ {
3438
+ title: "MCP tools \u2014 advanced (triggers, skills, orchestration)",
3439
+ domain: "tool-use",
3440
+ priority: "p1",
3441
+ content: "create_trigger: set up a scheduled recurring agent job (cron). list_triggers: view active triggers. load_skill: load a slash-command skill dynamically. apply_starter_pack: import a pre-built behavior + identity pack for a role. export_orchestration: export full org state (tasks, behaviors, identities) as portable JSON. import_orchestration: import org state into a new instance. deploy_client: deploy a customer client instance. query_company_brain: unified RAG query across all company knowledge. create_reminder: set a text reminder (shown in boot brief). list_reminders: view pending reminders. complete_reminder: mark a reminder done. company_procedure: manage customer-owned company procedures (Layer 0; actions: store, list, deactivate). Legacy aliases: global_procedure, store_global_procedure, list_global_procedures, deactivate_global_procedure."
3442
+ }
3443
+ ];
3444
+ PLATFORM_PROCEDURE_TITLES = new Set(
3445
+ PLATFORM_PROCEDURES.map((p) => p.title)
3446
+ );
3447
+ }
3448
+ });
3449
+
3450
+ // src/lib/global-procedures.ts
3451
+ var global_procedures_exports = {};
3452
+ __export(global_procedures_exports, {
3453
+ deactivateGlobalProcedure: () => deactivateGlobalProcedure,
3454
+ getGlobalProceduresBlock: () => getGlobalProceduresBlock,
3455
+ loadGlobalProcedures: () => loadGlobalProcedures,
3456
+ storeGlobalProcedure: () => storeGlobalProcedure
3457
+ });
3458
+ import { randomUUID as randomUUID2 } from "crypto";
3459
+ async function loadGlobalProcedures() {
3460
+ const client = getClient();
3461
+ const result = await client.execute({
3462
+ sql: "SELECT * FROM company_procedures WHERE active = 1 ORDER BY priority ASC, created_at ASC",
3463
+ args: []
3428
3464
  });
3429
- return imported;
3430
- }
3431
- function readAllMemories() {
3432
- const map = getMemoriesMap();
3433
- const records = [];
3434
- map.forEach((entry, id) => {
3435
- records.push({
3436
- id,
3437
- agent_id: entry.get("agent_id"),
3438
- agent_role: entry.get("agent_role"),
3439
- session_id: entry.get("session_id"),
3440
- timestamp: entry.get("timestamp"),
3441
- tool_name: entry.get("tool_name"),
3442
- project_name: entry.get("project_name"),
3443
- has_error: entry.get("has_error"),
3444
- raw_text: entry.get("raw_text"),
3445
- version: entry.get("version"),
3446
- author_device_id: entry.get("author_device_id"),
3447
- scope: entry.get("scope")
3448
- });
3449
- });
3450
- return records;
3451
- }
3452
- function readAllBehaviors() {
3453
- const map = getBehaviorsMap();
3454
- const records = [];
3455
- map.forEach((entry, id) => {
3456
- records.push({
3457
- id,
3458
- agent_id: entry.get("agent_id"),
3459
- project_name: entry.get("project_name"),
3460
- domain: entry.get("domain"),
3461
- content: entry.get("content"),
3462
- active: entry.get("active"),
3463
- priority: entry.get("priority"),
3464
- created_at: entry.get("created_at"),
3465
- updated_at: entry.get("updated_at")
3466
- });
3467
- });
3468
- return records;
3469
- }
3470
- function onUpdate(callback) {
3471
- const d = initCrdtDoc();
3472
- const handler = (update) => callback(update);
3473
- d.on("update", handler);
3474
- return () => d.off("update", handler);
3475
- }
3476
- function persistState() {
3477
- if (!doc) return;
3478
- try {
3479
- const sp = getStatePath();
3480
- const dir = path9.dirname(sp);
3481
- if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
3482
- const state = Y.encodeStateAsUpdate(doc);
3483
- writeFileSync5(sp, Buffer.from(state));
3484
- } catch {
3465
+ const allRows = result.rows;
3466
+ const customerOnly = allRows.filter((p) => !PLATFORM_PROCEDURE_TITLES.has(p.title));
3467
+ if (customerOnly.length > 0) {
3468
+ _customerCache = customerOnly.map((p) => `### ${p.title}
3469
+ ${p.content}`).join("\n\n");
3470
+ } else {
3471
+ _customerCache = "";
3485
3472
  }
3473
+ _cacheLoaded = true;
3474
+ return customerOnly;
3486
3475
  }
3487
- function isCrdtSyncEnabled() {
3488
- return process.env.EXE_CRDT_SYNC !== "0";
3476
+ function getGlobalProceduresBlock() {
3477
+ const sections = [];
3478
+ if (_platformCache) sections.push(_platformCache);
3479
+ if (_cacheLoaded && _customerCache) sections.push(_customerCache);
3480
+ if (sections.length === 0) return "";
3481
+ return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
3482
+
3483
+ ${sections.join("\n\n")}
3484
+ `;
3489
3485
  }
3490
- async function rebuildFromDb() {
3491
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3492
- const client = getClient2();
3493
- const result = await client.execute(
3494
- "SELECT id, agent_id, agent_role, session_id, timestamp, tool_name, project_name, has_error, raw_text, version, author_device_id, scope FROM memories"
3495
- );
3496
- const memories = result.rows.map((row) => ({
3497
- id: String(row.id),
3498
- agent_id: row.agent_id,
3499
- agent_role: row.agent_role,
3500
- session_id: row.session_id,
3501
- timestamp: row.timestamp,
3502
- tool_name: row.tool_name,
3503
- project_name: row.project_name,
3504
- has_error: row.has_error,
3505
- raw_text: row.raw_text,
3506
- version: row.version,
3507
- author_device_id: row.author_device_id,
3508
- scope: row.scope
3509
- }));
3510
- const count = importExistingMemories(memories);
3511
- persistState();
3512
- return count;
3486
+ async function storeGlobalProcedure(input) {
3487
+ const id = randomUUID2();
3488
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3489
+ const client = getClient();
3490
+ await client.execute({
3491
+ sql: `INSERT INTO company_procedures (id, title, content, priority, domain, active, created_at, updated_at)
3492
+ VALUES (?, ?, ?, ?, ?, 1, ?, ?)`,
3493
+ args: [id, input.title, input.content, input.priority ?? "p0", input.domain ?? null, now, now]
3494
+ });
3495
+ await loadGlobalProcedures();
3496
+ return id;
3513
3497
  }
3514
- function destroyCrdtDoc() {
3515
- if (doc) {
3516
- doc.destroy();
3517
- doc = null;
3518
- }
3498
+ async function deactivateGlobalProcedure(id) {
3499
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3500
+ const client = getClient();
3501
+ const result = await client.execute({
3502
+ sql: "UPDATE company_procedures SET active = 0, updated_at = ? WHERE id = ?",
3503
+ args: [now, id]
3504
+ });
3505
+ await loadGlobalProcedures();
3506
+ return result.rowsAffected > 0;
3519
3507
  }
3520
- var DEFAULT_STATE_PATH, _statePathOverride, doc;
3521
- var init_crdt_sync = __esm({
3522
- "src/lib/crdt-sync.ts"() {
3508
+ var _customerCache, _cacheLoaded, _platformCache;
3509
+ var init_global_procedures = __esm({
3510
+ "src/lib/global-procedures.ts"() {
3523
3511
  "use strict";
3524
- DEFAULT_STATE_PATH = path9.join(homedir(), ".exe-os", "crdt-state.bin");
3525
- _statePathOverride = null;
3526
- doc = null;
3512
+ init_database();
3513
+ init_platform_procedures();
3514
+ _customerCache = "";
3515
+ _cacheLoaded = false;
3516
+ _platformCache = PLATFORM_PROCEDURES.map((p) => `### ${p.title}
3517
+ ${p.content}`).join("\n\n");
3527
3518
  }
3528
3519
  });
3529
3520
 
3530
- // src/lib/db-backup.ts
3531
- var db_backup_exports = {};
3532
- __export(db_backup_exports, {
3533
- createBackup: () => createBackup,
3534
- findActiveDb: () => findActiveDb,
3535
- getBackupDir: () => getBackupDir,
3536
- getLatestBackup: () => getLatestBackup,
3537
- hasBackupToday: () => hasBackupToday,
3538
- listBackups: () => listBackups,
3539
- rotateBackups: () => rotateBackups
3540
- });
3541
- import { copyFileSync, existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync, unlinkSync as unlinkSync4, statSync as statSync2 } from "fs";
3542
- import path10 from "path";
3543
- function findActiveDb() {
3544
- for (const name of DB_NAMES) {
3545
- const p = path10.join(EXE_AI_DIR, name);
3546
- if (existsSync10(p)) return p;
3521
+ // src/lib/agentic-ontology.ts
3522
+ import { createHash as createHash2 } from "crypto";
3523
+ function stableId(...parts) {
3524
+ return createHash2("sha256").update(parts.map((p) => String(p ?? "")).join("::")).digest("hex").slice(0, 32);
3525
+ }
3526
+ function clean(text, max = 240) {
3527
+ return text.replace(/\u0000/g, "").replace(/```[\s\S]*?```/g, " ").replace(/\s+/g, " ").trim().slice(0, max);
3528
+ }
3529
+ function inferOntologyEventType(row) {
3530
+ const lower = row.raw_text.toLowerCase();
3531
+ if (row.has_error) return "error";
3532
+ if (/\b(done|complete|completed|fixed|resolved|shipped|deployed|pushed|published)\b/.test(lower)) return "milestone";
3533
+ if (/\b(blocked|failed|error|bug|regression|broken)\b/.test(lower)) return "problem";
3534
+ if (/\b(decided|decision|adr|we chose|approved|rejected)\b/.test(lower)) return "decision";
3535
+ if (/\b(goal|need to|we need|want to|trying to|objective)\b/.test(lower)) return "goal_signal";
3536
+ if (["Bash", "Read", "Edit", "Write", "Grep", "Glob"].includes(row.tool_name)) return "tool_action";
3537
+ if (row.tool_name.startsWith("memory_card")) return "memory_card";
3538
+ return "memory_observation";
3539
+ }
3540
+ function inferIntention(row) {
3541
+ if (row.intent) return clean(row.intent, 220);
3542
+ const text = clean(row.raw_text, 1e3);
3543
+ const patterns = [
3544
+ /(?:we need to|need to|let'?s|i want to|we should|goal is to|objective is to|trying to)\s+([^.!?\n]{8,220})/i,
3545
+ /(?:so that|in order to)\s+([^.!?\n]{8,220})/i,
3546
+ /(?:task|plan):\s*([^.!?\n]{8,220})/i
3547
+ ];
3548
+ for (const p of patterns) {
3549
+ const m = text.match(p);
3550
+ if (m?.[1]) return clean(m[1], 220);
3551
+ }
3552
+ if (["Bash", "Read", "Edit", "Write", "Grep", "Glob"].includes(row.tool_name)) {
3553
+ return `${row.tool_name} during ${row.project_name}`;
3547
3554
  }
3548
3555
  return null;
3549
3556
  }
3550
- function createBackup(reason = "manual") {
3551
- const dbPath = findActiveDb();
3552
- if (!dbPath) return null;
3553
- mkdirSync5(BACKUP_DIR, { recursive: true });
3554
- const dbName = path10.basename(dbPath, ".db");
3555
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
3556
- const backupName = `${dbName}-${reason}-${timestamp}.db`;
3557
- const backupPath = path10.join(BACKUP_DIR, backupName);
3558
- copyFileSync(dbPath, backupPath);
3559
- const walPath = dbPath + "-wal";
3560
- if (existsSync10(walPath)) {
3561
- try {
3562
- copyFileSync(walPath, backupPath + "-wal");
3563
- } catch {
3564
- }
3565
- }
3566
- const shmPath = dbPath + "-shm";
3567
- if (existsSync10(shmPath)) {
3568
- try {
3569
- copyFileSync(shmPath, backupPath + "-shm");
3570
- } catch {
3571
- }
3572
- }
3573
- return backupPath;
3557
+ function inferOutcome(row) {
3558
+ if (row.outcome) return clean(row.outcome, 220);
3559
+ if (row.has_error) return "error";
3560
+ const lower = row.raw_text.toLowerCase();
3561
+ if (/\b(done|complete|completed|fixed|resolved|shipped|deployed|pushed|published|passed)\b/.test(lower)) return "success_signal";
3562
+ if (/\b(blocked|failed|error|regression|broken|not working|could not)\b/.test(lower)) return "failure_signal";
3563
+ if (/\b(warning|risk|concern|caveat)\b/.test(lower)) return "risk_signal";
3564
+ return null;
3574
3565
  }
3575
- function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
3576
- if (!existsSync10(BACKUP_DIR)) return 0;
3577
- const cutoff = Date.now() - keepDays * 24 * 60 * 60 * 1e3;
3578
- let deleted = 0;
3579
- try {
3580
- const files = readdirSync(BACKUP_DIR);
3581
- for (const file of files) {
3582
- if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
3583
- const filePath = path10.join(BACKUP_DIR, file);
3584
- try {
3585
- const stat = statSync2(filePath);
3586
- if (stat.mtimeMs < cutoff) {
3587
- unlinkSync4(filePath);
3588
- deleted++;
3589
- }
3590
- } catch {
3591
- }
3566
+ function extractGoalCandidates(row) {
3567
+ const text = clean(row.raw_text, 1600);
3568
+ const patterns = [
3569
+ /(?:we need to|need to|i want to|we should|goal is to|objective is to|trying to|let'?s)\s+([^.!?\n]{12,220})/gi,
3570
+ /(?:success means|success criteria|so that)\s+([^.!?\n]{12,220})/gi
3571
+ ];
3572
+ const out = [];
3573
+ for (const pattern of patterns) {
3574
+ for (const m of text.matchAll(pattern)) {
3575
+ const candidate = clean(m[1] ?? "", 220);
3576
+ if (candidate.length >= 12 && !out.some((x) => x.toLowerCase() === candidate.toLowerCase())) out.push(candidate);
3577
+ if (out.length >= 3) return out;
3592
3578
  }
3593
- } catch {
3594
3579
  }
3595
- return deleted;
3580
+ return out;
3596
3581
  }
3597
- function listBackups() {
3598
- if (!existsSync10(BACKUP_DIR)) return [];
3599
- try {
3600
- const files = readdirSync(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
3601
- return files.map((name) => {
3602
- const p = path10.join(BACKUP_DIR, name);
3603
- const stat = statSync2(p);
3604
- return { path: p, name, size: stat.size, date: stat.mtime };
3605
- }).sort((a, b) => b.date.getTime() - a.date.getTime());
3606
- } catch {
3607
- return [];
3582
+ function uniq(values, max = 6) {
3583
+ const out = [];
3584
+ for (const value of values.map((v) => clean(v, 220)).filter(Boolean)) {
3585
+ if (!out.some((x) => x.toLowerCase() === value.toLowerCase())) out.push(value);
3586
+ if (out.length >= max) break;
3608
3587
  }
3588
+ return out;
3609
3589
  }
3610
- function hasBackupToday(reason) {
3611
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3612
- const backups = listBackups();
3613
- return backups.some((b) => b.name.includes(reason) && b.name.includes(today.replace(/-/g, "-")));
3614
- }
3615
- function getLatestBackup() {
3616
- const backups = listBackups();
3617
- return backups.length > 0 ? backups[0].path : null;
3618
- }
3619
- function getBackupDir() {
3620
- return BACKUP_DIR;
3590
+ function extractMatches(text, patterns, max = 5) {
3591
+ const out = [];
3592
+ for (const pattern of patterns) {
3593
+ for (const match of text.matchAll(pattern)) {
3594
+ const value = match[1] ?? match[0];
3595
+ if (value) out.push(value);
3596
+ if (out.length >= max) return uniq(out, max);
3597
+ }
3598
+ }
3599
+ return uniq(out, max);
3600
+ }
3601
+ function inferSemanticLabel(row) {
3602
+ const text = clean(row.raw_text, 2400);
3603
+ const eventType = inferOntologyEventType(row);
3604
+ const intention = inferIntention(row);
3605
+ const outcome = inferOutcome(row);
3606
+ const goals = extractGoalCandidates(row);
3607
+ const milestones = extractMatches(text, [
3608
+ /\b(?:completed|finished|fixed|resolved|shipped|deployed|published|pushed|passed)\b([^.!?\n]{0,180})/gi,
3609
+ /(?:milestone|done):\s*([^.!?\n]{8,220})/gi
3610
+ ]);
3611
+ const problems = extractMatches(text, [
3612
+ /\b(?:blocked by|failed because|bug|regression|broken|not working|error)\b([^.!?\n]{0,180})/gi,
3613
+ /(?:problem|issue|risk):\s*([^.!?\n]{8,220})/gi
3614
+ ]);
3615
+ const decisions = extractMatches(text, [
3616
+ /(?:decided|decision|adr|we chose|approved|rejected)\s+([^.!?\n]{8,220})/gi
3617
+ ]);
3618
+ const temporalAnchors = extractMatches(text, [
3619
+ /\b(\d{4}-\d{2}-\d{2}(?:[T ][0-9:.+-Z]+)?)\b/g,
3620
+ /\b(today|yesterday|tomorrow|this week|next week|last week|morning|afternoon|tonight)\b/gi
3621
+ ], 8);
3622
+ const nextActions = extractMatches(text, [
3623
+ /(?:next|todo|follow[- ]?up|remaining|need to)\s*:?\s*([^.!?\n]{8,220})/gi
3624
+ ]);
3625
+ const actors = uniq([
3626
+ row.agent_id,
3627
+ ...extractMatches(text, [/\b(?:agent|employee|owner|assignee)[:= ]+([a-zA-Z][a-zA-Z0-9_-]{1,40})/gi], 5)
3628
+ ], 6);
3629
+ const successSignals = milestones.length ? milestones : outcome === "success_signal" ? [clean(text, 180)] : [];
3630
+ const failureSignals = problems.length ? problems : outcome === "failure_signal" || row.has_error ? [clean(text, 180)] : [];
3631
+ const impact = successSignals.length && failureSignals.length ? "mixed" : failureSignals.length ? "negative" : successSignals.length ? "positive" : "neutral";
3632
+ const signalCount = goals.length + milestones.length + problems.length + decisions.length + nextActions.length;
3633
+ return {
3634
+ labeler: "deterministic",
3635
+ schemaVersion: 1,
3636
+ eventType,
3637
+ intention,
3638
+ outcome,
3639
+ impact,
3640
+ confidence: Math.min(0.95, 0.45 + signalCount * 0.08 + (intention ? 0.1 : 0) + (outcome ? 0.1 : 0)),
3641
+ goals,
3642
+ milestones,
3643
+ problems,
3644
+ decisions,
3645
+ actors,
3646
+ temporalAnchors,
3647
+ successSignals,
3648
+ failureSignals,
3649
+ nextActions,
3650
+ summary: clean(text, 280)
3651
+ };
3621
3652
  }
3622
- var BACKUP_DIR, DEFAULT_KEEP_DAYS, DB_NAMES;
3623
- var init_db_backup = __esm({
3624
- "src/lib/db-backup.ts"() {
3653
+ var init_agentic_ontology = __esm({
3654
+ "src/lib/agentic-ontology.ts"() {
3625
3655
  "use strict";
3626
- init_config();
3627
- BACKUP_DIR = path10.join(EXE_AI_DIR, "backups");
3628
- DEFAULT_KEEP_DAYS = 3;
3629
- DB_NAMES = ["memories.db", "exe-mem.db", "exe-os.db", "exe.db"];
3630
3656
  }
3631
3657
  });
3632
3658
 
3633
- // src/lib/cloud-sync.ts
3634
- var cloud_sync_exports = {};
3635
- __export(cloud_sync_exports, {
3636
- CLOUD_RELINK_REQUIRED_MESSAGE: () => CLOUD_RELINK_REQUIRED_MESSAGE,
3637
- assertSecureEndpoint: () => assertSecureEndpoint,
3638
- buildRosterBlob: () => buildRosterBlob,
3639
- clearCloudRelinkRequired: () => clearCloudRelinkRequired,
3640
- cloudPull: () => cloudPull,
3641
- cloudPullBehaviors: () => cloudPullBehaviors,
3642
- cloudPullBlob: () => cloudPullBlob,
3643
- cloudPullConversations: () => cloudPullConversations,
3644
- cloudPullDocuments: () => cloudPullDocuments,
3645
- cloudPullGlobalProcedures: () => cloudPullGlobalProcedures,
3646
- cloudPullGraphRAG: () => cloudPullGraphRAG,
3647
- cloudPullRoster: () => cloudPullRoster,
3648
- cloudPullTasks: () => cloudPullTasks,
3649
- cloudPush: () => cloudPush,
3650
- cloudPushBehaviors: () => cloudPushBehaviors,
3651
- cloudPushBlob: () => cloudPushBlob,
3652
- cloudPushConversations: () => cloudPushConversations,
3653
- cloudPushDocuments: () => cloudPushDocuments,
3654
- cloudPushGlobalProcedures: () => cloudPushGlobalProcedures,
3655
- cloudPushGraphRAG: () => cloudPushGraphRAG,
3656
- cloudPushRoster: () => cloudPushRoster,
3657
- cloudPushTasks: () => cloudPushTasks,
3658
- cloudSync: () => cloudSync,
3659
- getCloudRelinkRequired: () => getCloudRelinkRequired,
3660
- mergeConfig: () => mergeConfig,
3661
- mergeRosterFromRemote: () => mergeRosterFromRemote,
3662
- pushToPostgres: () => pushToPostgres,
3663
- recordRosterDeletion: () => recordRosterDeletion
3664
- });
3665
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync11, readdirSync as readdirSync2, mkdirSync as mkdirSync6, appendFileSync, unlinkSync as unlinkSync5, openSync as openSync2, closeSync as closeSync2, statSync as statSync3 } from "fs";
3666
- import crypto3 from "crypto";
3667
- import path11 from "path";
3668
- import { homedir as homedir2 } from "os";
3669
- function sqlSafe(v) {
3670
- return v === void 0 ? null : v;
3671
- }
3672
- function logError(msg) {
3673
- try {
3674
- const logPath = path11.join(homedir2(), ".exe-os", "workers.log");
3675
- appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
3676
- `);
3677
- } catch {
3678
- }
3659
+ // src/lib/store.ts
3660
+ init_memory();
3661
+ init_database();
3662
+
3663
+ // src/lib/keychain.ts
3664
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3665
+ import { existsSync as existsSync6, statSync as statSync2 } from "fs";
3666
+ import { execSync as execSync2 } from "child_process";
3667
+ import path6 from "path";
3668
+ import os5 from "os";
3669
+ var SERVICE = "exe-os";
3670
+ var LEGACY_SERVICE = "exe-mem";
3671
+ var ACCOUNT = "master-key";
3672
+ function getKeyDir() {
3673
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
3674
+ }
3675
+ function getKeyPath() {
3676
+ return path6.join(getKeyDir(), "master.key");
3679
3677
  }
3680
- function isTruthyEnv(value) {
3681
- return /^(1|true|yes|on)$/i.test(value ?? "");
3678
+ function nativeKeychainAllowed() {
3679
+ return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
3682
3680
  }
3683
- function loadPgClient() {
3684
- if (_pgFailed) return null;
3685
- const configPath = path11.join(EXE_AI_DIR, "config.json");
3686
- let cloudPostgresUrl;
3687
- let configEnabled = false;
3681
+ var linuxSecretAvailability = null;
3682
+ function linuxSecretAvailable() {
3683
+ if (!nativeKeychainAllowed()) return false;
3684
+ if (process.platform !== "linux") return false;
3685
+ if (linuxSecretAvailability !== null) return linuxSecretAvailability;
3688
3686
  try {
3689
- if (existsSync11(configPath)) {
3690
- const cfg = JSON.parse(readFileSync8(configPath, "utf8"));
3691
- cloudPostgresUrl = cfg.cloud?.postgresUrl;
3692
- configEnabled = cfg.cloud?.syncToPostgres === true;
3693
- }
3687
+ execSync2("command -v secret-tool >/dev/null 2>&1", { timeout: 1e3 });
3694
3688
  } catch {
3689
+ linuxSecretAvailability = false;
3690
+ return false;
3695
3691
  }
3696
- const envEnabled = isTruthyEnv(process.env.EXE_CLOUD_SYNC_TO_POSTGRES);
3697
- if (!envEnabled && !configEnabled) {
3698
- return null;
3699
- }
3700
- const url = process.env.DATABASE_URL || cloudPostgresUrl;
3701
- if (!url) {
3702
- _pgFailed = true;
3703
- return null;
3704
- }
3705
- if (!_pgPromise) {
3706
- _pgPromise = (async () => {
3707
- if (!process.env.DATABASE_URL) process.env.DATABASE_URL = url;
3708
- const { createRequire: createRequire3 } = await import("module");
3709
- const { pathToFileURL: pathToFileURL3 } = await import("url");
3710
- const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
3711
- if (explicitPath) {
3712
- const mod2 = await import(pathToFileURL3(explicitPath).href);
3713
- const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
3714
- if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
3715
- return new Ctor2();
3716
- }
3717
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(homedir2(), "exe-db");
3718
- const req = createRequire3(path11.join(exeDbRoot, "package.json"));
3719
- const entry = req.resolve("@prisma/client");
3720
- const mod = await import(pathToFileURL3(entry).href);
3721
- const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
3722
- if (!Ctor) throw new Error("No PrismaClient");
3723
- return new Ctor();
3724
- })().catch(() => {
3725
- _pgFailed = true;
3726
- _pgPromise = null;
3727
- throw new Error("pg_unavailable");
3728
- });
3729
- }
3730
- return _pgPromise;
3731
- }
3732
- async function pushToPostgres(records) {
3733
- const loader = loadPgClient();
3734
- if (!loader) return 0;
3735
- let prisma;
3736
3692
  try {
3737
- prisma = await loader;
3693
+ execSync2("secret-tool search --all exe-os probe >/dev/null 2>&1", { timeout: 1e3 });
3694
+ linuxSecretAvailability = true;
3738
3695
  } catch {
3739
- return 0;
3740
- }
3741
- let inserted = 0;
3742
- for (const rec of records) {
3743
- try {
3744
- await prisma.$executeRawUnsafe(
3745
- `INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
3746
- VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
3747
- ON CONFLICT (source, source_id, event_type) DO NOTHING`,
3748
- String(rec.id ?? ""),
3749
- JSON.stringify(rec),
3750
- JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
3751
- rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
3752
- );
3753
- inserted++;
3754
- } catch {
3755
- }
3696
+ linuxSecretAvailability = false;
3756
3697
  }
3757
- return inserted;
3698
+ return linuxSecretAvailability;
3758
3699
  }
3759
- async function withRosterLock(fn) {
3760
- try {
3761
- const fd = openSync2(ROSTER_LOCK_PATH, "wx");
3762
- closeSync2(fd);
3763
- writeFileSync6(ROSTER_LOCK_PATH, String(Date.now()));
3764
- } catch (err) {
3765
- if (err.code === "EEXIST") {
3766
- try {
3767
- const ts = parseInt(readFileSync8(ROSTER_LOCK_PATH, "utf-8"), 10);
3768
- if (Date.now() - ts < LOCK_STALE_MS) {
3769
- throw new Error("Roster merge already in progress \u2014 another sync is running");
3770
- }
3771
- unlinkSync5(ROSTER_LOCK_PATH);
3772
- const fd = openSync2(ROSTER_LOCK_PATH, "wx");
3773
- closeSync2(fd);
3774
- writeFileSync6(ROSTER_LOCK_PATH, String(Date.now()));
3775
- } catch (retryErr) {
3776
- if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
3777
- throw new Error("Roster merge already in progress \u2014 another sync is running");
3778
- }
3779
- } else {
3780
- throw err;
3781
- }
3782
- }
3700
+ function isRootOnlyTrustedServerKeyFile(keyPath) {
3701
+ if (process.platform !== "linux") return false;
3783
3702
  try {
3784
- return await fn();
3785
- } finally {
3786
- try {
3787
- unlinkSync5(ROSTER_LOCK_PATH);
3788
- } catch {
3789
- }
3703
+ const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
3704
+ const st = statSync2(keyPath);
3705
+ if (!st.isFile() || (st.mode & 63) !== 0) return false;
3706
+ if (uid === 0) return true;
3707
+ const exeOsDir = process.env.EXE_OS_DIR;
3708
+ return Boolean(exeOsDir && path6.resolve(keyPath).startsWith(path6.resolve(exeOsDir) + path6.sep));
3709
+ } catch {
3710
+ return false;
3790
3711
  }
3791
3712
  }
3792
- async function fetchWithRetry(url, init) {
3793
- const MAX_RETRIES2 = 3;
3794
- const BASE_DELAY_MS2 = 200;
3795
- let lastError;
3796
- for (let attempt = 0; attempt <= MAX_RETRIES2; attempt++) {
3797
- try {
3798
- const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
3799
- const resp = await fetch(url, { ...init, signal });
3800
- if (resp && resp.status >= 500 && attempt < MAX_RETRIES2) {
3801
- await new Promise((r) => setTimeout(r, BASE_DELAY_MS2 * Math.pow(2, attempt)));
3802
- continue;
3803
- }
3804
- return resp;
3805
- } catch (err) {
3806
- lastError = err;
3807
- if (attempt === MAX_RETRIES2) throw err;
3808
- await new Promise((r) => setTimeout(r, BASE_DELAY_MS2 * Math.pow(2, attempt)));
3809
- }
3713
+ function macKeychainGet(service = SERVICE) {
3714
+ if (!nativeKeychainAllowed()) return null;
3715
+ if (process.platform !== "darwin") return null;
3716
+ try {
3717
+ return execSync2(
3718
+ `security find-generic-password -s "${service}" -a "${ACCOUNT}" -w 2>/dev/null`,
3719
+ { encoding: "utf-8", timeout: 5e3 }
3720
+ ).trim();
3721
+ } catch {
3722
+ return null;
3810
3723
  }
3811
- throw lastError;
3812
3724
  }
3813
- function assertSecureEndpoint(endpoint) {
3814
- if (endpoint.startsWith("https://")) return;
3815
- if (endpoint.startsWith("http://")) {
3725
+ function macKeychainSet(value, service = SERVICE) {
3726
+ if (!nativeKeychainAllowed()) return false;
3727
+ if (process.platform !== "darwin") return false;
3728
+ try {
3816
3729
  try {
3817
- const parsed = new URL(endpoint);
3818
- if (LOCALHOST_PATTERNS.test(parsed.hostname)) return;
3730
+ execSync2(
3731
+ `security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
3732
+ { timeout: 5e3 }
3733
+ );
3819
3734
  } catch {
3820
- return;
3821
3735
  }
3822
- throw new Error(
3823
- `Insecure cloud endpoint rejected: "${endpoint}". Use https:// for remote hosts. Plain http:// is only allowed for localhost.`
3736
+ execSync2(
3737
+ `security add-generic-password -s "${service}" -a "${ACCOUNT}" -w "${value}"`,
3738
+ { timeout: 5e3 }
3824
3739
  );
3740
+ return true;
3741
+ } catch {
3742
+ return false;
3825
3743
  }
3826
3744
  }
3827
- async function cloudPush(records, maxVersion, config) {
3828
- if (records.length === 0) return true;
3829
- assertSecureEndpoint(config.endpoint);
3745
+ function macKeychainDelete(service = SERVICE) {
3746
+ if (!nativeKeychainAllowed()) return false;
3747
+ if (process.platform !== "darwin") return false;
3830
3748
  try {
3831
- const json = JSON.stringify(records);
3832
- const compressed = compress(Buffer.from(json, "utf8"));
3833
- const blob = encryptSyncBlob(compressed);
3834
- const resp = await fetchWithRetry(`${config.endpoint}/sync/push`, {
3835
- method: "POST",
3836
- headers: {
3837
- Authorization: `Bearer ${config.apiKey}`,
3838
- "Content-Type": "application/json",
3839
- "X-Device-Id": loadDeviceId(),
3840
- "X-Expected-Version": String(maxVersion)
3841
- },
3842
- body: JSON.stringify({ version: maxVersion, blob })
3843
- });
3844
- if (resp == null) {
3845
- logError("[cloud-sync] PUSH FAILED: no response from server");
3846
- return false;
3847
- }
3848
- if (resp.status === 409) {
3849
- logError("[cloud-sync] PUSH VERSION CONFLICT \u2014 re-pull required before next push");
3850
- return false;
3851
- }
3852
- return resp.ok;
3853
- } catch (err) {
3854
- logError(`[cloud-sync] PUSH FAILED: ${err instanceof Error ? err.message : String(err)}`);
3749
+ execSync2(
3750
+ `security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
3751
+ { timeout: 5e3 }
3752
+ );
3753
+ return true;
3754
+ } catch {
3855
3755
  return false;
3856
3756
  }
3857
3757
  }
3858
- async function cloudPull(sinceVersion, config) {
3859
- assertSecureEndpoint(config.endpoint);
3758
+ function linuxSecretGet(service = SERVICE) {
3759
+ if (!linuxSecretAvailable()) return null;
3860
3760
  try {
3861
- const response = await fetchWithRetry(`${config.endpoint}/sync/pull`, {
3862
- method: "POST",
3863
- headers: {
3864
- Authorization: `Bearer ${config.apiKey}`,
3865
- "Content-Type": "application/json",
3866
- "X-Device-Id": loadDeviceId()
3867
- },
3868
- body: JSON.stringify({ since_version: sinceVersion })
3869
- });
3870
- if (response == null) {
3871
- logError("[cloud-sync] PULL FAILED: no response from server");
3872
- return { records: [], maxVersion: sinceVersion };
3873
- }
3874
- if (!response.ok) return { records: [], maxVersion: sinceVersion };
3875
- const data = await response.json();
3876
- const allRecords = [];
3877
- for (const { blob } of data.blobs ?? []) {
3878
- try {
3879
- const compressed = decryptSyncBlob(blob);
3880
- const json = decompress(compressed).toString("utf8");
3881
- const records = JSON.parse(json);
3882
- allRecords.push(...records);
3883
- } catch {
3884
- continue;
3885
- }
3886
- }
3887
- return { records: allRecords, maxVersion: data.max_version ?? sinceVersion };
3888
- } catch (err) {
3889
- logError(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}`);
3890
- return { records: [], maxVersion: sinceVersion };
3761
+ return execSync2(
3762
+ `secret-tool lookup service "${service}" account "${ACCOUNT}" 2>/dev/null`,
3763
+ { encoding: "utf-8", timeout: 5e3 }
3764
+ ).trim();
3765
+ } catch {
3766
+ return null;
3891
3767
  }
3892
3768
  }
3893
- async function getCloudRelinkRequired(client = getClient()) {
3769
+ function linuxSecretSet(value, service = SERVICE) {
3770
+ if (!linuxSecretAvailable()) return false;
3894
3771
  try {
3895
- await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
3896
- const relink = await client.execute("SELECT value FROM sync_meta WHERE key = 'cloud_relink_required' LIMIT 1");
3897
- return String(relink.rows[0]?.value ?? "") === "1";
3772
+ execSync2(
3773
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${service}" account "${ACCOUNT}" 2>/dev/null`,
3774
+ { timeout: 5e3 }
3775
+ );
3776
+ return true;
3898
3777
  } catch {
3899
3778
  return false;
3900
3779
  }
3901
3780
  }
3902
- async function clearCloudRelinkRequired(client = getClient()) {
3903
- await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
3904
- await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_relink_required', '0')");
3905
- await client.execute({
3906
- sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_relinked_at', ?)",
3907
- args: [(/* @__PURE__ */ new Date()).toISOString()]
3908
- });
3909
- await client.execute("DELETE FROM sync_meta WHERE key IN ('last_cloud_pull_version', 'last_cloud_push_version')");
3910
- }
3911
- async function cloudSync(config) {
3912
- if (!isSyncCryptoInitialized()) {
3913
- try {
3914
- const { getMasterKey: getMasterKey2 } = await Promise.resolve().then(() => (init_keychain(), keychain_exports));
3915
- const masterKey = await getMasterKey2();
3916
- if (masterKey) {
3917
- initSyncCrypto(masterKey);
3918
- } else {
3919
- throw new Error("No master key found");
3920
- }
3921
- } catch (err) {
3922
- throw new Error(`[cloud-sync] Cannot initialize encryption: ${err instanceof Error ? err.message : String(err)}`);
3923
- }
3924
- }
3925
- let client;
3781
+ function linuxSecretDelete(service = SERVICE) {
3782
+ if (!nativeKeychainAllowed()) return false;
3783
+ if (process.platform !== "linux") return false;
3926
3784
  try {
3927
- client = getClient();
3785
+ execSync2(
3786
+ `secret-tool clear service "${service}" account "${ACCOUNT}" 2>/dev/null`,
3787
+ { timeout: 5e3 }
3788
+ );
3789
+ return true;
3928
3790
  } catch {
3929
- throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
3930
- }
3931
- try {
3932
- if (await getCloudRelinkRequired(client)) throw new Error(CLOUD_RELINK_REQUIRED_MESSAGE);
3933
- } catch (err) {
3934
- const msg = err instanceof Error ? err.message : String(err);
3935
- if (msg.includes("Paused after key rotation")) throw err;
3791
+ return false;
3936
3792
  }
3793
+ }
3794
+ async function tryKeytar() {
3795
+ if (!nativeKeychainAllowed()) return null;
3937
3796
  try {
3938
- const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3939
- await getRawClient2().execute("PRAGMA wal_checkpoint(PASSIVE)");
3797
+ return await import("keytar");
3940
3798
  } catch {
3799
+ return null;
3941
3800
  }
3801
+ }
3802
+ var ENCRYPTED_PREFIX = "enc:";
3803
+ function deriveMachineKey() {
3942
3804
  try {
3943
- await client.execute(
3944
- "CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)"
3945
- );
3946
- } catch (e) {
3947
- logError(`[cloud-sync] sync_meta CREATE failed: ${e instanceof Error ? e.message : String(e)}`);
3948
- }
3949
- const pullMeta = await client.execute(
3950
- "SELECT value FROM sync_meta WHERE key = 'last_cloud_pull_version'"
3951
- );
3952
- const lastPullVersion = pullMeta.rows.length > 0 ? Number(pullMeta.rows[0].value) : 0;
3953
- const pullResult = await cloudPull(lastPullVersion, config);
3954
- let pulled = 0;
3955
- if (pullResult.records.length > 0) {
3956
- if (isCrdtSyncEnabled()) {
3957
- const { initCrdtDoc: initCrdtDoc2, importExistingMemories: importExistingMemories2, readAllMemories: readAllMemories2 } = await Promise.resolve().then(() => (init_crdt_sync(), crdt_sync_exports));
3958
- initCrdtDoc2();
3959
- importExistingMemories2(
3960
- pullResult.records.map((rec) => ({
3961
- id: String(rec.id ?? ""),
3962
- agent_id: rec.agent_id,
3963
- agent_role: rec.agent_role,
3964
- session_id: rec.session_id,
3965
- timestamp: rec.timestamp,
3966
- tool_name: rec.tool_name,
3967
- project_name: rec.project_name,
3968
- has_error: rec.has_error ?? 0,
3969
- raw_text: rec.raw_text ?? "",
3970
- version: rec.version ?? 0,
3971
- author_device_id: rec.author_device_id,
3972
- scope: rec.scope ?? "business"
3973
- }))
3974
- );
3975
- const pulledIds = new Set(pullResult.records.map((r) => String(r.id ?? "")));
3976
- const merged = readAllMemories2().filter((rec) => pulledIds.has(rec.id));
3977
- const stmts = merged.map((rec) => ({
3978
- sql: `INSERT OR REPLACE INTO memories
3979
- (id, agent_id, agent_role, session_id, timestamp,
3980
- tool_name, project_name, has_error, raw_text, version,
3981
- author_device_id, scope)
3982
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3983
- args: [
3984
- sqlSafe(rec.id),
3985
- sqlSafe(rec.agent_id),
3986
- sqlSafe(rec.agent_role),
3987
- sqlSafe(rec.session_id),
3988
- sqlSafe(rec.timestamp),
3989
- sqlSafe(rec.tool_name),
3990
- sqlSafe(rec.project_name),
3991
- sqlSafe(rec.has_error ?? 0),
3992
- sqlSafe(rec.raw_text ?? ""),
3993
- sqlSafe(rec.version ?? 0),
3994
- sqlSafe(rec.author_device_id),
3995
- sqlSafe(rec.scope ?? "business")
3996
- ]
3997
- }));
3998
- if (stmts.length > 0) await client.batch(stmts, "write");
3999
- pulled = pullResult.records.length;
4000
- } else {
4001
- const stmts = pullResult.records.map((rec) => ({
4002
- sql: `INSERT OR REPLACE INTO memories
4003
- (id, agent_id, agent_role, session_id, timestamp,
4004
- tool_name, project_name, has_error, raw_text, version,
4005
- author_device_id, scope)
4006
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4007
- args: [
4008
- sqlSafe(rec.id),
4009
- sqlSafe(rec.agent_id),
4010
- sqlSafe(rec.agent_role),
4011
- sqlSafe(rec.session_id),
4012
- sqlSafe(rec.timestamp),
4013
- sqlSafe(rec.tool_name),
4014
- sqlSafe(rec.project_name),
4015
- sqlSafe(rec.has_error ?? 0),
4016
- sqlSafe(rec.raw_text ?? ""),
4017
- sqlSafe(rec.version ?? 0),
4018
- sqlSafe(rec.author_device_id),
4019
- sqlSafe(rec.scope ?? "business")
4020
- ]
4021
- }));
4022
- await client.batch(stmts, "write");
4023
- pulled = pullResult.records.length;
4024
- }
4025
- }
4026
- if (pulled > 0) {
4027
- try {
4028
- await pushToPostgres(pullResult.records);
4029
- } catch {
4030
- }
4031
- }
4032
- if (pullResult.maxVersion > lastPullVersion) {
4033
- await client.execute({
4034
- sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_pull_version', ?)",
4035
- args: [String(pullResult.maxVersion)]
4036
- });
4037
- }
4038
- const pushMeta = await client.execute(
4039
- "SELECT value FROM sync_meta WHERE key = 'last_cloud_push_version'"
4040
- );
4041
- const lastPushVersion = pushMeta.rows.length > 0 ? Number(pushMeta.rows[0].value) : 0;
4042
- let pushed = 0;
4043
- let batchCursor = lastPushVersion;
4044
- while (true) {
4045
- const recordsResult = await client.execute({
4046
- sql: `SELECT id, agent_id, agent_role, session_id, timestamp,
4047
- tool_name, project_name, has_error, raw_text, version,
4048
- author_device_id, scope
4049
- FROM memories
4050
- WHERE version > ?
4051
- AND (scope IS NULL OR scope != 'personal')
4052
- ORDER BY version ASC
4053
- LIMIT ?`,
4054
- args: [batchCursor, PUSH_BATCH_SIZE]
4055
- });
4056
- if (recordsResult.rows.length === 0) break;
4057
- const records = recordsResult.rows.map((row) => ({
4058
- id: row.id,
4059
- agent_id: row.agent_id,
4060
- agent_role: row.agent_role,
4061
- session_id: row.session_id,
4062
- timestamp: row.timestamp,
4063
- tool_name: row.tool_name,
4064
- project_name: row.project_name,
4065
- has_error: row.has_error,
4066
- raw_text: row.raw_text,
4067
- version: row.version,
4068
- author_device_id: row.author_device_id,
4069
- scope: row.scope
4070
- }));
4071
- const maxVersion = Number(records[records.length - 1].version);
4072
- const pushOk = await cloudPush(records, maxVersion, config);
4073
- if (!pushOk) break;
4074
- try {
4075
- await pushToPostgres(records);
4076
- } catch {
4077
- }
4078
- await client.execute({
4079
- sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
4080
- args: [String(maxVersion)]
4081
- });
4082
- pushed += records.length;
4083
- batchCursor = maxVersion;
4084
- if (recordsResult.rows.length < PUSH_BATCH_SIZE) break;
4085
- }
4086
- try {
4087
- await cloudPushRoster(config);
4088
- } catch (err) {
4089
- logError(`[cloud-sync] Roster push: ${err instanceof Error ? err.message : String(err)}`);
4090
- }
4091
- try {
4092
- await cloudPullRoster(config);
4093
- } catch (err) {
4094
- logError(`[cloud-sync] Roster pull: ${err instanceof Error ? err.message : String(err)}`);
4095
- }
4096
- try {
4097
- await cloudPushGlobalProcedures(config);
4098
- } catch (err) {
4099
- logError(`[cloud-sync] Company procedures push: ${err instanceof Error ? err.message : String(err)}`);
4100
- }
4101
- try {
4102
- await cloudPullGlobalProcedures(config);
4103
- } catch (err) {
4104
- logError(`[cloud-sync] Company procedures pull: ${err instanceof Error ? err.message : String(err)}`);
4105
- }
4106
- const countRows = async (sql) => {
4107
- try {
4108
- return Number((await client.execute(sql)).rows[0]?.cnt ?? 0);
4109
- } catch {
4110
- return 0;
4111
- }
4112
- };
4113
- let behaviorsResult = { pushed: 0, pulled: 0 };
4114
- try {
4115
- await cloudPushBehaviors(config);
4116
- behaviorsResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM behaviors WHERE active = 1");
4117
- } catch (err) {
4118
- logError(`[cloud-sync] Behaviors push: ${err instanceof Error ? err.message : String(err)}`);
4119
- }
4120
- try {
4121
- const pullResult2 = await cloudPullBehaviors(config);
4122
- behaviorsResult.pulled = pullResult2.pulled;
4123
- } catch (err) {
4124
- logError(`[cloud-sync] Behaviors pull: ${err instanceof Error ? err.message : String(err)}`);
4125
- }
4126
- let graphragResult = { pushed: 0, pulled: 0 };
4127
- try {
4128
- await cloudPushGraphRAG(config);
4129
- graphragResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM entities");
4130
- } catch (err) {
4131
- logError(`[cloud-sync] GraphRAG push: ${err instanceof Error ? err.message : String(err)}`);
4132
- }
4133
- try {
4134
- const pullResult2 = await cloudPullGraphRAG(config);
4135
- graphragResult.pulled = pullResult2.pulled;
4136
- } catch (err) {
4137
- logError(`[cloud-sync] GraphRAG pull: ${err instanceof Error ? err.message : String(err)}`);
4138
- }
4139
- let tasksResult = { pushed: 0, pulled: 0 };
4140
- try {
4141
- await cloudPushTasks(config);
4142
- tasksResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM tasks");
4143
- } catch (err) {
4144
- logError(`[cloud-sync] Tasks push: ${err instanceof Error ? err.message : String(err)}`);
4145
- }
4146
- try {
4147
- const pullResult2 = await cloudPullTasks(config);
4148
- tasksResult.pulled = pullResult2.pulled;
4149
- } catch (err) {
4150
- logError(`[cloud-sync] Tasks pull: ${err instanceof Error ? err.message : String(err)}`);
4151
- }
4152
- let conversationsResult = { pushed: 0, pulled: 0 };
4153
- try {
4154
- await cloudPushConversations(config);
4155
- conversationsResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM conversations");
4156
- } catch (err) {
4157
- logError(`[cloud-sync] Conversations push: ${err instanceof Error ? err.message : String(err)}`);
4158
- }
4159
- try {
4160
- const pullResult2 = await cloudPullConversations(config);
4161
- conversationsResult.pulled = pullResult2.pulled;
4162
- } catch (err) {
4163
- logError(`[cloud-sync] Conversations pull: ${err instanceof Error ? err.message : String(err)}`);
4164
- }
4165
- let documentsResult = { pushed: 0, pulled: 0 };
4166
- try {
4167
- await cloudPushDocuments(config);
4168
- documentsResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM documents");
4169
- } catch (err) {
4170
- logError(`[cloud-sync] Documents push: ${err instanceof Error ? err.message : String(err)}`);
4171
- }
4172
- try {
4173
- const pullResult2 = await cloudPullDocuments(config);
4174
- documentsResult.pulled = pullResult2.pulled;
4175
- } catch (err) {
4176
- logError(`[cloud-sync] Documents pull: ${err instanceof Error ? err.message : String(err)}`);
4177
- }
4178
- let rosterResult = { employees: 0, identities: 0 };
4179
- try {
4180
- const employees = await loadEmployees();
4181
- rosterResult.employees = employees.length;
4182
- const idDir = path11.join(EXE_AI_DIR, "identity");
4183
- if (existsSync11(idDir)) {
4184
- rosterResult.identities = readdirSync2(idDir).filter((f) => f.endsWith(".md")).length;
4185
- }
3805
+ const crypto2 = __require("crypto");
3806
+ const material = [
3807
+ os5.hostname(),
3808
+ os5.userInfo().username,
3809
+ os5.arch(),
3810
+ os5.platform(),
3811
+ // Machine ID on Linux (stable across reboots)
3812
+ process.platform === "linux" ? readMachineId() : ""
3813
+ ].join("|");
3814
+ return crypto2.createHash("sha256").update(material).digest();
4186
3815
  } catch {
3816
+ return null;
4187
3817
  }
4188
- const totalMemories = await countRows("SELECT COUNT(*) as cnt FROM memories WHERE status = 'active' OR status IS NULL");
4189
- try {
4190
- const { getLatestBackup: getLatestBackup2 } = await Promise.resolve().then(() => (init_db_backup(), db_backup_exports));
4191
- const latestBackup = getLatestBackup2();
4192
- if (latestBackup) {
4193
- const backupSize = statSync3(latestBackup).size;
4194
- const MAX_CLOUD_BACKUP_BYTES = 50 * 1024 * 1024;
4195
- if (backupSize <= MAX_CLOUD_BACKUP_BYTES) {
4196
- const backupData = readFileSync8(latestBackup);
4197
- const deviceId = loadDeviceId() ?? "unknown";
4198
- const encrypted = encryptSyncBlob(backupData);
4199
- const backupRes = await fetchWithRetry(`${config.endpoint}/sync/push-db-backup`, {
4200
- method: "POST",
4201
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.apiKey}` },
4202
- body: JSON.stringify({
4203
- device_id: deviceId,
4204
- filename: path11.basename(latestBackup),
4205
- blob: encrypted,
4206
- size: backupData.length
4207
- })
4208
- });
4209
- if (backupRes && !backupRes.ok) {
4210
- logError(`[cloud-sync] DB backup upload failed: ${backupRes.status}`);
4211
- }
4212
- }
4213
- }
4214
- } catch (err) {
4215
- logError(`[cloud-sync] DB backup upload error: ${err instanceof Error ? err.message : String(err)}`);
4216
- }
4217
- return {
4218
- pushed,
4219
- pulled,
4220
- totalMemories,
4221
- behaviors: behaviorsResult,
4222
- graphrag: graphragResult,
4223
- tasks: tasksResult,
4224
- conversations: conversationsResult,
4225
- documents: documentsResult,
4226
- roster: rosterResult
4227
- };
4228
3818
  }
4229
- function recordRosterDeletion(name) {
4230
- let deletions = [];
3819
+ function readMachineId() {
4231
3820
  try {
4232
- if (existsSync11(ROSTER_DELETIONS_PATH)) {
4233
- deletions = JSON.parse(readFileSync8(ROSTER_DELETIONS_PATH, "utf-8"));
4234
- }
3821
+ const { readFileSync: readFileSync5 } = __require("fs");
3822
+ return readFileSync5("/etc/machine-id", "utf-8").trim();
4235
3823
  } catch {
3824
+ return "";
4236
3825
  }
4237
- if (!deletions.includes(name)) deletions.push(name);
4238
- writeFileSync6(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
4239
3826
  }
4240
- function consumeRosterDeletions() {
3827
+ function encryptWithMachineKey(plaintext, machineKey) {
3828
+ const crypto2 = __require("crypto");
3829
+ const iv = crypto2.randomBytes(12);
3830
+ const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
3831
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3832
+ encrypted += cipher.final("base64");
3833
+ const authTag = cipher.getAuthTag().toString("base64");
3834
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3835
+ }
3836
+ function decryptWithMachineKey(encrypted, machineKey) {
3837
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
4241
3838
  try {
4242
- if (!existsSync11(ROSTER_DELETIONS_PATH)) return [];
4243
- const deletions = JSON.parse(readFileSync8(ROSTER_DELETIONS_PATH, "utf-8"));
4244
- writeFileSync6(ROSTER_DELETIONS_PATH, "[]");
4245
- return deletions;
3839
+ const crypto2 = __require("crypto");
3840
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
3841
+ if (parts.length !== 3) return null;
3842
+ const [ivB64, tagB64, cipherB64] = parts;
3843
+ const iv = Buffer.from(ivB64, "base64");
3844
+ const authTag = Buffer.from(tagB64, "base64");
3845
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", machineKey, iv);
3846
+ decipher.setAuthTag(authTag);
3847
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
3848
+ decrypted += decipher.final("utf-8");
3849
+ return decrypted;
4246
3850
  } catch {
4247
- return [];
3851
+ return null;
4248
3852
  }
4249
3853
  }
4250
- function buildRosterBlob(paths) {
4251
- const rosterPath = paths?.rosterPath ?? path11.join(EXE_AI_DIR, "exe-employees.json");
4252
- const identityDir = paths?.identityDir ?? path11.join(EXE_AI_DIR, "identity");
4253
- const configPath = paths?.configPath ?? path11.join(EXE_AI_DIR, "config.json");
4254
- let roster = [];
4255
- if (existsSync11(rosterPath)) {
4256
- try {
4257
- roster = JSON.parse(readFileSync8(rosterPath, "utf-8"));
4258
- } catch {
4259
- }
3854
+ async function writeMachineBoundFileFallback(b64) {
3855
+ const dir = getKeyDir();
3856
+ await mkdir3(dir, { recursive: true });
3857
+ const keyPath = getKeyPath();
3858
+ const machineKey = deriveMachineKey();
3859
+ if (machineKey) {
3860
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3861
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3862
+ await chmod2(keyPath, 384);
3863
+ return "encrypted";
4260
3864
  }
4261
- const identities = {};
4262
- if (existsSync11(identityDir)) {
4263
- for (const file of readdirSync2(identityDir).filter((f) => f.endsWith(".md"))) {
4264
- try {
4265
- identities[file] = readFileSync8(path11.join(identityDir, file), "utf-8");
4266
- } catch {
3865
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3866
+ await chmod2(keyPath, 384);
3867
+ return "plaintext";
3868
+ }
3869
+ async function getMasterKey() {
3870
+ let nativeValue = macKeychainGet() ?? linuxSecretGet();
3871
+ if (!nativeValue) {
3872
+ const legacyValue = macKeychainGet(LEGACY_SERVICE) ?? linuxSecretGet(LEGACY_SERVICE);
3873
+ if (legacyValue) {
3874
+ const migrated = macKeychainSet(legacyValue) || linuxSecretSet(legacyValue);
3875
+ if (migrated) {
3876
+ macKeychainDelete(LEGACY_SERVICE);
3877
+ linuxSecretDelete(LEGACY_SERVICE);
3878
+ process.stderr.write("[keychain] Migrated keychain service from exe-mem to exe-os.\n");
4267
3879
  }
3880
+ nativeValue = legacyValue;
4268
3881
  }
4269
3882
  }
4270
- let config;
4271
- if (existsSync11(configPath)) {
4272
- try {
4273
- config = JSON.parse(readFileSync8(configPath, "utf-8"));
4274
- } catch {
4275
- }
3883
+ if (nativeValue) {
3884
+ return Buffer.from(nativeValue, "base64");
4276
3885
  }
4277
- let agentConfig;
4278
- const agentConfigPath = path11.join(EXE_AI_DIR, "agent-config.json");
4279
- if (existsSync11(agentConfigPath)) {
3886
+ const keytar = await tryKeytar();
3887
+ if (keytar) {
4280
3888
  try {
4281
- agentConfig = JSON.parse(readFileSync8(agentConfigPath, "utf-8"));
3889
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
3890
+ const legacyKeytarValue = keytarValue ?? await keytar.getPassword(LEGACY_SERVICE, ACCOUNT);
3891
+ if (legacyKeytarValue) {
3892
+ const migrated = macKeychainSet(legacyKeytarValue) || linuxSecretSet(legacyKeytarValue);
3893
+ if (migrated) {
3894
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
3895
+ try {
3896
+ await keytar.deletePassword(LEGACY_SERVICE, ACCOUNT);
3897
+ } catch {
3898
+ }
3899
+ }
3900
+ return Buffer.from(legacyKeytarValue, "base64");
3901
+ }
4282
3902
  } catch {
4283
3903
  }
4284
3904
  }
4285
- const deletedNames = consumeRosterDeletions();
4286
- const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
4287
- const hash = crypto3.createHash("sha256").update(content).digest("hex").slice(0, 16);
4288
- return { roster, identities, config, agentConfig, deletedNames, version: hash };
4289
- }
4290
- async function cloudPushRoster(config) {
4291
- assertSecureEndpoint(config.endpoint);
4292
- const blob = buildRosterBlob();
4293
- if (blob.roster.length === 0) return true;
4294
- try {
4295
- const client = getClient();
4296
- const meta = await client.execute(
4297
- "SELECT value FROM sync_meta WHERE key = 'last_roster_push_version'"
3905
+ const keyPath = getKeyPath();
3906
+ if (!existsSync6(keyPath)) {
3907
+ process.stderr.write(
3908
+ `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
3909
+ `
4298
3910
  );
4299
- const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
4300
- if (blob.version === lastVersion) return true;
4301
- } catch {
3911
+ return null;
4302
3912
  }
4303
3913
  try {
4304
- const json = JSON.stringify(blob);
4305
- const compressed = compress(Buffer.from(json, "utf8"));
4306
- const encrypted = encryptSyncBlob(compressed);
4307
- const resp = await fetchWithRetry(`${config.endpoint}/sync/push-roster`, {
4308
- method: "POST",
4309
- headers: {
4310
- Authorization: `Bearer ${config.apiKey}`,
4311
- "Content-Type": "application/json",
4312
- "X-Device-Id": loadDeviceId()
4313
- },
4314
- body: JSON.stringify({ blob: encrypted })
4315
- });
4316
- if (resp.ok) {
3914
+ const content = (await readFile3(keyPath, "utf-8")).trim();
3915
+ let b64Value;
3916
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
3917
+ const machineKey = deriveMachineKey();
3918
+ if (!machineKey) {
3919
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
3920
+ return null;
3921
+ }
3922
+ const decrypted = decryptWithMachineKey(content, machineKey);
3923
+ if (!decrypted) {
3924
+ process.stderr.write(
3925
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase during setup: exe-os setup\n"
3926
+ );
3927
+ return null;
3928
+ }
3929
+ b64Value = decrypted;
3930
+ } else {
3931
+ b64Value = content;
3932
+ }
3933
+ const key = Buffer.from(b64Value, "base64");
3934
+ if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
3935
+ return key;
3936
+ }
3937
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3938
+ if (migrated) {
3939
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
4317
3940
  try {
4318
- const client = getClient();
4319
- await client.execute({
4320
- sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_roster_push_version', ?)",
4321
- args: [String(blob.version)]
4322
- });
3941
+ await unlink(keyPath);
3942
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
4323
3943
  } catch {
4324
3944
  }
4325
- }
4326
- return resp.ok;
4327
- } catch (err) {
4328
- process.stderr.write(`[cloud-sync] ROSTER PUSH FAILED: ${err instanceof Error ? err.message : String(err)}
4329
- `);
4330
- return false;
4331
- }
4332
- }
4333
- async function cloudPullRoster(config) {
4334
- assertSecureEndpoint(config.endpoint);
4335
- try {
4336
- const resp = await fetchWithRetry(`${config.endpoint}/sync/pull-roster`, {
4337
- method: "GET",
4338
- headers: {
4339
- Authorization: `Bearer ${config.apiKey}`,
4340
- "X-Device-Id": loadDeviceId()
3945
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3946
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3947
+ if (fallback === "encrypted") {
3948
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3949
+ } else {
3950
+ process.stderr.write(
3951
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3952
+ );
4341
3953
  }
4342
- });
4343
- if (!resp.ok) return { added: 0 };
4344
- const data = await resp.json();
4345
- if (!data.blob) return { added: 0 };
4346
- const compressed = decryptSyncBlob(data.blob);
4347
- const json = decompress(compressed).toString("utf8");
4348
- const remote = JSON.parse(json);
4349
- return mergeRosterFromRemote(remote);
3954
+ }
3955
+ return key;
4350
3956
  } catch (err) {
4351
- process.stderr.write(`[cloud-sync] ROSTER PULL FAILED: ${err instanceof Error ? err.message : String(err)}
4352
- `);
4353
- return { added: 0 };
3957
+ process.stderr.write(
3958
+ `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
3959
+ `
3960
+ );
3961
+ return null;
4354
3962
  }
4355
3963
  }
4356
- function mergeConfig(remoteConfig, configPath) {
4357
- const cfgPath = configPath ?? path11.join(EXE_AI_DIR, "config.json");
4358
- let local = {};
4359
- if (existsSync11(cfgPath)) {
4360
- try {
4361
- local = JSON.parse(readFileSync8(cfgPath, "utf-8"));
4362
- } catch {
4363
- }
4364
- }
4365
- const merged = { ...remoteConfig, ...local };
4366
- const dir = path11.dirname(cfgPath);
4367
- ensurePrivateDirSync(dir);
4368
- writeFileSync6(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
4369
- enforcePrivateFileSync(cfgPath);
4370
- }
4371
- async function mergeRosterFromRemote(remote, paths) {
4372
- return withRosterLock(async () => {
4373
- const rosterPath = paths?.rosterPath ?? void 0;
4374
- const identityDir = paths?.identityDir ?? path11.join(EXE_AI_DIR, "identity");
4375
- const localEmployees = await loadEmployees(rosterPath);
4376
- const localNames = new Set(localEmployees.map((e) => e.name));
4377
- let added = 0;
4378
- let identitiesUpdated = 0;
4379
- for (const remoteEmp of remote.roster) {
4380
- if (!localNames.has(remoteEmp.name)) {
4381
- localEmployees.push(remoteEmp);
4382
- localNames.add(remoteEmp.name);
4383
- added++;
4384
- try {
4385
- registerBinSymlinks(remoteEmp.name);
4386
- } catch {
4387
- }
4388
- }
4389
- const lookupKey = `${remoteEmp.name}.md`;
4390
- const matchedKey = Object.keys(remote.identities).find(
4391
- (k) => k.toLowerCase() === lookupKey.toLowerCase()
4392
- ) ?? lookupKey;
4393
- const remoteIdentity = remote.identities[matchedKey];
4394
- if (remoteIdentity) {
4395
- if (!existsSync11(identityDir)) mkdirSync6(identityDir, { recursive: true });
4396
- const idPath = path11.join(identityDir, `${remoteEmp.name}.md`);
4397
- let localIdentity = null;
3964
+
3965
+ // src/lib/store.ts
3966
+ init_config();
3967
+
3968
+ // src/lib/state-bus.ts
3969
+ var StateBus = class {
3970
+ handlers = /* @__PURE__ */ new Map();
3971
+ globalHandlers = /* @__PURE__ */ new Set();
3972
+ /** Emit an event to all subscribers */
3973
+ emit(event) {
3974
+ const typeHandlers = this.handlers.get(event.type);
3975
+ if (typeHandlers) {
3976
+ for (const handler of typeHandlers) {
4398
3977
  try {
4399
- localIdentity = existsSync11(idPath) ? readFileSync8(idPath, "utf-8") : null;
3978
+ handler(event);
4400
3979
  } catch {
4401
3980
  }
4402
- if (localIdentity !== remoteIdentity) {
4403
- writeFileSync6(idPath, remoteIdentity, "utf-8");
4404
- identitiesUpdated++;
4405
- }
4406
3981
  }
4407
3982
  }
4408
- let removed = 0;
4409
- if (remote.deletedNames && remote.deletedNames.length > 0) {
4410
- const toRemove = new Set(remote.deletedNames);
4411
- const filtered = localEmployees.filter((e) => !toRemove.has(e.name));
4412
- removed = localEmployees.length - filtered.length;
4413
- if (removed > 0) {
4414
- localEmployees.length = 0;
4415
- localEmployees.push(...filtered);
4416
- }
4417
- }
4418
- if (added > 0 || removed > 0) {
4419
- await saveEmployees(localEmployees, rosterPath);
4420
- }
4421
- if (remote.config && Object.keys(remote.config).length > 0) {
3983
+ for (const handler of this.globalHandlers) {
4422
3984
  try {
4423
- mergeConfig(remote.config, paths?.configPath);
3985
+ handler(event);
4424
3986
  } catch {
4425
3987
  }
4426
3988
  }
4427
- if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
4428
- try {
4429
- const agentConfigPath = path11.join(EXE_AI_DIR, "agent-config.json");
4430
- let local = {};
4431
- if (existsSync11(agentConfigPath)) {
4432
- try {
4433
- local = JSON.parse(readFileSync8(agentConfigPath, "utf-8"));
4434
- } catch {
4435
- }
4436
- }
4437
- const merged = { ...remote.agentConfig, ...local };
4438
- ensurePrivateDirSync(path11.dirname(agentConfigPath));
4439
- writeFileSync6(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
4440
- enforcePrivateFileSync(agentConfigPath);
4441
- } catch {
4442
- }
3989
+ }
3990
+ /** Subscribe to a specific event type */
3991
+ on(type, handler) {
3992
+ if (!this.handlers.has(type)) {
3993
+ this.handlers.set(type, /* @__PURE__ */ new Set());
4443
3994
  }
4444
- return { added, identitiesUpdated };
4445
- });
3995
+ this.handlers.get(type).add(handler);
3996
+ }
3997
+ /** Subscribe to ALL events */
3998
+ onAny(handler) {
3999
+ this.globalHandlers.add(handler);
4000
+ }
4001
+ /** Unsubscribe from a specific event type */
4002
+ off(type, handler) {
4003
+ this.handlers.get(type)?.delete(handler);
4004
+ }
4005
+ /** Unsubscribe from ALL events */
4006
+ offAny(handler) {
4007
+ this.globalHandlers.delete(handler);
4008
+ }
4009
+ /** Remove all listeners */
4010
+ clear() {
4011
+ this.handlers.clear();
4012
+ this.globalHandlers.clear();
4013
+ }
4014
+ };
4015
+ var orgBus = new StateBus();
4016
+
4017
+ // src/lib/memory-write-governor.ts
4018
+ import { createHash } from "crypto";
4019
+
4020
+ // src/lib/store.ts
4021
+ var INIT_MAX_RETRIES = 3;
4022
+ var INIT_RETRY_DELAY_MS = 1e3;
4023
+ function isBusyError2(err) {
4024
+ if (err instanceof Error) {
4025
+ const msg = err.message.toLowerCase();
4026
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
4027
+ }
4028
+ return false;
4446
4029
  }
4447
- async function cloudPushBlob(route, data, metaKey, config) {
4448
- if (data.length === 0) return { ok: true };
4449
- assertSecureEndpoint(config.endpoint);
4450
- const json = JSON.stringify(data);
4451
- const version = Buffer.from(json).length;
4030
+ async function retryOnBusy2(fn, label) {
4031
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
4032
+ try {
4033
+ return await fn();
4034
+ } catch (err) {
4035
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
4036
+ process.stderr.write(
4037
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
4038
+ `
4039
+ );
4040
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
4041
+ }
4042
+ }
4043
+ throw new Error("unreachable");
4044
+ }
4045
+ var _pendingRecords = [];
4046
+ var _batchSize = 20;
4047
+ var _flushIntervalMs = 1e4;
4048
+ var _flushTimer = null;
4049
+ var _flushing = false;
4050
+ var _nextVersion = 1;
4051
+ async function initStore(options) {
4052
+ if (_flushTimer !== null) {
4053
+ clearInterval(_flushTimer);
4054
+ _flushTimer = null;
4055
+ }
4056
+ _pendingRecords = [];
4057
+ _flushing = false;
4058
+ _batchSize = options?.batchSize ?? 20;
4059
+ _flushIntervalMs = options?.flushIntervalMs ?? 1e4;
4060
+ let dbPath = options?.dbPath;
4061
+ if (!dbPath) {
4062
+ const config = await loadConfig();
4063
+ dbPath = config.dbPath;
4064
+ }
4065
+ let masterKey = options?.masterKey ?? null;
4066
+ if (!masterKey) {
4067
+ masterKey = await getMasterKey();
4068
+ if (!masterKey) {
4069
+ throw new Error(
4070
+ "No encryption key found. Run /exe-setup to generate one."
4071
+ );
4072
+ }
4073
+ }
4074
+ const hexKey = masterKey.toString("hex");
4075
+ await initTurso({
4076
+ dbPath,
4077
+ encryptionKey: hexKey
4078
+ });
4079
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
4452
4080
  try {
4453
- const client = getClient();
4454
- const meta = await client.execute({
4455
- sql: "SELECT value FROM sync_meta WHERE key = ?",
4456
- args: [metaKey]
4457
- });
4458
- const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
4459
- if (version === lastVersion) return { ok: true };
4081
+ const { initDaemonClient: initDaemonClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
4082
+ await initDaemonClient2();
4460
4083
  } catch {
4461
4084
  }
4462
- try {
4463
- const compressed = compress(Buffer.from(json, "utf8"));
4464
- const encrypted = encryptSyncBlob(compressed);
4465
- const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
4466
- method: "POST",
4467
- headers: {
4468
- Authorization: `Bearer ${config.apiKey}`,
4469
- "Content-Type": "application/json",
4470
- "X-Device-Id": loadDeviceId()
4471
- },
4472
- body: JSON.stringify({ blob: encrypted })
4473
- });
4474
- if (resp.ok) {
4475
- try {
4476
- const client = getClient();
4477
- await client.execute({
4478
- sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES (?, ?)",
4479
- args: [metaKey, String(version)]
4480
- });
4481
- } catch {
4482
- }
4085
+ if (!options?.lightweight) {
4086
+ try {
4087
+ const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
4088
+ initShardManager2(hexKey);
4089
+ } catch {
4090
+ }
4091
+ const client = getClient();
4092
+ const vResult = await retryOnBusy2(
4093
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
4094
+ "version-query"
4095
+ );
4096
+ _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
4097
+ try {
4098
+ const { loadGlobalProcedures: loadGlobalProcedures2 } = await Promise.resolve().then(() => (init_global_procedures(), global_procedures_exports));
4099
+ await loadGlobalProcedures2();
4100
+ } catch {
4483
4101
  }
4484
- return { ok: resp.ok };
4485
- } catch (err) {
4486
- logError(`[cloud-sync] PUSH ${route}: ${err instanceof Error ? err.message : String(err)}`);
4487
- return { ok: false };
4488
4102
  }
4489
4103
  }
4490
- async function cloudPullBlob(route, config) {
4491
- assertSecureEndpoint(config.endpoint);
4104
+
4105
+ // src/bin/agentic-semantic-label.ts
4106
+ init_database();
4107
+ init_agentic_ontology();
4108
+
4109
+ // src/lib/agentic-llm-labeler.ts
4110
+ init_agentic_ontology();
4111
+ import OpenAI from "openai";
4112
+ function jsonFromText(text) {
4113
+ const trimmed = text.trim().replace(/^```json\s*/i, "").replace(/^```\s*/i, "").replace(/```$/i, "").trim();
4492
4114
  try {
4493
- const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
4494
- method: "GET",
4495
- headers: {
4496
- Authorization: `Bearer ${config.apiKey}`,
4497
- "X-Device-Id": loadDeviceId()
4498
- }
4499
- });
4500
- if (!resp.ok) return null;
4501
- const data = await resp.json();
4502
- if (!data.blob) return null;
4503
- const compressed = decryptSyncBlob(data.blob);
4504
- const json = decompress(compressed).toString("utf8");
4505
- return JSON.parse(json);
4506
- } catch (err) {
4507
- logError(`[cloud-sync] PULL ${route}: ${err instanceof Error ? err.message : String(err)}`);
4115
+ return JSON.parse(trimmed);
4116
+ } catch {
4508
4117
  return null;
4509
4118
  }
4510
4119
  }
4511
- async function cloudPushGlobalProcedures(config) {
4512
- const client = getClient();
4513
- const result = await client.execute("SELECT * FROM company_procedures LIMIT 1000");
4514
- const rows = result.rows;
4515
- const { ok } = await cloudPushBlob(
4516
- "/sync/push-global-procedures",
4517
- rows,
4518
- "last_company_procedures_push_version",
4519
- config
4520
- );
4521
- return ok;
4522
- }
4523
- async function cloudPullGlobalProcedures(config) {
4524
- const remoteProcs = await cloudPullBlob(
4525
- "/sync/pull-global-procedures",
4526
- config
4527
- );
4528
- if (!remoteProcs || remoteProcs.length === 0) return { pulled: 0 };
4529
- const client = getClient();
4530
- const stmts = remoteProcs.map((p) => ({
4531
- sql: `INSERT INTO company_procedures
4532
- (id, title, content, priority, domain, active, created_at, updated_at)
4533
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
4534
- ON CONFLICT(id) DO UPDATE SET
4535
- title = excluded.title,
4536
- content = excluded.content,
4537
- priority = excluded.priority,
4538
- domain = excluded.domain,
4539
- active = excluded.active,
4540
- updated_at = excluded.updated_at
4541
- WHERE excluded.updated_at > company_procedures.updated_at`,
4542
- args: [
4543
- sqlSafe(p.id),
4544
- sqlSafe(p.title),
4545
- sqlSafe(p.content),
4546
- sqlSafe(p.priority ?? "p0"),
4547
- sqlSafe(p.domain),
4548
- sqlSafe(p.active ?? 1),
4549
- sqlSafe(p.created_at),
4550
- sqlSafe(p.updated_at)
4551
- ]
4552
- }));
4553
- await client.batch(stmts, "write");
4554
- return { pulled: remoteProcs.length };
4555
- }
4556
- async function cloudPushBehaviors(config) {
4557
- const client = getClient();
4558
- const result = await client.execute("SELECT * FROM behaviors LIMIT 10000");
4559
- const rows = result.rows;
4560
- const { ok } = await cloudPushBlob(
4561
- "/sync/push-behaviors",
4562
- rows,
4563
- "last_behaviors_push_version",
4564
- config
4565
- );
4566
- return ok;
4567
- }
4568
- async function cloudPullBehaviors(config) {
4569
- const remoteBehaviors = await cloudPullBlob(
4570
- "/sync/pull-behaviors",
4571
- config
4572
- );
4573
- if (!remoteBehaviors || remoteBehaviors.length === 0) return { pulled: 0 };
4574
- const client = getClient();
4575
- let pulled = 0;
4576
- for (const behavior of remoteBehaviors) {
4577
- const existing = await client.execute({
4578
- sql: `SELECT COUNT(*) as cnt FROM behaviors
4579
- WHERE agent_id = ? AND content = ?`,
4580
- args: [sqlSafe(behavior.agent_id), sqlSafe(behavior.content)]
4581
- });
4582
- if (Number(existing.rows[0]?.cnt) > 0) continue;
4583
- await client.execute({
4584
- sql: `INSERT OR IGNORE INTO behaviors
4585
- (id, agent_id, project_name, domain, content, active, priority, created_at, updated_at)
4586
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4587
- args: [
4588
- sqlSafe(behavior.id),
4589
- sqlSafe(behavior.agent_id),
4590
- sqlSafe(behavior.project_name),
4591
- sqlSafe(behavior.domain),
4592
- sqlSafe(behavior.content),
4593
- sqlSafe(behavior.active ?? 1),
4594
- sqlSafe(behavior.priority ?? "p1"),
4595
- sqlSafe(behavior.created_at),
4596
- sqlSafe(behavior.updated_at)
4597
- ]
4598
- });
4599
- pulled++;
4600
- }
4601
- return { pulled };
4120
+ function stringArray(value, max = 6) {
4121
+ if (!Array.isArray(value)) return [];
4122
+ return value.map(String).map((v) => clean(v, 220)).filter(Boolean).slice(0, max);
4602
4123
  }
4603
- async function cloudPushGraphRAG(config) {
4604
- const client = getClient();
4605
- const [entities, relationships, aliases, entityMems, relMems, hyperedges, hyperedgeNodes] = await Promise.all([
4606
- client.execute("SELECT * FROM entities LIMIT 50000"),
4607
- client.execute("SELECT * FROM relationships LIMIT 50000"),
4608
- client.execute("SELECT * FROM entity_aliases LIMIT 50000"),
4609
- client.execute("SELECT * FROM entity_memories LIMIT 50000"),
4610
- client.execute("SELECT * FROM relationship_memories LIMIT 50000"),
4611
- client.execute("SELECT * FROM hyperedges LIMIT 50000"),
4612
- client.execute("SELECT * FROM hyperedge_nodes LIMIT 50000")
4613
- ]);
4614
- const blob = {
4615
- entities: entities.rows,
4616
- relationships: relationships.rows,
4617
- entity_aliases: aliases.rows,
4618
- entity_memories: entityMems.rows,
4619
- relationship_memories: relMems.rows,
4620
- hyperedges: hyperedges.rows,
4621
- hyperedge_nodes: hyperedgeNodes.rows
4124
+ function normalizeLlmLabel(raw, fallback) {
4125
+ const impact = ["positive", "negative", "neutral", "mixed"].includes(String(raw.impact)) ? String(raw.impact) : fallback.impact;
4126
+ const confidenceRaw = Number(raw.confidence ?? fallback.confidence);
4127
+ return {
4128
+ labeler: "llm",
4129
+ schemaVersion: 1,
4130
+ eventType: String(raw.eventType ?? fallback.eventType),
4131
+ intention: raw.intention == null ? fallback.intention : clean(String(raw.intention), 260),
4132
+ outcome: raw.outcome == null ? fallback.outcome : clean(String(raw.outcome), 260),
4133
+ impact,
4134
+ confidence: Number.isFinite(confidenceRaw) ? Math.max(0, Math.min(1, confidenceRaw)) : fallback.confidence,
4135
+ goals: stringArray(raw.goals).length ? stringArray(raw.goals) : fallback.goals,
4136
+ milestones: stringArray(raw.milestones).length ? stringArray(raw.milestones) : fallback.milestones,
4137
+ problems: stringArray(raw.problems).length ? stringArray(raw.problems) : fallback.problems,
4138
+ decisions: stringArray(raw.decisions).length ? stringArray(raw.decisions) : fallback.decisions,
4139
+ actors: stringArray(raw.actors).length ? stringArray(raw.actors) : fallback.actors,
4140
+ temporalAnchors: stringArray(raw.temporalAnchors, 10).length ? stringArray(raw.temporalAnchors, 10) : fallback.temporalAnchors,
4141
+ successSignals: stringArray(raw.successSignals).length ? stringArray(raw.successSignals) : fallback.successSignals,
4142
+ failureSignals: stringArray(raw.failureSignals).length ? stringArray(raw.failureSignals) : fallback.failureSignals,
4143
+ nextActions: stringArray(raw.nextActions).length ? stringArray(raw.nextActions) : fallback.nextActions,
4144
+ summary: clean(String(raw.summary ?? fallback.summary), 360)
4622
4145
  };
4623
- const { ok } = await cloudPushBlob(
4624
- "/sync/push-graphrag",
4625
- [blob],
4626
- "last_graphrag_push_version",
4627
- config
4628
- );
4629
- return ok;
4630
- }
4631
- async function cloudPullGraphRAG(config) {
4632
- const data = await cloudPullBlob(
4633
- "/sync/pull-graphrag",
4634
- config
4635
- );
4636
- if (!data || data.length === 0) return { pulled: 0 };
4637
- const blob = data[0];
4638
- const client = getClient();
4639
- let pulled = 0;
4640
- if (blob.entities.length > 0) {
4641
- const stmts = blob.entities.map((e) => ({
4642
- sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen, properties)
4643
- VALUES (?, ?, ?, ?, ?, ?)`,
4644
- args: [sqlSafe(e.id), sqlSafe(e.name), sqlSafe(e.type), sqlSafe(e.first_seen), sqlSafe(e.last_seen), sqlSafe(e.properties ?? "{}")]
4645
- }));
4646
- await client.batch(stmts, "write");
4647
- pulled += stmts.length;
4648
- }
4649
- if (blob.relationships.length > 0) {
4650
- const stmts = blob.relationships.map((r) => ({
4651
- sql: `INSERT OR IGNORE INTO relationships
4652
- (id, source_entity_id, target_entity_id, type, weight, timestamp, properties, confidence, confidence_label)
4653
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4654
- args: [
4655
- sqlSafe(r.id),
4656
- sqlSafe(r.source_entity_id),
4657
- sqlSafe(r.target_entity_id),
4658
- sqlSafe(r.type),
4659
- sqlSafe(r.weight ?? 1),
4660
- sqlSafe(r.timestamp),
4661
- sqlSafe(r.properties ?? "{}"),
4662
- sqlSafe(r.confidence ?? 1),
4663
- sqlSafe(r.confidence_label ?? "extracted")
4664
- ]
4665
- }));
4666
- await client.batch(stmts, "write");
4667
- pulled += stmts.length;
4668
- }
4669
- if (blob.entity_aliases.length > 0) {
4670
- const stmts = blob.entity_aliases.map((a) => ({
4671
- sql: `INSERT OR IGNORE INTO entity_aliases (alias, canonical_entity_id) VALUES (?, ?)`,
4672
- args: [sqlSafe(a.alias), sqlSafe(a.canonical_entity_id)]
4673
- }));
4674
- await client.batch(stmts, "write");
4675
- pulled += stmts.length;
4676
- }
4677
- if (blob.entity_memories.length > 0) {
4678
- const stmts = blob.entity_memories.map((em) => ({
4679
- sql: `INSERT OR IGNORE INTO entity_memories (entity_id, memory_id) VALUES (?, ?)`,
4680
- args: [sqlSafe(em.entity_id), sqlSafe(em.memory_id)]
4681
- }));
4682
- await client.batch(stmts, "write");
4683
- pulled += stmts.length;
4684
- }
4685
- if (blob.relationship_memories.length > 0) {
4686
- const stmts = blob.relationship_memories.map((rm) => ({
4687
- sql: `INSERT OR IGNORE INTO relationship_memories (relationship_id, memory_id) VALUES (?, ?)`,
4688
- args: [sqlSafe(rm.relationship_id), sqlSafe(rm.memory_id)]
4689
- }));
4690
- await client.batch(stmts, "write");
4691
- pulled += stmts.length;
4692
- }
4693
- if (blob.hyperedges.length > 0) {
4694
- const stmts = blob.hyperedges.map((h) => ({
4695
- sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
4696
- VALUES (?, ?, ?, ?, ?)`,
4697
- args: [sqlSafe(h.id), sqlSafe(h.label), sqlSafe(h.relation), sqlSafe(h.confidence ?? 1), sqlSafe(h.timestamp)]
4698
- }));
4699
- await client.batch(stmts, "write");
4700
- pulled += stmts.length;
4701
- }
4702
- if (blob.hyperedge_nodes.length > 0) {
4703
- const stmts = blob.hyperedge_nodes.map((hn) => ({
4704
- sql: `INSERT OR IGNORE INTO hyperedge_nodes (hyperedge_id, entity_id) VALUES (?, ?)`,
4705
- args: [sqlSafe(hn.hyperedge_id), sqlSafe(hn.entity_id)]
4706
- }));
4707
- await client.batch(stmts, "write");
4708
- pulled += stmts.length;
4709
- }
4710
- return { pulled };
4711
- }
4712
- async function cloudPushTasks(config) {
4713
- const client = getClient();
4714
- const result = await client.execute("SELECT * FROM tasks LIMIT 10000");
4715
- const rows = result.rows;
4716
- const { ok } = await cloudPushBlob(
4717
- "/sync/push-tasks",
4718
- rows,
4719
- "last_tasks_push_version",
4720
- config
4721
- );
4722
- return ok;
4723
- }
4724
- async function cloudPullTasks(config) {
4725
- const remoteTasks = await cloudPullBlob(
4726
- "/sync/pull-tasks",
4727
- config
4728
- );
4729
- if (!remoteTasks || remoteTasks.length === 0) return { pulled: 0 };
4730
- const client = getClient();
4731
- const stmts = remoteTasks.map((t) => ({
4732
- sql: `INSERT OR IGNORE INTO tasks
4733
- (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, created_at, updated_at,
4734
- blocked_by, parent_task_id, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at)
4735
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4736
- args: [
4737
- sqlSafe(t.id),
4738
- sqlSafe(t.title),
4739
- sqlSafe(t.assigned_to),
4740
- sqlSafe(t.assigned_by),
4741
- sqlSafe(t.project_name),
4742
- sqlSafe(t.priority ?? "p1"),
4743
- sqlSafe(t.status ?? "open"),
4744
- sqlSafe(t.task_file),
4745
- sqlSafe(t.created_at),
4746
- sqlSafe(t.updated_at),
4747
- sqlSafe(t.blocked_by),
4748
- sqlSafe(t.parent_task_id),
4749
- sqlSafe(t.budget_tokens),
4750
- sqlSafe(t.budget_fallback_model),
4751
- sqlSafe(t.tokens_used ?? 0),
4752
- sqlSafe(t.tokens_warned_at)
4753
- ]
4754
- }));
4755
- await client.batch(stmts, "write");
4756
- return { pulled: remoteTasks.length };
4757
- }
4758
- async function cloudPushConversations(config) {
4759
- const client = getClient();
4760
- const result = await client.execute("SELECT * FROM conversations LIMIT 50000");
4761
- const rows = result.rows;
4762
- const { ok } = await cloudPushBlob(
4763
- "/sync/push-conversations",
4764
- rows,
4765
- "last_conversations_push_version",
4766
- config
4767
- );
4768
- return ok;
4769
- }
4770
- async function cloudPullConversations(config) {
4771
- const remoteConvos = await cloudPullBlob(
4772
- "/sync/pull-conversations",
4773
- config
4774
- );
4775
- if (!remoteConvos || remoteConvos.length === 0) return { pulled: 0 };
4776
- const client = getClient();
4777
- const stmts = remoteConvos.map((c) => ({
4778
- sql: `INSERT OR IGNORE INTO conversations
4779
- (id, platform, external_id, sender_id, sender_name, sender_phone, sender_email,
4780
- recipient_id, channel_id, thread_id, reply_to_id, content_text, content_media,
4781
- content_metadata, agent_response, agent_name, timestamp, ingested_at)
4782
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4783
- args: [
4784
- sqlSafe(c.id),
4785
- sqlSafe(c.platform),
4786
- sqlSafe(c.external_id),
4787
- sqlSafe(c.sender_id),
4788
- sqlSafe(c.sender_name),
4789
- sqlSafe(c.sender_phone),
4790
- sqlSafe(c.sender_email),
4791
- sqlSafe(c.recipient_id),
4792
- sqlSafe(c.channel_id),
4793
- sqlSafe(c.thread_id),
4794
- sqlSafe(c.reply_to_id),
4795
- sqlSafe(c.content_text),
4796
- sqlSafe(c.content_media),
4797
- sqlSafe(c.content_metadata),
4798
- sqlSafe(c.agent_response),
4799
- sqlSafe(c.agent_name),
4800
- sqlSafe(c.timestamp),
4801
- sqlSafe(c.ingested_at)
4802
- ]
4803
- }));
4804
- await client.batch(stmts, "write");
4805
- return { pulled: remoteConvos.length };
4806
4146
  }
4807
- async function cloudPushDocuments(config) {
4808
- const client = getClient();
4809
- const [workspaces, documents] = await Promise.all([
4810
- client.execute("SELECT * FROM workspaces LIMIT 1000"),
4811
- client.execute("SELECT * FROM documents LIMIT 10000")
4812
- ]);
4813
- const blob = {
4814
- workspaces: workspaces.rows,
4815
- documents: documents.rows
4816
- };
4817
- const { ok } = await cloudPushBlob(
4818
- "/sync/push-documents",
4819
- [blob],
4820
- "last_documents_push_version",
4821
- config
4822
- );
4823
- return ok;
4824
- }
4825
- async function cloudPullDocuments(config) {
4826
- const data = await cloudPullBlob(
4827
- "/sync/pull-documents",
4828
- config
4829
- );
4830
- if (!data || data.length === 0) return { pulled: 0 };
4831
- const blob = data[0];
4832
- const client = getClient();
4833
- let pulled = 0;
4834
- if (blob.workspaces.length > 0) {
4835
- const stmts = blob.workspaces.map((w) => ({
4836
- sql: `INSERT OR IGNORE INTO workspaces (id, slug, name, owner_agent_id, created_at, metadata)
4837
- VALUES (?, ?, ?, ?, ?, ?)`,
4838
- args: [sqlSafe(w.id), sqlSafe(w.slug), sqlSafe(w.name), sqlSafe(w.owner_agent_id), sqlSafe(w.created_at), sqlSafe(w.metadata)]
4839
- }));
4840
- await client.batch(stmts, "write");
4841
- pulled += stmts.length;
4842
- }
4843
- if (blob.documents.length > 0) {
4844
- const stmts = blob.documents.map((d) => ({
4845
- sql: `INSERT OR IGNORE INTO documents
4846
- (id, workspace_id, filename, mime, source_type, user_id, uploaded_at, metadata)
4847
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
4848
- args: [
4849
- sqlSafe(d.id),
4850
- sqlSafe(d.workspace_id),
4851
- sqlSafe(d.filename),
4852
- sqlSafe(d.mime),
4853
- sqlSafe(d.source_type),
4854
- sqlSafe(d.user_id),
4855
- sqlSafe(d.uploaded_at),
4856
- sqlSafe(d.metadata)
4857
- ]
4858
- }));
4859
- await client.batch(stmts, "write");
4860
- pulled += stmts.length;
4861
- }
4862
- return { pulled };
4863
- }
4864
- var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, CLOUD_RELINK_REQUIRED_MESSAGE, ROSTER_DELETIONS_PATH;
4865
- var init_cloud_sync = __esm({
4866
- "src/lib/cloud-sync.ts"() {
4867
- "use strict";
4868
- init_database();
4869
- init_crypto();
4870
- init_compress();
4871
- init_license();
4872
- init_config();
4873
- init_crdt_sync();
4874
- init_employees();
4875
- init_secure_files();
4876
- LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
4877
- FETCH_TIMEOUT_MS = 3e4;
4878
- PUSH_BATCH_SIZE = 5e3;
4879
- ROSTER_LOCK_PATH = path11.join(EXE_AI_DIR, "roster-merge.lock");
4880
- LOCK_STALE_MS = 3e4;
4881
- _pgPromise = null;
4882
- _pgFailed = false;
4883
- CLOUD_RELINK_REQUIRED_MESSAGE = "[cloud-sync] Paused after key rotation. Run `exe-os cloud relink --dry-run` for the safe relink checklist.";
4884
- ROSTER_DELETIONS_PATH = path11.join(EXE_AI_DIR, "roster-deletions.json");
4885
- }
4886
- });
4147
+ async function labelMemoryWithLlm(row, opts = {}) {
4148
+ const fallback = inferSemanticLabel(row);
4149
+ const apiKey = opts.apiKey ?? process.env.OPENAI_API_KEY ?? process.env.API_ROUTER_KEY;
4150
+ const baseURL = opts.baseUrl ?? process.env.OPENAI_BASE_URL ?? process.env.API_ROUTER_URL;
4151
+ const model = opts.model ?? process.env.EXE_AGENTIC_LABEL_MODEL;
4152
+ if (!apiKey || !model) return fallback;
4153
+ const client = new OpenAI({ apiKey, baseURL: baseURL || void 0 });
4154
+ const prompt = `Label this agent-memory event for agentic memory. Return ONLY valid JSON with keys:
4155
+ {
4156
+ "eventType": "tool_action|milestone|problem|error|decision|goal_signal|memory_observation|memory_card",
4157
+ "intention": string|null,
4158
+ "outcome": string|null,
4159
+ "impact": "positive|negative|neutral|mixed",
4160
+ "confidence": number between 0 and 1,
4161
+ "goals": string[],
4162
+ "milestones": string[],
4163
+ "problems": string[],
4164
+ "decisions": string[],
4165
+ "actors": string[],
4166
+ "temporalAnchors": string[],
4167
+ "successSignals": string[],
4168
+ "failureSignals": string[],
4169
+ "nextActions": string[],
4170
+ "summary": string
4171
+ }
4172
+ Focus on explicit goals, chronology, intended action, actual outcome, and whether the event moved the goal forward.
4887
4173
 
4888
- // src/bin/exe-link.ts
4889
- init_keychain();
4890
- import { createInterface } from "readline";
4891
-
4892
- // src/lib/is-main.ts
4893
- import { realpathSync } from "fs";
4894
- import { fileURLToPath } from "url";
4895
- function isMainModule(importMetaUrl) {
4896
- if (process.argv[1] == null) return false;
4897
- if (process.argv[1].includes("mcp/server")) return false;
4174
+ Metadata: ${JSON.stringify({ agent_id: row.agent_id, role: row.agent_role, project: row.project_name, session: row.session_id, timestamp: row.timestamp, tool: row.tool_name, has_error: row.has_error })}
4175
+ Text: ${clean(row.raw_text, 5e3)}`;
4898
4176
  try {
4899
- const scriptPath = realpathSync(process.argv[1]);
4900
- const modulePath = realpathSync(fileURLToPath(importMetaUrl));
4901
- return scriptPath === modulePath;
4177
+ const response = await client.chat.completions.create({
4178
+ model,
4179
+ messages: [
4180
+ { role: "system", content: "You are a precise ontology labeler. Output compact JSON only." },
4181
+ { role: "user", content: prompt }
4182
+ ],
4183
+ max_tokens: opts.maxTokens ?? 900,
4184
+ response_format: { type: "json_object" }
4185
+ });
4186
+ const raw = jsonFromText(response.choices[0]?.message?.content ?? "");
4187
+ return raw ? normalizeLlmLabel(raw, fallback) : fallback;
4902
4188
  } catch {
4903
- return importMetaUrl === `file://${process.argv[1]}` || importMetaUrl === new URL(process.argv[1], "file://").href;
4904
- }
4905
- }
4906
-
4907
- // src/bin/exe-link.ts
4908
- function isInteractiveTerminal() {
4909
- return Boolean(process.stdin.isTTY && process.stdout.isTTY && !process.env.CI);
4910
- }
4911
- function assertLocalTerminalExportAllowed() {
4912
- if (!isInteractiveTerminal()) {
4913
- throw new Error(
4914
- "Refusing to reveal the recovery phrase outside a local interactive terminal.\nRun this yourself in Terminal: exe-os link export --local-terminal-only"
4915
- );
4189
+ return fallback;
4916
4190
  }
4917
4191
  }
4918
- async function printStatus() {
4919
- const key = await getMasterKey();
4920
- if (key) {
4921
- console.log("Master key: FOUND");
4922
- console.log(`Key length: ${key.length} bytes`);
4923
- console.log("Recovery export: available from a local interactive terminal");
4924
- } else {
4925
- console.log("Master key: MISSING");
4926
- console.log("Checked normal storage paths: OS keychain/keytar/file fallback");
4927
- process.exitCode = 1;
4192
+ async function resolveClient(client) {
4193
+ if (client) return client;
4194
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
4195
+ return getClient2();
4196
+ }
4197
+ async function upsertSemanticLabel(row, label, client) {
4198
+ const db = await resolveClient(client);
4199
+ const eventId = stableId("event", row.id);
4200
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4201
+ await db.execute({
4202
+ sql: `INSERT INTO agent_semantic_labels
4203
+ (id, source_memory_id, event_id, labeler, schema_version, confidence, labels, created_at, updated_at)
4204
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
4205
+ ON CONFLICT(id) DO UPDATE SET confidence = excluded.confidence, labels = excluded.labels,
4206
+ updated_at = excluded.updated_at`,
4207
+ args: [
4208
+ stableId("semantic", row.id, label.labeler, label.schemaVersion),
4209
+ row.id,
4210
+ eventId,
4211
+ label.labeler,
4212
+ label.schemaVersion,
4213
+ label.confidence,
4214
+ JSON.stringify(label),
4215
+ now,
4216
+ now
4217
+ ]
4218
+ });
4219
+ if (label.labeler === "llm") {
4220
+ await db.execute({
4221
+ sql: `UPDATE agent_events SET intention = COALESCE(?, intention), outcome = COALESCE(?, outcome),
4222
+ impact = COALESCE(?, impact)
4223
+ WHERE id = ?`,
4224
+ args: [label.intention, label.outcome, label.impact, eventId]
4225
+ });
4928
4226
  }
4929
4227
  }
4930
- async function exportKey(argv = process.argv.slice(2)) {
4931
- const key = await getMasterKey();
4932
- if (!key) {
4933
- console.error("No master key found. Run /exe-setup first or import your recovery phrase with exe-os link import.");
4934
- process.exit(1);
4935
- }
4936
- const showFull = argv.includes("--show-full") || argv.includes("--local-terminal-only");
4937
- if (showFull) {
4938
- assertLocalTerminalExportAllowed();
4939
- }
4940
- const mnemonic = await exportMnemonic(key);
4941
- console.log("Your 24-word recovery phrase:\n");
4942
- if (showFull) {
4943
- console.log(` ${mnemonic}
4228
+
4229
+ // src/bin/agentic-semantic-label.ts
4230
+ function arg(name) {
4231
+ const i = process.argv.indexOf(name);
4232
+ return i >= 0 ? process.argv[i + 1] : void 0;
4233
+ }
4234
+ async function main() {
4235
+ const limit = Number(arg("--limit") ?? "100");
4236
+ const project = arg("--project");
4237
+ const llm = process.argv.includes("--llm");
4238
+ await initStore();
4239
+ const db = getClient();
4240
+ const where = project ? "WHERE project_name = ?" : "";
4241
+ const rows = await db.execute({
4242
+ sql: `SELECT id, agent_id, agent_role, session_id, timestamp, tool_name, project_name, has_error, raw_text,
4243
+ version, task_id, intent, outcome, domain, trajectory
4244
+ FROM memories ${where}
4245
+ ORDER BY timestamp DESC
4246
+ LIMIT ?`,
4247
+ args: project ? [project, limit] : [limit]
4248
+ });
4249
+ let count = 0;
4250
+ for (const row of rows.rows) {
4251
+ const label = llm ? await labelMemoryWithLlm(row) : inferSemanticLabel(row);
4252
+ await upsertSemanticLabel(row, label, db);
4253
+ count++;
4254
+ if (count % 25 === 0) process.stderr.write(`[agentic-semantic-label] labeled ${count}
4944
4255
  `);
4945
- const { markKeyBackupConfirmed: markKeyBackupConfirmed2 } = await Promise.resolve().then(() => (init_key_backup_status(), key_backup_status_exports));
4946
- markKeyBackupConfirmed2("exe-os link export --local-terminal-only");
4947
- } else {
4948
- const words = mnemonic.split(" ");
4949
- const masked = words.length > 4 ? [...words.slice(0, 2), ...words.slice(2, -2).map(() => "****"), ...words.slice(-2)].join(" ") : mnemonic;
4950
- console.log(` ${masked}
4256
+ }
4257
+ process.stderr.write(`[agentic-semantic-label] Complete: ${count} memories labeled (${llm ? "llm-if-configured" : "deterministic"}).
4951
4258
  `);
4952
- console.log("To reveal full phrase locally: exe-os link export --local-terminal-only\n");
4953
- }
4954
- console.log("Write this down and enter it on your new device with /exe-link import.");
4955
- console.log("Anyone with this phrase can decrypt your memories.");
4956
- console.log("\u26A0 Clear your terminal history after copying.");
4957
- }
4958
- async function importKey() {
4959
- const rl = createInterface({ input: process.stdin, output: process.stdout });
4960
- const mnemonic = await new Promise((resolve) => {
4961
- rl.question("Paste your 24-word recovery phrase: ", (answer) => {
4962
- rl.close();
4963
- resolve(answer.trim());
4964
- });
4965
- });
4966
- try {
4967
- const key = await importMnemonic(mnemonic);
4968
- await setMasterKey(key);
4969
- console.log("Master key imported and stored securely.");
4970
- try {
4971
- const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
4972
- const { initSyncCrypto: initSyncCrypto2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
4973
- const { cloudPullRoster: cloudPullRoster2 } = await Promise.resolve().then(() => (init_cloud_sync(), cloud_sync_exports));
4974
- const config = await loadConfig2();
4975
- if (config.cloud?.apiKey && config.cloud?.endpoint) {
4976
- initSyncCrypto2(key);
4977
- const result = await cloudPullRoster2({ apiKey: config.cloud.apiKey, endpoint: config.cloud.endpoint });
4978
- if (result.added > 0) {
4979
- console.log(`Pulled ${result.added} employee(s) from Exe Cloud.`);
4980
- }
4981
- }
4982
- } catch {
4983
- }
4984
- console.log("Run /exe-setup to configure sync and download the embedding model.");
4985
- } catch (err) {
4986
- console.error(err instanceof Error ? err.message : String(err));
4987
- process.exit(1);
4988
- }
4989
- }
4990
- async function main(argv = process.argv.slice(2)) {
4991
- const mode = argv[0];
4992
- if (mode === "status") {
4993
- await printStatus();
4994
- } else if (mode === "export") {
4995
- await exportKey(argv);
4996
- } else if (mode === "import") {
4997
- await importKey();
4998
- } else {
4999
- console.error("Usage:");
5000
- console.error(" exe-link status \u2014 verify key availability without printing it");
5001
- console.error(" exe-link export \u2014 show masked 24-word mnemonic");
5002
- console.error(" exe-link export --local-terminal-only \u2014 reveal full phrase in local Terminal only");
5003
- console.error(" exe-link import \u2014 paste mnemonic to import key on new device");
5004
- process.exit(1);
5005
- }
5006
- }
5007
- if (isMainModule(import.meta.url) && process.argv[1]?.includes("exe-link")) {
5008
- main().catch((err) => {
5009
- console.error(err instanceof Error ? err.message : String(err));
5010
- process.exit(1);
5011
- });
4259
+ process.exit(0);
5012
4260
  }
5013
- export {
5014
- assertLocalTerminalExportAllowed,
5015
- isInteractiveTerminal,
5016
- main
5017
- };
4261
+ main().catch((err) => {
4262
+ process.stderr.write(`[agentic-semantic-label] FATAL: ${err instanceof Error ? err.message : String(err)}
4263
+ `);
4264
+ process.exit(1);
4265
+ });