@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
@@ -400,6 +400,18 @@ function logError(msg) {
400
400
  }
401
401
  }
402
402
  var LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
403
+ var FETCH_TIMEOUT_MS = 3e4;
404
+ async function fetchWithRetry(url, init) {
405
+ const attempt = async () => {
406
+ const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
407
+ return fetch(url, { ...init, signal });
408
+ };
409
+ const resp = await attempt();
410
+ if (resp.status >= 500) {
411
+ return attempt();
412
+ }
413
+ return resp;
414
+ }
403
415
  function assertSecureEndpoint(endpoint) {
404
416
  if (endpoint.startsWith("https://")) return;
405
417
  if (endpoint.startsWith("http://")) {
@@ -421,7 +433,7 @@ async function cloudPush(records, maxVersion, config) {
421
433
  const json = JSON.stringify(records);
422
434
  const compressed = compress(Buffer.from(json, "utf8"));
423
435
  const blob = encryptSyncBlob(compressed);
424
- const resp = await fetch(`${config.endpoint}/sync/push`, {
436
+ const resp = await fetchWithRetry(`${config.endpoint}/sync/push`, {
425
437
  method: "POST",
426
438
  headers: {
427
439
  Authorization: `Bearer ${config.apiKey}`,
@@ -439,7 +451,7 @@ async function cloudPush(records, maxVersion, config) {
439
451
  async function cloudPull(sinceVersion, config) {
440
452
  assertSecureEndpoint(config.endpoint);
441
453
  try {
442
- const response = await fetch(`${config.endpoint}/sync/pull`, {
454
+ const response = await fetchWithRetry(`${config.endpoint}/sync/pull`, {
443
455
  method: "POST",
444
456
  headers: {
445
457
  Authorization: `Bearer ${config.apiKey}`,
@@ -555,20 +567,87 @@ async function cloudSync(config) {
555
567
  try {
556
568
  await cloudPushRoster(config);
557
569
  } catch (err) {
558
- process.stderr.write(`[cloud-sync] Roster push warning: ${err instanceof Error ? err.message : String(err)}
559
- `);
570
+ logError(`[cloud-sync] Roster push: ${err instanceof Error ? err.message : String(err)}`);
560
571
  }
561
572
  try {
562
573
  await cloudPullRoster(config);
563
574
  } catch (err) {
564
- process.stderr.write(`[cloud-sync] Roster pull warning: ${err instanceof Error ? err.message : String(err)}
565
- `);
575
+ logError(`[cloud-sync] Roster pull: ${err instanceof Error ? err.message : String(err)}`);
576
+ }
577
+ let behaviorsResult = { pushed: false, pulled: 0 };
578
+ try {
579
+ behaviorsResult.pushed = await cloudPushBehaviors(config);
580
+ } catch (err) {
581
+ logError(`[cloud-sync] Behaviors push: ${err instanceof Error ? err.message : String(err)}`);
582
+ }
583
+ try {
584
+ const pullResult2 = await cloudPullBehaviors(config);
585
+ behaviorsResult.pulled = pullResult2.pulled;
586
+ } catch (err) {
587
+ logError(`[cloud-sync] Behaviors pull: ${err instanceof Error ? err.message : String(err)}`);
588
+ }
589
+ let graphragResult = { pushed: false, pulled: 0 };
590
+ try {
591
+ graphragResult.pushed = await cloudPushGraphRAG(config);
592
+ } catch (err) {
593
+ logError(`[cloud-sync] GraphRAG push: ${err instanceof Error ? err.message : String(err)}`);
594
+ }
595
+ try {
596
+ const pullResult2 = await cloudPullGraphRAG(config);
597
+ graphragResult.pulled = pullResult2.pulled;
598
+ } catch (err) {
599
+ logError(`[cloud-sync] GraphRAG pull: ${err instanceof Error ? err.message : String(err)}`);
600
+ }
601
+ let tasksResult = { pushed: false, pulled: 0 };
602
+ try {
603
+ tasksResult.pushed = await cloudPushTasks(config);
604
+ } catch (err) {
605
+ logError(`[cloud-sync] Tasks push: ${err instanceof Error ? err.message : String(err)}`);
566
606
  }
567
- return { pushed, pulled };
607
+ try {
608
+ const pullResult2 = await cloudPullTasks(config);
609
+ tasksResult.pulled = pullResult2.pulled;
610
+ } catch (err) {
611
+ logError(`[cloud-sync] Tasks pull: ${err instanceof Error ? err.message : String(err)}`);
612
+ }
613
+ let conversationsResult = { pushed: false, pulled: 0 };
614
+ try {
615
+ conversationsResult.pushed = await cloudPushConversations(config);
616
+ } catch (err) {
617
+ logError(`[cloud-sync] Conversations push: ${err instanceof Error ? err.message : String(err)}`);
618
+ }
619
+ try {
620
+ const pullResult2 = await cloudPullConversations(config);
621
+ conversationsResult.pulled = pullResult2.pulled;
622
+ } catch (err) {
623
+ logError(`[cloud-sync] Conversations pull: ${err instanceof Error ? err.message : String(err)}`);
624
+ }
625
+ let documentsResult = { pushed: false, pulled: 0 };
626
+ try {
627
+ documentsResult.pushed = await cloudPushDocuments(config);
628
+ } catch (err) {
629
+ logError(`[cloud-sync] Documents push: ${err instanceof Error ? err.message : String(err)}`);
630
+ }
631
+ try {
632
+ const pullResult2 = await cloudPullDocuments(config);
633
+ documentsResult.pulled = pullResult2.pulled;
634
+ } catch (err) {
635
+ logError(`[cloud-sync] Documents pull: ${err instanceof Error ? err.message : String(err)}`);
636
+ }
637
+ return {
638
+ pushed,
639
+ pulled,
640
+ behaviors: behaviorsResult,
641
+ graphrag: graphragResult,
642
+ tasks: tasksResult,
643
+ conversations: conversationsResult,
644
+ documents: documentsResult
645
+ };
568
646
  }
569
647
  function buildRosterBlob(paths) {
570
648
  const rosterPath = paths?.rosterPath ?? path5.join(EXE_AI_DIR, "exe-employees.json");
571
649
  const identityDir = paths?.identityDir ?? path5.join(EXE_AI_DIR, "identity");
650
+ const configPath = paths?.configPath ?? path5.join(EXE_AI_DIR, "config.json");
572
651
  let roster = [];
573
652
  if (existsSync5(rosterPath)) {
574
653
  try {
@@ -585,9 +664,16 @@ function buildRosterBlob(paths) {
585
664
  }
586
665
  }
587
666
  }
588
- const content = JSON.stringify({ roster, identities });
667
+ let config;
668
+ if (existsSync5(configPath)) {
669
+ try {
670
+ config = JSON.parse(readFileSync5(configPath, "utf-8"));
671
+ } catch {
672
+ }
673
+ }
674
+ const content = JSON.stringify({ roster, identities, config });
589
675
  const hash = Buffer.from(content).length;
590
- return { roster, identities, version: hash };
676
+ return { roster, identities, config, version: hash };
591
677
  }
592
678
  async function cloudPushRoster(config) {
593
679
  assertSecureEndpoint(config.endpoint);
@@ -606,7 +692,7 @@ async function cloudPushRoster(config) {
606
692
  const json = JSON.stringify(blob);
607
693
  const compressed = compress(Buffer.from(json, "utf8"));
608
694
  const encrypted = encryptSyncBlob(compressed);
609
- const resp = await fetch(`${config.endpoint}/sync/push-roster`, {
695
+ const resp = await fetchWithRetry(`${config.endpoint}/sync/push-roster`, {
610
696
  method: "POST",
611
697
  headers: {
612
698
  Authorization: `Bearer ${config.apiKey}`,
@@ -635,7 +721,7 @@ async function cloudPushRoster(config) {
635
721
  async function cloudPullRoster(config) {
636
722
  assertSecureEndpoint(config.endpoint);
637
723
  try {
638
- const resp = await fetch(`${config.endpoint}/sync/pull-roster`, {
724
+ const resp = await fetchWithRetry(`${config.endpoint}/sync/pull-roster`, {
639
725
  method: "GET",
640
726
  headers: {
641
727
  Authorization: `Bearer ${config.apiKey}`,
@@ -655,6 +741,20 @@ async function cloudPullRoster(config) {
655
741
  return { added: 0 };
656
742
  }
657
743
  }
744
+ function mergeConfig(remoteConfig, configPath) {
745
+ const cfgPath = configPath ?? path5.join(EXE_AI_DIR, "config.json");
746
+ let local = {};
747
+ if (existsSync5(cfgPath)) {
748
+ try {
749
+ local = JSON.parse(readFileSync5(cfgPath, "utf-8"));
750
+ } catch {
751
+ }
752
+ }
753
+ const merged = { ...remoteConfig, ...local };
754
+ const dir = path5.dirname(cfgPath);
755
+ if (!existsSync5(dir)) mkdirSync2(dir, { recursive: true });
756
+ writeFileSync2(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
757
+ }
658
758
  async function mergeRosterFromRemote(remote, paths) {
659
759
  const rosterPath = paths?.rosterPath ?? void 0;
660
760
  const identityDir = paths?.identityDir ?? path5.join(EXE_AI_DIR, "identity");
@@ -681,14 +781,405 @@ async function mergeRosterFromRemote(remote, paths) {
681
781
  if (added > 0) {
682
782
  await saveEmployees(localEmployees, rosterPath);
683
783
  }
784
+ if (remote.config && Object.keys(remote.config).length > 0) {
785
+ try {
786
+ mergeConfig(remote.config, paths?.configPath);
787
+ } catch {
788
+ }
789
+ }
684
790
  return { added };
685
791
  }
792
+ async function cloudPushBlob(route, data, metaKey, config) {
793
+ if (data.length === 0) return { ok: true };
794
+ assertSecureEndpoint(config.endpoint);
795
+ const json = JSON.stringify(data);
796
+ const version = Buffer.from(json).length;
797
+ try {
798
+ const client = getClient();
799
+ const meta = await client.execute({
800
+ sql: "SELECT value FROM sync_meta WHERE key = ?",
801
+ args: [metaKey]
802
+ });
803
+ const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
804
+ if (version === lastVersion) return { ok: true };
805
+ } catch {
806
+ }
807
+ try {
808
+ const compressed = compress(Buffer.from(json, "utf8"));
809
+ const encrypted = encryptSyncBlob(compressed);
810
+ const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
811
+ method: "POST",
812
+ headers: {
813
+ Authorization: `Bearer ${config.apiKey}`,
814
+ "Content-Type": "application/json",
815
+ "X-Device-Id": loadDeviceId()
816
+ },
817
+ body: JSON.stringify({ blob: encrypted })
818
+ });
819
+ if (resp.ok) {
820
+ try {
821
+ const client = getClient();
822
+ await client.execute({
823
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES (?, ?)",
824
+ args: [metaKey, String(version)]
825
+ });
826
+ } catch {
827
+ }
828
+ }
829
+ return { ok: resp.ok };
830
+ } catch (err) {
831
+ logError(`[cloud-sync] PUSH ${route}: ${err instanceof Error ? err.message : String(err)}`);
832
+ return { ok: false };
833
+ }
834
+ }
835
+ async function cloudPullBlob(route, config) {
836
+ assertSecureEndpoint(config.endpoint);
837
+ try {
838
+ const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
839
+ method: "GET",
840
+ headers: {
841
+ Authorization: `Bearer ${config.apiKey}`,
842
+ "X-Device-Id": loadDeviceId()
843
+ }
844
+ });
845
+ if (!resp.ok) return null;
846
+ const data = await resp.json();
847
+ if (!data.blob) return null;
848
+ const compressed = decryptSyncBlob(data.blob);
849
+ const json = decompress(compressed).toString("utf8");
850
+ return JSON.parse(json);
851
+ } catch (err) {
852
+ logError(`[cloud-sync] PULL ${route}: ${err instanceof Error ? err.message : String(err)}`);
853
+ return null;
854
+ }
855
+ }
856
+ async function cloudPushBehaviors(config) {
857
+ const client = getClient();
858
+ const result = await client.execute("SELECT * FROM behaviors");
859
+ const rows = result.rows;
860
+ const { ok } = await cloudPushBlob(
861
+ "/sync/push-behaviors",
862
+ rows,
863
+ "last_behaviors_push_version",
864
+ config
865
+ );
866
+ return ok;
867
+ }
868
+ async function cloudPullBehaviors(config) {
869
+ const remoteBehaviors = await cloudPullBlob(
870
+ "/sync/pull-behaviors",
871
+ config
872
+ );
873
+ if (!remoteBehaviors || remoteBehaviors.length === 0) return { pulled: 0 };
874
+ const client = getClient();
875
+ let pulled = 0;
876
+ for (const behavior of remoteBehaviors) {
877
+ const existing = await client.execute({
878
+ sql: `SELECT COUNT(*) as cnt FROM behaviors
879
+ WHERE agent_id = ? AND content = ?`,
880
+ args: [behavior.agent_id, behavior.content]
881
+ });
882
+ if (Number(existing.rows[0]?.cnt) > 0) continue;
883
+ await client.execute({
884
+ sql: `INSERT OR IGNORE INTO behaviors
885
+ (id, agent_id, project_name, domain, content, active, priority, created_at, updated_at)
886
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
887
+ args: [
888
+ behavior.id,
889
+ behavior.agent_id,
890
+ behavior.project_name ?? null,
891
+ behavior.domain ?? null,
892
+ behavior.content,
893
+ behavior.active,
894
+ behavior.priority ?? "p1",
895
+ behavior.created_at,
896
+ behavior.updated_at
897
+ ]
898
+ });
899
+ pulled++;
900
+ }
901
+ return { pulled };
902
+ }
903
+ async function cloudPushGraphRAG(config) {
904
+ const client = getClient();
905
+ const [entities, relationships, aliases, entityMems, relMems, hyperedges, hyperedgeNodes] = await Promise.all([
906
+ client.execute("SELECT * FROM entities"),
907
+ client.execute("SELECT * FROM relationships"),
908
+ client.execute("SELECT * FROM entity_aliases"),
909
+ client.execute("SELECT * FROM entity_memories"),
910
+ client.execute("SELECT * FROM relationship_memories"),
911
+ client.execute("SELECT * FROM hyperedges"),
912
+ client.execute("SELECT * FROM hyperedge_nodes")
913
+ ]);
914
+ const blob = {
915
+ entities: entities.rows,
916
+ relationships: relationships.rows,
917
+ entity_aliases: aliases.rows,
918
+ entity_memories: entityMems.rows,
919
+ relationship_memories: relMems.rows,
920
+ hyperedges: hyperedges.rows,
921
+ hyperedge_nodes: hyperedgeNodes.rows
922
+ };
923
+ const { ok } = await cloudPushBlob(
924
+ "/sync/push-graphrag",
925
+ [blob],
926
+ "last_graphrag_push_version",
927
+ config
928
+ );
929
+ return ok;
930
+ }
931
+ async function cloudPullGraphRAG(config) {
932
+ const data = await cloudPullBlob(
933
+ "/sync/pull-graphrag",
934
+ config
935
+ );
936
+ if (!data || data.length === 0) return { pulled: 0 };
937
+ const blob = data[0];
938
+ const client = getClient();
939
+ let pulled = 0;
940
+ if (blob.entities.length > 0) {
941
+ const stmts = blob.entities.map((e) => ({
942
+ sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen, properties)
943
+ VALUES (?, ?, ?, ?, ?, ?)`,
944
+ args: [e.id, e.name, e.type, e.first_seen, e.last_seen, e.properties ?? "{}"]
945
+ }));
946
+ await client.batch(stmts, "write");
947
+ pulled += stmts.length;
948
+ }
949
+ if (blob.relationships.length > 0) {
950
+ const stmts = blob.relationships.map((r) => ({
951
+ sql: `INSERT OR IGNORE INTO relationships
952
+ (id, source_entity_id, target_entity_id, type, weight, timestamp, properties, confidence, confidence_label)
953
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
954
+ args: [
955
+ r.id,
956
+ r.source_entity_id,
957
+ r.target_entity_id,
958
+ r.type,
959
+ r.weight ?? 1,
960
+ r.timestamp,
961
+ r.properties ?? "{}",
962
+ r.confidence ?? 1,
963
+ r.confidence_label ?? "extracted"
964
+ ]
965
+ }));
966
+ await client.batch(stmts, "write");
967
+ pulled += stmts.length;
968
+ }
969
+ if (blob.entity_aliases.length > 0) {
970
+ const stmts = blob.entity_aliases.map((a) => ({
971
+ sql: `INSERT OR IGNORE INTO entity_aliases (alias, canonical_entity_id) VALUES (?, ?)`,
972
+ args: [a.alias, a.canonical_entity_id]
973
+ }));
974
+ await client.batch(stmts, "write");
975
+ pulled += stmts.length;
976
+ }
977
+ if (blob.entity_memories.length > 0) {
978
+ const stmts = blob.entity_memories.map((em) => ({
979
+ sql: `INSERT OR IGNORE INTO entity_memories (entity_id, memory_id) VALUES (?, ?)`,
980
+ args: [em.entity_id, em.memory_id]
981
+ }));
982
+ await client.batch(stmts, "write");
983
+ pulled += stmts.length;
984
+ }
985
+ if (blob.relationship_memories.length > 0) {
986
+ const stmts = blob.relationship_memories.map((rm) => ({
987
+ sql: `INSERT OR IGNORE INTO relationship_memories (relationship_id, memory_id) VALUES (?, ?)`,
988
+ args: [rm.relationship_id, rm.memory_id]
989
+ }));
990
+ await client.batch(stmts, "write");
991
+ pulled += stmts.length;
992
+ }
993
+ if (blob.hyperedges.length > 0) {
994
+ const stmts = blob.hyperedges.map((h) => ({
995
+ sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
996
+ VALUES (?, ?, ?, ?, ?)`,
997
+ args: [h.id, h.label, h.relation, h.confidence ?? 1, h.timestamp]
998
+ }));
999
+ await client.batch(stmts, "write");
1000
+ pulled += stmts.length;
1001
+ }
1002
+ if (blob.hyperedge_nodes.length > 0) {
1003
+ const stmts = blob.hyperedge_nodes.map((hn) => ({
1004
+ sql: `INSERT OR IGNORE INTO hyperedge_nodes (hyperedge_id, entity_id) VALUES (?, ?)`,
1005
+ args: [hn.hyperedge_id, hn.entity_id]
1006
+ }));
1007
+ await client.batch(stmts, "write");
1008
+ pulled += stmts.length;
1009
+ }
1010
+ return { pulled };
1011
+ }
1012
+ async function cloudPushTasks(config) {
1013
+ const client = getClient();
1014
+ const result = await client.execute("SELECT * FROM tasks");
1015
+ const rows = result.rows;
1016
+ const { ok } = await cloudPushBlob(
1017
+ "/sync/push-tasks",
1018
+ rows,
1019
+ "last_tasks_push_version",
1020
+ config
1021
+ );
1022
+ return ok;
1023
+ }
1024
+ async function cloudPullTasks(config) {
1025
+ const remoteTasks = await cloudPullBlob(
1026
+ "/sync/pull-tasks",
1027
+ config
1028
+ );
1029
+ if (!remoteTasks || remoteTasks.length === 0) return { pulled: 0 };
1030
+ const client = getClient();
1031
+ const stmts = remoteTasks.map((t) => ({
1032
+ sql: `INSERT OR IGNORE INTO tasks
1033
+ (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, created_at, updated_at,
1034
+ blocked_by, parent_task_id, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at)
1035
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1036
+ args: [
1037
+ t.id,
1038
+ t.title,
1039
+ t.assigned_to,
1040
+ t.assigned_by,
1041
+ t.project_name,
1042
+ t.priority ?? "p1",
1043
+ t.status ?? "open",
1044
+ t.task_file ?? null,
1045
+ t.created_at,
1046
+ t.updated_at,
1047
+ t.blocked_by ?? null,
1048
+ t.parent_task_id ?? null,
1049
+ t.budget_tokens ?? null,
1050
+ t.budget_fallback_model ?? null,
1051
+ t.tokens_used ?? 0,
1052
+ t.tokens_warned_at ?? null
1053
+ ]
1054
+ }));
1055
+ await client.batch(stmts, "write");
1056
+ return { pulled: remoteTasks.length };
1057
+ }
1058
+ async function cloudPushConversations(config) {
1059
+ const client = getClient();
1060
+ const result = await client.execute("SELECT * FROM conversations");
1061
+ const rows = result.rows;
1062
+ const { ok } = await cloudPushBlob(
1063
+ "/sync/push-conversations",
1064
+ rows,
1065
+ "last_conversations_push_version",
1066
+ config
1067
+ );
1068
+ return ok;
1069
+ }
1070
+ async function cloudPullConversations(config) {
1071
+ const remoteConvos = await cloudPullBlob(
1072
+ "/sync/pull-conversations",
1073
+ config
1074
+ );
1075
+ if (!remoteConvos || remoteConvos.length === 0) return { pulled: 0 };
1076
+ const client = getClient();
1077
+ const stmts = remoteConvos.map((c) => ({
1078
+ sql: `INSERT OR IGNORE INTO conversations
1079
+ (id, platform, external_id, sender_id, sender_name, sender_phone, sender_email,
1080
+ recipient_id, channel_id, thread_id, reply_to_id, content_text, content_media,
1081
+ content_metadata, agent_response, agent_name, timestamp, ingested_at)
1082
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1083
+ args: [
1084
+ c.id,
1085
+ c.platform,
1086
+ c.external_id ?? null,
1087
+ c.sender_id,
1088
+ c.sender_name ?? null,
1089
+ c.sender_phone ?? null,
1090
+ c.sender_email ?? null,
1091
+ c.recipient_id ?? null,
1092
+ c.channel_id,
1093
+ c.thread_id ?? null,
1094
+ c.reply_to_id ?? null,
1095
+ c.content_text ?? null,
1096
+ c.content_media ?? null,
1097
+ c.content_metadata ?? null,
1098
+ c.agent_response ?? null,
1099
+ c.agent_name ?? null,
1100
+ c.timestamp,
1101
+ c.ingested_at
1102
+ ]
1103
+ }));
1104
+ await client.batch(stmts, "write");
1105
+ return { pulled: remoteConvos.length };
1106
+ }
1107
+ async function cloudPushDocuments(config) {
1108
+ const client = getClient();
1109
+ const [workspaces, documents] = await Promise.all([
1110
+ client.execute("SELECT * FROM workspaces"),
1111
+ client.execute("SELECT * FROM documents")
1112
+ ]);
1113
+ const blob = {
1114
+ workspaces: workspaces.rows,
1115
+ documents: documents.rows
1116
+ };
1117
+ const { ok } = await cloudPushBlob(
1118
+ "/sync/push-documents",
1119
+ [blob],
1120
+ "last_documents_push_version",
1121
+ config
1122
+ );
1123
+ return ok;
1124
+ }
1125
+ async function cloudPullDocuments(config) {
1126
+ const data = await cloudPullBlob(
1127
+ "/sync/pull-documents",
1128
+ config
1129
+ );
1130
+ if (!data || data.length === 0) return { pulled: 0 };
1131
+ const blob = data[0];
1132
+ const client = getClient();
1133
+ let pulled = 0;
1134
+ if (blob.workspaces.length > 0) {
1135
+ const stmts = blob.workspaces.map((w) => ({
1136
+ sql: `INSERT OR IGNORE INTO workspaces (id, slug, name, owner_agent_id, created_at, metadata)
1137
+ VALUES (?, ?, ?, ?, ?, ?)`,
1138
+ args: [w.id, w.slug, w.name, w.owner_agent_id ?? null, w.created_at, w.metadata ?? null]
1139
+ }));
1140
+ await client.batch(stmts, "write");
1141
+ pulled += stmts.length;
1142
+ }
1143
+ if (blob.documents.length > 0) {
1144
+ const stmts = blob.documents.map((d) => ({
1145
+ sql: `INSERT OR IGNORE INTO documents
1146
+ (id, workspace_id, filename, mime, source_type, user_id, uploaded_at, metadata)
1147
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
1148
+ args: [
1149
+ d.id,
1150
+ d.workspace_id,
1151
+ d.filename,
1152
+ d.mime ?? null,
1153
+ d.source_type ?? null,
1154
+ d.user_id ?? null,
1155
+ d.uploaded_at,
1156
+ d.metadata ?? null
1157
+ ]
1158
+ }));
1159
+ await client.batch(stmts, "write");
1160
+ pulled += stmts.length;
1161
+ }
1162
+ return { pulled };
1163
+ }
686
1164
  export {
687
1165
  buildRosterBlob,
688
1166
  cloudPull,
1167
+ cloudPullBehaviors,
1168
+ cloudPullBlob,
1169
+ cloudPullConversations,
1170
+ cloudPullDocuments,
1171
+ cloudPullGraphRAG,
689
1172
  cloudPullRoster,
1173
+ cloudPullTasks,
690
1174
  cloudPush,
1175
+ cloudPushBehaviors,
1176
+ cloudPushBlob,
1177
+ cloudPushConversations,
1178
+ cloudPushDocuments,
1179
+ cloudPushGraphRAG,
691
1180
  cloudPushRoster,
1181
+ cloudPushTasks,
692
1182
  cloudSync,
1183
+ mergeConfig,
693
1184
  mergeRosterFromRemote
694
1185
  };