@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
@@ -2579,6 +2579,42 @@ import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSy
2579
2579
  import path11 from "path";
2580
2580
  import os5 from "os";
2581
2581
  import { fileURLToPath as fileURLToPath2 } from "url";
2582
+ import { unlinkSync as unlinkSync3 } from "fs";
2583
+ function spawnLockPath(sessionName) {
2584
+ return path11.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
2585
+ }
2586
+ function isProcessAlive(pid) {
2587
+ try {
2588
+ process.kill(pid, 0);
2589
+ return true;
2590
+ } catch {
2591
+ return false;
2592
+ }
2593
+ }
2594
+ function acquireSpawnLock(sessionName) {
2595
+ if (!existsSync10(SPAWN_LOCK_DIR)) {
2596
+ mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
2597
+ }
2598
+ const lockFile = spawnLockPath(sessionName);
2599
+ if (existsSync10(lockFile)) {
2600
+ try {
2601
+ const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
2602
+ const age = Date.now() - lock.timestamp;
2603
+ if (isProcessAlive(lock.pid) && age < 6e4) {
2604
+ return false;
2605
+ }
2606
+ } catch {
2607
+ }
2608
+ }
2609
+ writeFileSync5(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
2610
+ return true;
2611
+ }
2612
+ function releaseSpawnLock(sessionName) {
2613
+ try {
2614
+ unlinkSync3(spawnLockPath(sessionName));
2615
+ } catch {
2616
+ }
2617
+ }
2582
2618
  function resolveBehaviorsExporterScript() {
2583
2619
  try {
2584
2620
  const thisFile = fileURLToPath2(import.meta.url);
@@ -2659,10 +2695,10 @@ function isEmployeeAlive(sessionName) {
2659
2695
  }
2660
2696
  function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
2661
2697
  const base = employeeSessionName(employeeName, exeSession);
2662
- if (!isAlive(base)) return 0;
2698
+ if (!isAlive(base) && acquireSpawnLock(base)) return 0;
2663
2699
  for (let i = 2; i <= maxInstances; i++) {
2664
2700
  const candidate = employeeSessionName(employeeName, exeSession, i);
2665
- if (!isAlive(candidate)) return i;
2701
+ if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
2666
2702
  }
2667
2703
  return null;
2668
2704
  }
@@ -2996,6 +3032,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2996
3032
  command: spawnCommand
2997
3033
  });
2998
3034
  if (spawnResult.error) {
3035
+ releaseSpawnLock(sessionName);
2999
3036
  return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
3000
3037
  }
3001
3038
  transport.pipeLog(sessionName, logFile);
@@ -3033,6 +3070,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3033
3070
  }
3034
3071
  }
3035
3072
  if (!booted) {
3073
+ releaseSpawnLock(sessionName);
3036
3074
  return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
3037
3075
  }
3038
3076
  if (!useExeAgent) {
@@ -3049,9 +3087,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3049
3087
  pid: 0,
3050
3088
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
3051
3089
  });
3090
+ releaseSpawnLock(sessionName);
3052
3091
  return { sessionName };
3053
3092
  }
3054
- var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
3093
+ var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
3055
3094
  var init_tmux_routing = __esm({
3056
3095
  "src/lib/tmux-routing.ts"() {
3057
3096
  "use strict";
@@ -3063,6 +3102,7 @@ var init_tmux_routing = __esm({
3063
3102
  init_provider_table();
3064
3103
  init_intercom_queue();
3065
3104
  init_plan_limits();
3105
+ SPAWN_LOCK_DIR = path11.join(os5.homedir(), ".exe-os", "spawn-locks");
3066
3106
  SESSION_CACHE = path11.join(os5.homedir(), ".exe-os", "session-cache");
3067
3107
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
3068
3108
  INTERCOM_DEBOUNCE_MS = 3e4;
@@ -3662,7 +3702,7 @@ var init_tasks_crud = __esm({
3662
3702
 
3663
3703
  // src/lib/tasks-review.ts
3664
3704
  import path15 from "path";
3665
- import { existsSync as existsSync13, readdirSync as readdirSync4, unlinkSync as unlinkSync3 } from "fs";
3705
+ import { existsSync as existsSync13, readdirSync as readdirSync4, unlinkSync as unlinkSync4 } from "fs";
3666
3706
  async function countPendingReviews() {
3667
3707
  const client = getClient();
3668
3708
  const result = await client.execute({
@@ -3786,7 +3826,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3786
3826
  if (existsSync13(cacheDir)) {
3787
3827
  for (const f of readdirSync4(cacheDir)) {
3788
3828
  if (f.startsWith("review-notified-")) {
3789
- unlinkSync3(path15.join(cacheDir, f));
3829
+ unlinkSync4(path15.join(cacheDir, f));
3790
3830
  }
3791
3831
  }
3792
3832
  }
@@ -4345,7 +4385,7 @@ __export(tasks_exports, {
4345
4385
  writeCheckpoint: () => writeCheckpoint
4346
4386
  });
4347
4387
  import path17 from "path";
4348
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, unlinkSync as unlinkSync4 } from "fs";
4388
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
4349
4389
  async function createTask(input) {
4350
4390
  const result = await createTaskCore(input);
4351
4391
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -4371,7 +4411,7 @@ async function updateTask(input) {
4371
4411
  writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4372
4412
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
4373
4413
  try {
4374
- unlinkSync4(cachePath);
4414
+ unlinkSync5(cachePath);
4375
4415
  } catch {
4376
4416
  }
4377
4417
  }
@@ -4669,10 +4709,23 @@ var cloud_sync_exports = {};
4669
4709
  __export(cloud_sync_exports, {
4670
4710
  buildRosterBlob: () => buildRosterBlob,
4671
4711
  cloudPull: () => cloudPull,
4712
+ cloudPullBehaviors: () => cloudPullBehaviors,
4713
+ cloudPullBlob: () => cloudPullBlob,
4714
+ cloudPullConversations: () => cloudPullConversations,
4715
+ cloudPullDocuments: () => cloudPullDocuments,
4716
+ cloudPullGraphRAG: () => cloudPullGraphRAG,
4672
4717
  cloudPullRoster: () => cloudPullRoster,
4718
+ cloudPullTasks: () => cloudPullTasks,
4673
4719
  cloudPush: () => cloudPush,
4720
+ cloudPushBehaviors: () => cloudPushBehaviors,
4721
+ cloudPushBlob: () => cloudPushBlob,
4722
+ cloudPushConversations: () => cloudPushConversations,
4723
+ cloudPushDocuments: () => cloudPushDocuments,
4724
+ cloudPushGraphRAG: () => cloudPushGraphRAG,
4674
4725
  cloudPushRoster: () => cloudPushRoster,
4726
+ cloudPushTasks: () => cloudPushTasks,
4675
4727
  cloudSync: () => cloudSync,
4728
+ mergeConfig: () => mergeConfig,
4676
4729
  mergeRosterFromRemote: () => mergeRosterFromRemote
4677
4730
  });
4678
4731
  import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync14, readdirSync as readdirSync5, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2 } from "fs";
@@ -4686,6 +4739,17 @@ function logError(msg) {
4686
4739
  } catch {
4687
4740
  }
4688
4741
  }
4742
+ async function fetchWithRetry(url, init) {
4743
+ const attempt = async () => {
4744
+ const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
4745
+ return fetch(url, { ...init, signal });
4746
+ };
4747
+ const resp = await attempt();
4748
+ if (resp.status >= 500) {
4749
+ return attempt();
4750
+ }
4751
+ return resp;
4752
+ }
4689
4753
  function assertSecureEndpoint(endpoint) {
4690
4754
  if (endpoint.startsWith("https://")) return;
4691
4755
  if (endpoint.startsWith("http://")) {
@@ -4707,7 +4771,7 @@ async function cloudPush(records, maxVersion, config) {
4707
4771
  const json = JSON.stringify(records);
4708
4772
  const compressed = compress(Buffer.from(json, "utf8"));
4709
4773
  const blob = encryptSyncBlob(compressed);
4710
- const resp = await fetch(`${config.endpoint}/sync/push`, {
4774
+ const resp = await fetchWithRetry(`${config.endpoint}/sync/push`, {
4711
4775
  method: "POST",
4712
4776
  headers: {
4713
4777
  Authorization: `Bearer ${config.apiKey}`,
@@ -4725,7 +4789,7 @@ async function cloudPush(records, maxVersion, config) {
4725
4789
  async function cloudPull(sinceVersion, config) {
4726
4790
  assertSecureEndpoint(config.endpoint);
4727
4791
  try {
4728
- const response = await fetch(`${config.endpoint}/sync/pull`, {
4792
+ const response = await fetchWithRetry(`${config.endpoint}/sync/pull`, {
4729
4793
  method: "POST",
4730
4794
  headers: {
4731
4795
  Authorization: `Bearer ${config.apiKey}`,
@@ -4841,20 +4905,87 @@ async function cloudSync(config) {
4841
4905
  try {
4842
4906
  await cloudPushRoster(config);
4843
4907
  } catch (err) {
4844
- process.stderr.write(`[cloud-sync] Roster push warning: ${err instanceof Error ? err.message : String(err)}
4845
- `);
4908
+ logError(`[cloud-sync] Roster push: ${err instanceof Error ? err.message : String(err)}`);
4846
4909
  }
4847
4910
  try {
4848
4911
  await cloudPullRoster(config);
4849
4912
  } catch (err) {
4850
- process.stderr.write(`[cloud-sync] Roster pull warning: ${err instanceof Error ? err.message : String(err)}
4851
- `);
4913
+ logError(`[cloud-sync] Roster pull: ${err instanceof Error ? err.message : String(err)}`);
4852
4914
  }
4853
- return { pushed, pulled };
4915
+ let behaviorsResult = { pushed: false, pulled: 0 };
4916
+ try {
4917
+ behaviorsResult.pushed = await cloudPushBehaviors(config);
4918
+ } catch (err) {
4919
+ logError(`[cloud-sync] Behaviors push: ${err instanceof Error ? err.message : String(err)}`);
4920
+ }
4921
+ try {
4922
+ const pullResult2 = await cloudPullBehaviors(config);
4923
+ behaviorsResult.pulled = pullResult2.pulled;
4924
+ } catch (err) {
4925
+ logError(`[cloud-sync] Behaviors pull: ${err instanceof Error ? err.message : String(err)}`);
4926
+ }
4927
+ let graphragResult = { pushed: false, pulled: 0 };
4928
+ try {
4929
+ graphragResult.pushed = await cloudPushGraphRAG(config);
4930
+ } catch (err) {
4931
+ logError(`[cloud-sync] GraphRAG push: ${err instanceof Error ? err.message : String(err)}`);
4932
+ }
4933
+ try {
4934
+ const pullResult2 = await cloudPullGraphRAG(config);
4935
+ graphragResult.pulled = pullResult2.pulled;
4936
+ } catch (err) {
4937
+ logError(`[cloud-sync] GraphRAG pull: ${err instanceof Error ? err.message : String(err)}`);
4938
+ }
4939
+ let tasksResult = { pushed: false, pulled: 0 };
4940
+ try {
4941
+ tasksResult.pushed = await cloudPushTasks(config);
4942
+ } catch (err) {
4943
+ logError(`[cloud-sync] Tasks push: ${err instanceof Error ? err.message : String(err)}`);
4944
+ }
4945
+ try {
4946
+ const pullResult2 = await cloudPullTasks(config);
4947
+ tasksResult.pulled = pullResult2.pulled;
4948
+ } catch (err) {
4949
+ logError(`[cloud-sync] Tasks pull: ${err instanceof Error ? err.message : String(err)}`);
4950
+ }
4951
+ let conversationsResult = { pushed: false, pulled: 0 };
4952
+ try {
4953
+ conversationsResult.pushed = await cloudPushConversations(config);
4954
+ } catch (err) {
4955
+ logError(`[cloud-sync] Conversations push: ${err instanceof Error ? err.message : String(err)}`);
4956
+ }
4957
+ try {
4958
+ const pullResult2 = await cloudPullConversations(config);
4959
+ conversationsResult.pulled = pullResult2.pulled;
4960
+ } catch (err) {
4961
+ logError(`[cloud-sync] Conversations pull: ${err instanceof Error ? err.message : String(err)}`);
4962
+ }
4963
+ let documentsResult = { pushed: false, pulled: 0 };
4964
+ try {
4965
+ documentsResult.pushed = await cloudPushDocuments(config);
4966
+ } catch (err) {
4967
+ logError(`[cloud-sync] Documents push: ${err instanceof Error ? err.message : String(err)}`);
4968
+ }
4969
+ try {
4970
+ const pullResult2 = await cloudPullDocuments(config);
4971
+ documentsResult.pulled = pullResult2.pulled;
4972
+ } catch (err) {
4973
+ logError(`[cloud-sync] Documents pull: ${err instanceof Error ? err.message : String(err)}`);
4974
+ }
4975
+ return {
4976
+ pushed,
4977
+ pulled,
4978
+ behaviors: behaviorsResult,
4979
+ graphrag: graphragResult,
4980
+ tasks: tasksResult,
4981
+ conversations: conversationsResult,
4982
+ documents: documentsResult
4983
+ };
4854
4984
  }
4855
4985
  function buildRosterBlob(paths) {
4856
4986
  const rosterPath = paths?.rosterPath ?? path18.join(EXE_AI_DIR, "exe-employees.json");
4857
4987
  const identityDir = paths?.identityDir ?? path18.join(EXE_AI_DIR, "identity");
4988
+ const configPath = paths?.configPath ?? path18.join(EXE_AI_DIR, "config.json");
4858
4989
  let roster = [];
4859
4990
  if (existsSync14(rosterPath)) {
4860
4991
  try {
@@ -4871,9 +5002,16 @@ function buildRosterBlob(paths) {
4871
5002
  }
4872
5003
  }
4873
5004
  }
4874
- const content = JSON.stringify({ roster, identities });
5005
+ let config;
5006
+ if (existsSync14(configPath)) {
5007
+ try {
5008
+ config = JSON.parse(readFileSync12(configPath, "utf-8"));
5009
+ } catch {
5010
+ }
5011
+ }
5012
+ const content = JSON.stringify({ roster, identities, config });
4875
5013
  const hash = Buffer.from(content).length;
4876
- return { roster, identities, version: hash };
5014
+ return { roster, identities, config, version: hash };
4877
5015
  }
4878
5016
  async function cloudPushRoster(config) {
4879
5017
  assertSecureEndpoint(config.endpoint);
@@ -4892,7 +5030,7 @@ async function cloudPushRoster(config) {
4892
5030
  const json = JSON.stringify(blob);
4893
5031
  const compressed = compress(Buffer.from(json, "utf8"));
4894
5032
  const encrypted = encryptSyncBlob(compressed);
4895
- const resp = await fetch(`${config.endpoint}/sync/push-roster`, {
5033
+ const resp = await fetchWithRetry(`${config.endpoint}/sync/push-roster`, {
4896
5034
  method: "POST",
4897
5035
  headers: {
4898
5036
  Authorization: `Bearer ${config.apiKey}`,
@@ -4921,7 +5059,7 @@ async function cloudPushRoster(config) {
4921
5059
  async function cloudPullRoster(config) {
4922
5060
  assertSecureEndpoint(config.endpoint);
4923
5061
  try {
4924
- const resp = await fetch(`${config.endpoint}/sync/pull-roster`, {
5062
+ const resp = await fetchWithRetry(`${config.endpoint}/sync/pull-roster`, {
4925
5063
  method: "GET",
4926
5064
  headers: {
4927
5065
  Authorization: `Bearer ${config.apiKey}`,
@@ -4941,6 +5079,20 @@ async function cloudPullRoster(config) {
4941
5079
  return { added: 0 };
4942
5080
  }
4943
5081
  }
5082
+ function mergeConfig(remoteConfig, configPath) {
5083
+ const cfgPath = configPath ?? path18.join(EXE_AI_DIR, "config.json");
5084
+ let local = {};
5085
+ if (existsSync14(cfgPath)) {
5086
+ try {
5087
+ local = JSON.parse(readFileSync12(cfgPath, "utf-8"));
5088
+ } catch {
5089
+ }
5090
+ }
5091
+ const merged = { ...remoteConfig, ...local };
5092
+ const dir = path18.dirname(cfgPath);
5093
+ if (!existsSync14(dir)) mkdirSync8(dir, { recursive: true });
5094
+ writeFileSync7(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
5095
+ }
4944
5096
  async function mergeRosterFromRemote(remote, paths) {
4945
5097
  const rosterPath = paths?.rosterPath ?? void 0;
4946
5098
  const identityDir = paths?.identityDir ?? path18.join(EXE_AI_DIR, "identity");
@@ -4967,9 +5119,387 @@ async function mergeRosterFromRemote(remote, paths) {
4967
5119
  if (added > 0) {
4968
5120
  await saveEmployees(localEmployees, rosterPath);
4969
5121
  }
5122
+ if (remote.config && Object.keys(remote.config).length > 0) {
5123
+ try {
5124
+ mergeConfig(remote.config, paths?.configPath);
5125
+ } catch {
5126
+ }
5127
+ }
4970
5128
  return { added };
4971
5129
  }
4972
- var LOCALHOST_PATTERNS;
5130
+ async function cloudPushBlob(route, data, metaKey, config) {
5131
+ if (data.length === 0) return { ok: true };
5132
+ assertSecureEndpoint(config.endpoint);
5133
+ const json = JSON.stringify(data);
5134
+ const version = Buffer.from(json).length;
5135
+ try {
5136
+ const client = getClient();
5137
+ const meta = await client.execute({
5138
+ sql: "SELECT value FROM sync_meta WHERE key = ?",
5139
+ args: [metaKey]
5140
+ });
5141
+ const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
5142
+ if (version === lastVersion) return { ok: true };
5143
+ } catch {
5144
+ }
5145
+ try {
5146
+ const compressed = compress(Buffer.from(json, "utf8"));
5147
+ const encrypted = encryptSyncBlob(compressed);
5148
+ const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
5149
+ method: "POST",
5150
+ headers: {
5151
+ Authorization: `Bearer ${config.apiKey}`,
5152
+ "Content-Type": "application/json",
5153
+ "X-Device-Id": loadDeviceId()
5154
+ },
5155
+ body: JSON.stringify({ blob: encrypted })
5156
+ });
5157
+ if (resp.ok) {
5158
+ try {
5159
+ const client = getClient();
5160
+ await client.execute({
5161
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES (?, ?)",
5162
+ args: [metaKey, String(version)]
5163
+ });
5164
+ } catch {
5165
+ }
5166
+ }
5167
+ return { ok: resp.ok };
5168
+ } catch (err) {
5169
+ logError(`[cloud-sync] PUSH ${route}: ${err instanceof Error ? err.message : String(err)}`);
5170
+ return { ok: false };
5171
+ }
5172
+ }
5173
+ async function cloudPullBlob(route, config) {
5174
+ assertSecureEndpoint(config.endpoint);
5175
+ try {
5176
+ const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
5177
+ method: "GET",
5178
+ headers: {
5179
+ Authorization: `Bearer ${config.apiKey}`,
5180
+ "X-Device-Id": loadDeviceId()
5181
+ }
5182
+ });
5183
+ if (!resp.ok) return null;
5184
+ const data = await resp.json();
5185
+ if (!data.blob) return null;
5186
+ const compressed = decryptSyncBlob(data.blob);
5187
+ const json = decompress(compressed).toString("utf8");
5188
+ return JSON.parse(json);
5189
+ } catch (err) {
5190
+ logError(`[cloud-sync] PULL ${route}: ${err instanceof Error ? err.message : String(err)}`);
5191
+ return null;
5192
+ }
5193
+ }
5194
+ async function cloudPushBehaviors(config) {
5195
+ const client = getClient();
5196
+ const result = await client.execute("SELECT * FROM behaviors");
5197
+ const rows = result.rows;
5198
+ const { ok } = await cloudPushBlob(
5199
+ "/sync/push-behaviors",
5200
+ rows,
5201
+ "last_behaviors_push_version",
5202
+ config
5203
+ );
5204
+ return ok;
5205
+ }
5206
+ async function cloudPullBehaviors(config) {
5207
+ const remoteBehaviors = await cloudPullBlob(
5208
+ "/sync/pull-behaviors",
5209
+ config
5210
+ );
5211
+ if (!remoteBehaviors || remoteBehaviors.length === 0) return { pulled: 0 };
5212
+ const client = getClient();
5213
+ let pulled = 0;
5214
+ for (const behavior of remoteBehaviors) {
5215
+ const existing = await client.execute({
5216
+ sql: `SELECT COUNT(*) as cnt FROM behaviors
5217
+ WHERE agent_id = ? AND content = ?`,
5218
+ args: [behavior.agent_id, behavior.content]
5219
+ });
5220
+ if (Number(existing.rows[0]?.cnt) > 0) continue;
5221
+ await client.execute({
5222
+ sql: `INSERT OR IGNORE INTO behaviors
5223
+ (id, agent_id, project_name, domain, content, active, priority, created_at, updated_at)
5224
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5225
+ args: [
5226
+ behavior.id,
5227
+ behavior.agent_id,
5228
+ behavior.project_name ?? null,
5229
+ behavior.domain ?? null,
5230
+ behavior.content,
5231
+ behavior.active,
5232
+ behavior.priority ?? "p1",
5233
+ behavior.created_at,
5234
+ behavior.updated_at
5235
+ ]
5236
+ });
5237
+ pulled++;
5238
+ }
5239
+ return { pulled };
5240
+ }
5241
+ async function cloudPushGraphRAG(config) {
5242
+ const client = getClient();
5243
+ const [entities, relationships, aliases, entityMems, relMems, hyperedges, hyperedgeNodes] = await Promise.all([
5244
+ client.execute("SELECT * FROM entities"),
5245
+ client.execute("SELECT * FROM relationships"),
5246
+ client.execute("SELECT * FROM entity_aliases"),
5247
+ client.execute("SELECT * FROM entity_memories"),
5248
+ client.execute("SELECT * FROM relationship_memories"),
5249
+ client.execute("SELECT * FROM hyperedges"),
5250
+ client.execute("SELECT * FROM hyperedge_nodes")
5251
+ ]);
5252
+ const blob = {
5253
+ entities: entities.rows,
5254
+ relationships: relationships.rows,
5255
+ entity_aliases: aliases.rows,
5256
+ entity_memories: entityMems.rows,
5257
+ relationship_memories: relMems.rows,
5258
+ hyperedges: hyperedges.rows,
5259
+ hyperedge_nodes: hyperedgeNodes.rows
5260
+ };
5261
+ const { ok } = await cloudPushBlob(
5262
+ "/sync/push-graphrag",
5263
+ [blob],
5264
+ "last_graphrag_push_version",
5265
+ config
5266
+ );
5267
+ return ok;
5268
+ }
5269
+ async function cloudPullGraphRAG(config) {
5270
+ const data = await cloudPullBlob(
5271
+ "/sync/pull-graphrag",
5272
+ config
5273
+ );
5274
+ if (!data || data.length === 0) return { pulled: 0 };
5275
+ const blob = data[0];
5276
+ const client = getClient();
5277
+ let pulled = 0;
5278
+ if (blob.entities.length > 0) {
5279
+ const stmts = blob.entities.map((e) => ({
5280
+ sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen, properties)
5281
+ VALUES (?, ?, ?, ?, ?, ?)`,
5282
+ args: [e.id, e.name, e.type, e.first_seen, e.last_seen, e.properties ?? "{}"]
5283
+ }));
5284
+ await client.batch(stmts, "write");
5285
+ pulled += stmts.length;
5286
+ }
5287
+ if (blob.relationships.length > 0) {
5288
+ const stmts = blob.relationships.map((r) => ({
5289
+ sql: `INSERT OR IGNORE INTO relationships
5290
+ (id, source_entity_id, target_entity_id, type, weight, timestamp, properties, confidence, confidence_label)
5291
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5292
+ args: [
5293
+ r.id,
5294
+ r.source_entity_id,
5295
+ r.target_entity_id,
5296
+ r.type,
5297
+ r.weight ?? 1,
5298
+ r.timestamp,
5299
+ r.properties ?? "{}",
5300
+ r.confidence ?? 1,
5301
+ r.confidence_label ?? "extracted"
5302
+ ]
5303
+ }));
5304
+ await client.batch(stmts, "write");
5305
+ pulled += stmts.length;
5306
+ }
5307
+ if (blob.entity_aliases.length > 0) {
5308
+ const stmts = blob.entity_aliases.map((a) => ({
5309
+ sql: `INSERT OR IGNORE INTO entity_aliases (alias, canonical_entity_id) VALUES (?, ?)`,
5310
+ args: [a.alias, a.canonical_entity_id]
5311
+ }));
5312
+ await client.batch(stmts, "write");
5313
+ pulled += stmts.length;
5314
+ }
5315
+ if (blob.entity_memories.length > 0) {
5316
+ const stmts = blob.entity_memories.map((em) => ({
5317
+ sql: `INSERT OR IGNORE INTO entity_memories (entity_id, memory_id) VALUES (?, ?)`,
5318
+ args: [em.entity_id, em.memory_id]
5319
+ }));
5320
+ await client.batch(stmts, "write");
5321
+ pulled += stmts.length;
5322
+ }
5323
+ if (blob.relationship_memories.length > 0) {
5324
+ const stmts = blob.relationship_memories.map((rm) => ({
5325
+ sql: `INSERT OR IGNORE INTO relationship_memories (relationship_id, memory_id) VALUES (?, ?)`,
5326
+ args: [rm.relationship_id, rm.memory_id]
5327
+ }));
5328
+ await client.batch(stmts, "write");
5329
+ pulled += stmts.length;
5330
+ }
5331
+ if (blob.hyperedges.length > 0) {
5332
+ const stmts = blob.hyperedges.map((h) => ({
5333
+ sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
5334
+ VALUES (?, ?, ?, ?, ?)`,
5335
+ args: [h.id, h.label, h.relation, h.confidence ?? 1, h.timestamp]
5336
+ }));
5337
+ await client.batch(stmts, "write");
5338
+ pulled += stmts.length;
5339
+ }
5340
+ if (blob.hyperedge_nodes.length > 0) {
5341
+ const stmts = blob.hyperedge_nodes.map((hn) => ({
5342
+ sql: `INSERT OR IGNORE INTO hyperedge_nodes (hyperedge_id, entity_id) VALUES (?, ?)`,
5343
+ args: [hn.hyperedge_id, hn.entity_id]
5344
+ }));
5345
+ await client.batch(stmts, "write");
5346
+ pulled += stmts.length;
5347
+ }
5348
+ return { pulled };
5349
+ }
5350
+ async function cloudPushTasks(config) {
5351
+ const client = getClient();
5352
+ const result = await client.execute("SELECT * FROM tasks");
5353
+ const rows = result.rows;
5354
+ const { ok } = await cloudPushBlob(
5355
+ "/sync/push-tasks",
5356
+ rows,
5357
+ "last_tasks_push_version",
5358
+ config
5359
+ );
5360
+ return ok;
5361
+ }
5362
+ async function cloudPullTasks(config) {
5363
+ const remoteTasks = await cloudPullBlob(
5364
+ "/sync/pull-tasks",
5365
+ config
5366
+ );
5367
+ if (!remoteTasks || remoteTasks.length === 0) return { pulled: 0 };
5368
+ const client = getClient();
5369
+ const stmts = remoteTasks.map((t) => ({
5370
+ sql: `INSERT OR IGNORE INTO tasks
5371
+ (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, created_at, updated_at,
5372
+ blocked_by, parent_task_id, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at)
5373
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5374
+ args: [
5375
+ t.id,
5376
+ t.title,
5377
+ t.assigned_to,
5378
+ t.assigned_by,
5379
+ t.project_name,
5380
+ t.priority ?? "p1",
5381
+ t.status ?? "open",
5382
+ t.task_file ?? null,
5383
+ t.created_at,
5384
+ t.updated_at,
5385
+ t.blocked_by ?? null,
5386
+ t.parent_task_id ?? null,
5387
+ t.budget_tokens ?? null,
5388
+ t.budget_fallback_model ?? null,
5389
+ t.tokens_used ?? 0,
5390
+ t.tokens_warned_at ?? null
5391
+ ]
5392
+ }));
5393
+ await client.batch(stmts, "write");
5394
+ return { pulled: remoteTasks.length };
5395
+ }
5396
+ async function cloudPushConversations(config) {
5397
+ const client = getClient();
5398
+ const result = await client.execute("SELECT * FROM conversations");
5399
+ const rows = result.rows;
5400
+ const { ok } = await cloudPushBlob(
5401
+ "/sync/push-conversations",
5402
+ rows,
5403
+ "last_conversations_push_version",
5404
+ config
5405
+ );
5406
+ return ok;
5407
+ }
5408
+ async function cloudPullConversations(config) {
5409
+ const remoteConvos = await cloudPullBlob(
5410
+ "/sync/pull-conversations",
5411
+ config
5412
+ );
5413
+ if (!remoteConvos || remoteConvos.length === 0) return { pulled: 0 };
5414
+ const client = getClient();
5415
+ const stmts = remoteConvos.map((c) => ({
5416
+ sql: `INSERT OR IGNORE INTO conversations
5417
+ (id, platform, external_id, sender_id, sender_name, sender_phone, sender_email,
5418
+ recipient_id, channel_id, thread_id, reply_to_id, content_text, content_media,
5419
+ content_metadata, agent_response, agent_name, timestamp, ingested_at)
5420
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5421
+ args: [
5422
+ c.id,
5423
+ c.platform,
5424
+ c.external_id ?? null,
5425
+ c.sender_id,
5426
+ c.sender_name ?? null,
5427
+ c.sender_phone ?? null,
5428
+ c.sender_email ?? null,
5429
+ c.recipient_id ?? null,
5430
+ c.channel_id,
5431
+ c.thread_id ?? null,
5432
+ c.reply_to_id ?? null,
5433
+ c.content_text ?? null,
5434
+ c.content_media ?? null,
5435
+ c.content_metadata ?? null,
5436
+ c.agent_response ?? null,
5437
+ c.agent_name ?? null,
5438
+ c.timestamp,
5439
+ c.ingested_at
5440
+ ]
5441
+ }));
5442
+ await client.batch(stmts, "write");
5443
+ return { pulled: remoteConvos.length };
5444
+ }
5445
+ async function cloudPushDocuments(config) {
5446
+ const client = getClient();
5447
+ const [workspaces, documents] = await Promise.all([
5448
+ client.execute("SELECT * FROM workspaces"),
5449
+ client.execute("SELECT * FROM documents")
5450
+ ]);
5451
+ const blob = {
5452
+ workspaces: workspaces.rows,
5453
+ documents: documents.rows
5454
+ };
5455
+ const { ok } = await cloudPushBlob(
5456
+ "/sync/push-documents",
5457
+ [blob],
5458
+ "last_documents_push_version",
5459
+ config
5460
+ );
5461
+ return ok;
5462
+ }
5463
+ async function cloudPullDocuments(config) {
5464
+ const data = await cloudPullBlob(
5465
+ "/sync/pull-documents",
5466
+ config
5467
+ );
5468
+ if (!data || data.length === 0) return { pulled: 0 };
5469
+ const blob = data[0];
5470
+ const client = getClient();
5471
+ let pulled = 0;
5472
+ if (blob.workspaces.length > 0) {
5473
+ const stmts = blob.workspaces.map((w) => ({
5474
+ sql: `INSERT OR IGNORE INTO workspaces (id, slug, name, owner_agent_id, created_at, metadata)
5475
+ VALUES (?, ?, ?, ?, ?, ?)`,
5476
+ args: [w.id, w.slug, w.name, w.owner_agent_id ?? null, w.created_at, w.metadata ?? null]
5477
+ }));
5478
+ await client.batch(stmts, "write");
5479
+ pulled += stmts.length;
5480
+ }
5481
+ if (blob.documents.length > 0) {
5482
+ const stmts = blob.documents.map((d) => ({
5483
+ sql: `INSERT OR IGNORE INTO documents
5484
+ (id, workspace_id, filename, mime, source_type, user_id, uploaded_at, metadata)
5485
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
5486
+ args: [
5487
+ d.id,
5488
+ d.workspace_id,
5489
+ d.filename,
5490
+ d.mime ?? null,
5491
+ d.source_type ?? null,
5492
+ d.user_id ?? null,
5493
+ d.uploaded_at,
5494
+ d.metadata ?? null
5495
+ ]
5496
+ }));
5497
+ await client.batch(stmts, "write");
5498
+ pulled += stmts.length;
5499
+ }
5500
+ return { pulled };
5501
+ }
5502
+ var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS;
4973
5503
  var init_cloud_sync = __esm({
4974
5504
  "src/lib/cloud-sync.ts"() {
4975
5505
  "use strict";
@@ -4981,6 +5511,7 @@ var init_cloud_sync = __esm({
4981
5511
  init_config();
4982
5512
  init_employees();
4983
5513
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
5514
+ FETCH_TIMEOUT_MS = 3e4;
4984
5515
  }
4985
5516
  });
4986
5517
 
@@ -5164,7 +5695,7 @@ var init_schedules = __esm({
5164
5695
  init_employees();
5165
5696
  import path19 from "path";
5166
5697
  import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
5167
- import { existsSync as existsSync15, readFileSync as readFileSync13, readdirSync as readdirSync6, unlinkSync as unlinkSync5 } from "fs";
5698
+ import { existsSync as existsSync15, readFileSync as readFileSync13, readdirSync as readdirSync6, unlinkSync as unlinkSync6 } from "fs";
5168
5699
  import os6 from "os";
5169
5700
 
5170
5701
  // src/lib/employee-templates.ts
@@ -5721,7 +6252,7 @@ async function boot(options) {
5721
6252
  for (const f of readdirSync6(exeExeDir)) {
5722
6253
  if (f.startsWith("review-") && f.endsWith(".md")) {
5723
6254
  try {
5724
- unlinkSync5(path19.join(exeExeDir, f));
6255
+ unlinkSync6(path19.join(exeExeDir, f));
5725
6256
  } catch {
5726
6257
  }
5727
6258
  }
@@ -6323,8 +6854,8 @@ async function boot(options) {
6323
6854
  try {
6324
6855
  const flagPath = path19.join(EXE_AI_DIR, "session-cache", "needs-backfill");
6325
6856
  if (existsSync15(flagPath)) {
6326
- const { unlinkSync: unlinkSync6 } = await import("fs");
6327
- unlinkSync6(flagPath);
6857
+ const { unlinkSync: unlinkSync7 } = await import("fs");
6858
+ unlinkSync7(flagPath);
6328
6859
  }
6329
6860
  } catch {
6330
6861
  }