@askexenow/exe-os 0.9.65 → 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 (113) 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} +1532 -2173
  6. package/dist/bin/backfill-conversations.js +528 -20
  7. package/dist/bin/backfill-responses.js +528 -20
  8. package/dist/bin/backfill-vectors.js +255 -20
  9. package/dist/bin/bulk-sync-postgres.js +4876 -0
  10. package/dist/bin/cleanup-stale-review-tasks.js +529 -21
  11. package/dist/bin/cli.js +3471 -1491
  12. package/dist/bin/exe-agent-config.js +4 -0
  13. package/dist/bin/exe-agent.js +16 -0
  14. package/dist/bin/exe-assign.js +528 -20
  15. package/dist/bin/exe-boot.js +492 -54
  16. package/dist/bin/exe-call.js +16 -0
  17. package/dist/bin/exe-cloud.js +7415 -518
  18. package/dist/bin/exe-dispatch.js +540 -22
  19. package/dist/bin/exe-doctor.js +3404 -1225
  20. package/dist/bin/exe-export-behaviors.js +542 -24
  21. package/dist/bin/exe-forget.js +529 -21
  22. package/dist/bin/exe-gateway.js +595 -25
  23. package/dist/bin/exe-heartbeat.js +541 -24
  24. package/dist/bin/exe-kill.js +529 -21
  25. package/dist/bin/exe-launch-agent.js +2334 -1067
  26. package/dist/bin/exe-new-employee.js +324 -166
  27. package/dist/bin/exe-pending-messages.js +529 -21
  28. package/dist/bin/exe-pending-notifications.js +529 -21
  29. package/dist/bin/exe-pending-reviews.js +529 -21
  30. package/dist/bin/exe-rename.js +529 -21
  31. package/dist/bin/exe-review.js +529 -21
  32. package/dist/bin/exe-search.js +542 -24
  33. package/dist/bin/exe-session-cleanup.js +540 -22
  34. package/dist/bin/exe-settings.js +14 -0
  35. package/dist/bin/exe-start-codex.js +817 -144
  36. package/dist/bin/exe-start-opencode.js +776 -80
  37. package/dist/bin/exe-status.js +529 -21
  38. package/dist/bin/exe-team.js +529 -21
  39. package/dist/bin/git-sweep.js +540 -22
  40. package/dist/bin/graph-backfill.js +580 -21
  41. package/dist/bin/graph-export.js +529 -21
  42. package/dist/bin/graph-layer-benchmark.js +109 -0
  43. package/dist/bin/install.js +420 -289
  44. package/dist/bin/intercom-check.js +540 -22
  45. package/dist/bin/postgres-agentic-reflection-backfill.js +187 -0
  46. package/dist/bin/postgres-agentic-semantic-backfill.js +237 -0
  47. package/dist/bin/scan-tasks.js +540 -22
  48. package/dist/bin/setup.js +790 -206
  49. package/dist/bin/shard-migrate.js +528 -20
  50. package/dist/bin/update.js +4 -0
  51. package/dist/gateway/index.js +593 -23
  52. package/dist/hooks/bug-report-worker.js +651 -64
  53. package/dist/hooks/codex-stop-task-finalizer.js +540 -22
  54. package/dist/hooks/commit-complete.js +540 -22
  55. package/dist/hooks/error-recall.js +542 -24
  56. package/dist/hooks/exe-heartbeat-hook.js +4 -0
  57. package/dist/hooks/ingest-worker.js +4 -0
  58. package/dist/hooks/ingest.js +539 -22
  59. package/dist/hooks/instructions-loaded.js +529 -21
  60. package/dist/hooks/notification.js +529 -21
  61. package/dist/hooks/post-compact.js +529 -21
  62. package/dist/hooks/post-tool-combined.js +543 -25
  63. package/dist/hooks/pre-compact.js +772 -127
  64. package/dist/hooks/pre-tool-use.js +529 -21
  65. package/dist/hooks/prompt-submit.js +543 -25
  66. package/dist/hooks/session-end.js +673 -140
  67. package/dist/hooks/session-start.js +662 -26
  68. package/dist/hooks/stop.js +540 -23
  69. package/dist/hooks/subagent-stop.js +529 -21
  70. package/dist/hooks/summary-worker.js +571 -126
  71. package/dist/index.js +593 -23
  72. package/dist/lib/agent-config.js +4 -0
  73. package/dist/lib/cloud-sync.js +408 -47
  74. package/dist/lib/config.js +25 -1
  75. package/dist/lib/consolidation.js +5 -1
  76. package/dist/lib/database.js +128 -0
  77. package/dist/lib/db-daemon-client.js +4 -0
  78. package/dist/lib/db.js +128 -0
  79. package/dist/lib/device-registry.js +128 -0
  80. package/dist/lib/embedder.js +25 -1
  81. package/dist/lib/employee-templates.js +16 -0
  82. package/dist/lib/employees.js +4 -0
  83. package/dist/lib/exe-daemon-client.js +4 -0
  84. package/dist/lib/exe-daemon.js +3158 -930
  85. package/dist/lib/hybrid-search.js +542 -24
  86. package/dist/lib/identity.js +7 -0
  87. package/dist/lib/keychain.js +178 -22
  88. package/dist/lib/license.js +4 -0
  89. package/dist/lib/messaging.js +7 -0
  90. package/dist/lib/reminders.js +7 -0
  91. package/dist/lib/schedules.js +255 -20
  92. package/dist/lib/skill-learning.js +28 -1
  93. package/dist/lib/status-brief.js +39 -0
  94. package/dist/lib/store.js +528 -20
  95. package/dist/lib/task-router.js +4 -0
  96. package/dist/lib/tasks.js +28 -1
  97. package/dist/lib/tmux-routing.js +28 -1
  98. package/dist/lib/token-spend.js +7 -0
  99. package/dist/mcp/server.js +2739 -813
  100. package/dist/mcp/tools/complete-reminder.js +7 -0
  101. package/dist/mcp/tools/create-reminder.js +7 -0
  102. package/dist/mcp/tools/create-task.js +28 -1
  103. package/dist/mcp/tools/deactivate-behavior.js +7 -0
  104. package/dist/mcp/tools/list-reminders.js +7 -0
  105. package/dist/mcp/tools/list-tasks.js +7 -0
  106. package/dist/mcp/tools/send-message.js +7 -0
  107. package/dist/mcp/tools/update-task.js +28 -1
  108. package/dist/runtime/index.js +540 -22
  109. package/dist/tui/App.js +618 -29
  110. package/package.json +9 -5
  111. package/src/commands/exe/cloud.md +11 -8
  112. package/stack.release.json +3 -3
  113. package/src/commands/exe/link.md +0 -17
@@ -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: readFileSync8 } = __require("fs");
141
- return readFileSync8("/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
@@ -460,14 +188,19 @@ function normalizeAutoUpdate(raw) {
460
188
  const userAU = raw.autoUpdate ?? {};
461
189
  raw.autoUpdate = { ...defaultAU, ...userAU };
462
190
  }
191
+ function normalizeOrchestration(raw) {
192
+ const defaultOrg = DEFAULT_CONFIG.orchestration;
193
+ const userOrg = raw.orchestration ?? {};
194
+ raw.orchestration = { ...defaultOrg, ...userOrg };
195
+ }
463
196
  async function loadConfig() {
464
197
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
465
198
  await ensurePrivateDir(dir);
466
- const configPath = path2.join(dir, "config.json");
467
- if (!existsSync3(configPath)) {
468
- 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") };
469
202
  }
470
- const raw = await readFile2(configPath, "utf-8");
203
+ const raw = await readFile(configPath, "utf-8");
471
204
  try {
472
205
  let parsed = JSON.parse(raw);
473
206
  parsed = migrateLegacyConfig(parsed);
@@ -476,7 +209,7 @@ async function loadConfig() {
476
209
  process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
477
210
  `);
478
211
  try {
479
- await writeFile2(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
212
+ await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
480
213
  await enforcePrivateFile(configPath);
481
214
  } catch {
482
215
  }
@@ -484,53 +217,18 @@ async function loadConfig() {
484
217
  normalizeScalingRoadmap(migratedCfg);
485
218
  normalizeSessionLifecycle(migratedCfg);
486
219
  normalizeAutoUpdate(migratedCfg);
487
- const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
220
+ normalizeOrchestration(migratedCfg);
221
+ const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
488
222
  if (config.dbPath.startsWith("~")) {
489
- 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;
490
228
  }
491
229
  return config;
492
230
  } catch {
493
- return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
494
- }
495
- }
496
- function loadConfigSync() {
497
- const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
498
- const configPath = path2.join(dir, "config.json");
499
- if (!existsSync3(configPath)) {
500
- return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
501
- }
502
- try {
503
- const raw = readFileSync(configPath, "utf-8");
504
- let parsed = JSON.parse(raw);
505
- parsed = migrateLegacyConfig(parsed);
506
- const { config: migratedCfg } = migrateConfig(parsed);
507
- normalizeScalingRoadmap(migratedCfg);
508
- normalizeSessionLifecycle(migratedCfg);
509
- normalizeAutoUpdate(migratedCfg);
510
- return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
511
- } catch {
512
- return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
513
- }
514
- }
515
- async function saveConfig(config) {
516
- const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
517
- await ensurePrivateDir(dir);
518
- const configPath = path2.join(dir, "config.json");
519
- await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n");
520
- await enforcePrivateFile(configPath);
521
- }
522
- async function loadConfigFrom(configPath) {
523
- const raw = await readFile2(configPath, "utf-8");
524
- try {
525
- let parsed = JSON.parse(raw);
526
- parsed = migrateLegacyConfig(parsed);
527
- const { config: migratedCfg } = migrateConfig(parsed);
528
- normalizeScalingRoadmap(migratedCfg);
529
- normalizeSessionLifecycle(migratedCfg);
530
- normalizeAutoUpdate(migratedCfg);
531
- return { ...DEFAULT_CONFIG, ...migratedCfg };
532
- } catch {
533
- return { ...DEFAULT_CONFIG };
231
+ return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
534
232
  }
535
233
  }
536
234
  var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
@@ -539,10 +237,10 @@ var init_config = __esm({
539
237
  "use strict";
540
238
  init_secure_files();
541
239
  EXE_AI_DIR = resolveDataDir();
542
- DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
543
- MODELS_DIR = path2.join(EXE_AI_DIR, "models");
544
- CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
545
- 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");
546
244
  CURRENT_CONFIG_VERSION = 1;
547
245
  DEFAULT_CONFIG = {
548
246
  config_version: CURRENT_CONFIG_VERSION,
@@ -599,6 +297,10 @@ var init_config = __esm({
599
297
  checkOnBoot: true,
600
298
  autoInstall: false,
601
299
  checkIntervalMs: 24 * 60 * 60 * 1e3
300
+ },
301
+ orchestration: {
302
+ phase: "phase_1_coo",
303
+ phaseSetBy: "default"
602
304
  }
603
305
  };
604
306
  CONFIG_MIGRATIONS = [
@@ -614,126 +316,12 @@ var init_config = __esm({
614
316
  }
615
317
  });
616
318
 
617
- // src/lib/crypto.ts
618
- var crypto_exports = {};
619
- __export(crypto_exports, {
620
- decryptSyncBlob: () => decryptSyncBlob,
621
- encryptSyncBlob: () => encryptSyncBlob,
622
- initSyncCrypto: () => initSyncCrypto,
623
- isSyncCryptoInitialized: () => isSyncCryptoInitialized
624
- });
625
- import crypto from "crypto";
626
- function initSyncCrypto(masterKey) {
627
- if (masterKey.length !== 32) {
628
- throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
629
- }
630
- _syncKey = Buffer.from(
631
- crypto.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
632
- );
633
- }
634
- function isSyncCryptoInitialized() {
635
- return _syncKey !== null;
636
- }
637
- function requireSyncKey() {
638
- if (!_syncKey) {
639
- throw new Error("Sync crypto not initialized. Call initSyncCrypto(masterKey) first.");
640
- }
641
- return _syncKey;
642
- }
643
- function encryptSyncBlob(data) {
644
- const key = requireSyncKey();
645
- const iv = crypto.randomBytes(IV_LENGTH);
646
- const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
647
- const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
648
- const tag = cipher.getAuthTag();
649
- return Buffer.concat([iv, encrypted, tag]).toString("base64");
650
- }
651
- function decryptSyncBlob(ciphertext) {
652
- const key = requireSyncKey();
653
- const combined = Buffer.from(ciphertext, "base64");
654
- if (combined.length < IV_LENGTH + TAG_LENGTH) {
655
- throw new Error("Sync blob too short to contain IV + tag");
656
- }
657
- const iv = combined.subarray(0, IV_LENGTH);
658
- const tag = combined.subarray(combined.length - TAG_LENGTH);
659
- const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
660
- const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
661
- decipher.setAuthTag(tag);
662
- return Buffer.concat([decipher.update(encrypted), decipher.final()]);
663
- }
664
- var ALGORITHM, IV_LENGTH, TAG_LENGTH, SYNC_HKDF_INFO, _syncKey;
665
- var init_crypto = __esm({
666
- "src/lib/crypto.ts"() {
667
- "use strict";
668
- ALGORITHM = "aes-256-gcm";
669
- IV_LENGTH = 12;
670
- TAG_LENGTH = 16;
671
- SYNC_HKDF_INFO = "exe-mem-sync-v2";
672
- _syncKey = null;
673
- }
674
- });
675
-
676
- // src/lib/db-retry.ts
677
- function isBusyError(err) {
678
- if (err instanceof Error) {
679
- const msg = err.message.toLowerCase();
680
- return msg.includes("sqlite_busy") || msg.includes("database is locked");
681
- }
682
- return false;
683
- }
684
- function delay(ms) {
685
- return new Promise((resolve) => setTimeout(resolve, ms));
686
- }
687
- async function retryOnBusy(fn, label) {
688
- let lastError;
689
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
690
- try {
691
- return await fn();
692
- } catch (err) {
693
- lastError = err;
694
- if (!isBusyError(err) || attempt === MAX_RETRIES) {
695
- throw err;
696
- }
697
- const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
698
- const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
699
- process.stderr.write(
700
- `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
701
- `
702
- );
703
- await delay(backoff + jitter);
704
- }
705
- }
706
- throw lastError;
707
- }
708
- function wrapWithRetry(client) {
709
- return new Proxy(client, {
710
- get(target, prop, receiver) {
711
- if (prop === "execute") {
712
- return (sql) => retryOnBusy(() => target.execute(sql), "execute");
713
- }
714
- if (prop === "batch") {
715
- return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
716
- }
717
- return Reflect.get(target, prop, receiver);
718
- }
719
- });
720
- }
721
- var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
722
- var init_db_retry = __esm({
723
- "src/lib/db-retry.ts"() {
724
- "use strict";
725
- MAX_RETRIES = 5;
726
- BASE_DELAY_MS = 250;
727
- MAX_JITTER_MS = 400;
728
- }
729
- });
730
-
731
319
  // src/lib/employees.ts
732
- import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
733
- import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
734
- import { execSync as execSync2 } from "child_process";
735
- import path3 from "path";
736
- 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";
737
325
  function normalizeRole(role) {
738
326
  return (role ?? "").trim().toLowerCase();
739
327
  }
@@ -746,84 +334,29 @@ function getCoordinatorEmployee(employees) {
746
334
  function getCoordinatorName(employees = loadEmployeesSync()) {
747
335
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
748
336
  }
749
- async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
750
- if (!existsSync4(employeesPath)) {
751
- return [];
752
- }
753
- const raw = await readFile3(employeesPath, "utf-8");
754
- try {
755
- return JSON.parse(raw);
756
- } catch {
757
- return [];
758
- }
759
- }
760
- async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
761
- await mkdir3(path3.dirname(employeesPath), { recursive: true });
762
- await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
763
- }
764
337
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
765
- if (!existsSync4(employeesPath)) return [];
338
+ if (!existsSync3(employeesPath)) return [];
766
339
  try {
767
340
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
768
341
  } catch {
769
342
  return [];
770
343
  }
771
344
  }
772
- function findExeBin() {
773
- try {
774
- return execSync2(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
775
- } catch {
776
- return null;
777
- }
778
- }
779
- function registerBinSymlinks(name) {
780
- const created = [];
781
- const skipped = [];
782
- const errors = [];
783
- const exeBinPath = findExeBin();
784
- if (!exeBinPath) {
785
- errors.push("Could not find 'exe-os' in PATH");
786
- return { created, skipped, errors };
787
- }
788
- const binDir = path3.dirname(exeBinPath);
789
- let target;
790
- try {
791
- target = readlinkSync(exeBinPath);
792
- } catch {
793
- errors.push("Could not read 'exe' symlink");
794
- return { created, skipped, errors };
795
- }
796
- for (const suffix of ["", "-opencode"]) {
797
- const linkName = `${name}${suffix}`;
798
- const linkPath = path3.join(binDir, linkName);
799
- if (existsSync4(linkPath)) {
800
- skipped.push(linkName);
801
- continue;
802
- }
803
- try {
804
- symlinkSync(target, linkPath);
805
- created.push(linkName);
806
- } catch (err) {
807
- errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
808
- }
809
- }
810
- return { created, skipped, errors };
811
- }
812
345
  var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
813
346
  var init_employees = __esm({
814
347
  "src/lib/employees.ts"() {
815
348
  "use strict";
816
349
  init_config();
817
- EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
350
+ EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
818
351
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
819
352
  COORDINATOR_ROLE = "COO";
820
- IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
353
+ IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
821
354
  }
822
355
  });
823
356
 
824
357
  // src/lib/database-adapter.ts
825
- import os4 from "os";
826
- import path4 from "path";
358
+ import os3 from "os";
359
+ import path3 from "path";
827
360
  import { createRequire } from "module";
828
361
  import { pathToFileURL } from "url";
829
362
  function quotedIdentifier(identifier) {
@@ -1134,8 +667,8 @@ async function loadPrismaClient() {
1134
667
  }
1135
668
  return new PrismaClient2();
1136
669
  }
1137
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os4.homedir(), "exe-db");
1138
- const requireFromExeDb = createRequire(path4.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"));
1139
672
  const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1140
673
  const module = await import(pathToFileURL(prismaEntry).href);
1141
674
  const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
@@ -1405,19 +938,10 @@ var init_database_adapter = __esm({
1405
938
  }
1406
939
  });
1407
940
 
1408
- // src/types/memory.ts
1409
- var EMBEDDING_DIM;
1410
- var init_memory = __esm({
1411
- "src/types/memory.ts"() {
1412
- "use strict";
1413
- EMBEDDING_DIM = 1024;
1414
- }
1415
- });
1416
-
1417
941
  // src/lib/daemon-auth.ts
1418
- import crypto2 from "crypto";
1419
- import path5 from "path";
1420
- import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } 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";
1421
945
  function normalizeToken(token) {
1422
946
  if (!token) return null;
1423
947
  const trimmed = token.trim();
@@ -1425,7 +949,7 @@ function normalizeToken(token) {
1425
949
  }
1426
950
  function readDaemonToken() {
1427
951
  try {
1428
- if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
952
+ if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
1429
953
  return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
1430
954
  } catch {
1431
955
  return null;
@@ -1434,7 +958,7 @@ function readDaemonToken() {
1434
958
  function ensureDaemonToken(seed) {
1435
959
  const existing = readDaemonToken();
1436
960
  if (existing) return existing;
1437
- const token = normalizeToken(seed) ?? crypto2.randomBytes(32).toString("hex");
961
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1438
962
  ensurePrivateDirSync(EXE_AI_DIR);
1439
963
  writeFileSync2(DAEMON_TOKEN_PATH, `${token}
1440
964
  `, "utf8");
@@ -1447,18 +971,18 @@ var init_daemon_auth = __esm({
1447
971
  "use strict";
1448
972
  init_config();
1449
973
  init_secure_files();
1450
- DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
974
+ DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
1451
975
  }
1452
976
  });
1453
977
 
1454
978
  // src/lib/exe-daemon-client.ts
1455
979
  import net from "net";
1456
- import os5 from "os";
980
+ import os4 from "os";
1457
981
  import { spawn } from "child_process";
1458
982
  import { randomUUID } from "crypto";
1459
- import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
1460
- import path6 from "path";
1461
- 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";
1462
986
  function handleData(chunk) {
1463
987
  _buffer += chunk.toString();
1464
988
  if (_buffer.length > MAX_BUFFER) {
@@ -1485,7 +1009,7 @@ function handleData(chunk) {
1485
1009
  }
1486
1010
  }
1487
1011
  function cleanupStaleFiles() {
1488
- if (existsSync6(PID_PATH)) {
1012
+ if (existsSync5(PID_PATH)) {
1489
1013
  try {
1490
1014
  const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1491
1015
  if (pid > 0) {
@@ -1508,11 +1032,11 @@ function cleanupStaleFiles() {
1508
1032
  }
1509
1033
  }
1510
1034
  function findPackageRoot() {
1511
- let dir = path6.dirname(fileURLToPath2(import.meta.url));
1512
- const { root } = path6.parse(dir);
1035
+ let dir = path5.dirname(fileURLToPath(import.meta.url));
1036
+ const { root } = path5.parse(dir);
1513
1037
  while (dir !== root) {
1514
- if (existsSync6(path6.join(dir, "package.json"))) return dir;
1515
- dir = path6.dirname(dir);
1038
+ if (existsSync5(path5.join(dir, "package.json"))) return dir;
1039
+ dir = path5.dirname(dir);
1516
1040
  }
1517
1041
  return null;
1518
1042
  }
@@ -1532,14 +1056,14 @@ function getAvailableMemoryGB() {
1532
1056
  const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
1533
1057
  return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
1534
1058
  } catch {
1535
- return os5.freemem() / (1024 * 1024 * 1024);
1059
+ return os4.freemem() / (1024 * 1024 * 1024);
1536
1060
  }
1537
1061
  }
1538
- return os5.freemem() / (1024 * 1024 * 1024);
1062
+ return os4.freemem() / (1024 * 1024 * 1024);
1539
1063
  }
1540
1064
  function spawnDaemon() {
1541
1065
  const freeGB = getAvailableMemoryGB();
1542
- const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
1066
+ const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
1543
1067
  if (totalGB <= 8) {
1544
1068
  process.stderr.write(
1545
1069
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -1559,8 +1083,8 @@ function spawnDaemon() {
1559
1083
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1560
1084
  return;
1561
1085
  }
1562
- const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1563
- if (!existsSync6(daemonPath)) {
1086
+ const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1087
+ if (!existsSync5(daemonPath)) {
1564
1088
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1565
1089
  `);
1566
1090
  return;
@@ -1569,7 +1093,7 @@ function spawnDaemon() {
1569
1093
  const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1570
1094
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1571
1095
  `);
1572
- const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
1096
+ const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
1573
1097
  let stderrFd = "ignore";
1574
1098
  try {
1575
1099
  stderrFd = openSync(logPath, "a");
@@ -1719,9 +1243,9 @@ var init_exe_daemon_client = __esm({
1719
1243
  "use strict";
1720
1244
  init_config();
1721
1245
  init_daemon_auth();
1722
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
1723
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
1724
- SPAWN_LOCK_PATH = path6.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");
1725
1249
  SPAWN_LOCK_STALE_MS = 3e4;
1726
1250
  CONNECT_TIMEOUT_MS = 15e3;
1727
1251
  REQUEST_TIMEOUT_MS = 3e4;
@@ -2009,6 +1533,9 @@ function getClient() {
2009
1533
  if (_daemonClient && _daemonClient._isDaemonActive()) {
2010
1534
  return _daemonClient;
2011
1535
  }
1536
+ if (!_resilientClient) {
1537
+ return _adapterClient;
1538
+ }
2012
1539
  return _resilientClient;
2013
1540
  }
2014
1541
  async function initDaemonClient() {
@@ -3041,6 +2568,127 @@ async function ensureSchema() {
3041
2568
  VALUES (new.rowid, new.content, new.subject, new.predicate, new.object);
3042
2569
  END;
3043
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
+ `);
3044
2692
  try {
3045
2693
  await client.execute({
3046
2694
  sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
@@ -3188,1719 +2836,1430 @@ var init_database = __esm({
3188
2836
  }
3189
2837
  });
3190
2838
 
3191
- // src/lib/compress.ts
3192
- import { brotliCompressSync, brotliDecompressSync, constants } from "zlib";
3193
- function compress(input) {
3194
- if (input.length === 0) return Buffer.alloc(0);
3195
- return brotliCompressSync(input, {
3196
- params: {
3197
- [constants.BROTLI_PARAM_QUALITY]: 4
3198
- }
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
3199
2893
  });
2894
+ _shards.set(safeName, client);
2895
+ _shardLastAccess.set(safeName, Date.now());
2896
+ return client;
3200
2897
  }
3201
- function decompress(input) {
3202
- if (input.length === 0) return Buffer.alloc(0);
3203
- 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
+ };
3204
2972
  }
3205
- var init_compress = __esm({
3206
- "src/lib/compress.ts"() {
3207
- "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 {
3208
2979
  }
3209
- });
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
+ );
3210
2994
 
3211
- // src/lib/license.ts
3212
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
3213
- import { randomUUID as randomUUID2 } from "crypto";
3214
- import { createRequire as createRequire2 } from "module";
3215
- import { pathToFileURL as pathToFileURL2 } from "url";
3216
- import os6 from "os";
3217
- import path7 from "path";
3218
- import { jwtVerify, importSPKI } from "jose";
3219
- function loadDeviceId() {
3220
- const deviceJsonPath = path7.join(EXE_AI_DIR, "device.json");
3221
- try {
3222
- if (existsSync7(deviceJsonPath)) {
3223
- const data = JSON.parse(readFileSync5(deviceJsonPath, "utf8"));
3224
- 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 {
3225
3068
  }
3226
- } catch {
3227
3069
  }
3228
- try {
3229
- if (existsSync7(DEVICE_ID_PATH)) {
3230
- const id2 = readFileSync5(DEVICE_ID_PATH, "utf8").trim();
3231
- 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 {
3232
3078
  }
3079
+ }
3080
+ try {
3081
+ await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
3233
3082
  } catch {
3234
3083
  }
3235
- const id = randomUUID2();
3236
- mkdirSync2(EXE_AI_DIR, { recursive: true });
3237
- writeFileSync3(DEVICE_ID_PATH, id, "utf8");
3238
- return id;
3239
- }
3240
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
3241
- var init_license = __esm({
3242
- "src/lib/license.ts"() {
3243
- "use strict";
3244
- init_config();
3245
- LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
3246
- CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
3247
- DEVICE_ID_PATH = path7.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
+ }
3248
3093
  }
3249
- });
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
+ );
3250
3104
 
3251
- // src/lib/crdt-sync.ts
3252
- var crdt_sync_exports = {};
3253
- __export(crdt_sync_exports, {
3254
- _setStatePath: () => _setStatePath,
3255
- applyRemoteUpdate: () => applyRemoteUpdate,
3256
- destroyCrdtDoc: () => destroyCrdtDoc,
3257
- getDiffUpdate: () => getDiffUpdate,
3258
- getFullState: () => getFullState,
3259
- getStateVector: () => getStateVector,
3260
- importExistingBehaviors: () => importExistingBehaviors,
3261
- importExistingMemories: () => importExistingMemories,
3262
- initCrdtDoc: () => initCrdtDoc,
3263
- isCrdtSyncEnabled: () => isCrdtSyncEnabled,
3264
- onUpdate: () => onUpdate,
3265
- readAllBehaviors: () => readAllBehaviors,
3266
- readAllMemories: () => readAllMemories,
3267
- rebuildFromDb: () => rebuildFromDb
3268
- });
3269
- import * as Y from "yjs";
3270
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3 } from "fs";
3271
- import path8 from "path";
3272
- import { homedir } from "os";
3273
- function getStatePath() {
3274
- return _statePathOverride ?? DEFAULT_STATE_PATH;
3275
- }
3276
- function _setStatePath(p) {
3277
- _statePathOverride = p;
3278
- }
3279
- function initCrdtDoc() {
3280
- if (doc) return doc;
3281
- doc = new Y.Doc();
3282
- const sp = getStatePath();
3283
- if (existsSync8(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
+ ]) {
3284
3152
  try {
3285
- const state = readFileSync6(sp);
3286
- Y.applyUpdate(doc, new Uint8Array(state));
3153
+ await client.execute(col);
3287
3154
  } catch {
3288
- console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
3289
- try {
3290
- unlinkSync3(sp);
3291
- } catch {
3292
- }
3293
- rebuildFromDb().catch((err) => {
3294
- console.warn("[crdt-sync] rebuild from DB failed:", err);
3295
- });
3296
3155
  }
3297
3156
  }
3298
- doc.on("update", () => {
3299
- persistState();
3300
- });
3301
- return doc;
3302
- }
3303
- function getMemoriesMap() {
3304
- const d = initCrdtDoc();
3305
- return d.getMap("memories");
3306
- }
3307
- function getBehaviorsMap() {
3308
- const d = initCrdtDoc();
3309
- return d.getMap("behaviors");
3310
- }
3311
- function applyRemoteUpdate(update) {
3312
- const d = initCrdtDoc();
3313
- Y.applyUpdate(d, update);
3314
- }
3315
- function getFullState() {
3316
- const d = initCrdtDoc();
3317
- return Y.encodeStateAsUpdate(d);
3318
- }
3319
- function getDiffUpdate(remoteStateVector) {
3320
- const d = initCrdtDoc();
3321
- return Y.encodeStateAsUpdate(d, remoteStateVector);
3322
- }
3323
- function getStateVector() {
3324
- const d = initCrdtDoc();
3325
- return Y.encodeStateVector(d);
3326
- }
3327
- function importExistingMemories(memories) {
3328
- const map = getMemoriesMap();
3329
- const d = initCrdtDoc();
3330
- let imported = 0;
3331
- d.transact(() => {
3332
- for (const mem of memories) {
3333
- if (!mem.id) continue;
3334
- if (map.has(mem.id)) continue;
3335
- const entry = new Y.Map();
3336
- entry.set("id", mem.id);
3337
- entry.set("agent_id", mem.agent_id ?? null);
3338
- entry.set("agent_role", mem.agent_role ?? null);
3339
- entry.set("session_id", mem.session_id ?? null);
3340
- entry.set("timestamp", mem.timestamp ?? null);
3341
- entry.set("tool_name", mem.tool_name ?? null);
3342
- entry.set("project_name", mem.project_name ?? null);
3343
- entry.set("has_error", mem.has_error ?? 0);
3344
- entry.set("raw_text", mem.raw_text ?? "");
3345
- entry.set("version", mem.version ?? 0);
3346
- entry.set("author_device_id", mem.author_device_id ?? null);
3347
- entry.set("scope", mem.scope ?? "business");
3348
- map.set(mem.id, entry);
3349
- 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
+ );
3350
3180
  }
3351
- });
3352
- return imported;
3353
- }
3354
- function importExistingBehaviors(behaviors) {
3355
- const map = getBehaviorsMap();
3356
- const d = initCrdtDoc();
3357
- let imported = 0;
3358
- d.transact(() => {
3359
- for (const beh of behaviors) {
3360
- if (!beh.id) continue;
3361
- if (map.has(beh.id)) continue;
3362
- const entry = new Y.Map();
3363
- entry.set("id", beh.id);
3364
- entry.set("agent_id", beh.agent_id ?? null);
3365
- entry.set("project_name", beh.project_name ?? null);
3366
- entry.set("domain", beh.domain ?? null);
3367
- entry.set("content", beh.content ?? null);
3368
- entry.set("active", beh.active ?? 1);
3369
- entry.set("priority", beh.priority ?? "p1");
3370
- entry.set("created_at", beh.created_at ?? null);
3371
- entry.set("updated_at", beh.updated_at ?? null);
3372
- map.set(beh.id, entry);
3373
- 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;
3374
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: []
3375
3464
  });
3376
- return imported;
3377
- }
3378
- function readAllMemories() {
3379
- const map = getMemoriesMap();
3380
- const records = [];
3381
- map.forEach((entry, id) => {
3382
- records.push({
3383
- id,
3384
- agent_id: entry.get("agent_id"),
3385
- agent_role: entry.get("agent_role"),
3386
- session_id: entry.get("session_id"),
3387
- timestamp: entry.get("timestamp"),
3388
- tool_name: entry.get("tool_name"),
3389
- project_name: entry.get("project_name"),
3390
- has_error: entry.get("has_error"),
3391
- raw_text: entry.get("raw_text"),
3392
- version: entry.get("version"),
3393
- author_device_id: entry.get("author_device_id"),
3394
- scope: entry.get("scope")
3395
- });
3396
- });
3397
- return records;
3398
- }
3399
- function readAllBehaviors() {
3400
- const map = getBehaviorsMap();
3401
- const records = [];
3402
- map.forEach((entry, id) => {
3403
- records.push({
3404
- id,
3405
- agent_id: entry.get("agent_id"),
3406
- project_name: entry.get("project_name"),
3407
- domain: entry.get("domain"),
3408
- content: entry.get("content"),
3409
- active: entry.get("active"),
3410
- priority: entry.get("priority"),
3411
- created_at: entry.get("created_at"),
3412
- updated_at: entry.get("updated_at")
3413
- });
3414
- });
3415
- return records;
3416
- }
3417
- function onUpdate(callback) {
3418
- const d = initCrdtDoc();
3419
- const handler = (update) => callback(update);
3420
- d.on("update", handler);
3421
- return () => d.off("update", handler);
3422
- }
3423
- function persistState() {
3424
- if (!doc) return;
3425
- try {
3426
- const sp = getStatePath();
3427
- const dir = path8.dirname(sp);
3428
- if (!existsSync8(dir)) mkdirSync3(dir, { recursive: true });
3429
- const state = Y.encodeStateAsUpdate(doc);
3430
- writeFileSync4(sp, Buffer.from(state));
3431
- } 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 = "";
3432
3472
  }
3473
+ _cacheLoaded = true;
3474
+ return customerOnly;
3433
3475
  }
3434
- function isCrdtSyncEnabled() {
3435
- 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
+ `;
3436
3485
  }
3437
- async function rebuildFromDb() {
3438
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3439
- const client = getClient2();
3440
- const result = await client.execute(
3441
- "SELECT id, agent_id, agent_role, session_id, timestamp, tool_name, project_name, has_error, raw_text, version, author_device_id, scope FROM memories"
3442
- );
3443
- const memories = result.rows.map((row) => ({
3444
- id: String(row.id),
3445
- agent_id: row.agent_id,
3446
- agent_role: row.agent_role,
3447
- session_id: row.session_id,
3448
- timestamp: row.timestamp,
3449
- tool_name: row.tool_name,
3450
- project_name: row.project_name,
3451
- has_error: row.has_error,
3452
- raw_text: row.raw_text,
3453
- version: row.version,
3454
- author_device_id: row.author_device_id,
3455
- scope: row.scope
3456
- }));
3457
- const count = importExistingMemories(memories);
3458
- persistState();
3459
- 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;
3460
3497
  }
3461
- function destroyCrdtDoc() {
3462
- if (doc) {
3463
- doc.destroy();
3464
- doc = null;
3465
- }
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;
3466
3507
  }
3467
- var DEFAULT_STATE_PATH, _statePathOverride, doc;
3468
- var init_crdt_sync = __esm({
3469
- "src/lib/crdt-sync.ts"() {
3508
+ var _customerCache, _cacheLoaded, _platformCache;
3509
+ var init_global_procedures = __esm({
3510
+ "src/lib/global-procedures.ts"() {
3470
3511
  "use strict";
3471
- DEFAULT_STATE_PATH = path8.join(homedir(), ".exe-os", "crdt-state.bin");
3472
- _statePathOverride = null;
3473
- 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");
3474
3518
  }
3475
3519
  });
3476
3520
 
3477
- // src/lib/db-backup.ts
3478
- var db_backup_exports = {};
3479
- __export(db_backup_exports, {
3480
- createBackup: () => createBackup,
3481
- findActiveDb: () => findActiveDb,
3482
- getBackupDir: () => getBackupDir,
3483
- getLatestBackup: () => getLatestBackup,
3484
- hasBackupToday: () => hasBackupToday,
3485
- listBackups: () => listBackups,
3486
- rotateBackups: () => rotateBackups
3487
- });
3488
- import { copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync, unlinkSync as unlinkSync4, statSync as statSync2 } from "fs";
3489
- import path9 from "path";
3490
- function findActiveDb() {
3491
- for (const name of DB_NAMES) {
3492
- const p = path9.join(EXE_AI_DIR, name);
3493
- if (existsSync9(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}`;
3494
3554
  }
3495
3555
  return null;
3496
3556
  }
3497
- function createBackup(reason = "manual") {
3498
- const dbPath = findActiveDb();
3499
- if (!dbPath) return null;
3500
- mkdirSync4(BACKUP_DIR, { recursive: true });
3501
- const dbName = path9.basename(dbPath, ".db");
3502
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
3503
- const backupName = `${dbName}-${reason}-${timestamp}.db`;
3504
- const backupPath = path9.join(BACKUP_DIR, backupName);
3505
- copyFileSync(dbPath, backupPath);
3506
- const walPath = dbPath + "-wal";
3507
- if (existsSync9(walPath)) {
3508
- try {
3509
- copyFileSync(walPath, backupPath + "-wal");
3510
- } catch {
3511
- }
3512
- }
3513
- const shmPath = dbPath + "-shm";
3514
- if (existsSync9(shmPath)) {
3515
- try {
3516
- copyFileSync(shmPath, backupPath + "-shm");
3517
- } catch {
3518
- }
3519
- }
3520
- 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;
3521
3565
  }
3522
- function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
3523
- if (!existsSync9(BACKUP_DIR)) return 0;
3524
- const cutoff = Date.now() - keepDays * 24 * 60 * 60 * 1e3;
3525
- let deleted = 0;
3526
- try {
3527
- const files = readdirSync(BACKUP_DIR);
3528
- for (const file of files) {
3529
- if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
3530
- const filePath = path9.join(BACKUP_DIR, file);
3531
- try {
3532
- const stat = statSync2(filePath);
3533
- if (stat.mtimeMs < cutoff) {
3534
- unlinkSync4(filePath);
3535
- deleted++;
3536
- }
3537
- } catch {
3538
- }
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;
3539
3578
  }
3540
- } catch {
3541
3579
  }
3542
- return deleted;
3580
+ return out;
3543
3581
  }
3544
- function listBackups() {
3545
- if (!existsSync9(BACKUP_DIR)) return [];
3546
- try {
3547
- const files = readdirSync(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
3548
- return files.map((name) => {
3549
- const p = path9.join(BACKUP_DIR, name);
3550
- const stat = statSync2(p);
3551
- return { path: p, name, size: stat.size, date: stat.mtime };
3552
- }).sort((a, b) => b.date.getTime() - a.date.getTime());
3553
- } catch {
3554
- 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;
3555
3587
  }
3588
+ return out;
3556
3589
  }
3557
- function hasBackupToday(reason) {
3558
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3559
- const backups = listBackups();
3560
- return backups.some((b) => b.name.includes(reason) && b.name.includes(today.replace(/-/g, "-")));
3561
- }
3562
- function getLatestBackup() {
3563
- const backups = listBackups();
3564
- return backups.length > 0 ? backups[0].path : null;
3565
- }
3566
- function getBackupDir() {
3567
- 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
+ };
3568
3652
  }
3569
- var BACKUP_DIR, DEFAULT_KEEP_DAYS, DB_NAMES;
3570
- var init_db_backup = __esm({
3571
- "src/lib/db-backup.ts"() {
3653
+ var init_agentic_ontology = __esm({
3654
+ "src/lib/agentic-ontology.ts"() {
3572
3655
  "use strict";
3573
- init_config();
3574
- BACKUP_DIR = path9.join(EXE_AI_DIR, "backups");
3575
- DEFAULT_KEEP_DAYS = 3;
3576
- DB_NAMES = ["memories.db", "exe-mem.db", "exe-os.db", "exe.db"];
3577
3656
  }
3578
3657
  });
3579
3658
 
3580
- // src/lib/cloud-sync.ts
3581
- var cloud_sync_exports = {};
3582
- __export(cloud_sync_exports, {
3583
- assertSecureEndpoint: () => assertSecureEndpoint,
3584
- buildRosterBlob: () => buildRosterBlob,
3585
- cloudPull: () => cloudPull,
3586
- cloudPullBehaviors: () => cloudPullBehaviors,
3587
- cloudPullBlob: () => cloudPullBlob,
3588
- cloudPullConversations: () => cloudPullConversations,
3589
- cloudPullDocuments: () => cloudPullDocuments,
3590
- cloudPullGlobalProcedures: () => cloudPullGlobalProcedures,
3591
- cloudPullGraphRAG: () => cloudPullGraphRAG,
3592
- cloudPullRoster: () => cloudPullRoster,
3593
- cloudPullTasks: () => cloudPullTasks,
3594
- cloudPush: () => cloudPush,
3595
- cloudPushBehaviors: () => cloudPushBehaviors,
3596
- cloudPushBlob: () => cloudPushBlob,
3597
- cloudPushConversations: () => cloudPushConversations,
3598
- cloudPushDocuments: () => cloudPushDocuments,
3599
- cloudPushGlobalProcedures: () => cloudPushGlobalProcedures,
3600
- cloudPushGraphRAG: () => cloudPushGraphRAG,
3601
- cloudPushRoster: () => cloudPushRoster,
3602
- cloudPushTasks: () => cloudPushTasks,
3603
- cloudSync: () => cloudSync,
3604
- mergeConfig: () => mergeConfig,
3605
- mergeRosterFromRemote: () => mergeRosterFromRemote,
3606
- pushToPostgres: () => pushToPostgres,
3607
- recordRosterDeletion: () => recordRosterDeletion
3608
- });
3609
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync10, readdirSync as readdirSync2, mkdirSync as mkdirSync5, appendFileSync, unlinkSync as unlinkSync5, openSync as openSync2, closeSync as closeSync2, statSync as statSync3 } from "fs";
3610
- import crypto3 from "crypto";
3611
- import path10 from "path";
3612
- import { homedir as homedir2 } from "os";
3613
- function sqlSafe(v) {
3614
- return v === void 0 ? null : v;
3615
- }
3616
- function logError(msg) {
3617
- try {
3618
- const logPath = path10.join(homedir2(), ".exe-os", "workers.log");
3619
- appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
3620
- `);
3621
- } catch {
3622
- }
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");
3623
3674
  }
3624
- function isTruthyEnv(value) {
3625
- return /^(1|true|yes|on)$/i.test(value ?? "");
3675
+ function getKeyPath() {
3676
+ return path6.join(getKeyDir(), "master.key");
3677
+ }
3678
+ function nativeKeychainAllowed() {
3679
+ return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
3626
3680
  }
3627
- function loadPgClient() {
3628
- if (_pgFailed) return null;
3629
- const configPath = path10.join(EXE_AI_DIR, "config.json");
3630
- let cloudPostgresUrl;
3631
- 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;
3632
3686
  try {
3633
- if (existsSync10(configPath)) {
3634
- const cfg = JSON.parse(readFileSync7(configPath, "utf8"));
3635
- cloudPostgresUrl = cfg.cloud?.postgresUrl;
3636
- configEnabled = cfg.cloud?.syncToPostgres === true;
3637
- }
3687
+ execSync2("command -v secret-tool >/dev/null 2>&1", { timeout: 1e3 });
3638
3688
  } catch {
3689
+ linuxSecretAvailability = false;
3690
+ return false;
3639
3691
  }
3640
- const envEnabled = isTruthyEnv(process.env.EXE_CLOUD_SYNC_TO_POSTGRES);
3641
- if (!envEnabled && !configEnabled) {
3642
- return null;
3643
- }
3644
- const url = process.env.DATABASE_URL || cloudPostgresUrl;
3645
- if (!url) {
3646
- _pgFailed = true;
3647
- return null;
3648
- }
3649
- if (!_pgPromise) {
3650
- _pgPromise = (async () => {
3651
- if (!process.env.DATABASE_URL) process.env.DATABASE_URL = url;
3652
- const { createRequire: createRequire3 } = await import("module");
3653
- const { pathToFileURL: pathToFileURL3 } = await import("url");
3654
- const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
3655
- if (explicitPath) {
3656
- const mod2 = await import(pathToFileURL3(explicitPath).href);
3657
- const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
3658
- if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
3659
- return new Ctor2();
3660
- }
3661
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path10.join(homedir2(), "exe-db");
3662
- const req = createRequire3(path10.join(exeDbRoot, "package.json"));
3663
- const entry = req.resolve("@prisma/client");
3664
- const mod = await import(pathToFileURL3(entry).href);
3665
- const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
3666
- if (!Ctor) throw new Error("No PrismaClient");
3667
- return new Ctor();
3668
- })().catch(() => {
3669
- _pgFailed = true;
3670
- _pgPromise = null;
3671
- throw new Error("pg_unavailable");
3672
- });
3673
- }
3674
- return _pgPromise;
3675
- }
3676
- async function pushToPostgres(records) {
3677
- const loader = loadPgClient();
3678
- if (!loader) return 0;
3679
- let prisma;
3680
3692
  try {
3681
- prisma = await loader;
3693
+ execSync2("secret-tool search --all exe-os probe >/dev/null 2>&1", { timeout: 1e3 });
3694
+ linuxSecretAvailability = true;
3682
3695
  } catch {
3683
- return 0;
3684
- }
3685
- let inserted = 0;
3686
- for (const rec of records) {
3687
- try {
3688
- await prisma.$executeRawUnsafe(
3689
- `INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
3690
- VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
3691
- ON CONFLICT (source, source_id, event_type) DO NOTHING`,
3692
- String(rec.id ?? ""),
3693
- JSON.stringify(rec),
3694
- JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
3695
- rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
3696
- );
3697
- inserted++;
3698
- } catch {
3699
- }
3696
+ linuxSecretAvailability = false;
3700
3697
  }
3701
- return inserted;
3698
+ return linuxSecretAvailability;
3702
3699
  }
3703
- async function withRosterLock(fn) {
3704
- try {
3705
- const fd = openSync2(ROSTER_LOCK_PATH, "wx");
3706
- closeSync2(fd);
3707
- writeFileSync5(ROSTER_LOCK_PATH, String(Date.now()));
3708
- } catch (err) {
3709
- if (err.code === "EEXIST") {
3710
- try {
3711
- const ts = parseInt(readFileSync7(ROSTER_LOCK_PATH, "utf-8"), 10);
3712
- if (Date.now() - ts < LOCK_STALE_MS) {
3713
- throw new Error("Roster merge already in progress \u2014 another sync is running");
3714
- }
3715
- unlinkSync5(ROSTER_LOCK_PATH);
3716
- const fd = openSync2(ROSTER_LOCK_PATH, "wx");
3717
- closeSync2(fd);
3718
- writeFileSync5(ROSTER_LOCK_PATH, String(Date.now()));
3719
- } catch (retryErr) {
3720
- if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
3721
- throw new Error("Roster merge already in progress \u2014 another sync is running");
3722
- }
3723
- } else {
3724
- throw err;
3725
- }
3726
- }
3700
+ function isRootOnlyTrustedServerKeyFile(keyPath) {
3701
+ if (process.platform !== "linux") return false;
3727
3702
  try {
3728
- return await fn();
3729
- } finally {
3730
- try {
3731
- unlinkSync5(ROSTER_LOCK_PATH);
3732
- } catch {
3733
- }
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;
3734
3711
  }
3735
3712
  }
3736
- async function fetchWithRetry(url, init) {
3737
- const MAX_RETRIES2 = 3;
3738
- const BASE_DELAY_MS2 = 200;
3739
- let lastError;
3740
- for (let attempt = 0; attempt <= MAX_RETRIES2; attempt++) {
3741
- try {
3742
- const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
3743
- const resp = await fetch(url, { ...init, signal });
3744
- if (resp && resp.status >= 500 && attempt < MAX_RETRIES2) {
3745
- await new Promise((r) => setTimeout(r, BASE_DELAY_MS2 * Math.pow(2, attempt)));
3746
- continue;
3747
- }
3748
- return resp;
3749
- } catch (err) {
3750
- lastError = err;
3751
- if (attempt === MAX_RETRIES2) throw err;
3752
- await new Promise((r) => setTimeout(r, BASE_DELAY_MS2 * Math.pow(2, attempt)));
3753
- }
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;
3754
3723
  }
3755
- throw lastError;
3756
3724
  }
3757
- function assertSecureEndpoint(endpoint) {
3758
- if (endpoint.startsWith("https://")) return;
3759
- if (endpoint.startsWith("http://")) {
3725
+ function macKeychainSet(value, service = SERVICE) {
3726
+ if (!nativeKeychainAllowed()) return false;
3727
+ if (process.platform !== "darwin") return false;
3728
+ try {
3760
3729
  try {
3761
- const parsed = new URL(endpoint);
3762
- 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
+ );
3763
3734
  } catch {
3764
- return;
3765
3735
  }
3766
- throw new Error(
3767
- `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 }
3768
3739
  );
3769
- }
3770
- }
3771
- async function cloudPush(records, maxVersion, config) {
3772
- if (records.length === 0) return true;
3773
- assertSecureEndpoint(config.endpoint);
3774
- try {
3775
- const json = JSON.stringify(records);
3776
- const compressed = compress(Buffer.from(json, "utf8"));
3777
- const blob = encryptSyncBlob(compressed);
3778
- const resp = await fetchWithRetry(`${config.endpoint}/sync/push`, {
3779
- method: "POST",
3780
- headers: {
3781
- Authorization: `Bearer ${config.apiKey}`,
3782
- "Content-Type": "application/json",
3783
- "X-Device-Id": loadDeviceId(),
3784
- "X-Expected-Version": String(maxVersion)
3785
- },
3786
- body: JSON.stringify({ version: maxVersion, blob })
3787
- });
3788
- if (resp == null) {
3789
- logError("[cloud-sync] PUSH FAILED: no response from server");
3790
- return false;
3791
- }
3792
- if (resp.status === 409) {
3793
- logError("[cloud-sync] PUSH VERSION CONFLICT \u2014 re-pull required before next push");
3794
- return false;
3795
- }
3796
- return resp.ok;
3797
- } catch (err) {
3798
- logError(`[cloud-sync] PUSH FAILED: ${err instanceof Error ? err.message : String(err)}`);
3740
+ return true;
3741
+ } catch {
3799
3742
  return false;
3800
3743
  }
3801
3744
  }
3802
- async function cloudPull(sinceVersion, config) {
3803
- assertSecureEndpoint(config.endpoint);
3745
+ function macKeychainDelete(service = SERVICE) {
3746
+ if (!nativeKeychainAllowed()) return false;
3747
+ if (process.platform !== "darwin") return false;
3804
3748
  try {
3805
- const response = await fetchWithRetry(`${config.endpoint}/sync/pull`, {
3806
- method: "POST",
3807
- headers: {
3808
- Authorization: `Bearer ${config.apiKey}`,
3809
- "Content-Type": "application/json",
3810
- "X-Device-Id": loadDeviceId()
3811
- },
3812
- body: JSON.stringify({ since_version: sinceVersion })
3813
- });
3814
- if (response == null) {
3815
- logError("[cloud-sync] PULL FAILED: no response from server");
3816
- return { records: [], maxVersion: sinceVersion };
3817
- }
3818
- if (!response.ok) return { records: [], maxVersion: sinceVersion };
3819
- const data = await response.json();
3820
- const allRecords = [];
3821
- for (const { blob } of data.blobs ?? []) {
3822
- try {
3823
- const compressed = decryptSyncBlob(blob);
3824
- const json = decompress(compressed).toString("utf8");
3825
- const records = JSON.parse(json);
3826
- allRecords.push(...records);
3827
- } catch {
3828
- continue;
3829
- }
3830
- }
3831
- return { records: allRecords, maxVersion: data.max_version ?? sinceVersion };
3832
- } catch (err) {
3833
- logError(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}`);
3834
- return { records: [], maxVersion: sinceVersion };
3749
+ execSync2(
3750
+ `security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
3751
+ { timeout: 5e3 }
3752
+ );
3753
+ return true;
3754
+ } catch {
3755
+ return false;
3835
3756
  }
3836
3757
  }
3837
- async function cloudSync(config) {
3838
- if (!isSyncCryptoInitialized()) {
3839
- try {
3840
- const { getMasterKey: getMasterKey2 } = await Promise.resolve().then(() => (init_keychain(), keychain_exports));
3841
- const masterKey = await getMasterKey2();
3842
- if (masterKey) {
3843
- initSyncCrypto(masterKey);
3844
- } else {
3845
- throw new Error("No master key found");
3846
- }
3847
- } catch (err) {
3848
- throw new Error(`[cloud-sync] Cannot initialize encryption: ${err instanceof Error ? err.message : String(err)}`);
3849
- }
3850
- }
3851
- let client;
3758
+ function linuxSecretGet(service = SERVICE) {
3759
+ if (!linuxSecretAvailable()) return null;
3852
3760
  try {
3853
- client = getClient();
3761
+ return execSync2(
3762
+ `secret-tool lookup service "${service}" account "${ACCOUNT}" 2>/dev/null`,
3763
+ { encoding: "utf-8", timeout: 5e3 }
3764
+ ).trim();
3854
3765
  } catch {
3855
- throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
3856
- }
3857
- try {
3858
- const relink = await client.execute("SELECT value FROM sync_meta WHERE key = 'cloud_relink_required' LIMIT 1");
3859
- if (String(relink.rows[0]?.value ?? "") === "1") {
3860
- throw new Error("[cloud-sync] Paused after key rotation. Re-link/reupload cloud sync with the new recovery phrase before syncing.");
3861
- }
3862
- } catch (err) {
3863
- const msg = err instanceof Error ? err.message : String(err);
3864
- if (msg.includes("Paused after key rotation")) throw err;
3766
+ return null;
3865
3767
  }
3768
+ }
3769
+ function linuxSecretSet(value, service = SERVICE) {
3770
+ if (!linuxSecretAvailable()) return false;
3866
3771
  try {
3867
- const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3868
- await getRawClient2().execute("PRAGMA wal_checkpoint(PASSIVE)");
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;
3869
3777
  } catch {
3778
+ return false;
3870
3779
  }
3780
+ }
3781
+ function linuxSecretDelete(service = SERVICE) {
3782
+ if (!nativeKeychainAllowed()) return false;
3783
+ if (process.platform !== "linux") return false;
3871
3784
  try {
3872
- await client.execute(
3873
- "CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)"
3785
+ execSync2(
3786
+ `secret-tool clear service "${service}" account "${ACCOUNT}" 2>/dev/null`,
3787
+ { timeout: 5e3 }
3874
3788
  );
3875
- } catch (e) {
3876
- logError(`[cloud-sync] sync_meta CREATE failed: ${e instanceof Error ? e.message : String(e)}`);
3877
- }
3878
- const pullMeta = await client.execute(
3879
- "SELECT value FROM sync_meta WHERE key = 'last_cloud_pull_version'"
3880
- );
3881
- const lastPullVersion = pullMeta.rows.length > 0 ? Number(pullMeta.rows[0].value) : 0;
3882
- const pullResult = await cloudPull(lastPullVersion, config);
3883
- let pulled = 0;
3884
- if (pullResult.records.length > 0) {
3885
- if (isCrdtSyncEnabled()) {
3886
- const { initCrdtDoc: initCrdtDoc2, importExistingMemories: importExistingMemories2, readAllMemories: readAllMemories2 } = await Promise.resolve().then(() => (init_crdt_sync(), crdt_sync_exports));
3887
- initCrdtDoc2();
3888
- importExistingMemories2(
3889
- pullResult.records.map((rec) => ({
3890
- id: String(rec.id ?? ""),
3891
- agent_id: rec.agent_id,
3892
- agent_role: rec.agent_role,
3893
- session_id: rec.session_id,
3894
- timestamp: rec.timestamp,
3895
- tool_name: rec.tool_name,
3896
- project_name: rec.project_name,
3897
- has_error: rec.has_error ?? 0,
3898
- raw_text: rec.raw_text ?? "",
3899
- version: rec.version ?? 0,
3900
- author_device_id: rec.author_device_id,
3901
- scope: rec.scope ?? "business"
3902
- }))
3903
- );
3904
- const pulledIds = new Set(pullResult.records.map((r) => String(r.id ?? "")));
3905
- const merged = readAllMemories2().filter((rec) => pulledIds.has(rec.id));
3906
- const stmts = merged.map((rec) => ({
3907
- sql: `INSERT OR REPLACE INTO memories
3908
- (id, agent_id, agent_role, session_id, timestamp,
3909
- tool_name, project_name, has_error, raw_text, version,
3910
- author_device_id, scope)
3911
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3912
- args: [
3913
- sqlSafe(rec.id),
3914
- sqlSafe(rec.agent_id),
3915
- sqlSafe(rec.agent_role),
3916
- sqlSafe(rec.session_id),
3917
- sqlSafe(rec.timestamp),
3918
- sqlSafe(rec.tool_name),
3919
- sqlSafe(rec.project_name),
3920
- sqlSafe(rec.has_error ?? 0),
3921
- sqlSafe(rec.raw_text ?? ""),
3922
- sqlSafe(rec.version ?? 0),
3923
- sqlSafe(rec.author_device_id),
3924
- sqlSafe(rec.scope ?? "business")
3925
- ]
3926
- }));
3927
- if (stmts.length > 0) await client.batch(stmts, "write");
3928
- pulled = pullResult.records.length;
3929
- } else {
3930
- const stmts = pullResult.records.map((rec) => ({
3931
- sql: `INSERT OR REPLACE INTO memories
3932
- (id, agent_id, agent_role, session_id, timestamp,
3933
- tool_name, project_name, has_error, raw_text, version,
3934
- author_device_id, scope)
3935
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3936
- args: [
3937
- sqlSafe(rec.id),
3938
- sqlSafe(rec.agent_id),
3939
- sqlSafe(rec.agent_role),
3940
- sqlSafe(rec.session_id),
3941
- sqlSafe(rec.timestamp),
3942
- sqlSafe(rec.tool_name),
3943
- sqlSafe(rec.project_name),
3944
- sqlSafe(rec.has_error ?? 0),
3945
- sqlSafe(rec.raw_text ?? ""),
3946
- sqlSafe(rec.version ?? 0),
3947
- sqlSafe(rec.author_device_id),
3948
- sqlSafe(rec.scope ?? "business")
3949
- ]
3950
- }));
3951
- await client.batch(stmts, "write");
3952
- pulled = pullResult.records.length;
3953
- }
3954
- }
3955
- if (pulled > 0) {
3956
- try {
3957
- await pushToPostgres(pullResult.records);
3958
- } catch {
3959
- }
3960
- }
3961
- if (pullResult.maxVersion > lastPullVersion) {
3962
- await client.execute({
3963
- sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_pull_version', ?)",
3964
- args: [String(pullResult.maxVersion)]
3965
- });
3966
- }
3967
- const pushMeta = await client.execute(
3968
- "SELECT value FROM sync_meta WHERE key = 'last_cloud_push_version'"
3969
- );
3970
- const lastPushVersion = pushMeta.rows.length > 0 ? Number(pushMeta.rows[0].value) : 0;
3971
- let pushed = 0;
3972
- let batchCursor = lastPushVersion;
3973
- while (true) {
3974
- const recordsResult = await client.execute({
3975
- sql: `SELECT id, agent_id, agent_role, session_id, timestamp,
3976
- tool_name, project_name, has_error, raw_text, version,
3977
- author_device_id, scope
3978
- FROM memories
3979
- WHERE version > ?
3980
- AND (scope IS NULL OR scope != 'personal')
3981
- ORDER BY version ASC
3982
- LIMIT ?`,
3983
- args: [batchCursor, PUSH_BATCH_SIZE]
3984
- });
3985
- if (recordsResult.rows.length === 0) break;
3986
- const records = recordsResult.rows.map((row) => ({
3987
- id: row.id,
3988
- agent_id: row.agent_id,
3989
- agent_role: row.agent_role,
3990
- session_id: row.session_id,
3991
- timestamp: row.timestamp,
3992
- tool_name: row.tool_name,
3993
- project_name: row.project_name,
3994
- has_error: row.has_error,
3995
- raw_text: row.raw_text,
3996
- version: row.version,
3997
- author_device_id: row.author_device_id,
3998
- scope: row.scope
3999
- }));
4000
- const maxVersion = Number(records[records.length - 1].version);
4001
- const pushOk = await cloudPush(records, maxVersion, config);
4002
- if (!pushOk) break;
4003
- try {
4004
- await pushToPostgres(records);
4005
- } catch {
4006
- }
4007
- await client.execute({
4008
- sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
4009
- args: [String(maxVersion)]
4010
- });
4011
- pushed += records.length;
4012
- batchCursor = maxVersion;
4013
- if (recordsResult.rows.length < PUSH_BATCH_SIZE) break;
4014
- }
4015
- try {
4016
- await cloudPushRoster(config);
4017
- } catch (err) {
4018
- logError(`[cloud-sync] Roster push: ${err instanceof Error ? err.message : String(err)}`);
4019
- }
4020
- try {
4021
- await cloudPullRoster(config);
4022
- } catch (err) {
4023
- logError(`[cloud-sync] Roster pull: ${err instanceof Error ? err.message : String(err)}`);
4024
- }
4025
- try {
4026
- await cloudPushGlobalProcedures(config);
4027
- } catch (err) {
4028
- logError(`[cloud-sync] Company procedures push: ${err instanceof Error ? err.message : String(err)}`);
4029
- }
4030
- try {
4031
- await cloudPullGlobalProcedures(config);
4032
- } catch (err) {
4033
- logError(`[cloud-sync] Company procedures pull: ${err instanceof Error ? err.message : String(err)}`);
4034
- }
4035
- const countRows = async (sql) => {
4036
- try {
4037
- return Number((await client.execute(sql)).rows[0]?.cnt ?? 0);
4038
- } catch {
4039
- return 0;
4040
- }
4041
- };
4042
- let behaviorsResult = { pushed: 0, pulled: 0 };
4043
- try {
4044
- await cloudPushBehaviors(config);
4045
- behaviorsResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM behaviors WHERE active = 1");
4046
- } catch (err) {
4047
- logError(`[cloud-sync] Behaviors push: ${err instanceof Error ? err.message : String(err)}`);
4048
- }
4049
- try {
4050
- const pullResult2 = await cloudPullBehaviors(config);
4051
- behaviorsResult.pulled = pullResult2.pulled;
4052
- } catch (err) {
4053
- logError(`[cloud-sync] Behaviors pull: ${err instanceof Error ? err.message : String(err)}`);
4054
- }
4055
- let graphragResult = { pushed: 0, pulled: 0 };
4056
- try {
4057
- await cloudPushGraphRAG(config);
4058
- graphragResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM entities");
4059
- } catch (err) {
4060
- logError(`[cloud-sync] GraphRAG push: ${err instanceof Error ? err.message : String(err)}`);
4061
- }
4062
- try {
4063
- const pullResult2 = await cloudPullGraphRAG(config);
4064
- graphragResult.pulled = pullResult2.pulled;
4065
- } catch (err) {
4066
- logError(`[cloud-sync] GraphRAG pull: ${err instanceof Error ? err.message : String(err)}`);
4067
- }
4068
- let tasksResult = { pushed: 0, pulled: 0 };
4069
- try {
4070
- await cloudPushTasks(config);
4071
- tasksResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM tasks");
4072
- } catch (err) {
4073
- logError(`[cloud-sync] Tasks push: ${err instanceof Error ? err.message : String(err)}`);
4074
- }
4075
- try {
4076
- const pullResult2 = await cloudPullTasks(config);
4077
- tasksResult.pulled = pullResult2.pulled;
4078
- } catch (err) {
4079
- logError(`[cloud-sync] Tasks pull: ${err instanceof Error ? err.message : String(err)}`);
4080
- }
4081
- let conversationsResult = { pushed: 0, pulled: 0 };
4082
- try {
4083
- await cloudPushConversations(config);
4084
- conversationsResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM conversations");
4085
- } catch (err) {
4086
- logError(`[cloud-sync] Conversations push: ${err instanceof Error ? err.message : String(err)}`);
4087
- }
4088
- try {
4089
- const pullResult2 = await cloudPullConversations(config);
4090
- conversationsResult.pulled = pullResult2.pulled;
4091
- } catch (err) {
4092
- logError(`[cloud-sync] Conversations pull: ${err instanceof Error ? err.message : String(err)}`);
4093
- }
4094
- let documentsResult = { pushed: 0, pulled: 0 };
4095
- try {
4096
- await cloudPushDocuments(config);
4097
- documentsResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM documents");
4098
- } catch (err) {
4099
- logError(`[cloud-sync] Documents push: ${err instanceof Error ? err.message : String(err)}`);
4100
- }
4101
- try {
4102
- const pullResult2 = await cloudPullDocuments(config);
4103
- documentsResult.pulled = pullResult2.pulled;
4104
- } catch (err) {
4105
- logError(`[cloud-sync] Documents pull: ${err instanceof Error ? err.message : String(err)}`);
3789
+ return true;
3790
+ } catch {
3791
+ return false;
4106
3792
  }
4107
- let rosterResult = { employees: 0, identities: 0 };
3793
+ }
3794
+ async function tryKeytar() {
3795
+ if (!nativeKeychainAllowed()) return null;
4108
3796
  try {
4109
- const employees = await loadEmployees();
4110
- rosterResult.employees = employees.length;
4111
- const idDir = path10.join(EXE_AI_DIR, "identity");
4112
- if (existsSync10(idDir)) {
4113
- rosterResult.identities = readdirSync2(idDir).filter((f) => f.endsWith(".md")).length;
4114
- }
3797
+ return await import("keytar");
4115
3798
  } catch {
3799
+ return null;
4116
3800
  }
4117
- const totalMemories = await countRows("SELECT COUNT(*) as cnt FROM memories WHERE status = 'active' OR status IS NULL");
3801
+ }
3802
+ var ENCRYPTED_PREFIX = "enc:";
3803
+ function deriveMachineKey() {
4118
3804
  try {
4119
- const { getLatestBackup: getLatestBackup2 } = await Promise.resolve().then(() => (init_db_backup(), db_backup_exports));
4120
- const latestBackup = getLatestBackup2();
4121
- if (latestBackup) {
4122
- const backupSize = statSync3(latestBackup).size;
4123
- const MAX_CLOUD_BACKUP_BYTES = 50 * 1024 * 1024;
4124
- if (backupSize <= MAX_CLOUD_BACKUP_BYTES) {
4125
- const backupData = readFileSync7(latestBackup);
4126
- const deviceId = loadDeviceId() ?? "unknown";
4127
- const encrypted = encryptSyncBlob(backupData);
4128
- const backupRes = await fetchWithRetry(`${config.endpoint}/sync/push-db-backup`, {
4129
- method: "POST",
4130
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.apiKey}` },
4131
- body: JSON.stringify({
4132
- device_id: deviceId,
4133
- filename: path10.basename(latestBackup),
4134
- blob: encrypted,
4135
- size: backupData.length
4136
- })
4137
- });
4138
- if (backupRes && !backupRes.ok) {
4139
- logError(`[cloud-sync] DB backup upload failed: ${backupRes.status}`);
4140
- }
4141
- }
4142
- }
4143
- } catch (err) {
4144
- logError(`[cloud-sync] DB backup upload error: ${err instanceof Error ? err.message : String(err)}`);
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();
3815
+ } catch {
3816
+ return null;
4145
3817
  }
4146
- return {
4147
- pushed,
4148
- pulled,
4149
- totalMemories,
4150
- behaviors: behaviorsResult,
4151
- graphrag: graphragResult,
4152
- tasks: tasksResult,
4153
- conversations: conversationsResult,
4154
- documents: documentsResult,
4155
- roster: rosterResult
4156
- };
4157
3818
  }
4158
- function recordRosterDeletion(name) {
4159
- let deletions = [];
3819
+ function readMachineId() {
4160
3820
  try {
4161
- if (existsSync10(ROSTER_DELETIONS_PATH)) {
4162
- deletions = JSON.parse(readFileSync7(ROSTER_DELETIONS_PATH, "utf-8"));
4163
- }
3821
+ const { readFileSync: readFileSync5 } = __require("fs");
3822
+ return readFileSync5("/etc/machine-id", "utf-8").trim();
4164
3823
  } catch {
3824
+ return "";
4165
3825
  }
4166
- if (!deletions.includes(name)) deletions.push(name);
4167
- writeFileSync5(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
4168
3826
  }
4169
- 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;
4170
3838
  try {
4171
- if (!existsSync10(ROSTER_DELETIONS_PATH)) return [];
4172
- const deletions = JSON.parse(readFileSync7(ROSTER_DELETIONS_PATH, "utf-8"));
4173
- writeFileSync5(ROSTER_DELETIONS_PATH, "[]");
4174
- 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;
4175
3850
  } catch {
4176
- return [];
3851
+ return null;
4177
3852
  }
4178
3853
  }
4179
- function buildRosterBlob(paths) {
4180
- const rosterPath = paths?.rosterPath ?? path10.join(EXE_AI_DIR, "exe-employees.json");
4181
- const identityDir = paths?.identityDir ?? path10.join(EXE_AI_DIR, "identity");
4182
- const configPath = paths?.configPath ?? path10.join(EXE_AI_DIR, "config.json");
4183
- let roster = [];
4184
- if (existsSync10(rosterPath)) {
4185
- try {
4186
- roster = JSON.parse(readFileSync7(rosterPath, "utf-8"));
4187
- } catch {
4188
- }
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";
4189
3864
  }
4190
- const identities = {};
4191
- if (existsSync10(identityDir)) {
4192
- for (const file of readdirSync2(identityDir).filter((f) => f.endsWith(".md"))) {
4193
- try {
4194
- identities[file] = readFileSync7(path10.join(identityDir, file), "utf-8");
4195
- } 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");
4196
3879
  }
3880
+ nativeValue = legacyValue;
4197
3881
  }
4198
3882
  }
4199
- let config;
4200
- if (existsSync10(configPath)) {
4201
- try {
4202
- config = JSON.parse(readFileSync7(configPath, "utf-8"));
4203
- } catch {
4204
- }
3883
+ if (nativeValue) {
3884
+ return Buffer.from(nativeValue, "base64");
4205
3885
  }
4206
- let agentConfig;
4207
- const agentConfigPath = path10.join(EXE_AI_DIR, "agent-config.json");
4208
- if (existsSync10(agentConfigPath)) {
3886
+ const keytar = await tryKeytar();
3887
+ if (keytar) {
4209
3888
  try {
4210
- agentConfig = JSON.parse(readFileSync7(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
+ }
4211
3902
  } catch {
4212
3903
  }
4213
3904
  }
4214
- const deletedNames = consumeRosterDeletions();
4215
- const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
4216
- const hash = crypto3.createHash("sha256").update(content).digest("hex").slice(0, 16);
4217
- return { roster, identities, config, agentConfig, deletedNames, version: hash };
4218
- }
4219
- async function cloudPushRoster(config) {
4220
- assertSecureEndpoint(config.endpoint);
4221
- const blob = buildRosterBlob();
4222
- if (blob.roster.length === 0) return true;
4223
- try {
4224
- const client = getClient();
4225
- const meta = await client.execute(
4226
- "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
+ `
4227
3910
  );
4228
- const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
4229
- if (blob.version === lastVersion) return true;
4230
- } catch {
3911
+ return null;
4231
3912
  }
4232
3913
  try {
4233
- const json = JSON.stringify(blob);
4234
- const compressed = compress(Buffer.from(json, "utf8"));
4235
- const encrypted = encryptSyncBlob(compressed);
4236
- const resp = await fetchWithRetry(`${config.endpoint}/sync/push-roster`, {
4237
- method: "POST",
4238
- headers: {
4239
- Authorization: `Bearer ${config.apiKey}`,
4240
- "Content-Type": "application/json",
4241
- "X-Device-Id": loadDeviceId()
4242
- },
4243
- body: JSON.stringify({ blob: encrypted })
4244
- });
4245
- 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");
4246
3940
  try {
4247
- const client = getClient();
4248
- await client.execute({
4249
- sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_roster_push_version', ?)",
4250
- args: [String(blob.version)]
4251
- });
3941
+ await unlink(keyPath);
3942
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
4252
3943
  } catch {
4253
3944
  }
4254
- }
4255
- return resp.ok;
4256
- } catch (err) {
4257
- process.stderr.write(`[cloud-sync] ROSTER PUSH FAILED: ${err instanceof Error ? err.message : String(err)}
4258
- `);
4259
- return false;
4260
- }
4261
- }
4262
- async function cloudPullRoster(config) {
4263
- assertSecureEndpoint(config.endpoint);
4264
- try {
4265
- const resp = await fetchWithRetry(`${config.endpoint}/sync/pull-roster`, {
4266
- method: "GET",
4267
- headers: {
4268
- Authorization: `Bearer ${config.apiKey}`,
4269
- "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
+ );
4270
3953
  }
4271
- });
4272
- if (!resp.ok) return { added: 0 };
4273
- const data = await resp.json();
4274
- if (!data.blob) return { added: 0 };
4275
- const compressed = decryptSyncBlob(data.blob);
4276
- const json = decompress(compressed).toString("utf8");
4277
- const remote = JSON.parse(json);
4278
- return mergeRosterFromRemote(remote);
3954
+ }
3955
+ return key;
4279
3956
  } catch (err) {
4280
- process.stderr.write(`[cloud-sync] ROSTER PULL FAILED: ${err instanceof Error ? err.message : String(err)}
4281
- `);
4282
- 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;
4283
3962
  }
4284
3963
  }
4285
- function mergeConfig(remoteConfig, configPath) {
4286
- const cfgPath = configPath ?? path10.join(EXE_AI_DIR, "config.json");
4287
- let local = {};
4288
- if (existsSync10(cfgPath)) {
4289
- try {
4290
- local = JSON.parse(readFileSync7(cfgPath, "utf-8"));
4291
- } catch {
4292
- }
4293
- }
4294
- const merged = { ...remoteConfig, ...local };
4295
- const dir = path10.dirname(cfgPath);
4296
- ensurePrivateDirSync(dir);
4297
- writeFileSync5(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
4298
- enforcePrivateFileSync(cfgPath);
4299
- }
4300
- async function mergeRosterFromRemote(remote, paths) {
4301
- return withRosterLock(async () => {
4302
- const rosterPath = paths?.rosterPath ?? void 0;
4303
- const identityDir = paths?.identityDir ?? path10.join(EXE_AI_DIR, "identity");
4304
- const localEmployees = await loadEmployees(rosterPath);
4305
- const localNames = new Set(localEmployees.map((e) => e.name));
4306
- let added = 0;
4307
- let identitiesUpdated = 0;
4308
- for (const remoteEmp of remote.roster) {
4309
- if (!localNames.has(remoteEmp.name)) {
4310
- localEmployees.push(remoteEmp);
4311
- localNames.add(remoteEmp.name);
4312
- added++;
4313
- try {
4314
- registerBinSymlinks(remoteEmp.name);
4315
- } catch {
4316
- }
4317
- }
4318
- const lookupKey = `${remoteEmp.name}.md`;
4319
- const matchedKey = Object.keys(remote.identities).find(
4320
- (k) => k.toLowerCase() === lookupKey.toLowerCase()
4321
- ) ?? lookupKey;
4322
- const remoteIdentity = remote.identities[matchedKey];
4323
- if (remoteIdentity) {
4324
- if (!existsSync10(identityDir)) mkdirSync5(identityDir, { recursive: true });
4325
- const idPath = path10.join(identityDir, `${remoteEmp.name}.md`);
4326
- 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) {
4327
3977
  try {
4328
- localIdentity = existsSync10(idPath) ? readFileSync7(idPath, "utf-8") : null;
3978
+ handler(event);
4329
3979
  } catch {
4330
3980
  }
4331
- if (localIdentity !== remoteIdentity) {
4332
- writeFileSync5(idPath, remoteIdentity, "utf-8");
4333
- identitiesUpdated++;
4334
- }
4335
3981
  }
4336
3982
  }
4337
- let removed = 0;
4338
- if (remote.deletedNames && remote.deletedNames.length > 0) {
4339
- const toRemove = new Set(remote.deletedNames);
4340
- const filtered = localEmployees.filter((e) => !toRemove.has(e.name));
4341
- removed = localEmployees.length - filtered.length;
4342
- if (removed > 0) {
4343
- localEmployees.length = 0;
4344
- localEmployees.push(...filtered);
4345
- }
4346
- }
4347
- if (added > 0 || removed > 0) {
4348
- await saveEmployees(localEmployees, rosterPath);
4349
- }
4350
- if (remote.config && Object.keys(remote.config).length > 0) {
3983
+ for (const handler of this.globalHandlers) {
4351
3984
  try {
4352
- mergeConfig(remote.config, paths?.configPath);
3985
+ handler(event);
4353
3986
  } catch {
4354
3987
  }
4355
3988
  }
4356
- if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
4357
- try {
4358
- const agentConfigPath = path10.join(EXE_AI_DIR, "agent-config.json");
4359
- let local = {};
4360
- if (existsSync10(agentConfigPath)) {
4361
- try {
4362
- local = JSON.parse(readFileSync7(agentConfigPath, "utf-8"));
4363
- } catch {
4364
- }
4365
- }
4366
- const merged = { ...remote.agentConfig, ...local };
4367
- ensurePrivateDirSync(path10.dirname(agentConfigPath));
4368
- writeFileSync5(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
4369
- enforcePrivateFileSync(agentConfigPath);
4370
- } catch {
4371
- }
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());
4372
3994
  }
4373
- return { added, identitiesUpdated };
4374
- });
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;
4375
4029
  }
4376
- async function cloudPushBlob(route, data, metaKey, config) {
4377
- if (data.length === 0) return { ok: true };
4378
- assertSecureEndpoint(config.endpoint);
4379
- const json = JSON.stringify(data);
4380
- 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");
4381
4080
  try {
4382
- const client = getClient();
4383
- const meta = await client.execute({
4384
- sql: "SELECT value FROM sync_meta WHERE key = ?",
4385
- args: [metaKey]
4386
- });
4387
- const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
4388
- if (version === lastVersion) return { ok: true };
4081
+ const { initDaemonClient: initDaemonClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
4082
+ await initDaemonClient2();
4389
4083
  } catch {
4390
4084
  }
4391
- try {
4392
- const compressed = compress(Buffer.from(json, "utf8"));
4393
- const encrypted = encryptSyncBlob(compressed);
4394
- const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
4395
- method: "POST",
4396
- headers: {
4397
- Authorization: `Bearer ${config.apiKey}`,
4398
- "Content-Type": "application/json",
4399
- "X-Device-Id": loadDeviceId()
4400
- },
4401
- body: JSON.stringify({ blob: encrypted })
4402
- });
4403
- if (resp.ok) {
4404
- try {
4405
- const client = getClient();
4406
- await client.execute({
4407
- sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES (?, ?)",
4408
- args: [metaKey, String(version)]
4409
- });
4410
- } catch {
4411
- }
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 {
4412
4101
  }
4413
- return { ok: resp.ok };
4414
- } catch (err) {
4415
- logError(`[cloud-sync] PUSH ${route}: ${err instanceof Error ? err.message : String(err)}`);
4416
- return { ok: false };
4417
4102
  }
4418
4103
  }
4419
- async function cloudPullBlob(route, config) {
4420
- 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();
4421
4114
  try {
4422
- const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
4423
- method: "GET",
4424
- headers: {
4425
- Authorization: `Bearer ${config.apiKey}`,
4426
- "X-Device-Id": loadDeviceId()
4427
- }
4428
- });
4429
- if (!resp.ok) return null;
4430
- const data = await resp.json();
4431
- if (!data.blob) return null;
4432
- const compressed = decryptSyncBlob(data.blob);
4433
- const json = decompress(compressed).toString("utf8");
4434
- return JSON.parse(json);
4435
- } catch (err) {
4436
- logError(`[cloud-sync] PULL ${route}: ${err instanceof Error ? err.message : String(err)}`);
4115
+ return JSON.parse(trimmed);
4116
+ } catch {
4437
4117
  return null;
4438
4118
  }
4439
4119
  }
4440
- async function cloudPushGlobalProcedures(config) {
4441
- const client = getClient();
4442
- const result = await client.execute("SELECT * FROM company_procedures LIMIT 1000");
4443
- const rows = result.rows;
4444
- const { ok } = await cloudPushBlob(
4445
- "/sync/push-global-procedures",
4446
- rows,
4447
- "last_company_procedures_push_version",
4448
- config
4449
- );
4450
- return ok;
4451
- }
4452
- async function cloudPullGlobalProcedures(config) {
4453
- const remoteProcs = await cloudPullBlob(
4454
- "/sync/pull-global-procedures",
4455
- config
4456
- );
4457
- if (!remoteProcs || remoteProcs.length === 0) return { pulled: 0 };
4458
- const client = getClient();
4459
- const stmts = remoteProcs.map((p) => ({
4460
- sql: `INSERT INTO company_procedures
4461
- (id, title, content, priority, domain, active, created_at, updated_at)
4462
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
4463
- ON CONFLICT(id) DO UPDATE SET
4464
- title = excluded.title,
4465
- content = excluded.content,
4466
- priority = excluded.priority,
4467
- domain = excluded.domain,
4468
- active = excluded.active,
4469
- updated_at = excluded.updated_at
4470
- WHERE excluded.updated_at > company_procedures.updated_at`,
4471
- args: [
4472
- sqlSafe(p.id),
4473
- sqlSafe(p.title),
4474
- sqlSafe(p.content),
4475
- sqlSafe(p.priority ?? "p0"),
4476
- sqlSafe(p.domain),
4477
- sqlSafe(p.active ?? 1),
4478
- sqlSafe(p.created_at),
4479
- sqlSafe(p.updated_at)
4480
- ]
4481
- }));
4482
- await client.batch(stmts, "write");
4483
- return { pulled: remoteProcs.length };
4484
- }
4485
- async function cloudPushBehaviors(config) {
4486
- const client = getClient();
4487
- const result = await client.execute("SELECT * FROM behaviors LIMIT 10000");
4488
- const rows = result.rows;
4489
- const { ok } = await cloudPushBlob(
4490
- "/sync/push-behaviors",
4491
- rows,
4492
- "last_behaviors_push_version",
4493
- config
4494
- );
4495
- return ok;
4496
- }
4497
- async function cloudPullBehaviors(config) {
4498
- const remoteBehaviors = await cloudPullBlob(
4499
- "/sync/pull-behaviors",
4500
- config
4501
- );
4502
- if (!remoteBehaviors || remoteBehaviors.length === 0) return { pulled: 0 };
4503
- const client = getClient();
4504
- let pulled = 0;
4505
- for (const behavior of remoteBehaviors) {
4506
- const existing = await client.execute({
4507
- sql: `SELECT COUNT(*) as cnt FROM behaviors
4508
- WHERE agent_id = ? AND content = ?`,
4509
- args: [sqlSafe(behavior.agent_id), sqlSafe(behavior.content)]
4510
- });
4511
- if (Number(existing.rows[0]?.cnt) > 0) continue;
4512
- await client.execute({
4513
- sql: `INSERT OR IGNORE INTO behaviors
4514
- (id, agent_id, project_name, domain, content, active, priority, created_at, updated_at)
4515
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4516
- args: [
4517
- sqlSafe(behavior.id),
4518
- sqlSafe(behavior.agent_id),
4519
- sqlSafe(behavior.project_name),
4520
- sqlSafe(behavior.domain),
4521
- sqlSafe(behavior.content),
4522
- sqlSafe(behavior.active ?? 1),
4523
- sqlSafe(behavior.priority ?? "p1"),
4524
- sqlSafe(behavior.created_at),
4525
- sqlSafe(behavior.updated_at)
4526
- ]
4527
- });
4528
- pulled++;
4529
- }
4530
- 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);
4531
4123
  }
4532
- async function cloudPushGraphRAG(config) {
4533
- const client = getClient();
4534
- const [entities, relationships, aliases, entityMems, relMems, hyperedges, hyperedgeNodes] = await Promise.all([
4535
- client.execute("SELECT * FROM entities LIMIT 50000"),
4536
- client.execute("SELECT * FROM relationships LIMIT 50000"),
4537
- client.execute("SELECT * FROM entity_aliases LIMIT 50000"),
4538
- client.execute("SELECT * FROM entity_memories LIMIT 50000"),
4539
- client.execute("SELECT * FROM relationship_memories LIMIT 50000"),
4540
- client.execute("SELECT * FROM hyperedges LIMIT 50000"),
4541
- client.execute("SELECT * FROM hyperedge_nodes LIMIT 50000")
4542
- ]);
4543
- const blob = {
4544
- entities: entities.rows,
4545
- relationships: relationships.rows,
4546
- entity_aliases: aliases.rows,
4547
- entity_memories: entityMems.rows,
4548
- relationship_memories: relMems.rows,
4549
- hyperedges: hyperedges.rows,
4550
- 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)
4551
4145
  };
4552
- const { ok } = await cloudPushBlob(
4553
- "/sync/push-graphrag",
4554
- [blob],
4555
- "last_graphrag_push_version",
4556
- config
4557
- );
4558
- return ok;
4559
- }
4560
- async function cloudPullGraphRAG(config) {
4561
- const data = await cloudPullBlob(
4562
- "/sync/pull-graphrag",
4563
- config
4564
- );
4565
- if (!data || data.length === 0) return { pulled: 0 };
4566
- const blob = data[0];
4567
- const client = getClient();
4568
- let pulled = 0;
4569
- if (blob.entities.length > 0) {
4570
- const stmts = blob.entities.map((e) => ({
4571
- sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen, properties)
4572
- VALUES (?, ?, ?, ?, ?, ?)`,
4573
- args: [sqlSafe(e.id), sqlSafe(e.name), sqlSafe(e.type), sqlSafe(e.first_seen), sqlSafe(e.last_seen), sqlSafe(e.properties ?? "{}")]
4574
- }));
4575
- await client.batch(stmts, "write");
4576
- pulled += stmts.length;
4577
- }
4578
- if (blob.relationships.length > 0) {
4579
- const stmts = blob.relationships.map((r) => ({
4580
- sql: `INSERT OR IGNORE INTO relationships
4581
- (id, source_entity_id, target_entity_id, type, weight, timestamp, properties, confidence, confidence_label)
4582
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4583
- args: [
4584
- sqlSafe(r.id),
4585
- sqlSafe(r.source_entity_id),
4586
- sqlSafe(r.target_entity_id),
4587
- sqlSafe(r.type),
4588
- sqlSafe(r.weight ?? 1),
4589
- sqlSafe(r.timestamp),
4590
- sqlSafe(r.properties ?? "{}"),
4591
- sqlSafe(r.confidence ?? 1),
4592
- sqlSafe(r.confidence_label ?? "extracted")
4593
- ]
4594
- }));
4595
- await client.batch(stmts, "write");
4596
- pulled += stmts.length;
4597
- }
4598
- if (blob.entity_aliases.length > 0) {
4599
- const stmts = blob.entity_aliases.map((a) => ({
4600
- sql: `INSERT OR IGNORE INTO entity_aliases (alias, canonical_entity_id) VALUES (?, ?)`,
4601
- args: [sqlSafe(a.alias), sqlSafe(a.canonical_entity_id)]
4602
- }));
4603
- await client.batch(stmts, "write");
4604
- pulled += stmts.length;
4605
- }
4606
- if (blob.entity_memories.length > 0) {
4607
- const stmts = blob.entity_memories.map((em) => ({
4608
- sql: `INSERT OR IGNORE INTO entity_memories (entity_id, memory_id) VALUES (?, ?)`,
4609
- args: [sqlSafe(em.entity_id), sqlSafe(em.memory_id)]
4610
- }));
4611
- await client.batch(stmts, "write");
4612
- pulled += stmts.length;
4613
- }
4614
- if (blob.relationship_memories.length > 0) {
4615
- const stmts = blob.relationship_memories.map((rm) => ({
4616
- sql: `INSERT OR IGNORE INTO relationship_memories (relationship_id, memory_id) VALUES (?, ?)`,
4617
- args: [sqlSafe(rm.relationship_id), sqlSafe(rm.memory_id)]
4618
- }));
4619
- await client.batch(stmts, "write");
4620
- pulled += stmts.length;
4621
- }
4622
- if (blob.hyperedges.length > 0) {
4623
- const stmts = blob.hyperedges.map((h) => ({
4624
- sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
4625
- VALUES (?, ?, ?, ?, ?)`,
4626
- args: [sqlSafe(h.id), sqlSafe(h.label), sqlSafe(h.relation), sqlSafe(h.confidence ?? 1), sqlSafe(h.timestamp)]
4627
- }));
4628
- await client.batch(stmts, "write");
4629
- pulled += stmts.length;
4630
- }
4631
- if (blob.hyperedge_nodes.length > 0) {
4632
- const stmts = blob.hyperedge_nodes.map((hn) => ({
4633
- sql: `INSERT OR IGNORE INTO hyperedge_nodes (hyperedge_id, entity_id) VALUES (?, ?)`,
4634
- args: [sqlSafe(hn.hyperedge_id), sqlSafe(hn.entity_id)]
4635
- }));
4636
- await client.batch(stmts, "write");
4637
- pulled += stmts.length;
4638
- }
4639
- return { pulled };
4640
- }
4641
- async function cloudPushTasks(config) {
4642
- const client = getClient();
4643
- const result = await client.execute("SELECT * FROM tasks LIMIT 10000");
4644
- const rows = result.rows;
4645
- const { ok } = await cloudPushBlob(
4646
- "/sync/push-tasks",
4647
- rows,
4648
- "last_tasks_push_version",
4649
- config
4650
- );
4651
- return ok;
4652
- }
4653
- async function cloudPullTasks(config) {
4654
- const remoteTasks = await cloudPullBlob(
4655
- "/sync/pull-tasks",
4656
- config
4657
- );
4658
- if (!remoteTasks || remoteTasks.length === 0) return { pulled: 0 };
4659
- const client = getClient();
4660
- const stmts = remoteTasks.map((t) => ({
4661
- sql: `INSERT OR IGNORE INTO tasks
4662
- (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, created_at, updated_at,
4663
- blocked_by, parent_task_id, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at)
4664
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4665
- args: [
4666
- sqlSafe(t.id),
4667
- sqlSafe(t.title),
4668
- sqlSafe(t.assigned_to),
4669
- sqlSafe(t.assigned_by),
4670
- sqlSafe(t.project_name),
4671
- sqlSafe(t.priority ?? "p1"),
4672
- sqlSafe(t.status ?? "open"),
4673
- sqlSafe(t.task_file),
4674
- sqlSafe(t.created_at),
4675
- sqlSafe(t.updated_at),
4676
- sqlSafe(t.blocked_by),
4677
- sqlSafe(t.parent_task_id),
4678
- sqlSafe(t.budget_tokens),
4679
- sqlSafe(t.budget_fallback_model),
4680
- sqlSafe(t.tokens_used ?? 0),
4681
- sqlSafe(t.tokens_warned_at)
4682
- ]
4683
- }));
4684
- await client.batch(stmts, "write");
4685
- return { pulled: remoteTasks.length };
4686
- }
4687
- async function cloudPushConversations(config) {
4688
- const client = getClient();
4689
- const result = await client.execute("SELECT * FROM conversations LIMIT 50000");
4690
- const rows = result.rows;
4691
- const { ok } = await cloudPushBlob(
4692
- "/sync/push-conversations",
4693
- rows,
4694
- "last_conversations_push_version",
4695
- config
4696
- );
4697
- return ok;
4698
- }
4699
- async function cloudPullConversations(config) {
4700
- const remoteConvos = await cloudPullBlob(
4701
- "/sync/pull-conversations",
4702
- config
4703
- );
4704
- if (!remoteConvos || remoteConvos.length === 0) return { pulled: 0 };
4705
- const client = getClient();
4706
- const stmts = remoteConvos.map((c) => ({
4707
- sql: `INSERT OR IGNORE INTO conversations
4708
- (id, platform, external_id, sender_id, sender_name, sender_phone, sender_email,
4709
- recipient_id, channel_id, thread_id, reply_to_id, content_text, content_media,
4710
- content_metadata, agent_response, agent_name, timestamp, ingested_at)
4711
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4712
- args: [
4713
- sqlSafe(c.id),
4714
- sqlSafe(c.platform),
4715
- sqlSafe(c.external_id),
4716
- sqlSafe(c.sender_id),
4717
- sqlSafe(c.sender_name),
4718
- sqlSafe(c.sender_phone),
4719
- sqlSafe(c.sender_email),
4720
- sqlSafe(c.recipient_id),
4721
- sqlSafe(c.channel_id),
4722
- sqlSafe(c.thread_id),
4723
- sqlSafe(c.reply_to_id),
4724
- sqlSafe(c.content_text),
4725
- sqlSafe(c.content_media),
4726
- sqlSafe(c.content_metadata),
4727
- sqlSafe(c.agent_response),
4728
- sqlSafe(c.agent_name),
4729
- sqlSafe(c.timestamp),
4730
- sqlSafe(c.ingested_at)
4731
- ]
4732
- }));
4733
- await client.batch(stmts, "write");
4734
- return { pulled: remoteConvos.length };
4735
4146
  }
4736
- async function cloudPushDocuments(config) {
4737
- const client = getClient();
4738
- const [workspaces, documents] = await Promise.all([
4739
- client.execute("SELECT * FROM workspaces LIMIT 1000"),
4740
- client.execute("SELECT * FROM documents LIMIT 10000")
4741
- ]);
4742
- const blob = {
4743
- workspaces: workspaces.rows,
4744
- documents: documents.rows
4745
- };
4746
- const { ok } = await cloudPushBlob(
4747
- "/sync/push-documents",
4748
- [blob],
4749
- "last_documents_push_version",
4750
- config
4751
- );
4752
- return ok;
4753
- }
4754
- async function cloudPullDocuments(config) {
4755
- const data = await cloudPullBlob(
4756
- "/sync/pull-documents",
4757
- config
4758
- );
4759
- if (!data || data.length === 0) return { pulled: 0 };
4760
- const blob = data[0];
4761
- const client = getClient();
4762
- let pulled = 0;
4763
- if (blob.workspaces.length > 0) {
4764
- const stmts = blob.workspaces.map((w) => ({
4765
- sql: `INSERT OR IGNORE INTO workspaces (id, slug, name, owner_agent_id, created_at, metadata)
4766
- VALUES (?, ?, ?, ?, ?, ?)`,
4767
- args: [sqlSafe(w.id), sqlSafe(w.slug), sqlSafe(w.name), sqlSafe(w.owner_agent_id), sqlSafe(w.created_at), sqlSafe(w.metadata)]
4768
- }));
4769
- await client.batch(stmts, "write");
4770
- pulled += stmts.length;
4771
- }
4772
- if (blob.documents.length > 0) {
4773
- const stmts = blob.documents.map((d) => ({
4774
- sql: `INSERT OR IGNORE INTO documents
4775
- (id, workspace_id, filename, mime, source_type, user_id, uploaded_at, metadata)
4776
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
4777
- args: [
4778
- sqlSafe(d.id),
4779
- sqlSafe(d.workspace_id),
4780
- sqlSafe(d.filename),
4781
- sqlSafe(d.mime),
4782
- sqlSafe(d.source_type),
4783
- sqlSafe(d.user_id),
4784
- sqlSafe(d.uploaded_at),
4785
- sqlSafe(d.metadata)
4786
- ]
4787
- }));
4788
- await client.batch(stmts, "write");
4789
- pulled += stmts.length;
4790
- }
4791
- return { pulled };
4792
- }
4793
- var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, ROSTER_DELETIONS_PATH;
4794
- var init_cloud_sync = __esm({
4795
- "src/lib/cloud-sync.ts"() {
4796
- "use strict";
4797
- init_database();
4798
- init_crypto();
4799
- init_compress();
4800
- init_license();
4801
- init_config();
4802
- init_crdt_sync();
4803
- init_employees();
4804
- init_secure_files();
4805
- LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
4806
- FETCH_TIMEOUT_MS = 3e4;
4807
- PUSH_BATCH_SIZE = 5e3;
4808
- ROSTER_LOCK_PATH = path10.join(EXE_AI_DIR, "roster-merge.lock");
4809
- LOCK_STALE_MS = 3e4;
4810
- _pgPromise = null;
4811
- _pgFailed = false;
4812
- ROSTER_DELETIONS_PATH = path10.join(EXE_AI_DIR, "roster-deletions.json");
4813
- }
4814
- });
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.
4815
4173
 
4816
- // src/bin/exe-link.ts
4817
- init_keychain();
4818
- import { createInterface } from "readline";
4819
-
4820
- // src/lib/is-main.ts
4821
- import { realpathSync } from "fs";
4822
- import { fileURLToPath } from "url";
4823
- function isMainModule(importMetaUrl) {
4824
- if (process.argv[1] == null) return false;
4825
- 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)}`;
4826
4176
  try {
4827
- const scriptPath = realpathSync(process.argv[1]);
4828
- const modulePath = realpathSync(fileURLToPath(importMetaUrl));
4829
- 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;
4830
4188
  } catch {
4831
- return importMetaUrl === `file://${process.argv[1]}` || importMetaUrl === new URL(process.argv[1], "file://").href;
4189
+ return fallback;
4832
4190
  }
4833
4191
  }
4834
-
4835
- // src/bin/exe-link.ts
4836
- async function main() {
4837
- const mode = process.argv[2];
4838
- if (mode === "export") {
4839
- const key = await getMasterKey();
4840
- if (!key) {
4841
- console.error("No master key found. Run /exe-setup first.");
4842
- process.exit(1);
4843
- }
4844
- const mnemonic = await exportMnemonic(key);
4845
- console.log("Your 24-word recovery phrase:\n");
4846
- const showFull = process.argv.includes("--show-full");
4847
- if (showFull) {
4848
- console.log(` ${mnemonic}
4849
- `);
4850
- } else {
4851
- const words = mnemonic.split(" ");
4852
- const masked = words.length > 4 ? [...words.slice(0, 2), ...words.slice(2, -2).map(() => "****"), ...words.slice(-2)].join(" ") : mnemonic;
4853
- console.log(` ${masked}
4854
- `);
4855
- console.log("To reveal full phrase: run with --show-full\n");
4856
- }
4857
- console.log("Write this down and enter it on your new device with /exe-link import.");
4858
- console.log("Anyone with this phrase can decrypt your memories.");
4859
- console.log("\u26A0 Clear your terminal history after copying.");
4860
- } else if (mode === "import") {
4861
- const rl = createInterface({ input: process.stdin, output: process.stdout });
4862
- const mnemonic = await new Promise((resolve) => {
4863
- rl.question("Paste your 24-word recovery phrase: ", (answer) => {
4864
- rl.close();
4865
- resolve(answer.trim());
4866
- });
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]
4867
4225
  });
4868
- try {
4869
- const key = await importMnemonic(mnemonic);
4870
- await setMasterKey(key);
4871
- console.log("Master key imported and stored securely.");
4872
- try {
4873
- const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
4874
- const { initSyncCrypto: initSyncCrypto2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
4875
- const { cloudPullRoster: cloudPullRoster2 } = await Promise.resolve().then(() => (init_cloud_sync(), cloud_sync_exports));
4876
- const config = await loadConfig2();
4877
- if (config.cloud?.apiKey && config.cloud?.endpoint) {
4878
- initSyncCrypto2(key);
4879
- const result = await cloudPullRoster2({ apiKey: config.cloud.apiKey, endpoint: config.cloud.endpoint });
4880
- if (result.added > 0) {
4881
- console.log(`Pulled ${result.added} employee(s) from Exe Cloud.`);
4882
- }
4883
- }
4884
- } catch {
4885
- }
4886
- console.log("Run /exe-setup to configure sync and download the embedding model.");
4887
- } catch (err) {
4888
- console.error(err instanceof Error ? err.message : String(err));
4889
- process.exit(1);
4890
- }
4891
- } else {
4892
- console.error("Usage:");
4893
- console.error(" exe-link export \u2014 show 24-word mnemonic for current key");
4894
- console.error(" exe-link import \u2014 paste mnemonic to import key on new device");
4895
- process.exit(1);
4896
4226
  }
4897
4227
  }
4898
- if (isMainModule(import.meta.url)) {
4899
- main().catch((err) => {
4900
- console.error(err instanceof Error ? err.message : String(err));
4901
- process.exit(1);
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]
4902
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}
4255
+ `);
4256
+ }
4257
+ process.stderr.write(`[agentic-semantic-label] Complete: ${count} memories labeled (${llm ? "llm-if-configured" : "deterministic"}).
4258
+ `);
4259
+ process.exit(0);
4903
4260
  }
4904
- export {
4905
- main
4906
- };
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
+ });