@alfe.ai/openclaw-sync 0.3.4 → 0.3.6

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.
package/dist/plugin2.cjs CHANGED
@@ -5,6 +5,7 @@ let node_path = require("node:path");
5
5
  let chokidar = require("chokidar");
6
6
  let node_module = require("node:module");
7
7
  let _alfe_ai_config = require("@alfe.ai/config");
8
+ let _alfe_ai_openclaw_plugin_kit = require("@alfe.ai/openclaw-plugin-kit");
8
9
  let _alfe_ai_agent_api_client = require("@alfe.ai/agent-api-client");
9
10
  //#region src/watcher.ts
10
11
  /**
@@ -301,6 +302,7 @@ const SYNC_CAPABILITIES = [
301
302
  "sync.pull",
302
303
  "sync.fullSync"
303
304
  ];
305
+ const SYNC_ACTIVATION_KEY = (0, _alfe_ai_openclaw_plugin_kit.getActivationKey)("sync");
304
306
  const SYNC_RELAY_RECONNECT_BASE_MS = 1e3;
305
307
  const SYNC_RELAY_RECONNECT_MAX_MS = 3e4;
306
308
  const SYNC_RELAY_DEBOUNCE_MS = 500;
@@ -358,64 +360,34 @@ function setupSchedule(schedule, log) {
358
360
  }, intervalMs);
359
361
  scheduledInterval.unref();
360
362
  }
361
- async function connectToDaemon(socketPath, log) {
362
- try {
363
- const ipc = new (await (import("@alfe.ai/openclaw"))).IPCClient(socketPath, log);
364
- ipc.on("connected", () => {
363
+ function handleDaemonMessage(msg, log) {
364
+ if (msg.type === "SYNC_NOW" || msg.command === "SYNC_NOW") {
365
+ log.info("Received SYNC_NOW command triggering immediate sync...");
366
+ if (syncEngine) {
367
+ const engine = syncEngine;
365
368
  (async () => {
366
- log.info("Connected to Alfe daemon — registering sync capabilities...");
367
- const response = await ipc.request("capability.register", {
368
- plugin: "@alfe.ai/openclaw-sync",
369
- capabilities: [...SYNC_CAPABILITIES]
370
- });
371
- if (response.ok) log.info("Sync capabilities registered with daemon");
372
- else log.warn(`Failed to register sync capabilities: ${response.error?.message ?? "unknown"}`);
373
- })();
374
- });
375
- ipc.on("disconnected", (...args) => {
376
- const reason = typeof args[0] === "string" ? args[0] : String(args[0]);
377
- log.warn(`Disconnected from Alfe daemon: ${reason}`);
378
- });
379
- ipc.on("message", (...args) => {
380
- const msg = args[0];
381
- if (msg?.type === "SYNC_NOW" || msg?.command === "SYNC_NOW") {
382
- log.info("Received SYNC_NOW command — triggering immediate sync...");
383
- if (syncEngine) {
384
- const engine = syncEngine;
385
- (async () => {
386
- try {
387
- lastSyncResult = await engine.fullSync({ quiet: true });
388
- log.info(`SYNC_NOW complete: ${String(lastSyncResult.pushed)} pushed, ${String(lastSyncResult.pulled)} pulled`);
389
- } catch (err) {
390
- log.error(`SYNC_NOW failed: ${err instanceof Error ? err.message : String(err)}`);
391
- }
392
- })();
369
+ try {
370
+ lastSyncResult = await engine.fullSync({ quiet: true });
371
+ log.info(`SYNC_NOW complete: ${String(lastSyncResult.pushed)} pushed, ${String(lastSyncResult.pulled)} pulled`);
372
+ } catch (err) {
373
+ log.error(`SYNC_NOW failed: ${err instanceof Error ? err.message : String(err)}`);
393
374
  }
394
- }
395
- if (msg?.type === "SHARED_SCOPES") {
396
- const scopes = msg.scopes;
397
- const engine = sharedSyncEngine;
398
- if (scopes && engine) {
399
- log.info(`Received SHARED_SCOPES update: ${String(scopes.length)} scope(s)`);
400
- (async () => {
401
- try {
402
- await engine.updateScopes(scopes);
403
- } catch (err) {
404
- log.error(`SHARED_SCOPES update failed: ${err instanceof Error ? err.message : String(err)}`);
405
- }
406
- })();
375
+ })();
376
+ }
377
+ }
378
+ if (msg.type === "SHARED_SCOPES") {
379
+ const scopes = msg.scopes;
380
+ const engine = sharedSyncEngine;
381
+ if (scopes && engine) {
382
+ log.info(`Received SHARED_SCOPES update: ${String(scopes.length)} scope(s)`);
383
+ (async () => {
384
+ try {
385
+ await engine.updateScopes(scopes);
386
+ } catch (err) {
387
+ log.error(`SHARED_SCOPES update failed: ${err instanceof Error ? err.message : String(err)}`);
407
388
  }
408
- }
409
- });
410
- ipc.on("error", (...args) => {
411
- const err = args[0];
412
- log.debug(`Daemon IPC error: ${err instanceof Error ? err.message : String(err)}`);
413
- });
414
- ipc.start();
415
- return ipc;
416
- } catch {
417
- log.info("Alfe daemon not available — Sync plugin running standalone");
418
- return null;
389
+ })();
390
+ }
419
391
  }
420
392
  }
421
393
  function clearSyncRelayReconnect() {
@@ -581,118 +553,123 @@ const plugin = {
581
553
  ];
582
554
  const syncSchedule = pluginConfig.syncSchedule ?? "realtime";
583
555
  const socketPath = pluginConfig.socketPath ?? alfeConfig?.socketPath ?? _alfe_ai_config.DEFAULT_SOCKET_PATH;
584
- const startSyncService = async () => {
585
- if (globalThis.__alfeSyncPluginActivated === true) {
586
- log.debug("Alfe Sync plugin already activated — skipping duplicate");
587
- return;
588
- }
589
- globalThis.__alfeSyncPluginActivated = true;
590
- log.info("Alfe Sync plugin activating...");
591
- log.info(`Sync scope: ${syncScope.join(", ")}`);
592
- log.info(`Sync schedule: ${syncSchedule}`);
593
- log.info(`Workspace: ${workspacePath}`);
594
- if (!(0, _alfe_ai_config.configExists)()) {
595
- log.info("Sync skipped — no Alfe config found. Run `alfe login` to enable.");
596
- return;
597
- }
598
- let syncCfg;
599
- try {
600
- syncCfg = (0, _alfe_ai_config.resolveConfig)();
601
- } catch (err) {
602
- log.warn(`Sync skipped — failed to resolve credentials from ~/.alfe/config.toml: ${err instanceof Error ? err.message : String(err)}`);
603
- return;
604
- }
605
- client = new _alfe_ai_agent_api_client.AgentApiClient({
606
- apiKey: syncCfg.apiKey,
607
- apiUrl: syncCfg.apiUrl
608
- });
609
- syncEngine = require_sync_engine.createSyncEngine({
610
- workspacePath,
611
- client,
612
- runtime
613
- });
614
- log.info("Sync engine initialized");
615
- if (syncSchedule === "realtime") {
556
+ const startSyncService = () => {
557
+ (0, _alfe_ai_openclaw_plugin_kit.guardedStart)(SYNC_ACTIVATION_KEY, log, async () => {
558
+ log.info("Alfe Sync plugin activating...");
559
+ log.info(`Sync scope: ${syncScope.join(", ")}`);
560
+ log.info(`Sync schedule: ${syncSchedule}`);
561
+ log.info(`Workspace: ${workspacePath}`);
562
+ if (!(0, _alfe_ai_config.configExists)()) {
563
+ log.info("Sync skipped — no Alfe config found. Run `alfe login` to enable.");
564
+ (0, _alfe_ai_openclaw_plugin_kit.resetActivation)(SYNC_ACTIVATION_KEY);
565
+ return;
566
+ }
567
+ let syncCfg;
616
568
  try {
617
- lastSyncResult = await syncEngine.firstRunReconcile({ quiet: true });
618
- if (lastSyncResult.conflicts > 0) log.info(`Initial workspace reconcile complete — ${String(lastSyncResult.conflicts)} diverged local file(s) handled (see RECOVERY-*.md in the agent workspace)`);
619
- else log.info("Initial workspace reconcile complete");
569
+ syncCfg = (0, _alfe_ai_config.resolveConfig)();
620
570
  } catch (err) {
621
- log.warn(`Initial workspace reconcile failed: ${err instanceof Error ? err.message : String(err)}`);
571
+ log.warn(`Sync skipped failed to resolve credentials from ~/.alfe/config.toml: ${err instanceof Error ? err.message : String(err)}`);
572
+ (0, _alfe_ai_openclaw_plugin_kit.resetActivation)(SYNC_ACTIVATION_KEY);
573
+ return;
622
574
  }
623
- syncEngine.pruneIgnored({ quiet: true }).then((pruned) => {
624
- if (pruned.pushed > 0) log.info(`Pruned ${String(pruned.pushed)} ignored file(s) from cloud`);
625
- }).catch((err) => {
626
- log.warn(`Ignored-file prune failed: ${err instanceof Error ? err.message : String(err)}`);
575
+ client = new _alfe_ai_agent_api_client.AgentApiClient({
576
+ apiKey: syncCfg.apiKey,
577
+ apiUrl: syncCfg.apiUrl
627
578
  });
628
- deleteBrake = createDeleteBrake();
629
- try {
630
- stopWatcher = await startWatcher({
631
- workspacePath,
632
- runtime,
633
- debounceMs: 2e3,
634
- onChanges: async (paths) => {
635
- if (!syncEngine) return;
636
- const existing = [];
637
- const missing = [];
638
- for (const p of paths) if ((0, node_fs.existsSync)((0, node_path.join)(workspacePath, p))) existing.push(p);
639
- else missing.push(p);
640
- log.debug(`Realtime sync: ${String(existing.length)} upload(s), ${String(missing.length)} delete(s)`);
641
- if (existing.length > 0) try {
642
- lastSyncResult = await syncEngine.push(existing, { quiet: true });
643
- } catch (err) {
644
- log.error(`Realtime push failed: ${err instanceof Error ? err.message : String(err)}`);
645
- }
646
- if (missing.length > 0) {
647
- const artifactDeletes = missing.filter((p) => require_sync_engine.isRecoveryArtifact(p));
648
- const regularDeletes = missing.filter((p) => !require_sync_engine.isRecoveryArtifact(p));
649
- const manifest = await require_sync_engine.readManifest(workspacePath);
650
- const manifestSize = Object.keys(manifest.files).length;
651
- let toDelete = missing;
652
- if (deleteBrake && !deleteBrake.check(regularDeletes.length, manifestSize)) {
653
- log.error(`Sync delete brake tripped: ${String(deleteBrake.windowSum() + regularDeletes.length)} deletes in last 60s vs manifest size ${String(manifestSize)} (threshold 30%). Refusing batch — investigate the workspace state.`);
654
- toDelete = artifactDeletes;
655
- }
656
- if (toDelete.length > 0) try {
657
- lastSyncResult = await syncEngine.pushDeletes(toDelete, { quiet: true });
579
+ syncEngine = require_sync_engine.createSyncEngine({
580
+ workspacePath,
581
+ client,
582
+ runtime
583
+ });
584
+ log.info("Sync engine initialized");
585
+ if (syncSchedule === "realtime") {
586
+ try {
587
+ lastSyncResult = await syncEngine.firstRunReconcile({ quiet: true });
588
+ if (lastSyncResult.conflicts > 0) log.info(`Initial workspace reconcile complete — ${String(lastSyncResult.conflicts)} diverged local file(s) handled (see RECOVERY-*.md in the agent workspace)`);
589
+ else log.info("Initial workspace reconcile complete");
590
+ } catch (err) {
591
+ log.warn(`Initial workspace reconcile failed: ${err instanceof Error ? err.message : String(err)}`);
592
+ }
593
+ syncEngine.pruneIgnored({ quiet: true }).then((pruned) => {
594
+ if (pruned.pushed > 0) log.info(`Pruned ${String(pruned.pushed)} ignored file(s) from cloud`);
595
+ }).catch((err) => {
596
+ log.warn(`Ignored-file prune failed: ${err instanceof Error ? err.message : String(err)}`);
597
+ });
598
+ deleteBrake = createDeleteBrake();
599
+ try {
600
+ stopWatcher = await startWatcher({
601
+ workspacePath,
602
+ runtime,
603
+ debounceMs: 2e3,
604
+ onChanges: async (paths) => {
605
+ if (!syncEngine) return;
606
+ const existing = [];
607
+ const missing = [];
608
+ for (const p of paths) if ((0, node_fs.existsSync)((0, node_path.join)(workspacePath, p))) existing.push(p);
609
+ else missing.push(p);
610
+ log.debug(`Realtime sync: ${String(existing.length)} upload(s), ${String(missing.length)} delete(s)`);
611
+ if (existing.length > 0) try {
612
+ lastSyncResult = await syncEngine.push(existing, { quiet: true });
658
613
  } catch (err) {
659
- log.error(`Realtime delete failed: ${err instanceof Error ? err.message : String(err)}`);
614
+ log.error(`Realtime push failed: ${err instanceof Error ? err.message : String(err)}`);
615
+ }
616
+ if (missing.length > 0) {
617
+ const artifactDeletes = missing.filter((p) => require_sync_engine.isRecoveryArtifact(p));
618
+ const regularDeletes = missing.filter((p) => !require_sync_engine.isRecoveryArtifact(p));
619
+ const manifest = await require_sync_engine.readManifest(workspacePath);
620
+ const manifestSize = Object.keys(manifest.files).length;
621
+ let toDelete = missing;
622
+ if (deleteBrake && !deleteBrake.check(regularDeletes.length, manifestSize)) {
623
+ log.error(`Sync delete brake tripped: ${String(deleteBrake.windowSum() + regularDeletes.length)} deletes in last 60s vs manifest size ${String(manifestSize)} (threshold 30%). Refusing batch — investigate the workspace state.`);
624
+ toDelete = artifactDeletes;
625
+ }
626
+ if (toDelete.length > 0) try {
627
+ lastSyncResult = await syncEngine.pushDeletes(toDelete, { quiet: true });
628
+ } catch (err) {
629
+ log.error(`Realtime delete failed: ${err instanceof Error ? err.message : String(err)}`);
630
+ }
660
631
  }
661
632
  }
662
- }
663
- });
664
- log.info("File watcher started for realtime sync");
665
- } catch (err) {
666
- log.warn(`Failed to start file watcher: ${err instanceof Error ? err.message : String(err)}`);
667
- }
668
- } else setupSchedule(syncSchedule, log);
669
- daemonIpcClient = await connectToDaemon(socketPath, log);
670
- let registered = null;
671
- try {
672
- registered = (await client.syncRegister()).agent;
673
- agentId = registered.agentId;
674
- } catch (err) {
675
- log.warn(`Sync register failed: ${err instanceof Error ? err.message : String(err)}`);
676
- }
677
- if (registered) {
633
+ });
634
+ log.info("File watcher started for realtime sync");
635
+ } catch (err) {
636
+ log.warn(`Failed to start file watcher: ${err instanceof Error ? err.message : String(err)}`);
637
+ }
638
+ } else setupSchedule(syncSchedule, log);
639
+ daemonIpcClient = await (0, _alfe_ai_openclaw_plugin_kit.connectToDaemon)(socketPath, log, {
640
+ pluginId: "@alfe.ai/openclaw-sync",
641
+ capabilities: SYNC_CAPABILITIES,
642
+ onMessage: (msg) => {
643
+ handleDaemonMessage(msg, log);
644
+ },
645
+ standaloneNote: "Alfe daemon not available — Sync plugin running standalone"
646
+ });
647
+ let registered = null;
678
648
  try {
679
- syncRelayWs = await connectToSyncRelay(pluginConfig.syncRelayUrl ?? deriveRelayUrl(syncCfg.apiUrl), syncCfg.apiKey, registered.agentId, log);
649
+ registered = (await client.syncRegister()).agent;
650
+ agentId = registered.agentId;
680
651
  } catch (err) {
681
- log.debug(`Sync Relay connection skipped: ${err instanceof Error ? err.message : String(err)}`);
652
+ log.warn(`Sync register failed: ${err instanceof Error ? err.message : String(err)}`);
682
653
  }
683
- if (pluginConfig.sharedSync !== false) try {
684
- sharedSyncEngine = createSharedSyncEngine({
685
- workspacePath,
686
- client
687
- }, log);
688
- log.info("Shared sync engine created — waiting for SHARED_SCOPES from gateway");
689
- } catch (err) {
690
- log.debug(`Shared sync engine skipped: ${err instanceof Error ? err.message : String(err)}`);
654
+ if (registered) {
655
+ try {
656
+ syncRelayWs = await connectToSyncRelay(pluginConfig.syncRelayUrl ?? deriveRelayUrl(syncCfg.apiUrl), syncCfg.apiKey, registered.agentId, log);
657
+ } catch (err) {
658
+ log.debug(`Sync Relay connection skipped: ${err instanceof Error ? err.message : String(err)}`);
659
+ }
660
+ if (pluginConfig.sharedSync !== false) try {
661
+ sharedSyncEngine = createSharedSyncEngine({
662
+ workspacePath,
663
+ client
664
+ }, log);
665
+ log.info("Shared sync engine created — waiting for SHARED_SCOPES from gateway");
666
+ } catch (err) {
667
+ log.debug(`Shared sync engine skipped: ${err instanceof Error ? err.message : String(err)}`);
668
+ }
691
669
  }
692
- }
670
+ });
693
671
  };
694
672
  const stopSyncService = async () => {
695
- globalThis.__alfeSyncPluginActivated = false;
696
673
  clearSchedule();
697
674
  disconnectSyncRelay();
698
675
  if (stopWatcher) {
@@ -720,6 +697,7 @@ const plugin = {
720
697
  sharedSyncEngine = null;
721
698
  lastSyncResult = null;
722
699
  currentConfig = {};
700
+ (0, _alfe_ai_openclaw_plugin_kit.resetActivation)(SYNC_ACTIVATION_KEY);
723
701
  log.info("Alfe Sync plugin deactivated");
724
702
  };
725
703
  if (typeof api.registerGatewayMethod === "function") {
@@ -759,13 +737,14 @@ const plugin = {
759
737
  }
760
738
  api.registerService({
761
739
  id: "alfe-sync-engine",
762
- start: () => startSyncService(),
740
+ start: () => {
741
+ startSyncService();
742
+ },
763
743
  stop: () => stopSyncService()
764
744
  });
765
745
  log.info("Alfe Sync plugin activated");
766
746
  },
767
747
  async deactivate(api) {
768
- globalThis.__alfeSyncPluginActivated = false;
769
748
  const log = api.logger;
770
749
  log.info("Alfe Sync plugin deactivating...");
771
750
  clearSchedule();
@@ -794,6 +773,7 @@ const plugin = {
794
773
  sharedSyncEngine = null;
795
774
  lastSyncResult = null;
796
775
  currentConfig = {};
776
+ (0, _alfe_ai_openclaw_plugin_kit.resetActivation)(SYNC_ACTIVATION_KEY);
797
777
  log.info("Alfe Sync plugin deactivated");
798
778
  },
799
779
  async configure(api, config) {