@askexenow/exe-os 0.8.38 → 0.8.39

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 (91) hide show
  1. package/README.md +17 -8
  2. package/dist/bin/backfill-conversations.js +46 -10
  3. package/dist/bin/backfill-responses.js +46 -10
  4. package/dist/bin/backfill-vectors.js +42 -8
  5. package/dist/bin/cleanup-stale-review-tasks.js +37 -8
  6. package/dist/bin/cli.js +281 -154
  7. package/dist/bin/exe-agent.js +19 -4
  8. package/dist/bin/exe-assign.js +39 -5
  9. package/dist/bin/exe-boot.js +237 -111
  10. package/dist/bin/exe-call.js +11 -6
  11. package/dist/bin/exe-cloud.js +99 -28
  12. package/dist/bin/exe-dispatch.js +1 -1
  13. package/dist/bin/exe-doctor.js +37 -8
  14. package/dist/bin/exe-export-behaviors.js +39 -10
  15. package/dist/bin/exe-forget.js +38 -9
  16. package/dist/bin/exe-gateway.js +109 -42
  17. package/dist/bin/exe-heartbeat.js +49 -20
  18. package/dist/bin/exe-kill.js +39 -10
  19. package/dist/bin/exe-launch-agent.js +58 -22
  20. package/dist/bin/exe-link.js +184 -85
  21. package/dist/bin/exe-new-employee.js +21 -7
  22. package/dist/bin/exe-pending-messages.js +46 -17
  23. package/dist/bin/exe-pending-notifications.js +37 -8
  24. package/dist/bin/exe-pending-reviews.js +47 -18
  25. package/dist/bin/exe-rename.js +21 -7
  26. package/dist/bin/exe-review.js +34 -5
  27. package/dist/bin/exe-search.js +47 -10
  28. package/dist/bin/exe-session-cleanup.js +56 -19
  29. package/dist/bin/exe-settings.js +63 -2
  30. package/dist/bin/exe-status.js +34 -5
  31. package/dist/bin/exe-team.js +34 -5
  32. package/dist/bin/git-sweep.js +38 -9
  33. package/dist/bin/graph-backfill.js +37 -8
  34. package/dist/bin/graph-export.js +37 -8
  35. package/dist/bin/install.js +1 -1
  36. package/dist/bin/scan-tasks.js +40 -11
  37. package/dist/bin/setup.js +58 -24
  38. package/dist/bin/shard-migrate.js +37 -8
  39. package/dist/bin/wiki-sync.js +39 -9
  40. package/dist/gateway/index.js +102 -37
  41. package/dist/hooks/bug-report-worker.js +62 -28
  42. package/dist/hooks/commit-complete.js +38 -9
  43. package/dist/hooks/error-recall.js +49 -8
  44. package/dist/hooks/exe-heartbeat-hook.js +3 -2
  45. package/dist/hooks/ingest-worker.js +151 -37
  46. package/dist/hooks/ingest.js +74 -28
  47. package/dist/hooks/instructions-loaded.js +39 -9
  48. package/dist/hooks/notification.js +37 -7
  49. package/dist/hooks/post-compact.js +37 -7
  50. package/dist/hooks/pre-compact.js +35 -6
  51. package/dist/hooks/pre-tool-use.js +52 -14
  52. package/dist/hooks/prompt-ingest-worker.js +56 -10
  53. package/dist/hooks/prompt-submit.js +61 -23
  54. package/dist/hooks/response-ingest-worker.js +57 -11
  55. package/dist/hooks/session-end.js +43 -10
  56. package/dist/hooks/session-start.js +46 -8
  57. package/dist/hooks/stop.js +37 -7
  58. package/dist/hooks/subagent-stop.js +37 -7
  59. package/dist/hooks/summary-worker.js +317 -99
  60. package/dist/index.js +87 -22
  61. package/dist/lib/cloud-sync.js +172 -78
  62. package/dist/lib/config.js +4 -1
  63. package/dist/lib/consolidation.js +5 -4
  64. package/dist/lib/database.js +1 -0
  65. package/dist/lib/device-registry.js +2 -1
  66. package/dist/lib/embedder.js +9 -1
  67. package/dist/lib/employees.js +11 -6
  68. package/dist/lib/exe-daemon-client.js +6 -1
  69. package/dist/lib/exe-daemon.js +71 -28
  70. package/dist/lib/hybrid-search.js +47 -10
  71. package/dist/lib/identity.js +1 -1
  72. package/dist/lib/keychain.js +2 -1
  73. package/dist/lib/license.js +13 -4
  74. package/dist/lib/messaging.js +1 -1
  75. package/dist/lib/reminders.js +2 -2
  76. package/dist/lib/schedules.js +37 -8
  77. package/dist/lib/skill-learning.js +1 -1
  78. package/dist/lib/store.js +37 -8
  79. package/dist/lib/tasks.js +1 -1
  80. package/dist/lib/tmux-routing.js +1 -1
  81. package/dist/mcp/server.js +97 -43
  82. package/dist/mcp/tools/complete-reminder.js +1 -1
  83. package/dist/mcp/tools/create-task.js +14 -6
  84. package/dist/mcp/tools/deactivate-behavior.js +2 -2
  85. package/dist/mcp/tools/list-reminders.js +1 -1
  86. package/dist/mcp/tools/list-tasks.js +1 -1
  87. package/dist/mcp/tools/send-message.js +1 -1
  88. package/dist/mcp/tools/update-task.js +1 -1
  89. package/dist/runtime/index.js +35 -6
  90. package/dist/tui/App.js +177 -95
  91. package/package.json +3 -3
@@ -10,7 +10,7 @@ var __export = (target, all) => {
10
10
  };
11
11
 
12
12
  // src/lib/config.ts
13
- import { readFile, writeFile, mkdir } from "fs/promises";
13
+ import { readFile, writeFile, mkdir, chmod } from "fs/promises";
14
14
  import { readFileSync, existsSync, renameSync } from "fs";
15
15
  import path from "path";
16
16
  import os from "os";
@@ -193,15 +193,20 @@ function addEmployee(employees, employee) {
193
193
  }
194
194
  return [...employees, normalized];
195
195
  }
196
+ function findExeBin() {
197
+ try {
198
+ return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
199
+ } catch {
200
+ return null;
201
+ }
202
+ }
196
203
  function registerBinSymlinks(name) {
197
204
  const created = [];
198
205
  const skipped = [];
199
206
  const errors = [];
200
- let exeBinPath;
201
- try {
202
- exeBinPath = execSync("which exe", { encoding: "utf-8" }).trim();
203
- } catch {
204
- errors.push("Could not find 'exe' in PATH");
207
+ const exeBinPath = findExeBin();
208
+ if (!exeBinPath) {
209
+ errors.push("Could not find 'exe-os' in PATH");
205
210
  return { created, skipped, errors };
206
211
  }
207
212
  const binDir = path2.dirname(exeBinPath);
@@ -16,15 +16,15 @@ var __export = (target, all) => {
16
16
  };
17
17
 
18
18
  // src/lib/config.ts
19
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
19
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
20
20
  import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
21
21
  import path2 from "path";
22
- import os from "os";
22
+ import os2 from "os";
23
23
  function resolveDataDir() {
24
24
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
25
25
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
26
- const newDir = path2.join(os.homedir(), ".exe-os");
27
- const legacyDir = path2.join(os.homedir(), ".exe-mem");
26
+ const newDir = path2.join(os2.homedir(), ".exe-os");
27
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
28
28
  if (!existsSync2(newDir) && existsSync2(legacyDir)) {
29
29
  try {
30
30
  renameSync(legacyDir, newDir);
@@ -111,7 +111,7 @@ async function loadConfig() {
111
111
  normalizeAutoUpdate(migratedCfg);
112
112
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
113
113
  if (config.dbPath.startsWith("~")) {
114
- config.dbPath = config.dbPath.replace(/^~/, os.homedir());
114
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
115
115
  }
116
116
  return config;
117
117
  } catch {
@@ -123,6 +123,9 @@ async function saveConfig(config) {
123
123
  await mkdir2(dir, { recursive: true });
124
124
  const configPath = path2.join(dir, "config.json");
125
125
  await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n");
126
+ if (config.cloud?.apiKey) {
127
+ await chmod2(configPath, 384);
128
+ }
126
129
  }
127
130
  var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
128
131
  var init_config = __esm({
@@ -227,22 +230,30 @@ __export(license_exports, {
227
230
  stopLicenseRevalidation: () => stopLicenseRevalidation,
228
231
  validateLicense: () => validateLicense
229
232
  });
230
- import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3, mkdirSync } from "fs";
233
+ import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync4, mkdirSync } from "fs";
231
234
  import { randomUUID } from "crypto";
232
- import path3 from "path";
235
+ import path4 from "path";
233
236
  import { jwtVerify, importSPKI } from "jose";
237
+ async function fetchRetry(url, init) {
238
+ try {
239
+ return await fetch(url, init);
240
+ } catch {
241
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
242
+ return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
243
+ }
244
+ }
234
245
  function loadDeviceId() {
235
- const deviceJsonPath = path3.join(EXE_AI_DIR, "device.json");
246
+ const deviceJsonPath = path4.join(EXE_AI_DIR, "device.json");
236
247
  try {
237
- if (existsSync3(deviceJsonPath)) {
238
- const data = JSON.parse(readFileSync2(deviceJsonPath, "utf8"));
248
+ if (existsSync4(deviceJsonPath)) {
249
+ const data = JSON.parse(readFileSync3(deviceJsonPath, "utf8"));
239
250
  if (data.deviceId) return data.deviceId;
240
251
  }
241
252
  } catch {
242
253
  }
243
254
  try {
244
- if (existsSync3(DEVICE_ID_PATH)) {
245
- const id2 = readFileSync2(DEVICE_ID_PATH, "utf8").trim();
255
+ if (existsSync4(DEVICE_ID_PATH)) {
256
+ const id2 = readFileSync3(DEVICE_ID_PATH, "utf8").trim();
246
257
  if (id2) return id2;
247
258
  }
248
259
  } catch {
@@ -254,15 +265,15 @@ function loadDeviceId() {
254
265
  }
255
266
  function loadLicense() {
256
267
  try {
257
- if (!existsSync3(LICENSE_PATH)) return null;
258
- return readFileSync2(LICENSE_PATH, "utf8").trim();
268
+ if (!existsSync4(LICENSE_PATH)) return null;
269
+ return readFileSync3(LICENSE_PATH, "utf8").trim();
259
270
  } catch {
260
271
  return null;
261
272
  }
262
273
  }
263
274
  function saveLicense(apiKey) {
264
275
  mkdirSync(EXE_AI_DIR, { recursive: true });
265
- writeFileSync(LICENSE_PATH, apiKey.trim(), "utf8");
276
+ writeFileSync(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
266
277
  }
267
278
  async function verifyLicenseJwt(token) {
268
279
  try {
@@ -288,8 +299,8 @@ async function verifyLicenseJwt(token) {
288
299
  }
289
300
  async function getCachedLicense() {
290
301
  try {
291
- if (!existsSync3(CACHE_PATH)) return null;
292
- const raw = JSON.parse(readFileSync2(CACHE_PATH, "utf8"));
302
+ if (!existsSync4(CACHE_PATH)) return null;
303
+ const raw = JSON.parse(readFileSync3(CACHE_PATH, "utf8"));
293
304
  if (!raw.token || typeof raw.token !== "string") return null;
294
305
  return await verifyLicenseJwt(raw.token);
295
306
  } catch {
@@ -298,8 +309,8 @@ async function getCachedLicense() {
298
309
  }
299
310
  function readCachedToken() {
300
311
  try {
301
- if (!existsSync3(CACHE_PATH)) return null;
302
- const raw = JSON.parse(readFileSync2(CACHE_PATH, "utf8"));
312
+ if (!existsSync4(CACHE_PATH)) return null;
313
+ const raw = JSON.parse(readFileSync3(CACHE_PATH, "utf8"));
303
314
  return typeof raw.token === "string" ? raw.token : null;
304
315
  } catch {
305
316
  return null;
@@ -314,7 +325,7 @@ function cacheResponse(token) {
314
325
  async function validateLicense(apiKey, deviceId) {
315
326
  const did = deviceId ?? loadDeviceId();
316
327
  try {
317
- const res = await fetch(`${API_BASE}/auth/activate`, {
328
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
318
329
  method: "POST",
319
330
  headers: { "Content-Type": "application/json" },
320
331
  body: JSON.stringify({ apiKey, deviceId: did }),
@@ -405,7 +416,7 @@ async function assertVpsLicense(opts) {
405
416
  let explicitRejection = false;
406
417
  let transientFailure = false;
407
418
  try {
408
- const res = await fetch(`${API_BASE}/auth/activate`, {
419
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
409
420
  method: "POST",
410
421
  headers: { "Content-Type": "application/json" },
411
422
  body: JSON.stringify({ apiKey, deviceId }),
@@ -507,15 +518,16 @@ function stopLicenseRevalidation() {
507
518
  _revalTimer = null;
508
519
  }
509
520
  }
510
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
521
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
511
522
  var init_license = __esm({
512
523
  "src/lib/license.ts"() {
513
524
  "use strict";
514
525
  init_config();
515
- LICENSE_PATH = path3.join(EXE_AI_DIR, "license.key");
516
- CACHE_PATH = path3.join(EXE_AI_DIR, "license-cache.json");
517
- DEVICE_ID_PATH = path3.join(EXE_AI_DIR, "device-id");
526
+ LICENSE_PATH = path4.join(EXE_AI_DIR, "license.key");
527
+ CACHE_PATH = path4.join(EXE_AI_DIR, "license-cache.json");
528
+ DEVICE_ID_PATH = path4.join(EXE_AI_DIR, "device-id");
518
529
  API_BASE = "https://askexe.com/cloud";
530
+ RETRY_DELAY_MS = 500;
519
531
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
520
532
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
521
533
  4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
@@ -549,11 +561,12 @@ import { createInterface } from "readline";
549
561
  import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
550
562
  import { existsSync } from "fs";
551
563
  import path from "path";
564
+ import os from "os";
552
565
  import crypto from "crypto";
553
566
  var SERVICE = "exe-mem";
554
567
  var ACCOUNT = "master-key";
555
568
  function getKeyDir() {
556
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(process.env.HOME ?? "/tmp", ".exe-os");
569
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
557
570
  }
558
571
  function getKeyPath() {
559
572
  return path.join(getKeyDir(), "master.key");
@@ -696,6 +709,58 @@ function isMainModule(importMetaUrl) {
696
709
  }
697
710
  }
698
711
 
712
+ // src/lib/cloud-sync.ts
713
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync6, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync } from "fs";
714
+ import path6 from "path";
715
+ import { homedir } from "os";
716
+
717
+ // src/lib/database.ts
718
+ import { createClient } from "@libsql/client";
719
+
720
+ // src/lib/crypto.ts
721
+ import crypto3 from "crypto";
722
+
723
+ // src/lib/compress.ts
724
+ import { brotliCompressSync, brotliDecompressSync, constants } from "zlib";
725
+
726
+ // src/lib/plan-limits.ts
727
+ import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
728
+ import path5 from "path";
729
+
730
+ // src/lib/employees.ts
731
+ init_config();
732
+ import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
733
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
734
+ import { execSync } from "child_process";
735
+ import path3 from "path";
736
+ var EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
737
+
738
+ // src/lib/plan-limits.ts
739
+ init_license();
740
+ init_config();
741
+ var CACHE_PATH2 = path5.join(EXE_AI_DIR, "license-cache.json");
742
+
743
+ // src/lib/cloud-sync.ts
744
+ init_license();
745
+ init_config();
746
+ var LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
747
+ var ROSTER_LOCK_PATH = path6.join(EXE_AI_DIR, "roster-merge.lock");
748
+ function assertSecureEndpoint(endpoint) {
749
+ if (endpoint.startsWith("https://")) return;
750
+ if (endpoint.startsWith("http://")) {
751
+ try {
752
+ const parsed = new URL(endpoint);
753
+ if (LOCALHOST_PATTERNS.test(parsed.hostname)) return;
754
+ } catch {
755
+ return;
756
+ }
757
+ throw new Error(
758
+ `Insecure cloud endpoint rejected: "${endpoint}". Use https:// for remote hosts. Plain http:// is only allowed for localhost.`
759
+ );
760
+ }
761
+ }
762
+ var ROSTER_DELETIONS_PATH = path6.join(EXE_AI_DIR, "roster-deletions.json");
763
+
699
764
  // src/bin/exe-cloud.ts
700
765
  var BAR = "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550";
701
766
  function ask(rl, question) {
@@ -743,6 +808,7 @@ async function setupMode() {
743
808
  const orgId = deriveOrgId(masterKey);
744
809
  const authTokenHash = hashAuthToken(deriveWsAuthToken(masterKey));
745
810
  try {
811
+ assertSecureEndpoint(config.cloud.endpoint);
746
812
  const regResp = await fetch(`${config.cloud.endpoint}/api/register-org`, {
747
813
  method: "POST",
748
814
  headers: { Authorization: `Bearer ${config.cloud.apiKey}`, "Content-Type": "application/json" },
@@ -764,6 +830,7 @@ async function setupMode() {
764
830
  const endpoint = await ask(rl, " Endpoint [https://askexe.com/cloud]: ") || "https://askexe.com/cloud";
765
831
  console.log(" Validating...");
766
832
  try {
833
+ assertSecureEndpoint(endpoint);
767
834
  const resp = await fetch(`${endpoint}/auth/verify`, {
768
835
  method: "POST",
769
836
  headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" }
@@ -774,7 +841,8 @@ async function setupMode() {
774
841
  try {
775
842
  const { mirrorLicenseKey: mirrorLicenseKey2 } = await Promise.resolve().then(() => (init_license(), license_exports));
776
843
  mirrorLicenseKey2(apiKey);
777
- } catch {
844
+ } catch (err) {
845
+ console.log(` \u26A0 License mirror failed: ${err instanceof Error ? err.message : String(err)}`);
778
846
  }
779
847
  console.log(" \u2713 Cloud sync configured.\n");
780
848
  const masterKey = await getMasterKey();
@@ -783,6 +851,7 @@ async function setupMode() {
783
851
  const orgId = deriveOrgId(masterKey);
784
852
  const authTokenHash = hashAuthToken(deriveWsAuthToken(masterKey));
785
853
  try {
854
+ assertSecureEndpoint(endpoint);
786
855
  const regResp = await fetch(`${endpoint}/api/register-org`, {
787
856
  method: "POST",
788
857
  headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
@@ -830,9 +899,11 @@ async function syncMode() {
830
899
  console.log(" 24-word recovery phrase:");
831
900
  console.log(` ${mnemonic}
832
901
  `);
902
+ console.log(" \u26A0 Clear your terminal history after copying.\n");
833
903
  if (config.cloud?.apiKey) {
904
+ const maskedKey = config.cloud.apiKey.slice(0, 10) + "..." + config.cloud.apiKey.slice(-4);
834
905
  console.log(" Cloud API key:");
835
- console.log(` ${config.cloud.apiKey}
906
+ console.log(` ${maskedKey}
836
907
  `);
837
908
  console.log(" Endpoint:");
838
909
  console.log(` ${config.cloud.endpoint}
@@ -335,7 +335,7 @@ var init_database = __esm({
335
335
  });
336
336
 
337
337
  // src/lib/config.ts
338
- import { readFile, writeFile, mkdir } from "fs/promises";
338
+ import { readFile, writeFile, mkdir, chmod } from "fs/promises";
339
339
  import { readFileSync as readFileSync3, existsSync as existsSync3, renameSync as renameSync2 } from "fs";
340
340
  import path3 from "path";
341
341
  import os3 from "os";
@@ -10,15 +10,15 @@ var __export = (target, all) => {
10
10
  };
11
11
 
12
12
  // src/lib/config.ts
13
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
13
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
14
14
  import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
15
15
  import path2 from "path";
16
- import os from "os";
16
+ import os2 from "os";
17
17
  function resolveDataDir() {
18
18
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
19
19
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
20
- const newDir = path2.join(os.homedir(), ".exe-os");
21
- const legacyDir = path2.join(os.homedir(), ".exe-mem");
20
+ const newDir = path2.join(os2.homedir(), ".exe-os");
21
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
22
22
  if (!existsSync2(newDir) && existsSync2(legacyDir)) {
23
23
  try {
24
24
  renameSync(legacyDir, newDir);
@@ -105,7 +105,7 @@ async function loadConfig() {
105
105
  normalizeAutoUpdate(migratedCfg);
106
106
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
107
107
  if (config.dbPath.startsWith("~")) {
108
- config.dbPath = config.dbPath.replace(/^~/, os.homedir());
108
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
109
109
  }
110
110
  return config;
111
111
  } catch {
@@ -526,6 +526,7 @@ async function ensureSchema() {
526
526
  const client = getRawClient();
527
527
  await client.execute("PRAGMA journal_mode = WAL");
528
528
  await client.execute("PRAGMA busy_timeout = 30000");
529
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
529
530
  try {
530
531
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
531
532
  } catch {
@@ -1319,11 +1320,12 @@ async function ensureSchema() {
1319
1320
  import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
1320
1321
  import { existsSync } from "fs";
1321
1322
  import path from "path";
1323
+ import os from "os";
1322
1324
  import crypto from "crypto";
1323
1325
  var SERVICE = "exe-mem";
1324
1326
  var ACCOUNT = "master-key";
1325
1327
  function getKeyDir() {
1326
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(process.env.HOME ?? "/tmp", ".exe-os");
1328
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
1327
1329
  }
1328
1330
  function getKeyPath() {
1329
1331
  return path.join(getKeyDir(), "master.key");
@@ -1360,6 +1362,30 @@ async function getMasterKey() {
1360
1362
 
1361
1363
  // src/lib/store.ts
1362
1364
  init_config();
1365
+ var INIT_MAX_RETRIES = 3;
1366
+ var INIT_RETRY_DELAY_MS = 1e3;
1367
+ function isBusyError2(err) {
1368
+ if (err instanceof Error) {
1369
+ const msg = err.message.toLowerCase();
1370
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
1371
+ }
1372
+ return false;
1373
+ }
1374
+ async function retryOnBusy2(fn, label) {
1375
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
1376
+ try {
1377
+ return await fn();
1378
+ } catch (err) {
1379
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
1380
+ process.stderr.write(
1381
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
1382
+ `
1383
+ );
1384
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
1385
+ }
1386
+ }
1387
+ throw new Error("unreachable");
1388
+ }
1363
1389
  var _pendingRecords = [];
1364
1390
  var _batchSize = 20;
1365
1391
  var _flushIntervalMs = 1e4;
@@ -1394,14 +1420,17 @@ async function initStore(options) {
1394
1420
  dbPath,
1395
1421
  encryptionKey: hexKey
1396
1422
  });
1397
- await ensureSchema();
1423
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
1398
1424
  try {
1399
1425
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
1400
1426
  initShardManager2(hexKey);
1401
1427
  } catch {
1402
1428
  }
1403
1429
  const client = getClient();
1404
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1430
+ const vResult = await retryOnBusy2(
1431
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
1432
+ "version-query"
1433
+ );
1405
1434
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1406
1435
  }
1407
1436
 
@@ -10,15 +10,15 @@ var __export = (target, all) => {
10
10
  };
11
11
 
12
12
  // src/lib/config.ts
13
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
13
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
14
14
  import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
15
15
  import path2 from "path";
16
- import os from "os";
16
+ import os2 from "os";
17
17
  function resolveDataDir() {
18
18
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
19
19
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
20
- const newDir = path2.join(os.homedir(), ".exe-os");
21
- const legacyDir = path2.join(os.homedir(), ".exe-mem");
20
+ const newDir = path2.join(os2.homedir(), ".exe-os");
21
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
22
22
  if (!existsSync2(newDir) && existsSync2(legacyDir)) {
23
23
  try {
24
24
  renameSync(legacyDir, newDir);
@@ -105,7 +105,7 @@ async function loadConfig() {
105
105
  normalizeAutoUpdate(migratedCfg);
106
106
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
107
107
  if (config.dbPath.startsWith("~")) {
108
- config.dbPath = config.dbPath.replace(/^~/, os.homedir());
108
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
109
109
  }
110
110
  return config;
111
111
  } catch {
@@ -526,6 +526,7 @@ async function ensureSchema() {
526
526
  const client = getRawClient();
527
527
  await client.execute("PRAGMA journal_mode = WAL");
528
528
  await client.execute("PRAGMA busy_timeout = 30000");
529
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
529
530
  try {
530
531
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
531
532
  } catch {
@@ -1327,11 +1328,12 @@ async function disposeDatabase() {
1327
1328
  import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
1328
1329
  import { existsSync } from "fs";
1329
1330
  import path from "path";
1331
+ import os from "os";
1330
1332
  import crypto from "crypto";
1331
1333
  var SERVICE = "exe-mem";
1332
1334
  var ACCOUNT = "master-key";
1333
1335
  function getKeyDir() {
1334
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(process.env.HOME ?? "/tmp", ".exe-os");
1336
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
1335
1337
  }
1336
1338
  function getKeyPath() {
1337
1339
  return path.join(getKeyDir(), "master.key");
@@ -1368,6 +1370,30 @@ async function getMasterKey() {
1368
1370
 
1369
1371
  // src/lib/store.ts
1370
1372
  init_config();
1373
+ var INIT_MAX_RETRIES = 3;
1374
+ var INIT_RETRY_DELAY_MS = 1e3;
1375
+ function isBusyError2(err) {
1376
+ if (err instanceof Error) {
1377
+ const msg = err.message.toLowerCase();
1378
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
1379
+ }
1380
+ return false;
1381
+ }
1382
+ async function retryOnBusy2(fn, label) {
1383
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
1384
+ try {
1385
+ return await fn();
1386
+ } catch (err) {
1387
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
1388
+ process.stderr.write(
1389
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
1390
+ `
1391
+ );
1392
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
1393
+ }
1394
+ }
1395
+ throw new Error("unreachable");
1396
+ }
1371
1397
  var _pendingRecords = [];
1372
1398
  var _batchSize = 20;
1373
1399
  var _flushIntervalMs = 1e4;
@@ -1402,14 +1428,17 @@ async function initStore(options) {
1402
1428
  dbPath,
1403
1429
  encryptionKey: hexKey
1404
1430
  });
1405
- await ensureSchema();
1431
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
1406
1432
  try {
1407
1433
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
1408
1434
  initShardManager2(hexKey);
1409
1435
  } catch {
1410
1436
  }
1411
1437
  const client = getClient();
1412
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1438
+ const vResult = await retryOnBusy2(
1439
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
1440
+ "version-query"
1441
+ );
1413
1442
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1414
1443
  }
1415
1444
  async function flushBatch() {
@@ -1561,7 +1590,7 @@ function vectorToBlob(vector) {
1561
1590
  }
1562
1591
 
1563
1592
  // src/lib/behaviors-export.ts
1564
- import os2 from "os";
1593
+ import os3 from "os";
1565
1594
  import path4 from "path";
1566
1595
  import {
1567
1596
  existsSync as existsSync4,
@@ -1604,7 +1633,7 @@ async function listBehaviors(agentId2, projectName2, limit = 30) {
1604
1633
 
1605
1634
  // src/lib/behaviors-export.ts
1606
1635
  var BEHAVIORS_EXPORT_DIR = path4.join(
1607
- os2.homedir(),
1636
+ os3.homedir(),
1608
1637
  ".exe-os",
1609
1638
  "behaviors-export"
1610
1639
  );
@@ -104,6 +104,7 @@ async function ensureSchema() {
104
104
  const client = getRawClient();
105
105
  await client.execute("PRAGMA journal_mode = WAL");
106
106
  await client.execute("PRAGMA busy_timeout = 30000");
107
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
107
108
  try {
108
109
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
109
110
  } catch {
@@ -907,9 +908,10 @@ var init_database = __esm({
907
908
  import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
908
909
  import { existsSync } from "fs";
909
910
  import path from "path";
911
+ import os from "os";
910
912
  import crypto from "crypto";
911
913
  function getKeyDir() {
912
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(process.env.HOME ?? "/tmp", ".exe-os");
914
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
913
915
  }
914
916
  function getKeyPath() {
915
917
  return path.join(getKeyDir(), "master.key");
@@ -953,15 +955,15 @@ var init_keychain = __esm({
953
955
  });
954
956
 
955
957
  // src/lib/config.ts
956
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
958
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
957
959
  import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
958
960
  import path2 from "path";
959
- import os from "os";
961
+ import os2 from "os";
960
962
  function resolveDataDir() {
961
963
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
962
964
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
963
- const newDir = path2.join(os.homedir(), ".exe-os");
964
- const legacyDir = path2.join(os.homedir(), ".exe-mem");
965
+ const newDir = path2.join(os2.homedir(), ".exe-os");
966
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
965
967
  if (!existsSync2(newDir) && existsSync2(legacyDir)) {
966
968
  try {
967
969
  renameSync(legacyDir, newDir);
@@ -1048,7 +1050,7 @@ async function loadConfig() {
1048
1050
  normalizeAutoUpdate(migratedCfg);
1049
1051
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
1050
1052
  if (config.dbPath.startsWith("~")) {
1051
- config.dbPath = config.dbPath.replace(/^~/, os.homedir());
1053
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
1052
1054
  }
1053
1055
  return config;
1054
1056
  } catch {
@@ -1383,6 +1385,28 @@ var init_shard_manager = __esm({
1383
1385
  });
1384
1386
 
1385
1387
  // src/lib/store.ts
1388
+ function isBusyError2(err) {
1389
+ if (err instanceof Error) {
1390
+ const msg = err.message.toLowerCase();
1391
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
1392
+ }
1393
+ return false;
1394
+ }
1395
+ async function retryOnBusy2(fn, label) {
1396
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
1397
+ try {
1398
+ return await fn();
1399
+ } catch (err) {
1400
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
1401
+ process.stderr.write(
1402
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
1403
+ `
1404
+ );
1405
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
1406
+ }
1407
+ }
1408
+ throw new Error("unreachable");
1409
+ }
1386
1410
  async function initStore(options) {
1387
1411
  if (_flushTimer !== null) {
1388
1412
  clearInterval(_flushTimer);
@@ -1411,14 +1435,17 @@ async function initStore(options) {
1411
1435
  dbPath,
1412
1436
  encryptionKey: hexKey
1413
1437
  });
1414
- await ensureSchema();
1438
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
1415
1439
  try {
1416
1440
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
1417
1441
  initShardManager2(hexKey);
1418
1442
  } catch {
1419
1443
  }
1420
1444
  const client = getClient();
1421
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1445
+ const vResult = await retryOnBusy2(
1446
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
1447
+ "version-query"
1448
+ );
1422
1449
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1423
1450
  }
1424
1451
  function buildWikiScopeFilter(options, columnPrefix) {
@@ -1473,7 +1500,7 @@ async function attachDocumentMetadata(records) {
1473
1500
  }
1474
1501
  return records;
1475
1502
  }
1476
- var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
1503
+ var INIT_MAX_RETRIES, INIT_RETRY_DELAY_MS, _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
1477
1504
  var init_store = __esm({
1478
1505
  "src/lib/store.ts"() {
1479
1506
  "use strict";
@@ -1481,6 +1508,8 @@ var init_store = __esm({
1481
1508
  init_database();
1482
1509
  init_keychain();
1483
1510
  init_config();
1511
+ INIT_MAX_RETRIES = 3;
1512
+ INIT_RETRY_DELAY_MS = 1e3;
1484
1513
  _pendingRecords = [];
1485
1514
  _batchSize = 20;
1486
1515
  _flushIntervalMs = 1e4;