@askexenow/exe-os 0.9.13 → 0.9.14

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 (70) hide show
  1. package/dist/bin/backfill-conversations.js +31 -4
  2. package/dist/bin/backfill-responses.js +31 -4
  3. package/dist/bin/backfill-vectors.js +25 -4
  4. package/dist/bin/cleanup-stale-review-tasks.js +1 -1
  5. package/dist/bin/cli.js +55 -8
  6. package/dist/bin/exe-assign.js +31 -4
  7. package/dist/bin/exe-boot.js +25 -4
  8. package/dist/bin/exe-dispatch.js +1 -1
  9. package/dist/bin/exe-doctor.js +1 -1
  10. package/dist/bin/exe-export-behaviors.js +1 -1
  11. package/dist/bin/exe-forget.js +1 -1
  12. package/dist/bin/exe-gateway.js +447 -21
  13. package/dist/bin/exe-heartbeat.js +1 -1
  14. package/dist/bin/exe-kill.js +1 -1
  15. package/dist/bin/exe-launch-agent.js +1 -1
  16. package/dist/bin/exe-link.js +31 -4
  17. package/dist/bin/exe-pending-messages.js +1 -1
  18. package/dist/bin/exe-pending-notifications.js +1 -1
  19. package/dist/bin/exe-pending-reviews.js +1 -1
  20. package/dist/bin/exe-rename.js +31 -4
  21. package/dist/bin/exe-review.js +1 -1
  22. package/dist/bin/exe-search.js +31 -4
  23. package/dist/bin/exe-session-cleanup.js +25 -4
  24. package/dist/bin/exe-start-codex.js +1 -1
  25. package/dist/bin/exe-start-opencode.js +1 -1
  26. package/dist/bin/exe-status.js +1 -1
  27. package/dist/bin/exe-team.js +1 -1
  28. package/dist/bin/git-sweep.js +25 -4
  29. package/dist/bin/graph-backfill.js +1 -1
  30. package/dist/bin/graph-export.js +1 -1
  31. package/dist/bin/scan-tasks.js +25 -4
  32. package/dist/bin/setup.js +46 -8
  33. package/dist/bin/shard-migrate.js +1 -1
  34. package/dist/bin/wiki-sync.js +1 -1
  35. package/dist/gateway/index.js +128 -125
  36. package/dist/hooks/bug-report-worker.js +1 -1
  37. package/dist/hooks/codex-stop-task-finalizer.js +1 -1
  38. package/dist/hooks/commit-complete.js +25 -4
  39. package/dist/hooks/error-recall.js +31 -4
  40. package/dist/hooks/ingest-worker.js +25 -4
  41. package/dist/hooks/ingest.js +1 -1
  42. package/dist/hooks/instructions-loaded.js +31 -4
  43. package/dist/hooks/notification.js +31 -4
  44. package/dist/hooks/post-compact.js +31 -4
  45. package/dist/hooks/pre-compact.js +25 -4
  46. package/dist/hooks/pre-tool-use.js +31 -4
  47. package/dist/hooks/prompt-ingest-worker.js +25 -4
  48. package/dist/hooks/prompt-submit.js +25 -4
  49. package/dist/hooks/response-ingest-worker.js +25 -4
  50. package/dist/hooks/session-end.js +25 -4
  51. package/dist/hooks/session-start.js +31 -4
  52. package/dist/hooks/stop.js +25 -4
  53. package/dist/hooks/subagent-stop.js +31 -4
  54. package/dist/hooks/summary-worker.js +25 -4
  55. package/dist/index.js +128 -125
  56. package/dist/lib/cloud-sync.js +31 -4
  57. package/dist/lib/database.js +31 -4
  58. package/dist/lib/db-daemon-client.js +31 -3
  59. package/dist/lib/db.js +31 -4
  60. package/dist/lib/device-registry.js +31 -4
  61. package/dist/lib/embedder.js +30 -3
  62. package/dist/lib/exe-daemon-client.js +31 -3
  63. package/dist/lib/exe-daemon.js +1958 -153
  64. package/dist/lib/hybrid-search.js +31 -4
  65. package/dist/lib/schedules.js +1 -1
  66. package/dist/lib/store.js +1 -1
  67. package/dist/mcp/server.js +25 -4
  68. package/dist/runtime/index.js +25 -4
  69. package/dist/tui/App.js +34 -4
  70. package/package.json +1 -1
@@ -400,7 +400,7 @@ var init_daemon_protocol = __esm({
400
400
  });
401
401
 
402
402
  // src/lib/daemon-auth.ts
403
- import crypto from "crypto";
403
+ import crypto2 from "crypto";
404
404
  import path2 from "path";
405
405
  import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync } from "fs";
406
406
  function normalizeToken(token) {
@@ -419,7 +419,7 @@ function readDaemonToken() {
419
419
  function ensureDaemonToken(seed) {
420
420
  const existing = readDaemonToken();
421
421
  if (existing) return existing;
422
- const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
422
+ const token = normalizeToken(seed) ?? crypto2.randomBytes(32).toString("hex");
423
423
  ensurePrivateDirSync(EXE_AI_DIR);
424
424
  writeFileSync(DAEMON_TOKEN_PATH, `${token}
425
425
  `, "utf8");
@@ -976,8 +976,8 @@ function logQueue(msg) {
976
976
  process.stderr.write(`[intercom-queue] ${msg}
977
977
  `);
978
978
  try {
979
- const { appendFileSync: appendFileSync2 } = __require("fs");
980
- appendFileSync2(INTERCOM_LOG, line);
979
+ const { appendFileSync: appendFileSync3 } = __require("fs");
980
+ appendFileSync3(INTERCOM_LOG, line);
981
981
  } catch {
982
982
  }
983
983
  }
@@ -1635,7 +1635,7 @@ async function ensureCompatibilityViews(prisma) {
1635
1635
  for (const mapping of VIEW_MAPPINGS) {
1636
1636
  const relation = mapping.source.replace(/"/g, "");
1637
1637
  const rows = await prisma.$queryRawUnsafe(
1638
- "SELECT to_regclass($1) AS regclass",
1638
+ "SELECT to_regclass($1)::text AS regclass",
1639
1639
  relation
1640
1640
  );
1641
1641
  if (!rows[0]?.regclass) {
@@ -1952,8 +1952,29 @@ function findPackageRoot() {
1952
1952
  }
1953
1953
  return null;
1954
1954
  }
1955
+ function getAvailableMemoryGB() {
1956
+ if (process.platform === "darwin") {
1957
+ try {
1958
+ const { execSync: execSync12 } = __require("child_process");
1959
+ const vmstat = execSync12("vm_stat", { encoding: "utf8" });
1960
+ const pageSize = 16384;
1961
+ const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1962
+ const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
1963
+ const free = vmstat.match(/Pages free:\s+(\d+)/);
1964
+ const inactive = vmstat.match(/Pages inactive:\s+(\d+)/);
1965
+ const speculative = vmstat.match(/Pages speculative:\s+(\d+)/);
1966
+ const freePages = free ? parseInt(free[1], 10) : 0;
1967
+ const inactivePages = inactive ? parseInt(inactive[1], 10) : 0;
1968
+ const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
1969
+ return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
1970
+ } catch {
1971
+ return os6.freemem() / (1024 * 1024 * 1024);
1972
+ }
1973
+ }
1974
+ return os6.freemem() / (1024 * 1024 * 1024);
1975
+ }
1955
1976
  function spawnDaemon() {
1956
- const freeGB = os6.freemem() / (1024 * 1024 * 1024);
1977
+ const freeGB = getAvailableMemoryGB();
1957
1978
  const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
1958
1979
  if (totalGB <= 8) {
1959
1980
  process.stderr.write(
@@ -1962,9 +1983,9 @@ function spawnDaemon() {
1962
1983
  );
1963
1984
  return;
1964
1985
  }
1965
- if (totalGB <= 16 && freeGB < 4) {
1986
+ if (totalGB <= 16 && freeGB < 2) {
1966
1987
  process.stderr.write(
1967
- `[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB free / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
1988
+ `[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB available / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
1968
1989
  `
1969
1990
  );
1970
1991
  return;
@@ -3546,6 +3567,27 @@ import { pathToFileURL as pathToFileURL2 } from "url";
3546
3567
  import os7 from "os";
3547
3568
  import path9 from "path";
3548
3569
  import { jwtVerify, importSPKI } from "jose";
3570
+ function loadDeviceId() {
3571
+ const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
3572
+ try {
3573
+ if (existsSync9(deviceJsonPath)) {
3574
+ const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
3575
+ if (data.deviceId) return data.deviceId;
3576
+ }
3577
+ } catch {
3578
+ }
3579
+ try {
3580
+ if (existsSync9(DEVICE_ID_PATH)) {
3581
+ const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
3582
+ if (id2) return id2;
3583
+ }
3584
+ } catch {
3585
+ }
3586
+ const id = randomUUID2();
3587
+ mkdirSync4(EXE_AI_DIR, { recursive: true });
3588
+ writeFileSync6(DEVICE_ID_PATH, id, "utf8");
3589
+ return id;
3590
+ }
3549
3591
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
3550
3592
  var init_license = __esm({
3551
3593
  "src/lib/license.ts"() {
@@ -3676,7 +3718,7 @@ var init_task_scope = __esm({
3676
3718
  });
3677
3719
 
3678
3720
  // src/lib/notifications.ts
3679
- import crypto2 from "crypto";
3721
+ import crypto3 from "crypto";
3680
3722
  import path11 from "path";
3681
3723
  import os8 from "os";
3682
3724
  import {
@@ -3689,7 +3731,7 @@ import {
3689
3731
  async function writeNotification(notification) {
3690
3732
  try {
3691
3733
  const client = getClient();
3692
- const id = crypto2.randomUUID();
3734
+ const id = crypto3.randomUUID();
3693
3735
  const now = (/* @__PURE__ */ new Date()).toISOString();
3694
3736
  const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
3695
3737
  await client.execute({
@@ -3745,7 +3787,7 @@ __export(session_kill_telemetry_exports, {
3745
3787
  recordSessionKill: () => recordSessionKill,
3746
3788
  sumTokensSavedSince: () => sumTokensSavedSince
3747
3789
  });
3748
- import crypto3 from "crypto";
3790
+ import crypto4 from "crypto";
3749
3791
  async function recordSessionKill(input) {
3750
3792
  try {
3751
3793
  const client = getClient();
@@ -3755,7 +3797,7 @@ async function recordSessionKill(input) {
3755
3797
  ticks_idle, estimated_tokens_saved)
3756
3798
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
3757
3799
  args: [
3758
- crypto3.randomUUID(),
3800
+ crypto4.randomUUID(),
3759
3801
  input.sessionName,
3760
3802
  input.agentId,
3761
3803
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -3996,7 +4038,7 @@ var init_session_scope = __esm({
3996
4038
  });
3997
4039
 
3998
4040
  // src/lib/tasks-crud.ts
3999
- import crypto4 from "crypto";
4041
+ import crypto5 from "crypto";
4000
4042
  import path13 from "path";
4001
4043
  import os9 from "os";
4002
4044
  import { execSync as execSync6 } from "child_process";
@@ -4117,7 +4159,7 @@ async function resolveTask(client, identifier, scopeSession) {
4117
4159
  }
4118
4160
  async function createTaskCore(input) {
4119
4161
  const client = getClient();
4120
- const id = crypto4.randomUUID();
4162
+ const id = crypto5.randomUUID();
4121
4163
  const now = (/* @__PURE__ */ new Date()).toISOString();
4122
4164
  const slug = slugify(input.title);
4123
4165
  let earlySessionScope = null;
@@ -5063,10 +5105,10 @@ var init_tasks_notify = __esm({
5063
5105
  });
5064
5106
 
5065
5107
  // src/lib/behaviors.ts
5066
- import crypto5 from "crypto";
5108
+ import crypto6 from "crypto";
5067
5109
  async function storeBehavior(opts) {
5068
5110
  const client = getClient();
5069
- const id = crypto5.randomUUID();
5111
+ const id = crypto6.randomUUID();
5070
5112
  const now = (/* @__PURE__ */ new Date()).toISOString();
5071
5113
  await client.execute({
5072
5114
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -5095,7 +5137,7 @@ __export(skill_learning_exports, {
5095
5137
  storeTrajectory: () => storeTrajectory,
5096
5138
  sweepTrajectories: () => sweepTrajectories
5097
5139
  });
5098
- import crypto6 from "crypto";
5140
+ import crypto7 from "crypto";
5099
5141
  async function extractTrajectory(taskId, agentId) {
5100
5142
  const client = getClient();
5101
5143
  const result = await client.execute({
@@ -5124,11 +5166,11 @@ async function extractTrajectory(taskId, agentId) {
5124
5166
  return signature;
5125
5167
  }
5126
5168
  function hashSignature(signature) {
5127
- return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
5169
+ return crypto7.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
5128
5170
  }
5129
5171
  async function storeTrajectory(opts) {
5130
5172
  const client = getClient();
5131
- const id = crypto6.randomUUID();
5173
+ const id = crypto7.randomUUID();
5132
5174
  const now = (/* @__PURE__ */ new Date()).toISOString();
5133
5175
  const signatureHash = hashSignature(opts.signature);
5134
5176
  await client.execute({
@@ -7633,6 +7675,247 @@ var init_daemon_orchestration = __esm({
7633
7675
  }
7634
7676
  });
7635
7677
 
7678
+ // src/lib/projection-worker.ts
7679
+ var projection_worker_exports = {};
7680
+ __export(projection_worker_exports, {
7681
+ processProjectionBatch: () => processProjectionBatch,
7682
+ projectionHandlersForTests: () => projectionHandlersForTests,
7683
+ resetProjectionWorkerForTests: () => resetProjectionWorkerForTests,
7684
+ setProjectionWorkerPrismaClientForTests: () => setProjectionWorkerPrismaClientForTests,
7685
+ startProjectionWorker: () => startProjectionWorker,
7686
+ stopProjectionWorker: () => stopProjectionWorker
7687
+ });
7688
+ import os12 from "os";
7689
+ import path19 from "path";
7690
+ import { createRequire as createRequire3 } from "module";
7691
+ import { pathToFileURL as pathToFileURL3 } from "url";
7692
+ function loadPrisma() {
7693
+ if (!prismaPromise) {
7694
+ prismaPromise = (async () => {
7695
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
7696
+ if (explicitPath) {
7697
+ const mod2 = await import(pathToFileURL3(explicitPath).href);
7698
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
7699
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
7700
+ return new Ctor2();
7701
+ }
7702
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path19.join(os12.homedir(), "exe-db");
7703
+ const req = createRequire3(path19.join(exeDbRoot, "package.json"));
7704
+ const entry = req.resolve("@prisma/client");
7705
+ const mod = await import(pathToFileURL3(entry).href);
7706
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
7707
+ if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
7708
+ return new Ctor();
7709
+ })();
7710
+ }
7711
+ return prismaPromise;
7712
+ }
7713
+ function setProjectionWorkerPrismaClientForTests(client) {
7714
+ prismaPromise = client ? Promise.resolve(client) : null;
7715
+ }
7716
+ function resetProjectionWorkerForTests() {
7717
+ running = false;
7718
+ if (pollTimer) {
7719
+ clearTimeout(pollTimer);
7720
+ pollTimer = null;
7721
+ }
7722
+ prismaPromise = null;
7723
+ }
7724
+ async function processBatch() {
7725
+ const prisma = await loadPrisma();
7726
+ const events = await prisma.$queryRawUnsafe(
7727
+ `SELECT "id", "source", "source_id", "event_type", "payload", "metadata", "timestamp"
7728
+ FROM "raw"."raw_events"
7729
+ WHERE "processed_at" IS NULL
7730
+ ORDER BY "timestamp" ASC
7731
+ LIMIT $1`,
7732
+ BATCH_SIZE
7733
+ );
7734
+ if (events.length === 0) return 0;
7735
+ let processed = 0;
7736
+ for (const event of events) {
7737
+ try {
7738
+ const handler = projectionHandlers[event.source] ?? defaultHandler;
7739
+ const result = await handler(event, prisma);
7740
+ await prisma.$executeRawUnsafe(
7741
+ `UPDATE "raw"."raw_events"
7742
+ SET "processed_at" = NOW(), "projections" = $1::jsonb
7743
+ WHERE "id" = $2`,
7744
+ JSON.stringify({ targets: result.targets, skipped: result.skipped }),
7745
+ event.id
7746
+ );
7747
+ processed++;
7748
+ } catch (err) {
7749
+ const message = err instanceof Error ? err.message : String(err);
7750
+ process.stderr.write(
7751
+ `[projection-worker] Error processing event ${event.id}: ${message}
7752
+ `
7753
+ );
7754
+ await prisma.$executeRawUnsafe(
7755
+ `UPDATE "raw"."raw_events"
7756
+ SET "processed_at" = NOW(), "projections" = $1::jsonb
7757
+ WHERE "id" = $2`,
7758
+ JSON.stringify({ error: message }),
7759
+ event.id
7760
+ );
7761
+ }
7762
+ }
7763
+ return processed;
7764
+ }
7765
+ function startProjectionWorker() {
7766
+ if (running) return;
7767
+ running = true;
7768
+ process.stderr.write("[projection-worker] Starting...\n");
7769
+ const tick = async () => {
7770
+ if (!running) return;
7771
+ try {
7772
+ const count = await processBatch();
7773
+ if (count > 0) {
7774
+ process.stderr.write(`[projection-worker] Processed ${count} events.
7775
+ `);
7776
+ }
7777
+ } catch (err) {
7778
+ process.stderr.write(
7779
+ `[projection-worker] Poll error: ${err instanceof Error ? err.message : String(err)}
7780
+ `
7781
+ );
7782
+ }
7783
+ if (running) {
7784
+ pollTimer = setTimeout(tick, POLL_INTERVAL_MS);
7785
+ }
7786
+ };
7787
+ void tick();
7788
+ }
7789
+ function stopProjectionWorker() {
7790
+ running = false;
7791
+ if (pollTimer) {
7792
+ clearTimeout(pollTimer);
7793
+ pollTimer = null;
7794
+ }
7795
+ process.stderr.write("[projection-worker] Stopped.\n");
7796
+ }
7797
+ async function processProjectionBatch() {
7798
+ return processBatch();
7799
+ }
7800
+ var prismaPromise, projectionHandlers, projectionHandlersForTests, defaultHandler, BATCH_SIZE, POLL_INTERVAL_MS, running, pollTimer;
7801
+ var init_projection_worker = __esm({
7802
+ "src/lib/projection-worker.ts"() {
7803
+ "use strict";
7804
+ prismaPromise = null;
7805
+ projectionHandlers = {
7806
+ async whatsapp(event, prisma) {
7807
+ const targets = [];
7808
+ const payload = event.payload;
7809
+ await prisma.$executeRawUnsafe(
7810
+ `INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent")
7811
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
7812
+ ON CONFLICT ("id") DO NOTHING`,
7813
+ crypto.randomUUID(),
7814
+ "system",
7815
+ "ingest",
7816
+ "projection-worker",
7817
+ "ingest_raw",
7818
+ "exe-os",
7819
+ JSON.stringify(payload),
7820
+ "raw",
7821
+ "whatsapp",
7822
+ event.event_type
7823
+ );
7824
+ targets.push("memory");
7825
+ return { targets };
7826
+ },
7827
+ async shopify(event, prisma) {
7828
+ const targets = [];
7829
+ const payload = event.payload;
7830
+ await prisma.$executeRawUnsafe(
7831
+ `INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent")
7832
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
7833
+ ON CONFLICT ("id") DO NOTHING`,
7834
+ crypto.randomUUID(),
7835
+ "system",
7836
+ "ingest",
7837
+ "projection-worker",
7838
+ "ingest_raw",
7839
+ "exe-os",
7840
+ JSON.stringify(payload),
7841
+ "raw",
7842
+ "shopify",
7843
+ event.event_type
7844
+ );
7845
+ targets.push("memory");
7846
+ return { targets };
7847
+ },
7848
+ async cloud_sync(event, prisma) {
7849
+ const targets = [];
7850
+ const payload = event.payload;
7851
+ await prisma.$executeRawUnsafe(
7852
+ `INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent", "domain")
7853
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
7854
+ ON CONFLICT ("id") DO NOTHING`,
7855
+ payload.id ?? crypto.randomUUID(),
7856
+ payload.agent_id ?? "system",
7857
+ payload.agent_role ?? "ingest",
7858
+ payload.session_id ?? "projection-worker",
7859
+ payload.tool_name ?? "cloud_sync",
7860
+ payload.project_name ?? "exe-os",
7861
+ payload.raw_text ?? JSON.stringify(payload),
7862
+ payload.memory_type ?? "raw",
7863
+ "cloud_sync",
7864
+ event.event_type,
7865
+ payload.domain ?? null
7866
+ );
7867
+ targets.push("memory");
7868
+ return { targets };
7869
+ },
7870
+ async external_agent(event, prisma) {
7871
+ const targets = [];
7872
+ const payload = event.payload;
7873
+ await prisma.$executeRawUnsafe(
7874
+ `INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent", "domain")
7875
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
7876
+ ON CONFLICT ("id") DO NOTHING`,
7877
+ crypto.randomUUID(),
7878
+ "system",
7879
+ "ingest",
7880
+ "projection-worker",
7881
+ "ingest_raw",
7882
+ "exe-os",
7883
+ JSON.stringify(payload),
7884
+ "raw",
7885
+ "external_agent",
7886
+ event.event_type,
7887
+ payload.domain ?? null
7888
+ );
7889
+ targets.push("memory");
7890
+ return { targets };
7891
+ }
7892
+ };
7893
+ projectionHandlersForTests = projectionHandlers;
7894
+ defaultHandler = async (event, prisma) => {
7895
+ await prisma.$executeRawUnsafe(
7896
+ `INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent")
7897
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
7898
+ ON CONFLICT ("id") DO NOTHING`,
7899
+ crypto.randomUUID(),
7900
+ "system",
7901
+ "ingest",
7902
+ "projection-worker",
7903
+ "ingest_raw",
7904
+ "exe-os",
7905
+ JSON.stringify(event.payload),
7906
+ "raw",
7907
+ event.source,
7908
+ event.event_type
7909
+ );
7910
+ return { targets: ["memory"] };
7911
+ };
7912
+ BATCH_SIZE = 50;
7913
+ POLL_INTERVAL_MS = 1e4;
7914
+ running = false;
7915
+ pollTimer = null;
7916
+ }
7917
+ });
7918
+
7636
7919
  // src/lib/shard-manager.ts
7637
7920
  var shard_manager_exports = {};
7638
7921
  __export(shard_manager_exports, {
@@ -7647,7 +7930,7 @@ __export(shard_manager_exports, {
7647
7930
  listShards: () => listShards,
7648
7931
  shardExists: () => shardExists
7649
7932
  });
7650
- import path19 from "path";
7933
+ import path20 from "path";
7651
7934
  import { existsSync as existsSync17, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
7652
7935
  import { createClient as createClient2 } from "@libsql/client";
7653
7936
  function initShardManager(encryptionKey) {
@@ -7682,7 +7965,7 @@ function getShardClient(projectName) {
7682
7965
  while (_shards.size >= MAX_OPEN_SHARDS) {
7683
7966
  evictLRU();
7684
7967
  }
7685
- const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
7968
+ const dbPath = path20.join(SHARDS_DIR, `${safeName}.db`);
7686
7969
  const client = createClient2({
7687
7970
  url: `file:${dbPath}`,
7688
7971
  encryptionKey: _encryptionKey
@@ -7693,7 +7976,7 @@ function getShardClient(projectName) {
7693
7976
  }
7694
7977
  function shardExists(projectName) {
7695
7978
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
7696
- return existsSync17(path19.join(SHARDS_DIR, `${safeName}.db`));
7979
+ return existsSync17(path20.join(SHARDS_DIR, `${safeName}.db`));
7697
7980
  }
7698
7981
  function listShards() {
7699
7982
  if (!existsSync17(SHARDS_DIR)) return [];
@@ -7943,7 +8226,7 @@ var init_shard_manager = __esm({
7943
8226
  "src/lib/shard-manager.ts"() {
7944
8227
  "use strict";
7945
8228
  init_config();
7946
- SHARDS_DIR = path19.join(EXE_AI_DIR, "shards");
8229
+ SHARDS_DIR = path20.join(EXE_AI_DIR, "shards");
7947
8230
  SHARD_IDLE_MS = 5 * 60 * 1e3;
7948
8231
  MAX_OPEN_SHARDS = 10;
7949
8232
  EVICTION_INTERVAL_MS = 60 * 1e3;
@@ -7966,13 +8249,13 @@ __export(keychain_exports, {
7966
8249
  });
7967
8250
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
7968
8251
  import { existsSync as existsSync18 } from "fs";
7969
- import path20 from "path";
7970
- import os12 from "os";
8252
+ import path21 from "path";
8253
+ import os13 from "os";
7971
8254
  function getKeyDir() {
7972
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path20.join(os12.homedir(), ".exe-os");
8255
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path21.join(os13.homedir(), ".exe-os");
7973
8256
  }
7974
8257
  function getKeyPath() {
7975
- return path20.join(getKeyDir(), "master.key");
8258
+ return path21.join(getKeyDir(), "master.key");
7976
8259
  }
7977
8260
  async function tryKeytar() {
7978
8261
  try {
@@ -7995,7 +8278,7 @@ async function getMasterKey() {
7995
8278
  const keyPath = getKeyPath();
7996
8279
  if (!existsSync18(keyPath)) {
7997
8280
  process.stderr.write(
7998
- `[keychain] Key not found at ${keyPath} (HOME=${os12.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
8281
+ `[keychain] Key not found at ${keyPath} (HOME=${os13.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
7999
8282
  `
8000
8283
  );
8001
8284
  return null;
@@ -9215,16 +9498,16 @@ async function pushToWiki(consolidation, config) {
9215
9498
  const contentLines = consolidation.rawText.split("\n").filter((l) => l.trim() && !l.startsWith("CONSOLIDATION") && !l.match(/^[A-Z\s]+:$/)).join(" ");
9216
9499
  const keywords = contentLines.toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((w) => w.length > 3);
9217
9500
  let bestMatch = null;
9218
- for (const doc of docs) {
9219
- if (!doc.id || !doc.title) continue;
9220
- const titleWords = doc.title.toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((w) => w.length > 3);
9501
+ for (const doc2 of docs) {
9502
+ if (!doc2.id || !doc2.title) continue;
9503
+ const titleWords = doc2.title.toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((w) => w.length > 3);
9221
9504
  if (titleWords.length === 0) continue;
9222
9505
  const matchCount = titleWords.filter(
9223
9506
  (tw) => keywords.some((k) => k.includes(tw) || tw.includes(k))
9224
9507
  ).length;
9225
9508
  const score = matchCount / titleWords.length;
9226
9509
  if (score > (bestMatch?.score ?? 0)) {
9227
- bestMatch = { id: doc.id, title: doc.title, score };
9510
+ bestMatch = { id: doc2.id, title: doc2.title, score };
9228
9511
  }
9229
9512
  }
9230
9513
  if (bestMatch && bestMatch.score >= config.wikiAutoUpdateThreshold) {
@@ -9453,10 +9736,10 @@ async function disposeEmbedder() {
9453
9736
  async function embedDirect(text) {
9454
9737
  const llamaCpp = await import("node-llama-cpp");
9455
9738
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
9456
- const { existsSync: existsSync21 } = await import("fs");
9457
- const path25 = await import("path");
9458
- const modelPath = path25.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
9459
- if (!existsSync21(modelPath)) {
9739
+ const { existsSync: existsSync23 } = await import("fs");
9740
+ const path28 = await import("path");
9741
+ const modelPath = path28.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
9742
+ if (!existsSync23(modelPath)) {
9460
9743
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
9461
9744
  }
9462
9745
  const llama = await llamaCpp.getLlama();
@@ -9484,69 +9767,1554 @@ var init_embedder = __esm({
9484
9767
  }
9485
9768
  });
9486
9769
 
9487
- // src/lib/code-chunker.ts
9488
- import ts from "typescript";
9489
- function chunkSourceFile(source, fileName = "file.ts") {
9490
- const sourceFile = ts.createSourceFile(
9491
- fileName,
9492
- source,
9493
- ts.ScriptTarget.Latest,
9494
- true,
9495
- // setParentNodes
9496
- fileName.endsWith(".tsx") || fileName.endsWith(".jsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
9770
+ // src/lib/crypto.ts
9771
+ import crypto8 from "crypto";
9772
+ function initSyncCrypto(masterKey) {
9773
+ if (masterKey.length !== 32) {
9774
+ throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
9775
+ }
9776
+ _syncKey = Buffer.from(
9777
+ crypto8.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
9497
9778
  );
9498
- const chunks = [];
9499
- const lines = source.split("\n");
9500
- const importLines = [];
9501
- function getLineNumber(pos) {
9502
- return sourceFile.getLineAndCharacterOfPosition(pos).line + 1;
9779
+ }
9780
+ function isSyncCryptoInitialized() {
9781
+ return _syncKey !== null;
9782
+ }
9783
+ function requireSyncKey() {
9784
+ if (!_syncKey) {
9785
+ throw new Error("Sync crypto not initialized. Call initSyncCrypto(masterKey) first.");
9786
+ }
9787
+ return _syncKey;
9788
+ }
9789
+ function encryptSyncBlob(data) {
9790
+ const key = requireSyncKey();
9791
+ const iv = crypto8.randomBytes(IV_LENGTH);
9792
+ const cipher = crypto8.createCipheriv(ALGORITHM, key, iv);
9793
+ const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
9794
+ const tag = cipher.getAuthTag();
9795
+ return Buffer.concat([iv, encrypted, tag]).toString("base64");
9796
+ }
9797
+ function decryptSyncBlob(ciphertext) {
9798
+ const key = requireSyncKey();
9799
+ const combined = Buffer.from(ciphertext, "base64");
9800
+ if (combined.length < IV_LENGTH + TAG_LENGTH) {
9801
+ throw new Error("Sync blob too short to contain IV + tag");
9802
+ }
9803
+ const iv = combined.subarray(0, IV_LENGTH);
9804
+ const tag = combined.subarray(combined.length - TAG_LENGTH);
9805
+ const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
9806
+ const decipher = crypto8.createDecipheriv(ALGORITHM, key, iv);
9807
+ decipher.setAuthTag(tag);
9808
+ return Buffer.concat([decipher.update(encrypted), decipher.final()]);
9809
+ }
9810
+ var ALGORITHM, IV_LENGTH, TAG_LENGTH, SYNC_HKDF_INFO, _syncKey;
9811
+ var init_crypto = __esm({
9812
+ "src/lib/crypto.ts"() {
9813
+ "use strict";
9814
+ ALGORITHM = "aes-256-gcm";
9815
+ IV_LENGTH = 12;
9816
+ TAG_LENGTH = 16;
9817
+ SYNC_HKDF_INFO = "exe-mem-sync-v2";
9818
+ _syncKey = null;
9503
9819
  }
9504
- function getLeadingComment(node) {
9505
- const fullText = sourceFile.getFullText();
9506
- const ranges = ts.getLeadingCommentRanges(fullText, node.getFullStart());
9507
- if (!ranges || ranges.length === 0) return void 0;
9508
- return ranges.map((r) => fullText.slice(r.pos, r.end)).join("\n");
9820
+ });
9821
+
9822
+ // src/lib/compress.ts
9823
+ import { brotliCompressSync, brotliDecompressSync, constants } from "zlib";
9824
+ function compress(input) {
9825
+ if (input.length === 0) return Buffer.alloc(0);
9826
+ return brotliCompressSync(input, {
9827
+ params: {
9828
+ [constants.BROTLI_PARAM_QUALITY]: 4
9829
+ }
9830
+ });
9831
+ }
9832
+ function decompress(input) {
9833
+ if (input.length === 0) return Buffer.alloc(0);
9834
+ return brotliDecompressSync(input);
9835
+ }
9836
+ var init_compress = __esm({
9837
+ "src/lib/compress.ts"() {
9838
+ "use strict";
9509
9839
  }
9510
- function getNodeText(node) {
9511
- return node.getText(sourceFile);
9840
+ });
9841
+
9842
+ // src/lib/crdt-sync.ts
9843
+ var crdt_sync_exports = {};
9844
+ __export(crdt_sync_exports, {
9845
+ _setStatePath: () => _setStatePath,
9846
+ applyRemoteUpdate: () => applyRemoteUpdate,
9847
+ destroyCrdtDoc: () => destroyCrdtDoc,
9848
+ getDiffUpdate: () => getDiffUpdate,
9849
+ getFullState: () => getFullState,
9850
+ getStateVector: () => getStateVector,
9851
+ importExistingBehaviors: () => importExistingBehaviors,
9852
+ importExistingMemories: () => importExistingMemories,
9853
+ initCrdtDoc: () => initCrdtDoc,
9854
+ isCrdtSyncEnabled: () => isCrdtSyncEnabled,
9855
+ onUpdate: () => onUpdate,
9856
+ readAllBehaviors: () => readAllBehaviors,
9857
+ readAllMemories: () => readAllMemories,
9858
+ rebuildFromDb: () => rebuildFromDb
9859
+ });
9860
+ import * as Y from "yjs";
9861
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync10, existsSync as existsSync19, mkdirSync as mkdirSync8, unlinkSync as unlinkSync7 } from "fs";
9862
+ import path22 from "path";
9863
+ import { homedir as homedir2 } from "os";
9864
+ function getStatePath() {
9865
+ return _statePathOverride ?? DEFAULT_STATE_PATH;
9866
+ }
9867
+ function _setStatePath(p) {
9868
+ _statePathOverride = p;
9869
+ }
9870
+ function initCrdtDoc() {
9871
+ if (doc) return doc;
9872
+ doc = new Y.Doc();
9873
+ const sp = getStatePath();
9874
+ if (existsSync19(sp)) {
9875
+ try {
9876
+ const state = readFileSync15(sp);
9877
+ Y.applyUpdate(doc, new Uint8Array(state));
9878
+ } catch {
9879
+ console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
9880
+ try {
9881
+ unlinkSync7(sp);
9882
+ } catch {
9883
+ }
9884
+ rebuildFromDb().catch((err) => {
9885
+ console.warn("[crdt-sync] rebuild from DB failed:", err);
9886
+ });
9887
+ }
9512
9888
  }
9513
- function getName(node) {
9514
- if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) {
9515
- return node.name?.getText(sourceFile) ?? "(anonymous)";
9889
+ doc.on("update", () => {
9890
+ persistState();
9891
+ });
9892
+ return doc;
9893
+ }
9894
+ function getMemoriesMap() {
9895
+ const d = initCrdtDoc();
9896
+ return d.getMap("memories");
9897
+ }
9898
+ function getBehaviorsMap() {
9899
+ const d = initCrdtDoc();
9900
+ return d.getMap("behaviors");
9901
+ }
9902
+ function applyRemoteUpdate(update) {
9903
+ const d = initCrdtDoc();
9904
+ Y.applyUpdate(d, update);
9905
+ }
9906
+ function getFullState() {
9907
+ const d = initCrdtDoc();
9908
+ return Y.encodeStateAsUpdate(d);
9909
+ }
9910
+ function getDiffUpdate(remoteStateVector) {
9911
+ const d = initCrdtDoc();
9912
+ return Y.encodeStateAsUpdate(d, remoteStateVector);
9913
+ }
9914
+ function getStateVector() {
9915
+ const d = initCrdtDoc();
9916
+ return Y.encodeStateVector(d);
9917
+ }
9918
+ function importExistingMemories(memories) {
9919
+ const map = getMemoriesMap();
9920
+ const d = initCrdtDoc();
9921
+ let imported = 0;
9922
+ d.transact(() => {
9923
+ for (const mem of memories) {
9924
+ if (!mem.id) continue;
9925
+ if (map.has(mem.id)) continue;
9926
+ const entry = new Y.Map();
9927
+ entry.set("id", mem.id);
9928
+ entry.set("agent_id", mem.agent_id ?? null);
9929
+ entry.set("agent_role", mem.agent_role ?? null);
9930
+ entry.set("session_id", mem.session_id ?? null);
9931
+ entry.set("timestamp", mem.timestamp ?? null);
9932
+ entry.set("tool_name", mem.tool_name ?? null);
9933
+ entry.set("project_name", mem.project_name ?? null);
9934
+ entry.set("has_error", mem.has_error ?? 0);
9935
+ entry.set("raw_text", mem.raw_text ?? "");
9936
+ entry.set("version", mem.version ?? 0);
9937
+ entry.set("author_device_id", mem.author_device_id ?? null);
9938
+ entry.set("scope", mem.scope ?? "business");
9939
+ map.set(mem.id, entry);
9940
+ imported++;
9516
9941
  }
9517
- if (ts.isClassDeclaration(node)) {
9518
- return node.name?.getText(sourceFile) ?? "(anonymous class)";
9942
+ });
9943
+ return imported;
9944
+ }
9945
+ function importExistingBehaviors(behaviors) {
9946
+ const map = getBehaviorsMap();
9947
+ const d = initCrdtDoc();
9948
+ let imported = 0;
9949
+ d.transact(() => {
9950
+ for (const beh of behaviors) {
9951
+ if (!beh.id) continue;
9952
+ if (map.has(beh.id)) continue;
9953
+ const entry = new Y.Map();
9954
+ entry.set("id", beh.id);
9955
+ entry.set("agent_id", beh.agent_id ?? null);
9956
+ entry.set("project_name", beh.project_name ?? null);
9957
+ entry.set("domain", beh.domain ?? null);
9958
+ entry.set("content", beh.content ?? null);
9959
+ entry.set("active", beh.active ?? 1);
9960
+ entry.set("priority", beh.priority ?? "p1");
9961
+ entry.set("created_at", beh.created_at ?? null);
9962
+ entry.set("updated_at", beh.updated_at ?? null);
9963
+ map.set(beh.id, entry);
9964
+ imported++;
9519
9965
  }
9520
- if (ts.isInterfaceDeclaration(node)) {
9521
- return node.name.getText(sourceFile);
9966
+ });
9967
+ return imported;
9968
+ }
9969
+ function readAllMemories() {
9970
+ const map = getMemoriesMap();
9971
+ const records = [];
9972
+ map.forEach((entry, id) => {
9973
+ records.push({
9974
+ id,
9975
+ agent_id: entry.get("agent_id"),
9976
+ agent_role: entry.get("agent_role"),
9977
+ session_id: entry.get("session_id"),
9978
+ timestamp: entry.get("timestamp"),
9979
+ tool_name: entry.get("tool_name"),
9980
+ project_name: entry.get("project_name"),
9981
+ has_error: entry.get("has_error"),
9982
+ raw_text: entry.get("raw_text"),
9983
+ version: entry.get("version"),
9984
+ author_device_id: entry.get("author_device_id"),
9985
+ scope: entry.get("scope")
9986
+ });
9987
+ });
9988
+ return records;
9989
+ }
9990
+ function readAllBehaviors() {
9991
+ const map = getBehaviorsMap();
9992
+ const records = [];
9993
+ map.forEach((entry, id) => {
9994
+ records.push({
9995
+ id,
9996
+ agent_id: entry.get("agent_id"),
9997
+ project_name: entry.get("project_name"),
9998
+ domain: entry.get("domain"),
9999
+ content: entry.get("content"),
10000
+ active: entry.get("active"),
10001
+ priority: entry.get("priority"),
10002
+ created_at: entry.get("created_at"),
10003
+ updated_at: entry.get("updated_at")
10004
+ });
10005
+ });
10006
+ return records;
10007
+ }
10008
+ function onUpdate(callback) {
10009
+ const d = initCrdtDoc();
10010
+ const handler = (update) => callback(update);
10011
+ d.on("update", handler);
10012
+ return () => d.off("update", handler);
10013
+ }
10014
+ function persistState() {
10015
+ if (!doc) return;
10016
+ try {
10017
+ const sp = getStatePath();
10018
+ const dir = path22.dirname(sp);
10019
+ if (!existsSync19(dir)) mkdirSync8(dir, { recursive: true });
10020
+ const state = Y.encodeStateAsUpdate(doc);
10021
+ writeFileSync10(sp, Buffer.from(state));
10022
+ } catch {
10023
+ }
10024
+ }
10025
+ function isCrdtSyncEnabled() {
10026
+ return process.env.EXE_CRDT_SYNC !== "0";
10027
+ }
10028
+ async function rebuildFromDb() {
10029
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
10030
+ const client = getClient2();
10031
+ const result = await client.execute(
10032
+ "SELECT id, agent_id, agent_role, session_id, timestamp, tool_name, project_name, has_error, raw_text, version, author_device_id, scope FROM memories"
10033
+ );
10034
+ const memories = result.rows.map((row) => ({
10035
+ id: String(row.id),
10036
+ agent_id: row.agent_id,
10037
+ agent_role: row.agent_role,
10038
+ session_id: row.session_id,
10039
+ timestamp: row.timestamp,
10040
+ tool_name: row.tool_name,
10041
+ project_name: row.project_name,
10042
+ has_error: row.has_error,
10043
+ raw_text: row.raw_text,
10044
+ version: row.version,
10045
+ author_device_id: row.author_device_id,
10046
+ scope: row.scope
10047
+ }));
10048
+ const count = importExistingMemories(memories);
10049
+ persistState();
10050
+ return count;
10051
+ }
10052
+ function destroyCrdtDoc() {
10053
+ if (doc) {
10054
+ doc.destroy();
10055
+ doc = null;
10056
+ }
10057
+ }
10058
+ var DEFAULT_STATE_PATH, _statePathOverride, doc;
10059
+ var init_crdt_sync = __esm({
10060
+ "src/lib/crdt-sync.ts"() {
10061
+ "use strict";
10062
+ DEFAULT_STATE_PATH = path22.join(homedir2(), ".exe-os", "crdt-state.bin");
10063
+ _statePathOverride = null;
10064
+ doc = null;
10065
+ }
10066
+ });
10067
+
10068
+ // src/lib/cloud-sync.ts
10069
+ var cloud_sync_exports = {};
10070
+ __export(cloud_sync_exports, {
10071
+ assertSecureEndpoint: () => assertSecureEndpoint,
10072
+ buildRosterBlob: () => buildRosterBlob,
10073
+ cloudPull: () => cloudPull,
10074
+ cloudPullBehaviors: () => cloudPullBehaviors,
10075
+ cloudPullBlob: () => cloudPullBlob,
10076
+ cloudPullConversations: () => cloudPullConversations,
10077
+ cloudPullDocuments: () => cloudPullDocuments,
10078
+ cloudPullGlobalProcedures: () => cloudPullGlobalProcedures,
10079
+ cloudPullGraphRAG: () => cloudPullGraphRAG,
10080
+ cloudPullRoster: () => cloudPullRoster,
10081
+ cloudPullTasks: () => cloudPullTasks,
10082
+ cloudPush: () => cloudPush,
10083
+ cloudPushBehaviors: () => cloudPushBehaviors,
10084
+ cloudPushBlob: () => cloudPushBlob,
10085
+ cloudPushConversations: () => cloudPushConversations,
10086
+ cloudPushDocuments: () => cloudPushDocuments,
10087
+ cloudPushGlobalProcedures: () => cloudPushGlobalProcedures,
10088
+ cloudPushGraphRAG: () => cloudPushGraphRAG,
10089
+ cloudPushRoster: () => cloudPushRoster,
10090
+ cloudPushTasks: () => cloudPushTasks,
10091
+ cloudSync: () => cloudSync,
10092
+ mergeConfig: () => mergeConfig,
10093
+ mergeRosterFromRemote: () => mergeRosterFromRemote,
10094
+ pushToPostgres: () => pushToPostgres,
10095
+ recordRosterDeletion: () => recordRosterDeletion
10096
+ });
10097
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync11, existsSync as existsSync20, readdirSync as readdirSync5, mkdirSync as mkdirSync9, appendFileSync as appendFileSync2, unlinkSync as unlinkSync8, openSync as openSync2, closeSync as closeSync2 } from "fs";
10098
+ import crypto9 from "crypto";
10099
+ import path23 from "path";
10100
+ import { homedir as homedir3 } from "os";
10101
+ function sqlSafe(v) {
10102
+ return v === void 0 ? null : v;
10103
+ }
10104
+ function logError(msg) {
10105
+ try {
10106
+ const logPath = path23.join(homedir3(), ".exe-os", "workers.log");
10107
+ appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
10108
+ `);
10109
+ } catch {
10110
+ }
10111
+ }
10112
+ function loadPgClient() {
10113
+ if (_pgFailed) return null;
10114
+ const postgresUrl = process.env.DATABASE_URL;
10115
+ const configPath = path23.join(EXE_AI_DIR, "config.json");
10116
+ let cloudPostgresUrl;
10117
+ try {
10118
+ if (existsSync20(configPath)) {
10119
+ const cfg = JSON.parse(readFileSync16(configPath, "utf8"));
10120
+ cloudPostgresUrl = cfg.cloud?.postgresUrl;
10121
+ if (cfg.cloud?.syncToPostgres === false) {
10122
+ _pgFailed = true;
10123
+ return null;
10124
+ }
9522
10125
  }
9523
- if (ts.isTypeAliasDeclaration(node)) {
9524
- return node.name.getText(sourceFile);
10126
+ } catch {
10127
+ }
10128
+ const url = postgresUrl || cloudPostgresUrl;
10129
+ if (!url) {
10130
+ _pgFailed = true;
10131
+ return null;
10132
+ }
10133
+ if (!_pgPromise) {
10134
+ _pgPromise = (async () => {
10135
+ const { createRequire: createRequire4 } = await import("module");
10136
+ const { pathToFileURL: pathToFileURL4 } = await import("url");
10137
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path23.join(homedir3(), "exe-db");
10138
+ const req = createRequire4(path23.join(exeDbRoot, "package.json"));
10139
+ const entry = req.resolve("@prisma/client");
10140
+ const mod = await import(pathToFileURL4(entry).href);
10141
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
10142
+ if (!Ctor) throw new Error("No PrismaClient");
10143
+ return new Ctor();
10144
+ })().catch(() => {
10145
+ _pgFailed = true;
10146
+ _pgPromise = null;
10147
+ throw new Error("pg_unavailable");
10148
+ });
10149
+ }
10150
+ return _pgPromise;
10151
+ }
10152
+ async function pushToPostgres(records) {
10153
+ const loader = loadPgClient();
10154
+ if (!loader) return 0;
10155
+ let prisma;
10156
+ try {
10157
+ prisma = await loader;
10158
+ } catch {
10159
+ return 0;
10160
+ }
10161
+ let inserted = 0;
10162
+ for (const rec of records) {
10163
+ try {
10164
+ await prisma.$executeRawUnsafe(
10165
+ `INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
10166
+ VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
10167
+ ON CONFLICT (source, source_id, event_type) DO NOTHING`,
10168
+ String(rec.id ?? ""),
10169
+ JSON.stringify(rec),
10170
+ JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
10171
+ rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
10172
+ );
10173
+ inserted++;
10174
+ } catch {
9525
10175
  }
9526
- if (ts.isEnumDeclaration(node)) {
9527
- return node.name.getText(sourceFile);
10176
+ }
10177
+ return inserted;
10178
+ }
10179
+ async function withRosterLock(fn) {
10180
+ try {
10181
+ const fd = openSync2(ROSTER_LOCK_PATH, "wx");
10182
+ closeSync2(fd);
10183
+ writeFileSync11(ROSTER_LOCK_PATH, String(Date.now()));
10184
+ } catch (err) {
10185
+ if (err.code === "EEXIST") {
10186
+ try {
10187
+ const ts2 = parseInt(readFileSync16(ROSTER_LOCK_PATH, "utf-8"), 10);
10188
+ if (Date.now() - ts2 < LOCK_STALE_MS) {
10189
+ throw new Error("Roster merge already in progress \u2014 another sync is running");
10190
+ }
10191
+ unlinkSync8(ROSTER_LOCK_PATH);
10192
+ const fd = openSync2(ROSTER_LOCK_PATH, "wx");
10193
+ closeSync2(fd);
10194
+ writeFileSync11(ROSTER_LOCK_PATH, String(Date.now()));
10195
+ } catch (retryErr) {
10196
+ if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
10197
+ throw new Error("Roster merge already in progress \u2014 another sync is running");
10198
+ }
10199
+ } else {
10200
+ throw err;
9528
10201
  }
9529
- if (ts.isVariableStatement(node)) {
9530
- const decls = node.declarationList.declarations;
9531
- return decls.map((d) => d.name.getText(sourceFile)).join(", ");
10202
+ }
10203
+ try {
10204
+ return await fn();
10205
+ } finally {
10206
+ try {
10207
+ unlinkSync8(ROSTER_LOCK_PATH);
10208
+ } catch {
9532
10209
  }
9533
- if (ts.isExportAssignment(node)) {
9534
- return "default export";
10210
+ }
10211
+ }
10212
+ async function fetchWithRetry(url, init) {
10213
+ const MAX_RETRIES4 = 3;
10214
+ const BASE_DELAY_MS2 = 200;
10215
+ let lastError;
10216
+ for (let attempt = 0; attempt <= MAX_RETRIES4; attempt++) {
10217
+ try {
10218
+ const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
10219
+ const resp = await fetch(url, { ...init, signal });
10220
+ if (resp && resp.status >= 500 && attempt < MAX_RETRIES4) {
10221
+ await new Promise((r) => setTimeout(r, BASE_DELAY_MS2 * Math.pow(2, attempt)));
10222
+ continue;
10223
+ }
10224
+ return resp;
10225
+ } catch (err) {
10226
+ lastError = err;
10227
+ if (attempt === MAX_RETRIES4) throw err;
10228
+ await new Promise((r) => setTimeout(r, BASE_DELAY_MS2 * Math.pow(2, attempt)));
9535
10229
  }
9536
- return "(unknown)";
9537
10230
  }
9538
- function visitTopLevel(node) {
9539
- if (ts.isImportDeclaration(node)) {
9540
- importLines.push({
9541
- start: getLineNumber(node.getStart(sourceFile)),
9542
- end: getLineNumber(node.getEnd())
9543
- });
10231
+ throw lastError;
10232
+ }
10233
+ function assertSecureEndpoint(endpoint) {
10234
+ if (endpoint.startsWith("https://")) return;
10235
+ if (endpoint.startsWith("http://")) {
10236
+ try {
10237
+ const parsed = new URL(endpoint);
10238
+ if (LOCALHOST_PATTERNS.test(parsed.hostname)) return;
10239
+ } catch {
9544
10240
  return;
9545
10241
  }
9546
- if (ts.isFunctionDeclaration(node)) {
9547
- chunks.push({
9548
- kind: "function",
9549
- name: getName(node),
10242
+ throw new Error(
10243
+ `Insecure cloud endpoint rejected: "${endpoint}". Use https:// for remote hosts. Plain http:// is only allowed for localhost.`
10244
+ );
10245
+ }
10246
+ }
10247
+ async function cloudPush(records, maxVersion, config) {
10248
+ if (records.length === 0) return true;
10249
+ assertSecureEndpoint(config.endpoint);
10250
+ try {
10251
+ const json = JSON.stringify(records);
10252
+ const compressed = compress(Buffer.from(json, "utf8"));
10253
+ const blob = encryptSyncBlob(compressed);
10254
+ const resp = await fetchWithRetry(`${config.endpoint}/sync/push`, {
10255
+ method: "POST",
10256
+ headers: {
10257
+ Authorization: `Bearer ${config.apiKey}`,
10258
+ "Content-Type": "application/json",
10259
+ "X-Device-Id": loadDeviceId(),
10260
+ "X-Expected-Version": String(maxVersion)
10261
+ },
10262
+ body: JSON.stringify({ version: maxVersion, blob })
10263
+ });
10264
+ if (resp == null) {
10265
+ logError("[cloud-sync] PUSH FAILED: no response from server");
10266
+ return false;
10267
+ }
10268
+ if (resp.status === 409) {
10269
+ logError("[cloud-sync] PUSH VERSION CONFLICT \u2014 re-pull required before next push");
10270
+ return false;
10271
+ }
10272
+ return resp.ok;
10273
+ } catch (err) {
10274
+ logError(`[cloud-sync] PUSH FAILED: ${err instanceof Error ? err.message : String(err)}`);
10275
+ return false;
10276
+ }
10277
+ }
10278
+ async function cloudPull(sinceVersion, config) {
10279
+ assertSecureEndpoint(config.endpoint);
10280
+ try {
10281
+ const response = await fetchWithRetry(`${config.endpoint}/sync/pull`, {
10282
+ method: "POST",
10283
+ headers: {
10284
+ Authorization: `Bearer ${config.apiKey}`,
10285
+ "Content-Type": "application/json",
10286
+ "X-Device-Id": loadDeviceId()
10287
+ },
10288
+ body: JSON.stringify({ since_version: sinceVersion })
10289
+ });
10290
+ if (response == null) {
10291
+ logError("[cloud-sync] PULL FAILED: no response from server");
10292
+ return { records: [], maxVersion: sinceVersion };
10293
+ }
10294
+ if (!response.ok) return { records: [], maxVersion: sinceVersion };
10295
+ const data = await response.json();
10296
+ const allRecords = [];
10297
+ for (const { blob } of data.blobs ?? []) {
10298
+ try {
10299
+ const compressed = decryptSyncBlob(blob);
10300
+ const json = decompress(compressed).toString("utf8");
10301
+ const records = JSON.parse(json);
10302
+ allRecords.push(...records);
10303
+ } catch {
10304
+ continue;
10305
+ }
10306
+ }
10307
+ return { records: allRecords, maxVersion: data.max_version ?? sinceVersion };
10308
+ } catch (err) {
10309
+ logError(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}`);
10310
+ return { records: [], maxVersion: sinceVersion };
10311
+ }
10312
+ }
10313
+ async function cloudSync(config) {
10314
+ if (!isSyncCryptoInitialized()) {
10315
+ try {
10316
+ const { getMasterKey: getMasterKey2 } = await Promise.resolve().then(() => (init_keychain(), keychain_exports));
10317
+ const masterKey = await getMasterKey2();
10318
+ if (masterKey) {
10319
+ initSyncCrypto(masterKey);
10320
+ } else {
10321
+ throw new Error("No master key found");
10322
+ }
10323
+ } catch (err) {
10324
+ throw new Error(`[cloud-sync] Cannot initialize encryption: ${err instanceof Error ? err.message : String(err)}`);
10325
+ }
10326
+ }
10327
+ let client;
10328
+ try {
10329
+ client = getClient();
10330
+ } catch {
10331
+ throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
10332
+ }
10333
+ try {
10334
+ const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
10335
+ await getRawClient2().execute("PRAGMA wal_checkpoint(PASSIVE)");
10336
+ } catch {
10337
+ }
10338
+ try {
10339
+ await client.execute(
10340
+ "CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)"
10341
+ );
10342
+ } catch (e) {
10343
+ logError(`[cloud-sync] sync_meta CREATE failed: ${e instanceof Error ? e.message : String(e)}`);
10344
+ }
10345
+ const pullMeta = await client.execute(
10346
+ "SELECT value FROM sync_meta WHERE key = 'last_cloud_pull_version'"
10347
+ );
10348
+ const lastPullVersion = pullMeta.rows.length > 0 ? Number(pullMeta.rows[0].value) : 0;
10349
+ const pullResult = await cloudPull(lastPullVersion, config);
10350
+ let pulled = 0;
10351
+ if (pullResult.records.length > 0) {
10352
+ if (isCrdtSyncEnabled()) {
10353
+ const { initCrdtDoc: initCrdtDoc2, importExistingMemories: importExistingMemories2, readAllMemories: readAllMemories2 } = await Promise.resolve().then(() => (init_crdt_sync(), crdt_sync_exports));
10354
+ initCrdtDoc2();
10355
+ importExistingMemories2(
10356
+ pullResult.records.map((rec) => ({
10357
+ id: String(rec.id ?? ""),
10358
+ agent_id: rec.agent_id,
10359
+ agent_role: rec.agent_role,
10360
+ session_id: rec.session_id,
10361
+ timestamp: rec.timestamp,
10362
+ tool_name: rec.tool_name,
10363
+ project_name: rec.project_name,
10364
+ has_error: rec.has_error ?? 0,
10365
+ raw_text: rec.raw_text ?? "",
10366
+ version: rec.version ?? 0,
10367
+ author_device_id: rec.author_device_id,
10368
+ scope: rec.scope ?? "business"
10369
+ }))
10370
+ );
10371
+ const pulledIds = new Set(pullResult.records.map((r) => String(r.id ?? "")));
10372
+ const merged = readAllMemories2().filter((rec) => pulledIds.has(rec.id));
10373
+ const stmts = merged.map((rec) => ({
10374
+ sql: `INSERT OR REPLACE INTO memories
10375
+ (id, agent_id, agent_role, session_id, timestamp,
10376
+ tool_name, project_name, has_error, raw_text, version,
10377
+ author_device_id, scope)
10378
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
10379
+ args: [
10380
+ sqlSafe(rec.id),
10381
+ sqlSafe(rec.agent_id),
10382
+ sqlSafe(rec.agent_role),
10383
+ sqlSafe(rec.session_id),
10384
+ sqlSafe(rec.timestamp),
10385
+ sqlSafe(rec.tool_name),
10386
+ sqlSafe(rec.project_name),
10387
+ sqlSafe(rec.has_error ?? 0),
10388
+ sqlSafe(rec.raw_text ?? ""),
10389
+ sqlSafe(rec.version ?? 0),
10390
+ sqlSafe(rec.author_device_id),
10391
+ sqlSafe(rec.scope ?? "business")
10392
+ ]
10393
+ }));
10394
+ if (stmts.length > 0) await client.batch(stmts, "write");
10395
+ pulled = pullResult.records.length;
10396
+ } else {
10397
+ const stmts = pullResult.records.map((rec) => ({
10398
+ sql: `INSERT OR REPLACE INTO memories
10399
+ (id, agent_id, agent_role, session_id, timestamp,
10400
+ tool_name, project_name, has_error, raw_text, version,
10401
+ author_device_id, scope)
10402
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
10403
+ args: [
10404
+ sqlSafe(rec.id),
10405
+ sqlSafe(rec.agent_id),
10406
+ sqlSafe(rec.agent_role),
10407
+ sqlSafe(rec.session_id),
10408
+ sqlSafe(rec.timestamp),
10409
+ sqlSafe(rec.tool_name),
10410
+ sqlSafe(rec.project_name),
10411
+ sqlSafe(rec.has_error ?? 0),
10412
+ sqlSafe(rec.raw_text ?? ""),
10413
+ sqlSafe(rec.version ?? 0),
10414
+ sqlSafe(rec.author_device_id),
10415
+ sqlSafe(rec.scope ?? "business")
10416
+ ]
10417
+ }));
10418
+ await client.batch(stmts, "write");
10419
+ pulled = pullResult.records.length;
10420
+ }
10421
+ }
10422
+ if (pulled > 0) {
10423
+ try {
10424
+ await pushToPostgres(pullResult.records);
10425
+ } catch {
10426
+ }
10427
+ }
10428
+ if (pullResult.maxVersion > lastPullVersion) {
10429
+ await client.execute({
10430
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_pull_version', ?)",
10431
+ args: [String(pullResult.maxVersion)]
10432
+ });
10433
+ }
10434
+ const pushMeta = await client.execute(
10435
+ "SELECT value FROM sync_meta WHERE key = 'last_cloud_push_version'"
10436
+ );
10437
+ const lastPushVersion = pushMeta.rows.length > 0 ? Number(pushMeta.rows[0].value) : 0;
10438
+ let pushed = 0;
10439
+ let batchCursor = lastPushVersion;
10440
+ while (true) {
10441
+ const recordsResult = await client.execute({
10442
+ sql: `SELECT id, agent_id, agent_role, session_id, timestamp,
10443
+ tool_name, project_name, has_error, raw_text, version,
10444
+ author_device_id, scope
10445
+ FROM memories
10446
+ WHERE version > ?
10447
+ AND (scope IS NULL OR scope != 'personal')
10448
+ ORDER BY version ASC
10449
+ LIMIT ?`,
10450
+ args: [batchCursor, PUSH_BATCH_SIZE]
10451
+ });
10452
+ if (recordsResult.rows.length === 0) break;
10453
+ const records = recordsResult.rows.map((row) => ({
10454
+ id: row.id,
10455
+ agent_id: row.agent_id,
10456
+ agent_role: row.agent_role,
10457
+ session_id: row.session_id,
10458
+ timestamp: row.timestamp,
10459
+ tool_name: row.tool_name,
10460
+ project_name: row.project_name,
10461
+ has_error: row.has_error,
10462
+ raw_text: row.raw_text,
10463
+ version: row.version,
10464
+ author_device_id: row.author_device_id,
10465
+ scope: row.scope
10466
+ }));
10467
+ const maxVersion = Number(records[records.length - 1].version);
10468
+ const pushOk = await cloudPush(records, maxVersion, config);
10469
+ if (!pushOk) break;
10470
+ try {
10471
+ await pushToPostgres(records);
10472
+ } catch {
10473
+ }
10474
+ await client.execute({
10475
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
10476
+ args: [String(maxVersion)]
10477
+ });
10478
+ pushed += records.length;
10479
+ batchCursor = maxVersion;
10480
+ if (recordsResult.rows.length < PUSH_BATCH_SIZE) break;
10481
+ }
10482
+ try {
10483
+ await cloudPushRoster(config);
10484
+ } catch (err) {
10485
+ logError(`[cloud-sync] Roster push: ${err instanceof Error ? err.message : String(err)}`);
10486
+ }
10487
+ try {
10488
+ await cloudPullRoster(config);
10489
+ } catch (err) {
10490
+ logError(`[cloud-sync] Roster pull: ${err instanceof Error ? err.message : String(err)}`);
10491
+ }
10492
+ try {
10493
+ await cloudPushGlobalProcedures(config);
10494
+ } catch (err) {
10495
+ logError(`[cloud-sync] Global procedures push: ${err instanceof Error ? err.message : String(err)}`);
10496
+ }
10497
+ try {
10498
+ await cloudPullGlobalProcedures(config);
10499
+ } catch (err) {
10500
+ logError(`[cloud-sync] Global procedures pull: ${err instanceof Error ? err.message : String(err)}`);
10501
+ }
10502
+ const countRows = async (sql) => {
10503
+ try {
10504
+ return Number((await client.execute(sql)).rows[0]?.cnt ?? 0);
10505
+ } catch {
10506
+ return 0;
10507
+ }
10508
+ };
10509
+ let behaviorsResult = { pushed: 0, pulled: 0 };
10510
+ try {
10511
+ await cloudPushBehaviors(config);
10512
+ behaviorsResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM behaviors WHERE active = 1");
10513
+ } catch (err) {
10514
+ logError(`[cloud-sync] Behaviors push: ${err instanceof Error ? err.message : String(err)}`);
10515
+ }
10516
+ try {
10517
+ const pullResult2 = await cloudPullBehaviors(config);
10518
+ behaviorsResult.pulled = pullResult2.pulled;
10519
+ } catch (err) {
10520
+ logError(`[cloud-sync] Behaviors pull: ${err instanceof Error ? err.message : String(err)}`);
10521
+ }
10522
+ let graphragResult = { pushed: 0, pulled: 0 };
10523
+ try {
10524
+ await cloudPushGraphRAG(config);
10525
+ graphragResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM entities");
10526
+ } catch (err) {
10527
+ logError(`[cloud-sync] GraphRAG push: ${err instanceof Error ? err.message : String(err)}`);
10528
+ }
10529
+ try {
10530
+ const pullResult2 = await cloudPullGraphRAG(config);
10531
+ graphragResult.pulled = pullResult2.pulled;
10532
+ } catch (err) {
10533
+ logError(`[cloud-sync] GraphRAG pull: ${err instanceof Error ? err.message : String(err)}`);
10534
+ }
10535
+ let tasksResult = { pushed: 0, pulled: 0 };
10536
+ try {
10537
+ await cloudPushTasks(config);
10538
+ tasksResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM tasks");
10539
+ } catch (err) {
10540
+ logError(`[cloud-sync] Tasks push: ${err instanceof Error ? err.message : String(err)}`);
10541
+ }
10542
+ try {
10543
+ const pullResult2 = await cloudPullTasks(config);
10544
+ tasksResult.pulled = pullResult2.pulled;
10545
+ } catch (err) {
10546
+ logError(`[cloud-sync] Tasks pull: ${err instanceof Error ? err.message : String(err)}`);
10547
+ }
10548
+ let conversationsResult = { pushed: 0, pulled: 0 };
10549
+ try {
10550
+ await cloudPushConversations(config);
10551
+ conversationsResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM conversations");
10552
+ } catch (err) {
10553
+ logError(`[cloud-sync] Conversations push: ${err instanceof Error ? err.message : String(err)}`);
10554
+ }
10555
+ try {
10556
+ const pullResult2 = await cloudPullConversations(config);
10557
+ conversationsResult.pulled = pullResult2.pulled;
10558
+ } catch (err) {
10559
+ logError(`[cloud-sync] Conversations pull: ${err instanceof Error ? err.message : String(err)}`);
10560
+ }
10561
+ let documentsResult = { pushed: 0, pulled: 0 };
10562
+ try {
10563
+ await cloudPushDocuments(config);
10564
+ documentsResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM documents");
10565
+ } catch (err) {
10566
+ logError(`[cloud-sync] Documents push: ${err instanceof Error ? err.message : String(err)}`);
10567
+ }
10568
+ try {
10569
+ const pullResult2 = await cloudPullDocuments(config);
10570
+ documentsResult.pulled = pullResult2.pulled;
10571
+ } catch (err) {
10572
+ logError(`[cloud-sync] Documents pull: ${err instanceof Error ? err.message : String(err)}`);
10573
+ }
10574
+ let rosterResult = { employees: 0, identities: 0 };
10575
+ try {
10576
+ const employees = await loadEmployees();
10577
+ rosterResult.employees = employees.length;
10578
+ const idDir = path23.join(EXE_AI_DIR, "identity");
10579
+ if (existsSync20(idDir)) {
10580
+ rosterResult.identities = readdirSync5(idDir).filter((f) => f.endsWith(".md")).length;
10581
+ }
10582
+ } catch {
10583
+ }
10584
+ const totalMemories = await countRows("SELECT COUNT(*) as cnt FROM memories WHERE status = 'active' OR status IS NULL");
10585
+ return {
10586
+ pushed,
10587
+ pulled,
10588
+ totalMemories,
10589
+ behaviors: behaviorsResult,
10590
+ graphrag: graphragResult,
10591
+ tasks: tasksResult,
10592
+ conversations: conversationsResult,
10593
+ documents: documentsResult,
10594
+ roster: rosterResult
10595
+ };
10596
+ }
10597
+ function recordRosterDeletion(name) {
10598
+ let deletions = [];
10599
+ try {
10600
+ if (existsSync20(ROSTER_DELETIONS_PATH)) {
10601
+ deletions = JSON.parse(readFileSync16(ROSTER_DELETIONS_PATH, "utf-8"));
10602
+ }
10603
+ } catch {
10604
+ }
10605
+ if (!deletions.includes(name)) deletions.push(name);
10606
+ writeFileSync11(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
10607
+ }
10608
+ function consumeRosterDeletions() {
10609
+ try {
10610
+ if (!existsSync20(ROSTER_DELETIONS_PATH)) return [];
10611
+ const deletions = JSON.parse(readFileSync16(ROSTER_DELETIONS_PATH, "utf-8"));
10612
+ writeFileSync11(ROSTER_DELETIONS_PATH, "[]");
10613
+ return deletions;
10614
+ } catch {
10615
+ return [];
10616
+ }
10617
+ }
10618
+ function buildRosterBlob(paths) {
10619
+ const rosterPath = paths?.rosterPath ?? path23.join(EXE_AI_DIR, "exe-employees.json");
10620
+ const identityDir = paths?.identityDir ?? path23.join(EXE_AI_DIR, "identity");
10621
+ const configPath = paths?.configPath ?? path23.join(EXE_AI_DIR, "config.json");
10622
+ let roster = [];
10623
+ if (existsSync20(rosterPath)) {
10624
+ try {
10625
+ roster = JSON.parse(readFileSync16(rosterPath, "utf-8"));
10626
+ } catch {
10627
+ }
10628
+ }
10629
+ const identities = {};
10630
+ if (existsSync20(identityDir)) {
10631
+ for (const file of readdirSync5(identityDir).filter((f) => f.endsWith(".md"))) {
10632
+ try {
10633
+ identities[file] = readFileSync16(path23.join(identityDir, file), "utf-8");
10634
+ } catch {
10635
+ }
10636
+ }
10637
+ }
10638
+ let config;
10639
+ if (existsSync20(configPath)) {
10640
+ try {
10641
+ config = JSON.parse(readFileSync16(configPath, "utf-8"));
10642
+ } catch {
10643
+ }
10644
+ }
10645
+ let agentConfig;
10646
+ const agentConfigPath = path23.join(EXE_AI_DIR, "agent-config.json");
10647
+ if (existsSync20(agentConfigPath)) {
10648
+ try {
10649
+ agentConfig = JSON.parse(readFileSync16(agentConfigPath, "utf-8"));
10650
+ } catch {
10651
+ }
10652
+ }
10653
+ const deletedNames = consumeRosterDeletions();
10654
+ const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
10655
+ const hash = crypto9.createHash("sha256").update(content).digest("hex").slice(0, 16);
10656
+ return { roster, identities, config, agentConfig, deletedNames, version: hash };
10657
+ }
10658
+ async function cloudPushRoster(config) {
10659
+ assertSecureEndpoint(config.endpoint);
10660
+ const blob = buildRosterBlob();
10661
+ if (blob.roster.length === 0) return true;
10662
+ try {
10663
+ const client = getClient();
10664
+ const meta = await client.execute(
10665
+ "SELECT value FROM sync_meta WHERE key = 'last_roster_push_version'"
10666
+ );
10667
+ const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
10668
+ if (blob.version === lastVersion) return true;
10669
+ } catch {
10670
+ }
10671
+ try {
10672
+ const json = JSON.stringify(blob);
10673
+ const compressed = compress(Buffer.from(json, "utf8"));
10674
+ const encrypted = encryptSyncBlob(compressed);
10675
+ const resp = await fetchWithRetry(`${config.endpoint}/sync/push-roster`, {
10676
+ method: "POST",
10677
+ headers: {
10678
+ Authorization: `Bearer ${config.apiKey}`,
10679
+ "Content-Type": "application/json",
10680
+ "X-Device-Id": loadDeviceId()
10681
+ },
10682
+ body: JSON.stringify({ blob: encrypted })
10683
+ });
10684
+ if (resp.ok) {
10685
+ try {
10686
+ const client = getClient();
10687
+ await client.execute({
10688
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_roster_push_version', ?)",
10689
+ args: [String(blob.version)]
10690
+ });
10691
+ } catch {
10692
+ }
10693
+ }
10694
+ return resp.ok;
10695
+ } catch (err) {
10696
+ process.stderr.write(`[cloud-sync] ROSTER PUSH FAILED: ${err instanceof Error ? err.message : String(err)}
10697
+ `);
10698
+ return false;
10699
+ }
10700
+ }
10701
+ async function cloudPullRoster(config) {
10702
+ assertSecureEndpoint(config.endpoint);
10703
+ try {
10704
+ const resp = await fetchWithRetry(`${config.endpoint}/sync/pull-roster`, {
10705
+ method: "GET",
10706
+ headers: {
10707
+ Authorization: `Bearer ${config.apiKey}`,
10708
+ "X-Device-Id": loadDeviceId()
10709
+ }
10710
+ });
10711
+ if (!resp.ok) return { added: 0 };
10712
+ const data = await resp.json();
10713
+ if (!data.blob) return { added: 0 };
10714
+ const compressed = decryptSyncBlob(data.blob);
10715
+ const json = decompress(compressed).toString("utf8");
10716
+ const remote = JSON.parse(json);
10717
+ return mergeRosterFromRemote(remote);
10718
+ } catch (err) {
10719
+ process.stderr.write(`[cloud-sync] ROSTER PULL FAILED: ${err instanceof Error ? err.message : String(err)}
10720
+ `);
10721
+ return { added: 0 };
10722
+ }
10723
+ }
10724
+ function mergeConfig(remoteConfig, configPath) {
10725
+ const cfgPath = configPath ?? path23.join(EXE_AI_DIR, "config.json");
10726
+ let local = {};
10727
+ if (existsSync20(cfgPath)) {
10728
+ try {
10729
+ local = JSON.parse(readFileSync16(cfgPath, "utf-8"));
10730
+ } catch {
10731
+ }
10732
+ }
10733
+ const merged = { ...remoteConfig, ...local };
10734
+ const dir = path23.dirname(cfgPath);
10735
+ ensurePrivateDirSync(dir);
10736
+ writeFileSync11(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
10737
+ enforcePrivateFileSync(cfgPath);
10738
+ }
10739
+ async function mergeRosterFromRemote(remote, paths) {
10740
+ return withRosterLock(async () => {
10741
+ const rosterPath = paths?.rosterPath ?? void 0;
10742
+ const identityDir = paths?.identityDir ?? path23.join(EXE_AI_DIR, "identity");
10743
+ const localEmployees = await loadEmployees(rosterPath);
10744
+ const localNames = new Set(localEmployees.map((e) => e.name));
10745
+ let added = 0;
10746
+ let identitiesUpdated = 0;
10747
+ for (const remoteEmp of remote.roster) {
10748
+ if (!localNames.has(remoteEmp.name)) {
10749
+ localEmployees.push(remoteEmp);
10750
+ localNames.add(remoteEmp.name);
10751
+ added++;
10752
+ try {
10753
+ registerBinSymlinks(remoteEmp.name);
10754
+ } catch {
10755
+ }
10756
+ }
10757
+ const lookupKey = `${remoteEmp.name}.md`;
10758
+ const matchedKey = Object.keys(remote.identities).find(
10759
+ (k) => k.toLowerCase() === lookupKey.toLowerCase()
10760
+ ) ?? lookupKey;
10761
+ const remoteIdentity = remote.identities[matchedKey];
10762
+ if (remoteIdentity) {
10763
+ if (!existsSync20(identityDir)) mkdirSync9(identityDir, { recursive: true });
10764
+ const idPath = path23.join(identityDir, `${remoteEmp.name}.md`);
10765
+ let localIdentity = null;
10766
+ try {
10767
+ localIdentity = existsSync20(idPath) ? readFileSync16(idPath, "utf-8") : null;
10768
+ } catch {
10769
+ }
10770
+ if (localIdentity !== remoteIdentity) {
10771
+ writeFileSync11(idPath, remoteIdentity, "utf-8");
10772
+ identitiesUpdated++;
10773
+ }
10774
+ }
10775
+ }
10776
+ let removed = 0;
10777
+ if (remote.deletedNames && remote.deletedNames.length > 0) {
10778
+ const toRemove = new Set(remote.deletedNames);
10779
+ const filtered = localEmployees.filter((e) => !toRemove.has(e.name));
10780
+ removed = localEmployees.length - filtered.length;
10781
+ if (removed > 0) {
10782
+ localEmployees.length = 0;
10783
+ localEmployees.push(...filtered);
10784
+ }
10785
+ }
10786
+ if (added > 0 || removed > 0) {
10787
+ await saveEmployees(localEmployees, rosterPath);
10788
+ }
10789
+ if (remote.config && Object.keys(remote.config).length > 0) {
10790
+ try {
10791
+ mergeConfig(remote.config, paths?.configPath);
10792
+ } catch {
10793
+ }
10794
+ }
10795
+ if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
10796
+ try {
10797
+ const agentConfigPath = path23.join(EXE_AI_DIR, "agent-config.json");
10798
+ let local = {};
10799
+ if (existsSync20(agentConfigPath)) {
10800
+ try {
10801
+ local = JSON.parse(readFileSync16(agentConfigPath, "utf-8"));
10802
+ } catch {
10803
+ }
10804
+ }
10805
+ const merged = { ...remote.agentConfig, ...local };
10806
+ ensurePrivateDirSync(path23.dirname(agentConfigPath));
10807
+ writeFileSync11(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
10808
+ enforcePrivateFileSync(agentConfigPath);
10809
+ } catch {
10810
+ }
10811
+ }
10812
+ return { added, identitiesUpdated };
10813
+ });
10814
+ }
10815
+ async function cloudPushBlob(route, data, metaKey, config) {
10816
+ if (data.length === 0) return { ok: true };
10817
+ assertSecureEndpoint(config.endpoint);
10818
+ const json = JSON.stringify(data);
10819
+ const version = Buffer.from(json).length;
10820
+ try {
10821
+ const client = getClient();
10822
+ const meta = await client.execute({
10823
+ sql: "SELECT value FROM sync_meta WHERE key = ?",
10824
+ args: [metaKey]
10825
+ });
10826
+ const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
10827
+ if (version === lastVersion) return { ok: true };
10828
+ } catch {
10829
+ }
10830
+ try {
10831
+ const compressed = compress(Buffer.from(json, "utf8"));
10832
+ const encrypted = encryptSyncBlob(compressed);
10833
+ const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
10834
+ method: "POST",
10835
+ headers: {
10836
+ Authorization: `Bearer ${config.apiKey}`,
10837
+ "Content-Type": "application/json",
10838
+ "X-Device-Id": loadDeviceId()
10839
+ },
10840
+ body: JSON.stringify({ blob: encrypted })
10841
+ });
10842
+ if (resp.ok) {
10843
+ try {
10844
+ const client = getClient();
10845
+ await client.execute({
10846
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES (?, ?)",
10847
+ args: [metaKey, String(version)]
10848
+ });
10849
+ } catch {
10850
+ }
10851
+ }
10852
+ return { ok: resp.ok };
10853
+ } catch (err) {
10854
+ logError(`[cloud-sync] PUSH ${route}: ${err instanceof Error ? err.message : String(err)}`);
10855
+ return { ok: false };
10856
+ }
10857
+ }
10858
+ async function cloudPullBlob(route, config) {
10859
+ assertSecureEndpoint(config.endpoint);
10860
+ try {
10861
+ const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
10862
+ method: "GET",
10863
+ headers: {
10864
+ Authorization: `Bearer ${config.apiKey}`,
10865
+ "X-Device-Id": loadDeviceId()
10866
+ }
10867
+ });
10868
+ if (!resp.ok) return null;
10869
+ const data = await resp.json();
10870
+ if (!data.blob) return null;
10871
+ const compressed = decryptSyncBlob(data.blob);
10872
+ const json = decompress(compressed).toString("utf8");
10873
+ return JSON.parse(json);
10874
+ } catch (err) {
10875
+ logError(`[cloud-sync] PULL ${route}: ${err instanceof Error ? err.message : String(err)}`);
10876
+ return null;
10877
+ }
10878
+ }
10879
+ async function cloudPushGlobalProcedures(config) {
10880
+ const client = getClient();
10881
+ const result = await client.execute("SELECT * FROM global_procedures LIMIT 1000");
10882
+ const rows = result.rows;
10883
+ const { ok } = await cloudPushBlob(
10884
+ "/sync/push-global-procedures",
10885
+ rows,
10886
+ "last_global_procedures_push_version",
10887
+ config
10888
+ );
10889
+ return ok;
10890
+ }
10891
+ async function cloudPullGlobalProcedures(config) {
10892
+ const remoteProcs = await cloudPullBlob(
10893
+ "/sync/pull-global-procedures",
10894
+ config
10895
+ );
10896
+ if (!remoteProcs || remoteProcs.length === 0) return { pulled: 0 };
10897
+ const client = getClient();
10898
+ const stmts = remoteProcs.map((p) => ({
10899
+ sql: `INSERT INTO global_procedures
10900
+ (id, title, content, priority, domain, active, created_at, updated_at)
10901
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
10902
+ ON CONFLICT(id) DO UPDATE SET
10903
+ title = excluded.title,
10904
+ content = excluded.content,
10905
+ priority = excluded.priority,
10906
+ domain = excluded.domain,
10907
+ active = excluded.active,
10908
+ updated_at = excluded.updated_at
10909
+ WHERE excluded.updated_at > global_procedures.updated_at`,
10910
+ args: [
10911
+ sqlSafe(p.id),
10912
+ sqlSafe(p.title),
10913
+ sqlSafe(p.content),
10914
+ sqlSafe(p.priority ?? "p0"),
10915
+ sqlSafe(p.domain),
10916
+ sqlSafe(p.active ?? 1),
10917
+ sqlSafe(p.created_at),
10918
+ sqlSafe(p.updated_at)
10919
+ ]
10920
+ }));
10921
+ await client.batch(stmts, "write");
10922
+ return { pulled: remoteProcs.length };
10923
+ }
10924
+ async function cloudPushBehaviors(config) {
10925
+ const client = getClient();
10926
+ const result = await client.execute("SELECT * FROM behaviors LIMIT 10000");
10927
+ const rows = result.rows;
10928
+ const { ok } = await cloudPushBlob(
10929
+ "/sync/push-behaviors",
10930
+ rows,
10931
+ "last_behaviors_push_version",
10932
+ config
10933
+ );
10934
+ return ok;
10935
+ }
10936
+ async function cloudPullBehaviors(config) {
10937
+ const remoteBehaviors = await cloudPullBlob(
10938
+ "/sync/pull-behaviors",
10939
+ config
10940
+ );
10941
+ if (!remoteBehaviors || remoteBehaviors.length === 0) return { pulled: 0 };
10942
+ const client = getClient();
10943
+ let pulled = 0;
10944
+ for (const behavior of remoteBehaviors) {
10945
+ const existing = await client.execute({
10946
+ sql: `SELECT COUNT(*) as cnt FROM behaviors
10947
+ WHERE agent_id = ? AND content = ?`,
10948
+ args: [sqlSafe(behavior.agent_id), sqlSafe(behavior.content)]
10949
+ });
10950
+ if (Number(existing.rows[0]?.cnt) > 0) continue;
10951
+ await client.execute({
10952
+ sql: `INSERT OR IGNORE INTO behaviors
10953
+ (id, agent_id, project_name, domain, content, active, priority, created_at, updated_at)
10954
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
10955
+ args: [
10956
+ sqlSafe(behavior.id),
10957
+ sqlSafe(behavior.agent_id),
10958
+ sqlSafe(behavior.project_name),
10959
+ sqlSafe(behavior.domain),
10960
+ sqlSafe(behavior.content),
10961
+ sqlSafe(behavior.active ?? 1),
10962
+ sqlSafe(behavior.priority ?? "p1"),
10963
+ sqlSafe(behavior.created_at),
10964
+ sqlSafe(behavior.updated_at)
10965
+ ]
10966
+ });
10967
+ pulled++;
10968
+ }
10969
+ return { pulled };
10970
+ }
10971
+ async function cloudPushGraphRAG(config) {
10972
+ const client = getClient();
10973
+ const [entities, relationships, aliases, entityMems, relMems, hyperedges, hyperedgeNodes] = await Promise.all([
10974
+ client.execute("SELECT * FROM entities LIMIT 50000"),
10975
+ client.execute("SELECT * FROM relationships LIMIT 50000"),
10976
+ client.execute("SELECT * FROM entity_aliases LIMIT 50000"),
10977
+ client.execute("SELECT * FROM entity_memories LIMIT 50000"),
10978
+ client.execute("SELECT * FROM relationship_memories LIMIT 50000"),
10979
+ client.execute("SELECT * FROM hyperedges LIMIT 50000"),
10980
+ client.execute("SELECT * FROM hyperedge_nodes LIMIT 50000")
10981
+ ]);
10982
+ const blob = {
10983
+ entities: entities.rows,
10984
+ relationships: relationships.rows,
10985
+ entity_aliases: aliases.rows,
10986
+ entity_memories: entityMems.rows,
10987
+ relationship_memories: relMems.rows,
10988
+ hyperedges: hyperedges.rows,
10989
+ hyperedge_nodes: hyperedgeNodes.rows
10990
+ };
10991
+ const { ok } = await cloudPushBlob(
10992
+ "/sync/push-graphrag",
10993
+ [blob],
10994
+ "last_graphrag_push_version",
10995
+ config
10996
+ );
10997
+ return ok;
10998
+ }
10999
+ async function cloudPullGraphRAG(config) {
11000
+ const data = await cloudPullBlob(
11001
+ "/sync/pull-graphrag",
11002
+ config
11003
+ );
11004
+ if (!data || data.length === 0) return { pulled: 0 };
11005
+ const blob = data[0];
11006
+ const client = getClient();
11007
+ let pulled = 0;
11008
+ if (blob.entities.length > 0) {
11009
+ const stmts = blob.entities.map((e) => ({
11010
+ sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen, properties)
11011
+ VALUES (?, ?, ?, ?, ?, ?)`,
11012
+ args: [sqlSafe(e.id), sqlSafe(e.name), sqlSafe(e.type), sqlSafe(e.first_seen), sqlSafe(e.last_seen), sqlSafe(e.properties ?? "{}")]
11013
+ }));
11014
+ await client.batch(stmts, "write");
11015
+ pulled += stmts.length;
11016
+ }
11017
+ if (blob.relationships.length > 0) {
11018
+ const stmts = blob.relationships.map((r) => ({
11019
+ sql: `INSERT OR IGNORE INTO relationships
11020
+ (id, source_entity_id, target_entity_id, type, weight, timestamp, properties, confidence, confidence_label)
11021
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
11022
+ args: [
11023
+ sqlSafe(r.id),
11024
+ sqlSafe(r.source_entity_id),
11025
+ sqlSafe(r.target_entity_id),
11026
+ sqlSafe(r.type),
11027
+ sqlSafe(r.weight ?? 1),
11028
+ sqlSafe(r.timestamp),
11029
+ sqlSafe(r.properties ?? "{}"),
11030
+ sqlSafe(r.confidence ?? 1),
11031
+ sqlSafe(r.confidence_label ?? "extracted")
11032
+ ]
11033
+ }));
11034
+ await client.batch(stmts, "write");
11035
+ pulled += stmts.length;
11036
+ }
11037
+ if (blob.entity_aliases.length > 0) {
11038
+ const stmts = blob.entity_aliases.map((a) => ({
11039
+ sql: `INSERT OR IGNORE INTO entity_aliases (alias, canonical_entity_id) VALUES (?, ?)`,
11040
+ args: [sqlSafe(a.alias), sqlSafe(a.canonical_entity_id)]
11041
+ }));
11042
+ await client.batch(stmts, "write");
11043
+ pulled += stmts.length;
11044
+ }
11045
+ if (blob.entity_memories.length > 0) {
11046
+ const stmts = blob.entity_memories.map((em) => ({
11047
+ sql: `INSERT OR IGNORE INTO entity_memories (entity_id, memory_id) VALUES (?, ?)`,
11048
+ args: [sqlSafe(em.entity_id), sqlSafe(em.memory_id)]
11049
+ }));
11050
+ await client.batch(stmts, "write");
11051
+ pulled += stmts.length;
11052
+ }
11053
+ if (blob.relationship_memories.length > 0) {
11054
+ const stmts = blob.relationship_memories.map((rm) => ({
11055
+ sql: `INSERT OR IGNORE INTO relationship_memories (relationship_id, memory_id) VALUES (?, ?)`,
11056
+ args: [sqlSafe(rm.relationship_id), sqlSafe(rm.memory_id)]
11057
+ }));
11058
+ await client.batch(stmts, "write");
11059
+ pulled += stmts.length;
11060
+ }
11061
+ if (blob.hyperedges.length > 0) {
11062
+ const stmts = blob.hyperedges.map((h) => ({
11063
+ sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
11064
+ VALUES (?, ?, ?, ?, ?)`,
11065
+ args: [sqlSafe(h.id), sqlSafe(h.label), sqlSafe(h.relation), sqlSafe(h.confidence ?? 1), sqlSafe(h.timestamp)]
11066
+ }));
11067
+ await client.batch(stmts, "write");
11068
+ pulled += stmts.length;
11069
+ }
11070
+ if (blob.hyperedge_nodes.length > 0) {
11071
+ const stmts = blob.hyperedge_nodes.map((hn) => ({
11072
+ sql: `INSERT OR IGNORE INTO hyperedge_nodes (hyperedge_id, entity_id) VALUES (?, ?)`,
11073
+ args: [sqlSafe(hn.hyperedge_id), sqlSafe(hn.entity_id)]
11074
+ }));
11075
+ await client.batch(stmts, "write");
11076
+ pulled += stmts.length;
11077
+ }
11078
+ return { pulled };
11079
+ }
11080
+ async function cloudPushTasks(config) {
11081
+ const client = getClient();
11082
+ const result = await client.execute("SELECT * FROM tasks LIMIT 10000");
11083
+ const rows = result.rows;
11084
+ const { ok } = await cloudPushBlob(
11085
+ "/sync/push-tasks",
11086
+ rows,
11087
+ "last_tasks_push_version",
11088
+ config
11089
+ );
11090
+ return ok;
11091
+ }
11092
+ async function cloudPullTasks(config) {
11093
+ const remoteTasks = await cloudPullBlob(
11094
+ "/sync/pull-tasks",
11095
+ config
11096
+ );
11097
+ if (!remoteTasks || remoteTasks.length === 0) return { pulled: 0 };
11098
+ const client = getClient();
11099
+ const stmts = remoteTasks.map((t) => ({
11100
+ sql: `INSERT OR IGNORE INTO tasks
11101
+ (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, created_at, updated_at,
11102
+ blocked_by, parent_task_id, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at)
11103
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
11104
+ args: [
11105
+ sqlSafe(t.id),
11106
+ sqlSafe(t.title),
11107
+ sqlSafe(t.assigned_to),
11108
+ sqlSafe(t.assigned_by),
11109
+ sqlSafe(t.project_name),
11110
+ sqlSafe(t.priority ?? "p1"),
11111
+ sqlSafe(t.status ?? "open"),
11112
+ sqlSafe(t.task_file),
11113
+ sqlSafe(t.created_at),
11114
+ sqlSafe(t.updated_at),
11115
+ sqlSafe(t.blocked_by),
11116
+ sqlSafe(t.parent_task_id),
11117
+ sqlSafe(t.budget_tokens),
11118
+ sqlSafe(t.budget_fallback_model),
11119
+ sqlSafe(t.tokens_used ?? 0),
11120
+ sqlSafe(t.tokens_warned_at)
11121
+ ]
11122
+ }));
11123
+ await client.batch(stmts, "write");
11124
+ return { pulled: remoteTasks.length };
11125
+ }
11126
+ async function cloudPushConversations(config) {
11127
+ const client = getClient();
11128
+ const result = await client.execute("SELECT * FROM conversations LIMIT 50000");
11129
+ const rows = result.rows;
11130
+ const { ok } = await cloudPushBlob(
11131
+ "/sync/push-conversations",
11132
+ rows,
11133
+ "last_conversations_push_version",
11134
+ config
11135
+ );
11136
+ return ok;
11137
+ }
11138
+ async function cloudPullConversations(config) {
11139
+ const remoteConvos = await cloudPullBlob(
11140
+ "/sync/pull-conversations",
11141
+ config
11142
+ );
11143
+ if (!remoteConvos || remoteConvos.length === 0) return { pulled: 0 };
11144
+ const client = getClient();
11145
+ const stmts = remoteConvos.map((c) => ({
11146
+ sql: `INSERT OR IGNORE INTO conversations
11147
+ (id, platform, external_id, sender_id, sender_name, sender_phone, sender_email,
11148
+ recipient_id, channel_id, thread_id, reply_to_id, content_text, content_media,
11149
+ content_metadata, agent_response, agent_name, timestamp, ingested_at)
11150
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
11151
+ args: [
11152
+ sqlSafe(c.id),
11153
+ sqlSafe(c.platform),
11154
+ sqlSafe(c.external_id),
11155
+ sqlSafe(c.sender_id),
11156
+ sqlSafe(c.sender_name),
11157
+ sqlSafe(c.sender_phone),
11158
+ sqlSafe(c.sender_email),
11159
+ sqlSafe(c.recipient_id),
11160
+ sqlSafe(c.channel_id),
11161
+ sqlSafe(c.thread_id),
11162
+ sqlSafe(c.reply_to_id),
11163
+ sqlSafe(c.content_text),
11164
+ sqlSafe(c.content_media),
11165
+ sqlSafe(c.content_metadata),
11166
+ sqlSafe(c.agent_response),
11167
+ sqlSafe(c.agent_name),
11168
+ sqlSafe(c.timestamp),
11169
+ sqlSafe(c.ingested_at)
11170
+ ]
11171
+ }));
11172
+ await client.batch(stmts, "write");
11173
+ return { pulled: remoteConvos.length };
11174
+ }
11175
+ async function cloudPushDocuments(config) {
11176
+ const client = getClient();
11177
+ const [workspaces, documents] = await Promise.all([
11178
+ client.execute("SELECT * FROM workspaces LIMIT 1000"),
11179
+ client.execute("SELECT * FROM documents LIMIT 10000")
11180
+ ]);
11181
+ const blob = {
11182
+ workspaces: workspaces.rows,
11183
+ documents: documents.rows
11184
+ };
11185
+ const { ok } = await cloudPushBlob(
11186
+ "/sync/push-documents",
11187
+ [blob],
11188
+ "last_documents_push_version",
11189
+ config
11190
+ );
11191
+ return ok;
11192
+ }
11193
+ async function cloudPullDocuments(config) {
11194
+ const data = await cloudPullBlob(
11195
+ "/sync/pull-documents",
11196
+ config
11197
+ );
11198
+ if (!data || data.length === 0) return { pulled: 0 };
11199
+ const blob = data[0];
11200
+ const client = getClient();
11201
+ let pulled = 0;
11202
+ if (blob.workspaces.length > 0) {
11203
+ const stmts = blob.workspaces.map((w) => ({
11204
+ sql: `INSERT OR IGNORE INTO workspaces (id, slug, name, owner_agent_id, created_at, metadata)
11205
+ VALUES (?, ?, ?, ?, ?, ?)`,
11206
+ args: [sqlSafe(w.id), sqlSafe(w.slug), sqlSafe(w.name), sqlSafe(w.owner_agent_id), sqlSafe(w.created_at), sqlSafe(w.metadata)]
11207
+ }));
11208
+ await client.batch(stmts, "write");
11209
+ pulled += stmts.length;
11210
+ }
11211
+ if (blob.documents.length > 0) {
11212
+ const stmts = blob.documents.map((d) => ({
11213
+ sql: `INSERT OR IGNORE INTO documents
11214
+ (id, workspace_id, filename, mime, source_type, user_id, uploaded_at, metadata)
11215
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
11216
+ args: [
11217
+ sqlSafe(d.id),
11218
+ sqlSafe(d.workspace_id),
11219
+ sqlSafe(d.filename),
11220
+ sqlSafe(d.mime),
11221
+ sqlSafe(d.source_type),
11222
+ sqlSafe(d.user_id),
11223
+ sqlSafe(d.uploaded_at),
11224
+ sqlSafe(d.metadata)
11225
+ ]
11226
+ }));
11227
+ await client.batch(stmts, "write");
11228
+ pulled += stmts.length;
11229
+ }
11230
+ return { pulled };
11231
+ }
11232
+ var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, ROSTER_DELETIONS_PATH;
11233
+ var init_cloud_sync = __esm({
11234
+ "src/lib/cloud-sync.ts"() {
11235
+ "use strict";
11236
+ init_database();
11237
+ init_crypto();
11238
+ init_compress();
11239
+ init_license();
11240
+ init_config();
11241
+ init_crdt_sync();
11242
+ init_employees();
11243
+ init_secure_files();
11244
+ LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
11245
+ FETCH_TIMEOUT_MS = 3e4;
11246
+ PUSH_BATCH_SIZE = 5e3;
11247
+ ROSTER_LOCK_PATH = path23.join(EXE_AI_DIR, "roster-merge.lock");
11248
+ LOCK_STALE_MS = 3e4;
11249
+ _pgPromise = null;
11250
+ _pgFailed = false;
11251
+ ROSTER_DELETIONS_PATH = path23.join(EXE_AI_DIR, "roster-deletions.json");
11252
+ }
11253
+ });
11254
+
11255
+ // src/lib/code-chunker.ts
11256
+ import ts from "typescript";
11257
+ function chunkSourceFile(source, fileName = "file.ts") {
11258
+ const sourceFile = ts.createSourceFile(
11259
+ fileName,
11260
+ source,
11261
+ ts.ScriptTarget.Latest,
11262
+ true,
11263
+ // setParentNodes
11264
+ fileName.endsWith(".tsx") || fileName.endsWith(".jsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
11265
+ );
11266
+ const chunks = [];
11267
+ const lines = source.split("\n");
11268
+ const importLines = [];
11269
+ function getLineNumber(pos) {
11270
+ return sourceFile.getLineAndCharacterOfPosition(pos).line + 1;
11271
+ }
11272
+ function getLeadingComment(node) {
11273
+ const fullText = sourceFile.getFullText();
11274
+ const ranges = ts.getLeadingCommentRanges(fullText, node.getFullStart());
11275
+ if (!ranges || ranges.length === 0) return void 0;
11276
+ return ranges.map((r) => fullText.slice(r.pos, r.end)).join("\n");
11277
+ }
11278
+ function getNodeText(node) {
11279
+ return node.getText(sourceFile);
11280
+ }
11281
+ function getName(node) {
11282
+ if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) {
11283
+ return node.name?.getText(sourceFile) ?? "(anonymous)";
11284
+ }
11285
+ if (ts.isClassDeclaration(node)) {
11286
+ return node.name?.getText(sourceFile) ?? "(anonymous class)";
11287
+ }
11288
+ if (ts.isInterfaceDeclaration(node)) {
11289
+ return node.name.getText(sourceFile);
11290
+ }
11291
+ if (ts.isTypeAliasDeclaration(node)) {
11292
+ return node.name.getText(sourceFile);
11293
+ }
11294
+ if (ts.isEnumDeclaration(node)) {
11295
+ return node.name.getText(sourceFile);
11296
+ }
11297
+ if (ts.isVariableStatement(node)) {
11298
+ const decls = node.declarationList.declarations;
11299
+ return decls.map((d) => d.name.getText(sourceFile)).join(", ");
11300
+ }
11301
+ if (ts.isExportAssignment(node)) {
11302
+ return "default export";
11303
+ }
11304
+ return "(unknown)";
11305
+ }
11306
+ function visitTopLevel(node) {
11307
+ if (ts.isImportDeclaration(node)) {
11308
+ importLines.push({
11309
+ start: getLineNumber(node.getStart(sourceFile)),
11310
+ end: getLineNumber(node.getEnd())
11311
+ });
11312
+ return;
11313
+ }
11314
+ if (ts.isFunctionDeclaration(node)) {
11315
+ chunks.push({
11316
+ kind: "function",
11317
+ name: getName(node),
9550
11318
  text: getNodeText(node),
9551
11319
  startLine: getLineNumber(node.getStart(sourceFile)),
9552
11320
  endLine: getLineNumber(node.getEnd()),
@@ -9678,13 +11446,13 @@ __export(graph_rag_exports, {
9678
11446
  resolveAlias: () => resolveAlias,
9679
11447
  storeExtraction: () => storeExtraction
9680
11448
  });
9681
- import crypto7 from "crypto";
11449
+ import crypto10 from "crypto";
9682
11450
  function normalizeEntityName(name) {
9683
11451
  return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
9684
11452
  }
9685
11453
  function entityId(name, type) {
9686
11454
  const normalized = normalizeEntityName(name);
9687
- return crypto7.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
11455
+ return crypto10.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
9688
11456
  }
9689
11457
  async function resolveAlias(client, name) {
9690
11458
  const normalized = normalizeEntityName(name);
@@ -9934,7 +11702,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
9934
11702
  const targetAlias = await resolveAlias(client, r.target);
9935
11703
  const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
9936
11704
  const targetId = targetAlias ?? entityId(r.target, r.targetType);
9937
- const relId = crypto7.randomUUID().slice(0, 16);
11705
+ const relId = crypto10.randomUUID().slice(0, 16);
9938
11706
  try {
9939
11707
  await client.execute({
9940
11708
  sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
@@ -9997,7 +11765,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
9997
11765
  }
9998
11766
  }
9999
11767
  for (const h of extraction.hyperedges) {
10000
- const hId = crypto7.randomUUID().slice(0, 16);
11768
+ const hId = crypto10.randomUUID().slice(0, 16);
10001
11769
  try {
10002
11770
  await client.execute({
10003
11771
  sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
@@ -10061,7 +11829,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
10061
11829
  totalEntities += stored.entitiesStored;
10062
11830
  totalRelationships += stored.relationshipsStored;
10063
11831
  }
10064
- const contentHash = crypto7.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
11832
+ const contentHash = crypto10.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
10065
11833
  await client.execute({
10066
11834
  sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
10067
11835
  args: [contentHash, contentHash, memoryId]
@@ -10178,8 +11946,8 @@ __export(wiki_sync_exports, {
10178
11946
  listWorkspaces: () => listWorkspaces,
10179
11947
  syncMemories: () => syncMemories
10180
11948
  });
10181
- async function wikiRequest(config, path25, method = "GET", body) {
10182
- const url = `${config.wikiUrl}/api/v1${path25}`;
11949
+ async function wikiRequest(config, path28, method = "GET", body) {
11950
+ const url = `${config.wikiUrl}/api/v1${path28}`;
10183
11951
  const headers = {
10184
11952
  "Authorization": `Bearer ${config.wikiApiKey}`,
10185
11953
  "Content-Type": "application/json"
@@ -10191,7 +11959,7 @@ async function wikiRequest(config, path25, method = "GET", body) {
10191
11959
  signal: AbortSignal.timeout(3e4)
10192
11960
  });
10193
11961
  if (!response.ok) {
10194
- throw new Error(`Wiki API ${method} ${path25}: ${response.status} ${response.statusText}`);
11962
+ throw new Error(`Wiki API ${method} ${path28}: ${response.status} ${response.statusText}`);
10195
11963
  }
10196
11964
  return response.json();
10197
11965
  }
@@ -10303,8 +12071,8 @@ __export(token_spend_exports, {
10303
12071
  import { readdir } from "fs/promises";
10304
12072
  import { createReadStream } from "fs";
10305
12073
  import { createInterface } from "readline";
10306
- import path21 from "path";
10307
- import os13 from "os";
12074
+ import path24 from "path";
12075
+ import os14 from "os";
10308
12076
  function getPricing(model) {
10309
12077
  if (MODEL_PRICING[model]) return MODEL_PRICING[model];
10310
12078
  const stripped = model.replace(/-\d{8}$/, "");
@@ -10331,18 +12099,18 @@ async function getAgentSpend(period = "7d") {
10331
12099
  for (const row of dbResult.rows) {
10332
12100
  sessionAgent.set(row.session_uuid, row.agent_id);
10333
12101
  }
10334
- const claudeDir = path21.join(os13.homedir(), ".claude", "projects");
12102
+ const claudeDir = path24.join(os14.homedir(), ".claude", "projects");
10335
12103
  let projectDirs = [];
10336
12104
  try {
10337
12105
  const entries = await readdir(claudeDir);
10338
- projectDirs = entries.map((e) => path21.join(claudeDir, e));
12106
+ projectDirs = entries.map((e) => path24.join(claudeDir, e));
10339
12107
  } catch {
10340
12108
  return [];
10341
12109
  }
10342
12110
  const agentTotals = /* @__PURE__ */ new Map();
10343
12111
  for (const [sessionUuid, agentId] of sessionAgent) {
10344
12112
  for (const dir of projectDirs) {
10345
- const jsonlPath = path21.join(dir, `${sessionUuid}.jsonl`);
12113
+ const jsonlPath = path24.join(dir, `${sessionUuid}.jsonl`);
10346
12114
  try {
10347
12115
  const usage = await extractSessionUsage(jsonlPath);
10348
12116
  if (usage.input === 0 && usage.output === 0) continue;
@@ -10467,11 +12235,11 @@ __export(update_check_exports, {
10467
12235
  getRemoteVersion: () => getRemoteVersion
10468
12236
  });
10469
12237
  import { execSync as execSync11 } from "child_process";
10470
- import { readFileSync as readFileSync15 } from "fs";
10471
- import path22 from "path";
12238
+ import { readFileSync as readFileSync17 } from "fs";
12239
+ import path25 from "path";
10472
12240
  function getLocalVersion(packageRoot) {
10473
- const pkgPath = path22.join(packageRoot, "package.json");
10474
- const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
12241
+ const pkgPath = path25.join(packageRoot, "package.json");
12242
+ const pkg = JSON.parse(readFileSync17(pkgPath, "utf-8"));
10475
12243
  return pkg.version;
10476
12244
  }
10477
12245
  function getRemoteVersion() {
@@ -10514,16 +12282,16 @@ __export(ws_auth_exports, {
10514
12282
  deriveWsAuthToken: () => deriveWsAuthToken,
10515
12283
  hashAuthToken: () => hashAuthToken
10516
12284
  });
10517
- import crypto8 from "crypto";
12285
+ import crypto11 from "crypto";
10518
12286
  function deriveWsAuthToken(masterKey) {
10519
- return Buffer.from(crypto8.hkdfSync("sha256", masterKey, "", WS_AUTH_HKDF_INFO, 32));
12287
+ return Buffer.from(crypto11.hkdfSync("sha256", masterKey, "", WS_AUTH_HKDF_INFO, 32));
10520
12288
  }
10521
12289
  function deriveOrgId(masterKey) {
10522
- const raw = Buffer.from(crypto8.hkdfSync("sha256", masterKey, "", ORG_ID_HKDF_INFO, 32));
10523
- return crypto8.createHash("sha256").update(raw).digest("hex").slice(0, 32);
12290
+ const raw = Buffer.from(crypto11.hkdfSync("sha256", masterKey, "", ORG_ID_HKDF_INFO, 32));
12291
+ return crypto11.createHash("sha256").update(raw).digest("hex").slice(0, 32);
10524
12292
  }
10525
12293
  function hashAuthToken(token) {
10526
- return crypto8.createHash("sha256").update(token).digest("hex");
12294
+ return crypto11.createHash("sha256").update(token).digest("hex");
10527
12295
  }
10528
12296
  var WS_AUTH_HKDF_INFO, ORG_ID_HKDF_INFO;
10529
12297
  var init_ws_auth = __esm({
@@ -10541,14 +12309,14 @@ __export(device_registry_exports, {
10541
12309
  resolveTargetDevice: () => resolveTargetDevice,
10542
12310
  setFriendlyName: () => setFriendlyName
10543
12311
  });
10544
- import crypto9 from "crypto";
10545
- import os14 from "os";
10546
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync8, existsSync as existsSync19 } from "fs";
10547
- import path23 from "path";
12312
+ import crypto12 from "crypto";
12313
+ import os15 from "os";
12314
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, mkdirSync as mkdirSync10, existsSync as existsSync21 } from "fs";
12315
+ import path26 from "path";
10548
12316
  function getDeviceInfo() {
10549
- if (existsSync19(DEVICE_JSON_PATH)) {
12317
+ if (existsSync21(DEVICE_JSON_PATH)) {
10550
12318
  try {
10551
- const raw = readFileSync16(DEVICE_JSON_PATH, "utf8");
12319
+ const raw = readFileSync18(DEVICE_JSON_PATH, "utf8");
10552
12320
  const data = JSON.parse(raw);
10553
12321
  if (data.deviceId && data.friendlyName && data.hostname) {
10554
12322
  return data;
@@ -10556,20 +12324,20 @@ function getDeviceInfo() {
10556
12324
  } catch {
10557
12325
  }
10558
12326
  }
10559
- const hostname = os14.hostname();
12327
+ const hostname = os15.hostname();
10560
12328
  const info = {
10561
- deviceId: crypto9.randomUUID(),
12329
+ deviceId: crypto12.randomUUID(),
10562
12330
  friendlyName: hostname.replace(/\./g, "-").toLowerCase(),
10563
12331
  hostname
10564
12332
  };
10565
- mkdirSync8(path23.dirname(DEVICE_JSON_PATH), { recursive: true });
10566
- writeFileSync10(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
12333
+ mkdirSync10(path26.dirname(DEVICE_JSON_PATH), { recursive: true });
12334
+ writeFileSync12(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
10567
12335
  return info;
10568
12336
  }
10569
12337
  function setFriendlyName(name) {
10570
12338
  const info = getDeviceInfo();
10571
12339
  info.friendlyName = name;
10572
- writeFileSync10(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
12340
+ writeFileSync12(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
10573
12341
  }
10574
12342
  async function resolveTargetDevice(targetAgent, targetProject) {
10575
12343
  const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
@@ -10603,7 +12371,7 @@ var init_device_registry = __esm({
10603
12371
  "src/lib/device-registry.ts"() {
10604
12372
  "use strict";
10605
12373
  init_config();
10606
- DEVICE_JSON_PATH = path23.join(EXE_AI_DIR, "device.json");
12374
+ DEVICE_JSON_PATH = path26.join(EXE_AI_DIR, "device.json");
10607
12375
  }
10608
12376
  });
10609
12377
 
@@ -10628,18 +12396,18 @@ function assertSecureWsUrl(url) {
10628
12396
  `Malformed WebSocket URL rejected: "${url}".`
10629
12397
  );
10630
12398
  }
10631
- if (LOCALHOST_PATTERNS.test(parsed.hostname)) return;
12399
+ if (LOCALHOST_PATTERNS2.test(parsed.hostname)) return;
10632
12400
  throw new Error(
10633
12401
  `Insecure WebSocket URL rejected: "${url}". Use wss:// for remote hosts. Plain ws:// is only allowed for localhost.`
10634
12402
  );
10635
12403
  }
10636
12404
  }
10637
- var LOCALHOST_PATTERNS;
12405
+ var LOCALHOST_PATTERNS2;
10638
12406
  var init_gateway_client = __esm({
10639
12407
  "src/lib/gateway-client.ts"() {
10640
12408
  "use strict";
10641
12409
  init_message_queue();
10642
- LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
12410
+ LOCALHOST_PATTERNS2 = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
10643
12411
  }
10644
12412
  });
10645
12413
 
@@ -10827,10 +12595,10 @@ __export(messaging_exports, {
10827
12595
  sendMessage: () => sendMessage,
10828
12596
  setWsClientSend: () => setWsClientSend
10829
12597
  });
10830
- import crypto10 from "crypto";
12598
+ import crypto13 from "crypto";
10831
12599
  function generateUlid() {
10832
12600
  const timestamp = Date.now().toString(36).padStart(10, "0");
10833
- const random = crypto10.randomBytes(10).toString("hex").slice(0, 16);
12601
+ const random = crypto13.randomBytes(10).toString("hex").slice(0, 16);
10834
12602
  return (timestamp + random).toUpperCase();
10835
12603
  }
10836
12604
  function rowToMessage(row) {
@@ -11089,13 +12857,13 @@ init_memory();
11089
12857
  init_daemon_protocol();
11090
12858
  init_daemon_auth();
11091
12859
  init_daemon_orchestration();
11092
- import os15 from "os";
12860
+ import os16 from "os";
11093
12861
  import net2 from "net";
11094
- import { writeFileSync as writeFileSync11, unlinkSync as unlinkSync7, mkdirSync as mkdirSync9, existsSync as existsSync20, readFileSync as readFileSync17, chmodSync as chmodSync2 } from "fs";
11095
- import path24 from "path";
12862
+ import { writeFileSync as writeFileSync13, unlinkSync as unlinkSync9, mkdirSync as mkdirSync11, existsSync as existsSync22, readFileSync as readFileSync19, chmodSync as chmodSync2 } from "fs";
12863
+ import path27 from "path";
11096
12864
  import { getLlama } from "node-llama-cpp";
11097
- var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path24.join(EXE_AI_DIR, "exed.sock");
11098
- var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path24.join(EXE_AI_DIR, "exed.pid");
12865
+ var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path27.join(EXE_AI_DIR, "exed.sock");
12866
+ var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path27.join(EXE_AI_DIR, "exed.pid");
11099
12867
  var MODEL_FILE = "jina-embeddings-v5-small-q4_k_m.gguf";
11100
12868
  var IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
11101
12869
  var REVIEW_POLL_INTERVAL_MS = 60 * 1e3;
@@ -11123,8 +12891,8 @@ function enqueue(queue, entry) {
11123
12891
  queue.push(entry);
11124
12892
  }
11125
12893
  async function loadModel() {
11126
- const modelPath = path24.join(MODELS_DIR, MODEL_FILE);
11127
- if (!existsSync20(modelPath)) {
12894
+ const modelPath = path27.join(MODELS_DIR, MODEL_FILE);
12895
+ if (!existsSync22(modelPath)) {
11128
12896
  process.stderr.write(`[exed] FATAL: model not found at ${modelPath}
11129
12897
  `);
11130
12898
  process.exit(1);
@@ -11194,6 +12962,11 @@ function checkIdle() {
11194
12962
  }
11195
12963
  async function shutdown() {
11196
12964
  resetIdleTimer();
12965
+ try {
12966
+ const { stopProjectionWorker: stopProjectionWorker2 } = await Promise.resolve().then(() => (init_projection_worker(), projection_worker_exports));
12967
+ stopProjectionWorker2();
12968
+ } catch {
12969
+ }
11197
12970
  try {
11198
12971
  const { disposeShards: disposeShards2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
11199
12972
  disposeShards2();
@@ -11215,11 +12988,11 @@ async function shutdown() {
11215
12988
  }
11216
12989
  _llama = null;
11217
12990
  try {
11218
- unlinkSync7(SOCKET_PATH2);
12991
+ unlinkSync9(SOCKET_PATH2);
11219
12992
  } catch {
11220
12993
  }
11221
12994
  try {
11222
- unlinkSync7(PID_PATH2);
12995
+ unlinkSync9(PID_PATH2);
11223
12996
  } catch {
11224
12997
  }
11225
12998
  process.stderr.write("[exed] Shutdown complete.\n");
@@ -11355,28 +13128,28 @@ async function handleIngest(req) {
11355
13128
  }
11356
13129
  }
11357
13130
  function startServer() {
11358
- mkdirSync9(path24.dirname(SOCKET_PATH2), { recursive: true });
13131
+ mkdirSync11(path27.dirname(SOCKET_PATH2), { recursive: true });
11359
13132
  try {
11360
- chmodSync2(path24.dirname(SOCKET_PATH2), 448);
13133
+ chmodSync2(path27.dirname(SOCKET_PATH2), 448);
11361
13134
  } catch {
11362
13135
  }
11363
13136
  _daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV2] ?? null);
11364
13137
  for (const oldFile of ["embed.sock", "embed.pid"]) {
11365
- const oldPath = path24.join(path24.dirname(SOCKET_PATH2), oldFile);
13138
+ const oldPath = path27.join(path27.dirname(SOCKET_PATH2), oldFile);
11366
13139
  try {
11367
13140
  if (oldFile.endsWith(".pid")) {
11368
- const pid = parseInt(readFileSync17(oldPath, "utf8").trim(), 10);
13141
+ const pid = parseInt(readFileSync19(oldPath, "utf8").trim(), 10);
11369
13142
  if (pid > 0) try {
11370
13143
  process.kill(pid, "SIGKILL");
11371
13144
  } catch {
11372
13145
  }
11373
13146
  }
11374
- unlinkSync7(oldPath);
13147
+ unlinkSync9(oldPath);
11375
13148
  } catch {
11376
13149
  }
11377
13150
  }
11378
13151
  try {
11379
- unlinkSync7(SOCKET_PATH2);
13152
+ unlinkSync9(SOCKET_PATH2);
11380
13153
  } catch {
11381
13154
  }
11382
13155
  const server = net2.createServer((socket) => {
@@ -11466,7 +13239,7 @@ function startServer() {
11466
13239
  chmodSync2(SOCKET_PATH2, 384);
11467
13240
  } catch {
11468
13241
  }
11469
- writeFileSync11(PID_PATH2, String(process.pid));
13242
+ writeFileSync13(PID_PATH2, String(process.pid));
11470
13243
  try {
11471
13244
  chmodSync2(PID_PATH2, 384);
11472
13245
  } catch {
@@ -11666,6 +13439,35 @@ function startConsolidation() {
11666
13439
  `);
11667
13440
  });
11668
13441
  }
13442
+ var CLOUD_SYNC_INTERVAL_MS = Number(process.env.EXE_CLOUD_SYNC_INTERVAL_MS) || 5 * 60 * 1e3;
13443
+ function startCloudSyncTimer() {
13444
+ const tick = async () => {
13445
+ try {
13446
+ const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
13447
+ const config = await loadConfig2();
13448
+ if (!config.cloud?.apiKey || !config.cloud?.endpoint) return;
13449
+ const { cloudSync: cloudSync2 } = await Promise.resolve().then(() => (init_cloud_sync(), cloud_sync_exports));
13450
+ const result = await cloudSync2({
13451
+ apiKey: config.cloud.apiKey,
13452
+ endpoint: config.cloud.endpoint
13453
+ });
13454
+ if (result.pushed > 0 || result.pulled > 0) {
13455
+ process.stderr.write(
13456
+ `[exed] Cloud sync: pushed=${result.pushed} pulled=${result.pulled} total=${result.totalMemories}
13457
+ `
13458
+ );
13459
+ }
13460
+ } catch (err) {
13461
+ process.stderr.write(`[exed] Cloud sync error (non-fatal): ${err instanceof Error ? err.message : String(err)}
13462
+ `);
13463
+ }
13464
+ };
13465
+ const timer = setInterval(() => void tick(), CLOUD_SYNC_INTERVAL_MS);
13466
+ timer.unref();
13467
+ setTimeout(() => void tick(), 3e4);
13468
+ process.stderr.write(`[exed] Cloud sync timer started (every ${CLOUD_SYNC_INTERVAL_MS / 6e4}m)
13469
+ `);
13470
+ }
11669
13471
  var SKILL_SWEEP_INTERVAL_MS = 6 * 60 * 60 * 1e3;
11670
13472
  function startSkillSweep() {
11671
13473
  const tick = async () => {
@@ -11763,7 +13565,7 @@ function startWikiSync() {
11763
13565
  });
11764
13566
  }
11765
13567
  var AGENT_STATS_INTERVAL_MS = 60 * 1e3;
11766
- var AGENT_STATS_PATH = path24.join(EXE_AI_DIR, "agent-stats.json");
13568
+ var AGENT_STATS_PATH = path27.join(EXE_AI_DIR, "agent-stats.json");
11767
13569
  async function writeAgentStats() {
11768
13570
  if (!await ensureStoreForPolling()) return;
11769
13571
  try {
@@ -11821,7 +13623,7 @@ async function writeAgentStats() {
11821
13623
  pid: process.pid
11822
13624
  }
11823
13625
  };
11824
- writeFileSync11(AGENT_STATS_PATH, JSON.stringify(stats, null, 2), "utf8");
13626
+ writeFileSync13(AGENT_STATS_PATH, JSON.stringify(stats, null, 2), "utf8");
11825
13627
  } catch (err) {
11826
13628
  process.stderr.write(`[exed] Agent stats error: ${err instanceof Error ? err.message : String(err)}
11827
13629
  `);
@@ -11893,12 +13695,12 @@ function startIntercomQueueDrain() {
11893
13695
  const hasInProgressTask = (session) => {
11894
13696
  try {
11895
13697
  const { baseAgentName: ban } = (init_employees(), __toCommonJS(employees_exports));
11896
- const path25 = __require("path");
11897
- const { existsSync: existsSync21 } = __require("fs");
11898
- const os16 = __require("os");
13698
+ const path28 = __require("path");
13699
+ const { existsSync: existsSync23 } = __require("fs");
13700
+ const os17 = __require("os");
11899
13701
  const agent = ban(session.split("-")[0] ?? session);
11900
- const markerPath = path25.join(os16.homedir(), ".exe-os", "session-cache", `current-task-${agent}.json`);
11901
- return existsSync21(markerPath);
13702
+ const markerPath = path28.join(os17.homedir(), ".exe-os", "session-cache", `current-task-${agent}.json`);
13703
+ return existsSync23(markerPath);
11902
13704
  } catch {
11903
13705
  return false;
11904
13706
  }
@@ -11987,7 +13789,7 @@ function startAutoWake() {
11987
13789
  process.stderr.write(`[exed] Auto-wake started (every ${AUTO_WAKE_INTERVAL_MS / 1e3}s)
11988
13790
  `);
11989
13791
  }
11990
- var TOTAL_MEM_GB = os15.totalmem() / 1024 ** 3;
13792
+ var TOTAL_MEM_GB = os16.totalmem() / 1024 ** 3;
11991
13793
  var RSS_WARN_BYTES = Number(process.env.EXE_RSS_WARN_MB) * 1024 * 1024 || (TOTAL_MEM_GB >= 64 ? 6 * 1024 ** 3 : TOTAL_MEM_GB >= 32 ? 4 * 1024 ** 3 : 1024 ** 3);
11992
13794
  var RSS_RESTART_BYTES = Number(process.env.EXE_RSS_RESTART_MB) * 1024 * 1024 || (TOTAL_MEM_GB >= 64 ? 8 * 1024 ** 3 : TOTAL_MEM_GB >= 32 ? 6 * 1024 ** 3 : 2 * 1024 ** 3);
11993
13795
  var RSS_CHECK_INTERVAL_MS = 30 * 1e3;
@@ -12023,8 +13825,8 @@ process.on("SIGINT", () => void shutdown());
12023
13825
  process.on("SIGTERM", () => void shutdown());
12024
13826
  function checkExistingDaemon() {
12025
13827
  try {
12026
- if (!existsSync20(PID_PATH2)) return false;
12027
- const pid = parseInt(readFileSync17(PID_PATH2, "utf8").trim(), 10);
13828
+ if (!existsSync22(PID_PATH2)) return false;
13829
+ const pid = parseInt(readFileSync19(PID_PATH2, "utf8").trim(), 10);
12028
13830
  if (!pid || isNaN(pid)) return false;
12029
13831
  process.kill(pid, 0);
12030
13832
  process.stderr.write(`[exed] Another daemon is already running (PID ${pid}). Exiting.
@@ -12037,7 +13839,7 @@ function checkExistingDaemon() {
12037
13839
  return true;
12038
13840
  }
12039
13841
  try {
12040
- unlinkSync7(PID_PATH2);
13842
+ unlinkSync9(PID_PATH2);
12041
13843
  } catch {
12042
13844
  }
12043
13845
  return false;
@@ -12103,6 +13905,7 @@ try {
12103
13905
  startIdleKill();
12104
13906
  startConsolidation();
12105
13907
  startSkillSweep();
13908
+ startCloudSyncTimer();
12106
13909
  startOrphanReaper();
12107
13910
  startAgentStats();
12108
13911
  startCapacityMonitoring();
@@ -12113,6 +13916,8 @@ try {
12113
13916
  startConfidenceDecay();
12114
13917
  startAutoUpdateCheck();
12115
13918
  startRssWatchdog();
13919
+ const { startProjectionWorker: startProjectionWorker2 } = await Promise.resolve().then(() => (init_projection_worker(), projection_worker_exports));
13920
+ startProjectionWorker2();
12116
13921
  try {
12117
13922
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
12118
13923
  const config = await loadConfig2();
@@ -12206,11 +14011,11 @@ try {
12206
14011
  process.stderr.write(`[exed] FATAL: ${err instanceof Error ? err.message : String(err)}
12207
14012
  `);
12208
14013
  try {
12209
- unlinkSync7(SOCKET_PATH2);
14014
+ unlinkSync9(SOCKET_PATH2);
12210
14015
  } catch {
12211
14016
  }
12212
14017
  try {
12213
- unlinkSync7(PID_PATH2);
14018
+ unlinkSync9(PID_PATH2);
12214
14019
  } catch {
12215
14020
  }
12216
14021
  process.exit(1);