@askexenow/exe-os 0.8.36 → 0.8.37

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 (62) hide show
  1. package/dist/bin/backfill-conversations.js +9 -1
  2. package/dist/bin/backfill-responses.js +9 -1
  3. package/dist/bin/cli.js +255 -31
  4. package/dist/bin/exe-assign.js +9 -1
  5. package/dist/bin/exe-boot.js +554 -23
  6. package/dist/bin/exe-dispatch.js +43 -3
  7. package/dist/bin/exe-export-behaviors.js +7 -0
  8. package/dist/bin/exe-gateway.js +57 -9
  9. package/dist/bin/exe-heartbeat.js +2 -1
  10. package/dist/bin/exe-kill.js +7 -0
  11. package/dist/bin/exe-launch-agent.js +8 -1
  12. package/dist/bin/exe-link.js +503 -12
  13. package/dist/bin/exe-pending-messages.js +2 -1
  14. package/dist/bin/exe-pending-reviews.js +2 -1
  15. package/dist/bin/exe-review.js +9 -1
  16. package/dist/bin/exe-search.js +9 -1
  17. package/dist/bin/exe-session-cleanup.js +11 -2
  18. package/dist/bin/git-sweep.js +9 -1
  19. package/dist/bin/graph-backfill.js +7 -0
  20. package/dist/bin/graph-export.js +7 -0
  21. package/dist/bin/install.js +35 -5
  22. package/dist/bin/scan-tasks.js +9 -1
  23. package/dist/bin/shard-migrate.js +7 -0
  24. package/dist/bin/wiki-sync.js +7 -0
  25. package/dist/gateway/index.js +57 -9
  26. package/dist/hooks/bug-report-worker.js +45 -5
  27. package/dist/hooks/commit-complete.js +9 -1
  28. package/dist/hooks/error-recall.js +10 -2
  29. package/dist/hooks/exe-heartbeat-hook.js +1 -1
  30. package/dist/hooks/ingest-worker.js +56 -8
  31. package/dist/hooks/ingest.js +1 -1
  32. package/dist/hooks/instructions-loaded.js +10 -2
  33. package/dist/hooks/notification.js +10 -2
  34. package/dist/hooks/post-compact.js +10 -2
  35. package/dist/hooks/pre-compact.js +10 -2
  36. package/dist/hooks/pre-tool-use.js +10 -2
  37. package/dist/hooks/prompt-ingest-worker.js +9 -1
  38. package/dist/hooks/prompt-submit.js +56 -8
  39. package/dist/hooks/response-ingest-worker.js +9 -1
  40. package/dist/hooks/session-end.js +10 -2
  41. package/dist/hooks/session-start.js +10 -2
  42. package/dist/hooks/stop.js +10 -2
  43. package/dist/hooks/subagent-stop.js +10 -2
  44. package/dist/hooks/summary-worker.js +512 -13
  45. package/dist/index.js +65 -15
  46. package/dist/lib/cloud-sync.js +502 -11
  47. package/dist/lib/exe-daemon.js +73 -23
  48. package/dist/lib/hybrid-search.js +9 -1
  49. package/dist/lib/messaging.js +43 -3
  50. package/dist/lib/store.js +9 -1
  51. package/dist/lib/tasks.js +47 -7
  52. package/dist/lib/tmux-routing.js +45 -3
  53. package/dist/mcp/server.js +73 -16
  54. package/dist/mcp/tools/create-task.js +48 -8
  55. package/dist/mcp/tools/deactivate-behavior.js +1 -1
  56. package/dist/mcp/tools/list-tasks.js +2 -1
  57. package/dist/mcp/tools/send-message.js +46 -6
  58. package/dist/mcp/tools/update-task.js +3 -2
  59. package/dist/runtime/index.js +54 -4
  60. package/dist/tui/App.js +54 -4
  61. package/package.json +2 -2
  62. package/src/commands/exe/afk.md +116 -0
@@ -2385,10 +2385,23 @@ var cloud_sync_exports = {};
2385
2385
  __export(cloud_sync_exports, {
2386
2386
  buildRosterBlob: () => buildRosterBlob,
2387
2387
  cloudPull: () => cloudPull,
2388
+ cloudPullBehaviors: () => cloudPullBehaviors,
2389
+ cloudPullBlob: () => cloudPullBlob,
2390
+ cloudPullConversations: () => cloudPullConversations,
2391
+ cloudPullDocuments: () => cloudPullDocuments,
2392
+ cloudPullGraphRAG: () => cloudPullGraphRAG,
2388
2393
  cloudPullRoster: () => cloudPullRoster,
2394
+ cloudPullTasks: () => cloudPullTasks,
2389
2395
  cloudPush: () => cloudPush,
2396
+ cloudPushBehaviors: () => cloudPushBehaviors,
2397
+ cloudPushBlob: () => cloudPushBlob,
2398
+ cloudPushConversations: () => cloudPushConversations,
2399
+ cloudPushDocuments: () => cloudPushDocuments,
2400
+ cloudPushGraphRAG: () => cloudPushGraphRAG,
2390
2401
  cloudPushRoster: () => cloudPushRoster,
2402
+ cloudPushTasks: () => cloudPushTasks,
2391
2403
  cloudSync: () => cloudSync,
2404
+ mergeConfig: () => mergeConfig,
2392
2405
  mergeRosterFromRemote: () => mergeRosterFromRemote
2393
2406
  });
2394
2407
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync2, existsSync as existsSync9, readdirSync as readdirSync2, mkdirSync as mkdirSync3, appendFileSync } from "fs";
@@ -2402,6 +2415,17 @@ function logError(msg) {
2402
2415
  } catch {
2403
2416
  }
2404
2417
  }
2418
+ async function fetchWithRetry(url, init) {
2419
+ const attempt = async () => {
2420
+ const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
2421
+ return fetch(url, { ...init, signal });
2422
+ };
2423
+ const resp = await attempt();
2424
+ if (resp.status >= 500) {
2425
+ return attempt();
2426
+ }
2427
+ return resp;
2428
+ }
2405
2429
  function assertSecureEndpoint(endpoint) {
2406
2430
  if (endpoint.startsWith("https://")) return;
2407
2431
  if (endpoint.startsWith("http://")) {
@@ -2423,7 +2447,7 @@ async function cloudPush(records, maxVersion, config) {
2423
2447
  const json = JSON.stringify(records);
2424
2448
  const compressed = compress(Buffer.from(json, "utf8"));
2425
2449
  const blob = encryptSyncBlob(compressed);
2426
- const resp = await fetch(`${config.endpoint}/sync/push`, {
2450
+ const resp = await fetchWithRetry(`${config.endpoint}/sync/push`, {
2427
2451
  method: "POST",
2428
2452
  headers: {
2429
2453
  Authorization: `Bearer ${config.apiKey}`,
@@ -2441,7 +2465,7 @@ async function cloudPush(records, maxVersion, config) {
2441
2465
  async function cloudPull(sinceVersion, config) {
2442
2466
  assertSecureEndpoint(config.endpoint);
2443
2467
  try {
2444
- const response = await fetch(`${config.endpoint}/sync/pull`, {
2468
+ const response = await fetchWithRetry(`${config.endpoint}/sync/pull`, {
2445
2469
  method: "POST",
2446
2470
  headers: {
2447
2471
  Authorization: `Bearer ${config.apiKey}`,
@@ -2557,20 +2581,87 @@ async function cloudSync(config) {
2557
2581
  try {
2558
2582
  await cloudPushRoster(config);
2559
2583
  } catch (err) {
2560
- process.stderr.write(`[cloud-sync] Roster push warning: ${err instanceof Error ? err.message : String(err)}
2561
- `);
2584
+ logError(`[cloud-sync] Roster push: ${err instanceof Error ? err.message : String(err)}`);
2562
2585
  }
2563
2586
  try {
2564
2587
  await cloudPullRoster(config);
2565
2588
  } catch (err) {
2566
- process.stderr.write(`[cloud-sync] Roster pull warning: ${err instanceof Error ? err.message : String(err)}
2567
- `);
2589
+ logError(`[cloud-sync] Roster pull: ${err instanceof Error ? err.message : String(err)}`);
2590
+ }
2591
+ let behaviorsResult = { pushed: false, pulled: 0 };
2592
+ try {
2593
+ behaviorsResult.pushed = await cloudPushBehaviors(config);
2594
+ } catch (err) {
2595
+ logError(`[cloud-sync] Behaviors push: ${err instanceof Error ? err.message : String(err)}`);
2596
+ }
2597
+ try {
2598
+ const pullResult2 = await cloudPullBehaviors(config);
2599
+ behaviorsResult.pulled = pullResult2.pulled;
2600
+ } catch (err) {
2601
+ logError(`[cloud-sync] Behaviors pull: ${err instanceof Error ? err.message : String(err)}`);
2602
+ }
2603
+ let graphragResult = { pushed: false, pulled: 0 };
2604
+ try {
2605
+ graphragResult.pushed = await cloudPushGraphRAG(config);
2606
+ } catch (err) {
2607
+ logError(`[cloud-sync] GraphRAG push: ${err instanceof Error ? err.message : String(err)}`);
2608
+ }
2609
+ try {
2610
+ const pullResult2 = await cloudPullGraphRAG(config);
2611
+ graphragResult.pulled = pullResult2.pulled;
2612
+ } catch (err) {
2613
+ logError(`[cloud-sync] GraphRAG pull: ${err instanceof Error ? err.message : String(err)}`);
2614
+ }
2615
+ let tasksResult = { pushed: false, pulled: 0 };
2616
+ try {
2617
+ tasksResult.pushed = await cloudPushTasks(config);
2618
+ } catch (err) {
2619
+ logError(`[cloud-sync] Tasks push: ${err instanceof Error ? err.message : String(err)}`);
2620
+ }
2621
+ try {
2622
+ const pullResult2 = await cloudPullTasks(config);
2623
+ tasksResult.pulled = pullResult2.pulled;
2624
+ } catch (err) {
2625
+ logError(`[cloud-sync] Tasks pull: ${err instanceof Error ? err.message : String(err)}`);
2626
+ }
2627
+ let conversationsResult = { pushed: false, pulled: 0 };
2628
+ try {
2629
+ conversationsResult.pushed = await cloudPushConversations(config);
2630
+ } catch (err) {
2631
+ logError(`[cloud-sync] Conversations push: ${err instanceof Error ? err.message : String(err)}`);
2632
+ }
2633
+ try {
2634
+ const pullResult2 = await cloudPullConversations(config);
2635
+ conversationsResult.pulled = pullResult2.pulled;
2636
+ } catch (err) {
2637
+ logError(`[cloud-sync] Conversations pull: ${err instanceof Error ? err.message : String(err)}`);
2638
+ }
2639
+ let documentsResult = { pushed: false, pulled: 0 };
2640
+ try {
2641
+ documentsResult.pushed = await cloudPushDocuments(config);
2642
+ } catch (err) {
2643
+ logError(`[cloud-sync] Documents push: ${err instanceof Error ? err.message : String(err)}`);
2568
2644
  }
2569
- return { pushed, pulled };
2645
+ try {
2646
+ const pullResult2 = await cloudPullDocuments(config);
2647
+ documentsResult.pulled = pullResult2.pulled;
2648
+ } catch (err) {
2649
+ logError(`[cloud-sync] Documents pull: ${err instanceof Error ? err.message : String(err)}`);
2650
+ }
2651
+ return {
2652
+ pushed,
2653
+ pulled,
2654
+ behaviors: behaviorsResult,
2655
+ graphrag: graphragResult,
2656
+ tasks: tasksResult,
2657
+ conversations: conversationsResult,
2658
+ documents: documentsResult
2659
+ };
2570
2660
  }
2571
2661
  function buildRosterBlob(paths) {
2572
2662
  const rosterPath = paths?.rosterPath ?? path9.join(EXE_AI_DIR, "exe-employees.json");
2573
2663
  const identityDir = paths?.identityDir ?? path9.join(EXE_AI_DIR, "identity");
2664
+ const configPath = paths?.configPath ?? path9.join(EXE_AI_DIR, "config.json");
2574
2665
  let roster = [];
2575
2666
  if (existsSync9(rosterPath)) {
2576
2667
  try {
@@ -2587,9 +2678,16 @@ function buildRosterBlob(paths) {
2587
2678
  }
2588
2679
  }
2589
2680
  }
2590
- const content = JSON.stringify({ roster, identities });
2681
+ let config;
2682
+ if (existsSync9(configPath)) {
2683
+ try {
2684
+ config = JSON.parse(readFileSync7(configPath, "utf-8"));
2685
+ } catch {
2686
+ }
2687
+ }
2688
+ const content = JSON.stringify({ roster, identities, config });
2591
2689
  const hash = Buffer.from(content).length;
2592
- return { roster, identities, version: hash };
2690
+ return { roster, identities, config, version: hash };
2593
2691
  }
2594
2692
  async function cloudPushRoster(config) {
2595
2693
  assertSecureEndpoint(config.endpoint);
@@ -2608,7 +2706,7 @@ async function cloudPushRoster(config) {
2608
2706
  const json = JSON.stringify(blob);
2609
2707
  const compressed = compress(Buffer.from(json, "utf8"));
2610
2708
  const encrypted = encryptSyncBlob(compressed);
2611
- const resp = await fetch(`${config.endpoint}/sync/push-roster`, {
2709
+ const resp = await fetchWithRetry(`${config.endpoint}/sync/push-roster`, {
2612
2710
  method: "POST",
2613
2711
  headers: {
2614
2712
  Authorization: `Bearer ${config.apiKey}`,
@@ -2637,7 +2735,7 @@ async function cloudPushRoster(config) {
2637
2735
  async function cloudPullRoster(config) {
2638
2736
  assertSecureEndpoint(config.endpoint);
2639
2737
  try {
2640
- const resp = await fetch(`${config.endpoint}/sync/pull-roster`, {
2738
+ const resp = await fetchWithRetry(`${config.endpoint}/sync/pull-roster`, {
2641
2739
  method: "GET",
2642
2740
  headers: {
2643
2741
  Authorization: `Bearer ${config.apiKey}`,
@@ -2657,6 +2755,20 @@ async function cloudPullRoster(config) {
2657
2755
  return { added: 0 };
2658
2756
  }
2659
2757
  }
2758
+ function mergeConfig(remoteConfig, configPath) {
2759
+ const cfgPath = configPath ?? path9.join(EXE_AI_DIR, "config.json");
2760
+ let local = {};
2761
+ if (existsSync9(cfgPath)) {
2762
+ try {
2763
+ local = JSON.parse(readFileSync7(cfgPath, "utf-8"));
2764
+ } catch {
2765
+ }
2766
+ }
2767
+ const merged = { ...remoteConfig, ...local };
2768
+ const dir = path9.dirname(cfgPath);
2769
+ if (!existsSync9(dir)) mkdirSync3(dir, { recursive: true });
2770
+ writeFileSync2(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
2771
+ }
2660
2772
  async function mergeRosterFromRemote(remote, paths) {
2661
2773
  const rosterPath = paths?.rosterPath ?? void 0;
2662
2774
  const identityDir = paths?.identityDir ?? path9.join(EXE_AI_DIR, "identity");
@@ -2683,9 +2795,387 @@ async function mergeRosterFromRemote(remote, paths) {
2683
2795
  if (added > 0) {
2684
2796
  await saveEmployees(localEmployees, rosterPath);
2685
2797
  }
2798
+ if (remote.config && Object.keys(remote.config).length > 0) {
2799
+ try {
2800
+ mergeConfig(remote.config, paths?.configPath);
2801
+ } catch {
2802
+ }
2803
+ }
2686
2804
  return { added };
2687
2805
  }
2688
- var LOCALHOST_PATTERNS;
2806
+ async function cloudPushBlob(route, data, metaKey, config) {
2807
+ if (data.length === 0) return { ok: true };
2808
+ assertSecureEndpoint(config.endpoint);
2809
+ const json = JSON.stringify(data);
2810
+ const version = Buffer.from(json).length;
2811
+ try {
2812
+ const client = getClient();
2813
+ const meta = await client.execute({
2814
+ sql: "SELECT value FROM sync_meta WHERE key = ?",
2815
+ args: [metaKey]
2816
+ });
2817
+ const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
2818
+ if (version === lastVersion) return { ok: true };
2819
+ } catch {
2820
+ }
2821
+ try {
2822
+ const compressed = compress(Buffer.from(json, "utf8"));
2823
+ const encrypted = encryptSyncBlob(compressed);
2824
+ const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
2825
+ method: "POST",
2826
+ headers: {
2827
+ Authorization: `Bearer ${config.apiKey}`,
2828
+ "Content-Type": "application/json",
2829
+ "X-Device-Id": loadDeviceId()
2830
+ },
2831
+ body: JSON.stringify({ blob: encrypted })
2832
+ });
2833
+ if (resp.ok) {
2834
+ try {
2835
+ const client = getClient();
2836
+ await client.execute({
2837
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES (?, ?)",
2838
+ args: [metaKey, String(version)]
2839
+ });
2840
+ } catch {
2841
+ }
2842
+ }
2843
+ return { ok: resp.ok };
2844
+ } catch (err) {
2845
+ logError(`[cloud-sync] PUSH ${route}: ${err instanceof Error ? err.message : String(err)}`);
2846
+ return { ok: false };
2847
+ }
2848
+ }
2849
+ async function cloudPullBlob(route, config) {
2850
+ assertSecureEndpoint(config.endpoint);
2851
+ try {
2852
+ const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
2853
+ method: "GET",
2854
+ headers: {
2855
+ Authorization: `Bearer ${config.apiKey}`,
2856
+ "X-Device-Id": loadDeviceId()
2857
+ }
2858
+ });
2859
+ if (!resp.ok) return null;
2860
+ const data = await resp.json();
2861
+ if (!data.blob) return null;
2862
+ const compressed = decryptSyncBlob(data.blob);
2863
+ const json = decompress(compressed).toString("utf8");
2864
+ return JSON.parse(json);
2865
+ } catch (err) {
2866
+ logError(`[cloud-sync] PULL ${route}: ${err instanceof Error ? err.message : String(err)}`);
2867
+ return null;
2868
+ }
2869
+ }
2870
+ async function cloudPushBehaviors(config) {
2871
+ const client = getClient();
2872
+ const result = await client.execute("SELECT * FROM behaviors");
2873
+ const rows = result.rows;
2874
+ const { ok } = await cloudPushBlob(
2875
+ "/sync/push-behaviors",
2876
+ rows,
2877
+ "last_behaviors_push_version",
2878
+ config
2879
+ );
2880
+ return ok;
2881
+ }
2882
+ async function cloudPullBehaviors(config) {
2883
+ const remoteBehaviors = await cloudPullBlob(
2884
+ "/sync/pull-behaviors",
2885
+ config
2886
+ );
2887
+ if (!remoteBehaviors || remoteBehaviors.length === 0) return { pulled: 0 };
2888
+ const client = getClient();
2889
+ let pulled = 0;
2890
+ for (const behavior of remoteBehaviors) {
2891
+ const existing = await client.execute({
2892
+ sql: `SELECT COUNT(*) as cnt FROM behaviors
2893
+ WHERE agent_id = ? AND content = ?`,
2894
+ args: [behavior.agent_id, behavior.content]
2895
+ });
2896
+ if (Number(existing.rows[0]?.cnt) > 0) continue;
2897
+ await client.execute({
2898
+ sql: `INSERT OR IGNORE INTO behaviors
2899
+ (id, agent_id, project_name, domain, content, active, priority, created_at, updated_at)
2900
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2901
+ args: [
2902
+ behavior.id,
2903
+ behavior.agent_id,
2904
+ behavior.project_name ?? null,
2905
+ behavior.domain ?? null,
2906
+ behavior.content,
2907
+ behavior.active,
2908
+ behavior.priority ?? "p1",
2909
+ behavior.created_at,
2910
+ behavior.updated_at
2911
+ ]
2912
+ });
2913
+ pulled++;
2914
+ }
2915
+ return { pulled };
2916
+ }
2917
+ async function cloudPushGraphRAG(config) {
2918
+ const client = getClient();
2919
+ const [entities, relationships, aliases, entityMems, relMems, hyperedges, hyperedgeNodes] = await Promise.all([
2920
+ client.execute("SELECT * FROM entities"),
2921
+ client.execute("SELECT * FROM relationships"),
2922
+ client.execute("SELECT * FROM entity_aliases"),
2923
+ client.execute("SELECT * FROM entity_memories"),
2924
+ client.execute("SELECT * FROM relationship_memories"),
2925
+ client.execute("SELECT * FROM hyperedges"),
2926
+ client.execute("SELECT * FROM hyperedge_nodes")
2927
+ ]);
2928
+ const blob = {
2929
+ entities: entities.rows,
2930
+ relationships: relationships.rows,
2931
+ entity_aliases: aliases.rows,
2932
+ entity_memories: entityMems.rows,
2933
+ relationship_memories: relMems.rows,
2934
+ hyperedges: hyperedges.rows,
2935
+ hyperedge_nodes: hyperedgeNodes.rows
2936
+ };
2937
+ const { ok } = await cloudPushBlob(
2938
+ "/sync/push-graphrag",
2939
+ [blob],
2940
+ "last_graphrag_push_version",
2941
+ config
2942
+ );
2943
+ return ok;
2944
+ }
2945
+ async function cloudPullGraphRAG(config) {
2946
+ const data = await cloudPullBlob(
2947
+ "/sync/pull-graphrag",
2948
+ config
2949
+ );
2950
+ if (!data || data.length === 0) return { pulled: 0 };
2951
+ const blob = data[0];
2952
+ const client = getClient();
2953
+ let pulled = 0;
2954
+ if (blob.entities.length > 0) {
2955
+ const stmts = blob.entities.map((e) => ({
2956
+ sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen, properties)
2957
+ VALUES (?, ?, ?, ?, ?, ?)`,
2958
+ args: [e.id, e.name, e.type, e.first_seen, e.last_seen, e.properties ?? "{}"]
2959
+ }));
2960
+ await client.batch(stmts, "write");
2961
+ pulled += stmts.length;
2962
+ }
2963
+ if (blob.relationships.length > 0) {
2964
+ const stmts = blob.relationships.map((r) => ({
2965
+ sql: `INSERT OR IGNORE INTO relationships
2966
+ (id, source_entity_id, target_entity_id, type, weight, timestamp, properties, confidence, confidence_label)
2967
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2968
+ args: [
2969
+ r.id,
2970
+ r.source_entity_id,
2971
+ r.target_entity_id,
2972
+ r.type,
2973
+ r.weight ?? 1,
2974
+ r.timestamp,
2975
+ r.properties ?? "{}",
2976
+ r.confidence ?? 1,
2977
+ r.confidence_label ?? "extracted"
2978
+ ]
2979
+ }));
2980
+ await client.batch(stmts, "write");
2981
+ pulled += stmts.length;
2982
+ }
2983
+ if (blob.entity_aliases.length > 0) {
2984
+ const stmts = blob.entity_aliases.map((a) => ({
2985
+ sql: `INSERT OR IGNORE INTO entity_aliases (alias, canonical_entity_id) VALUES (?, ?)`,
2986
+ args: [a.alias, a.canonical_entity_id]
2987
+ }));
2988
+ await client.batch(stmts, "write");
2989
+ pulled += stmts.length;
2990
+ }
2991
+ if (blob.entity_memories.length > 0) {
2992
+ const stmts = blob.entity_memories.map((em) => ({
2993
+ sql: `INSERT OR IGNORE INTO entity_memories (entity_id, memory_id) VALUES (?, ?)`,
2994
+ args: [em.entity_id, em.memory_id]
2995
+ }));
2996
+ await client.batch(stmts, "write");
2997
+ pulled += stmts.length;
2998
+ }
2999
+ if (blob.relationship_memories.length > 0) {
3000
+ const stmts = blob.relationship_memories.map((rm) => ({
3001
+ sql: `INSERT OR IGNORE INTO relationship_memories (relationship_id, memory_id) VALUES (?, ?)`,
3002
+ args: [rm.relationship_id, rm.memory_id]
3003
+ }));
3004
+ await client.batch(stmts, "write");
3005
+ pulled += stmts.length;
3006
+ }
3007
+ if (blob.hyperedges.length > 0) {
3008
+ const stmts = blob.hyperedges.map((h) => ({
3009
+ sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
3010
+ VALUES (?, ?, ?, ?, ?)`,
3011
+ args: [h.id, h.label, h.relation, h.confidence ?? 1, h.timestamp]
3012
+ }));
3013
+ await client.batch(stmts, "write");
3014
+ pulled += stmts.length;
3015
+ }
3016
+ if (blob.hyperedge_nodes.length > 0) {
3017
+ const stmts = blob.hyperedge_nodes.map((hn) => ({
3018
+ sql: `INSERT OR IGNORE INTO hyperedge_nodes (hyperedge_id, entity_id) VALUES (?, ?)`,
3019
+ args: [hn.hyperedge_id, hn.entity_id]
3020
+ }));
3021
+ await client.batch(stmts, "write");
3022
+ pulled += stmts.length;
3023
+ }
3024
+ return { pulled };
3025
+ }
3026
+ async function cloudPushTasks(config) {
3027
+ const client = getClient();
3028
+ const result = await client.execute("SELECT * FROM tasks");
3029
+ const rows = result.rows;
3030
+ const { ok } = await cloudPushBlob(
3031
+ "/sync/push-tasks",
3032
+ rows,
3033
+ "last_tasks_push_version",
3034
+ config
3035
+ );
3036
+ return ok;
3037
+ }
3038
+ async function cloudPullTasks(config) {
3039
+ const remoteTasks = await cloudPullBlob(
3040
+ "/sync/pull-tasks",
3041
+ config
3042
+ );
3043
+ if (!remoteTasks || remoteTasks.length === 0) return { pulled: 0 };
3044
+ const client = getClient();
3045
+ const stmts = remoteTasks.map((t) => ({
3046
+ sql: `INSERT OR IGNORE INTO tasks
3047
+ (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, created_at, updated_at,
3048
+ blocked_by, parent_task_id, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at)
3049
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3050
+ args: [
3051
+ t.id,
3052
+ t.title,
3053
+ t.assigned_to,
3054
+ t.assigned_by,
3055
+ t.project_name,
3056
+ t.priority ?? "p1",
3057
+ t.status ?? "open",
3058
+ t.task_file ?? null,
3059
+ t.created_at,
3060
+ t.updated_at,
3061
+ t.blocked_by ?? null,
3062
+ t.parent_task_id ?? null,
3063
+ t.budget_tokens ?? null,
3064
+ t.budget_fallback_model ?? null,
3065
+ t.tokens_used ?? 0,
3066
+ t.tokens_warned_at ?? null
3067
+ ]
3068
+ }));
3069
+ await client.batch(stmts, "write");
3070
+ return { pulled: remoteTasks.length };
3071
+ }
3072
+ async function cloudPushConversations(config) {
3073
+ const client = getClient();
3074
+ const result = await client.execute("SELECT * FROM conversations");
3075
+ const rows = result.rows;
3076
+ const { ok } = await cloudPushBlob(
3077
+ "/sync/push-conversations",
3078
+ rows,
3079
+ "last_conversations_push_version",
3080
+ config
3081
+ );
3082
+ return ok;
3083
+ }
3084
+ async function cloudPullConversations(config) {
3085
+ const remoteConvos = await cloudPullBlob(
3086
+ "/sync/pull-conversations",
3087
+ config
3088
+ );
3089
+ if (!remoteConvos || remoteConvos.length === 0) return { pulled: 0 };
3090
+ const client = getClient();
3091
+ const stmts = remoteConvos.map((c) => ({
3092
+ sql: `INSERT OR IGNORE INTO conversations
3093
+ (id, platform, external_id, sender_id, sender_name, sender_phone, sender_email,
3094
+ recipient_id, channel_id, thread_id, reply_to_id, content_text, content_media,
3095
+ content_metadata, agent_response, agent_name, timestamp, ingested_at)
3096
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3097
+ args: [
3098
+ c.id,
3099
+ c.platform,
3100
+ c.external_id ?? null,
3101
+ c.sender_id,
3102
+ c.sender_name ?? null,
3103
+ c.sender_phone ?? null,
3104
+ c.sender_email ?? null,
3105
+ c.recipient_id ?? null,
3106
+ c.channel_id,
3107
+ c.thread_id ?? null,
3108
+ c.reply_to_id ?? null,
3109
+ c.content_text ?? null,
3110
+ c.content_media ?? null,
3111
+ c.content_metadata ?? null,
3112
+ c.agent_response ?? null,
3113
+ c.agent_name ?? null,
3114
+ c.timestamp,
3115
+ c.ingested_at
3116
+ ]
3117
+ }));
3118
+ await client.batch(stmts, "write");
3119
+ return { pulled: remoteConvos.length };
3120
+ }
3121
+ async function cloudPushDocuments(config) {
3122
+ const client = getClient();
3123
+ const [workspaces, documents] = await Promise.all([
3124
+ client.execute("SELECT * FROM workspaces"),
3125
+ client.execute("SELECT * FROM documents")
3126
+ ]);
3127
+ const blob = {
3128
+ workspaces: workspaces.rows,
3129
+ documents: documents.rows
3130
+ };
3131
+ const { ok } = await cloudPushBlob(
3132
+ "/sync/push-documents",
3133
+ [blob],
3134
+ "last_documents_push_version",
3135
+ config
3136
+ );
3137
+ return ok;
3138
+ }
3139
+ async function cloudPullDocuments(config) {
3140
+ const data = await cloudPullBlob(
3141
+ "/sync/pull-documents",
3142
+ config
3143
+ );
3144
+ if (!data || data.length === 0) return { pulled: 0 };
3145
+ const blob = data[0];
3146
+ const client = getClient();
3147
+ let pulled = 0;
3148
+ if (blob.workspaces.length > 0) {
3149
+ const stmts = blob.workspaces.map((w) => ({
3150
+ sql: `INSERT OR IGNORE INTO workspaces (id, slug, name, owner_agent_id, created_at, metadata)
3151
+ VALUES (?, ?, ?, ?, ?, ?)`,
3152
+ args: [w.id, w.slug, w.name, w.owner_agent_id ?? null, w.created_at, w.metadata ?? null]
3153
+ }));
3154
+ await client.batch(stmts, "write");
3155
+ pulled += stmts.length;
3156
+ }
3157
+ if (blob.documents.length > 0) {
3158
+ const stmts = blob.documents.map((d) => ({
3159
+ sql: `INSERT OR IGNORE INTO documents
3160
+ (id, workspace_id, filename, mime, source_type, user_id, uploaded_at, metadata)
3161
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
3162
+ args: [
3163
+ d.id,
3164
+ d.workspace_id,
3165
+ d.filename,
3166
+ d.mime ?? null,
3167
+ d.source_type ?? null,
3168
+ d.user_id ?? null,
3169
+ d.uploaded_at,
3170
+ d.metadata ?? null
3171
+ ]
3172
+ }));
3173
+ await client.batch(stmts, "write");
3174
+ pulled += stmts.length;
3175
+ }
3176
+ return { pulled };
3177
+ }
3178
+ var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS;
2689
3179
  var init_cloud_sync = __esm({
2690
3180
  "src/lib/cloud-sync.ts"() {
2691
3181
  "use strict";
@@ -2697,6 +3187,7 @@ var init_cloud_sync = __esm({
2697
3187
  init_config();
2698
3188
  init_employees();
2699
3189
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
3190
+ FETCH_TIMEOUT_MS = 3e4;
2700
3191
  }
2701
3192
  });
2702
3193
 
@@ -2771,7 +3262,8 @@ async function writeMemory(record) {
2771
3262
  has_error: record.has_error ? 1 : 0,
2772
3263
  raw_text: record.raw_text,
2773
3264
  vector: record.vector,
2774
- version: _nextVersion++,
3265
+ version: 0,
3266
+ // Placeholder — assigned atomically at flush time
2775
3267
  task_id: record.task_id ?? null,
2776
3268
  importance: record.importance ?? 5,
2777
3269
  status: record.status ?? "active",
@@ -2805,6 +3297,13 @@ async function flushBatch() {
2805
3297
  _flushing = true;
2806
3298
  try {
2807
3299
  const batch = _pendingRecords.slice(0);
3300
+ const client = getClient();
3301
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
3302
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
3303
+ for (const row of batch) {
3304
+ row.version = baseVersion++;
3305
+ }
3306
+ _nextVersion = baseVersion;
2808
3307
  const buildStmt = (row) => {
2809
3308
  const hasVector = row.vector !== null;
2810
3309
  const taskId = row.task_id ?? null;