@alfe.ai/openclaw-sync 0.0.14 → 0.0.15
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/index.cjs +4 -747
- package/dist/index.d.cts +4 -69
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +4 -69
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -746
- package/dist/plugin.cjs +2 -0
- package/dist/plugin.d.cts +69 -0
- package/dist/plugin.d.cts.map +1 -0
- package/dist/plugin.d.ts +69 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +2 -0
- package/dist/plugin2.cjs +764 -0
- package/dist/plugin2.js +749 -0
- package/dist/plugin2.js.map +1 -0
- package/package.json +6 -1
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin2.js","names":["resolveAlfeConfig"],"sources":["../src/watcher.ts","../src/shared-sync.ts","../src/plugin.ts"],"sourcesContent":["/**\n * AlfeSync watcher — recursive file watcher with debounce and ignore support.\n *\n * Uses chokidar to watch the workspace root, debounces per-file changes\n * by 2 seconds, and emits batches of changed paths.\n */\n\nimport { watch } from \"chokidar\";\nimport { relative } from \"node:path\";\nimport { loadIgnorePatterns, shouldIgnore } from \"./ignore.js\";\n\nexport interface WatcherOptions {\n workspacePath: string;\n /** Debounce delay per file in milliseconds. Default: 2000 */\n debounceMs?: number;\n /** Callback invoked with batches of changed relative paths */\n onChanges: (paths: string[]) => void | Promise<void>;\n}\n\n/**\n * Start watching a workspace for file changes.\n *\n * Returns a cleanup function to stop watching.\n */\nexport async function startWatcher(\n options: WatcherOptions,\n): Promise<() => Promise<void>> {\n const { workspacePath, debounceMs = 2000, onChanges } = options;\n const ignorePatterns = await loadIgnorePatterns(workspacePath);\n\n // Pending changes map: relativePath → timeout handle\n const pending = new Map<string, ReturnType<typeof setTimeout>>();\n // Batch accumulator for flushing\n let batchPaths = new Set<string>();\n let flushTimer: ReturnType<typeof setTimeout> | null = null;\n\n function scheduleBatch() {\n if (flushTimer) return;\n flushTimer = setTimeout(() => {\n flushTimer = null;\n if (batchPaths.size === 0) return;\n\n const paths = [...batchPaths];\n batchPaths = new Set();\n void onChanges(paths);\n }, debounceMs);\n }\n\n function handleChange(absolutePath: string) {\n const relativePath = relative(workspacePath, absolutePath);\n\n // Skip ignored files\n if (shouldIgnore(relativePath, ignorePatterns)) return;\n\n // Clear existing timer for this file\n const existingTimer = pending.get(relativePath);\n if (existingTimer) clearTimeout(existingTimer);\n\n // Debounce: wait before adding to batch\n const timer = setTimeout(() => {\n pending.delete(relativePath);\n batchPaths.add(relativePath);\n scheduleBatch();\n }, debounceMs);\n\n pending.set(relativePath, timer);\n }\n\n const watcher = watch(workspacePath, {\n persistent: true,\n ignoreInitial: true,\n followSymlinks: false,\n depth: undefined, // unlimited depth\n ignored: [\n \"**/node_modules/**\",\n \"**/.alfesync/**\",\n \"**/.git/**\",\n \"**/.sst/**\",\n ],\n });\n\n watcher.on(\"add\", handleChange);\n watcher.on(\"change\", handleChange);\n watcher.on(\"unlink\", handleChange);\n\n // Return cleanup function\n return async () => {\n // Clear all pending timers\n for (const timer of pending.values()) {\n clearTimeout(timer);\n }\n pending.clear();\n\n if (flushTimer) {\n clearTimeout(flushTimer);\n flushTimer = null;\n }\n\n await watcher.close();\n };\n}\n","/**\n * Shared file sync engine for org/team/project scoped files.\n *\n * Syncs shared files to a `shared/` directory in the agent's workspace,\n * organized by scope: shared/org/, shared/teams/{id}/, shared/projects/{id}/\n *\n * Uses the agent self-service API (/agents/org/...) with the agent's API key.\n */\n\nimport { join, dirname, normalize, sep } from \"node:path\";\nimport { mkdir, writeFile, unlink, rm } from \"node:fs/promises\";\n\nconst MAX_SHARED_FILE_SIZE = 100 * 1024 * 1024; // 100 MB\n\n/** Verify that resolvedPath stays within baseDir. Prevents path traversal. */\nfunction assertContained(baseDir: string, resolvedPath: string): void {\n const normalizedBase = normalize(baseDir) + sep;\n const normalizedPath = normalize(resolvedPath);\n if (!normalizedPath.startsWith(normalizedBase) && normalizedPath !== normalize(baseDir)) {\n throw new Error(`Path traversal blocked: ${resolvedPath} escapes ${baseDir}`);\n }\n}\n\n// ── Types ──────────────────────────────────────────────────\n\nexport interface SharedScope {\n scopeType: \"team\" | \"project\" | \"org\";\n scopeId: string;\n name: string;\n}\n\nexport interface SharedSyncConfig {\n workspacePath: string;\n apiUrl: string;\n token: string;\n agentId: string;\n}\n\ninterface SharedFileEntry {\n filePath: string;\n fileName: string;\n size: number;\n contentType?: string;\n}\n\ninterface PluginLogger {\n info(msg: string): void;\n warn(msg: string): void;\n error(msg: string): void;\n debug(msg: string): void;\n}\n\n// ── HTTP helpers ───────────────────────────────────────────\n\nasync function fetchJson<T>(url: string, token: string): Promise<T> {\n const response = await fetch(url, {\n headers: {\n \"Authorization\": `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n });\n if (!response.ok) {\n let errorBody = \"\";\n try {\n errorBody = await response.text();\n } catch {\n errorBody = \"(unable to read error body)\";\n }\n throw new Error(`HTTP ${String(response.status)}: ${errorBody}`);\n }\n return response.json() as Promise<T>;\n}\n\n// ── SharedSyncEngine ───────────────────────────────────────\n\nexport interface SharedSyncEngine {\n initialize(scopes: SharedScope[]): Promise<void>;\n updateScopes(scopes: SharedScope[]): Promise<void>;\n handleNotification(filePath: string, eventType: \"created\" | \"deleted\"): Promise<void>;\n fullSync(): Promise<void>;\n getScopes(): SharedScope[];\n}\n\nexport function createSharedSyncEngine(\n config: SharedSyncConfig,\n log: PluginLogger,\n): SharedSyncEngine {\n let activeScopes: SharedScope[] = [];\n const sharedDir = join(config.workspacePath, \"shared\");\n\n function scopeDir(scope: SharedScope): string {\n if (scope.scopeType === \"org\") return join(sharedDir, \"org\");\n const plural = scope.scopeType === \"team\" ? \"teams\" : \"projects\";\n return join(sharedDir, plural, scope.scopeId);\n }\n\n function apiBase(): string {\n return `${config.apiUrl}/agents/org`;\n }\n\n async function listRemoteFiles(scope: SharedScope): Promise<SharedFileEntry[]> {\n const url = `${apiBase()}/files/${scope.scopeType}/${scope.scopeId}`;\n const result = await fetchJson<{ files: SharedFileEntry[] }>(url, config.token);\n return result.files;\n }\n\n async function downloadFile(scope: SharedScope, filePath: string, localPath: string): Promise<void> {\n // Prevent path traversal — ensure localPath stays within scope directory\n assertContained(scopeDir(scope), localPath);\n\n const url = `${apiBase()}/files/${scope.scopeType}/${scope.scopeId}/${encodeURIComponent(filePath)}/download`;\n const result = await fetchJson<{ downloadUrl: string }>(url, config.token);\n\n const response = await fetch(result.downloadUrl);\n if (!response.ok) throw new Error(`Download failed: HTTP ${String(response.status)}`);\n\n const contentLength = parseInt(response.headers.get(\"content-length\") ?? \"0\", 10);\n if (contentLength > MAX_SHARED_FILE_SIZE) {\n throw new Error(`File too large: ${String(contentLength)} bytes exceeds ${String(MAX_SHARED_FILE_SIZE)} limit`);\n }\n\n const buffer = Buffer.from(await response.arrayBuffer());\n if (buffer.length > MAX_SHARED_FILE_SIZE) {\n throw new Error(`Downloaded file exceeds size limit: ${String(buffer.length)} bytes`);\n }\n\n await mkdir(dirname(localPath), { recursive: true });\n await writeFile(localPath, buffer);\n }\n\n async function syncScope(scope: SharedScope): Promise<void> {\n const dir = scopeDir(scope);\n await mkdir(dir, { recursive: true });\n\n try {\n const remoteFiles = await listRemoteFiles(scope);\n\n for (const file of remoteFiles) {\n const localPath = join(dir, file.filePath);\n try {\n await downloadFile(scope, file.filePath, localPath);\n log.debug(`Shared sync: downloaded ${scope.scopeType}/${scope.scopeId}/${file.filePath}`);\n } catch (err: unknown) {\n log.error(`Shared sync: failed to download ${file.filePath}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n } catch (err: unknown) {\n log.error(`Shared sync: failed to list files for ${scope.scopeType}/${scope.scopeId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n function parseScopedPath(filePath: string): { scope: SharedScope; relativePath: string } | null {\n // Reject any path traversal attempts\n if (filePath.includes(\"..\")) return null;\n\n // shared/org/{relativePath}\n const orgMatch = /^shared\\/org\\/(.+)$/.exec(filePath);\n if (orgMatch) {\n const scope = activeScopes.find((s) => s.scopeType === \"org\");\n if (scope) return { scope, relativePath: orgMatch[1] };\n }\n\n // shared/teams/{scopeId}/{relativePath}\n const teamMatch = /^shared\\/teams\\/([^/]+)\\/(.+)$/.exec(filePath);\n if (teamMatch) {\n const scope = activeScopes.find((s) => s.scopeType === \"team\" && s.scopeId === teamMatch[1]);\n if (scope) return { scope, relativePath: teamMatch[2] };\n }\n\n // shared/projects/{scopeId}/{relativePath}\n const projectMatch = /^shared\\/projects\\/([^/]+)\\/(.+)$/.exec(filePath);\n if (projectMatch) {\n const scope = activeScopes.find((s) => s.scopeType === \"project\" && s.scopeId === projectMatch[1]);\n if (scope) return { scope, relativePath: projectMatch[2] };\n }\n\n return null;\n }\n\n return {\n async initialize(scopes: SharedScope[]): Promise<void> {\n activeScopes = [...scopes];\n log.info(`Shared sync: initializing with ${String(scopes.length)} scope(s)`);\n\n await mkdir(sharedDir, { recursive: true });\n\n for (const scope of scopes) {\n await syncScope(scope);\n }\n\n log.info(\"Shared sync: initialization complete\");\n },\n\n async updateScopes(newScopes: SharedScope[]): Promise<void> {\n const oldIds = new Set(activeScopes.map((s) => `${s.scopeType}:${s.scopeId}`));\n const newIds = new Set(newScopes.map((s) => `${s.scopeType}:${s.scopeId}`));\n\n // Removed scopes — delete local directories\n for (const scope of activeScopes) {\n const key = `${scope.scopeType}:${scope.scopeId}`;\n if (!newIds.has(key)) {\n const dir = scopeDir(scope);\n try {\n await rm(dir, { recursive: true, force: true });\n log.info(`Shared sync: removed scope directory ${dir}`);\n } catch (err: unknown) {\n log.warn(`Shared sync: failed to remove ${dir}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n }\n\n // New scopes — create and sync\n for (const scope of newScopes) {\n const key = `${scope.scopeType}:${scope.scopeId}`;\n if (!oldIds.has(key)) {\n log.info(`Shared sync: new scope ${scope.scopeType}/${scope.scopeId} — syncing files`);\n await syncScope(scope);\n }\n }\n\n activeScopes = [...newScopes];\n },\n\n async handleNotification(filePath: string, eventType: \"created\" | \"deleted\"): Promise<void> {\n const parsed = parseScopedPath(filePath);\n if (!parsed) {\n log.debug(`Shared sync: ignoring notification for unknown path: ${filePath}`);\n return;\n }\n\n const dir = scopeDir(parsed.scope);\n const localPath = join(dir, parsed.relativePath);\n\n // Prevent path traversal — ensure localPath stays within scope directory\n try {\n assertContained(dir, localPath);\n } catch {\n log.warn(`Shared sync: path traversal blocked for ${filePath}`);\n return;\n }\n\n if (eventType === \"deleted\") {\n try {\n await unlink(localPath);\n log.debug(`Shared sync: deleted ${filePath}`);\n } catch {\n // Idempotent — file may not exist locally (out-of-order events)\n }\n return;\n }\n\n // eventType === \"created\"\n try {\n await downloadFile(parsed.scope, parsed.relativePath, localPath);\n log.debug(`Shared sync: pulled ${filePath}`);\n } catch (err: unknown) {\n log.error(`Shared sync: failed to pull ${filePath}: ${err instanceof Error ? err.message : String(err)}`);\n }\n },\n\n async fullSync(): Promise<void> {\n log.info(`Shared sync: full sync across ${String(activeScopes.length)} scope(s)`);\n for (const scope of activeScopes) {\n await syncScope(scope);\n }\n },\n\n getScopes(): SharedScope[] {\n return [...activeScopes];\n },\n };\n}\n","/**\n * @alfe.ai/openclaw-sync — OpenClaw Sync plugin.\n *\n * Wraps the existing sync engine as a lifecycle-managed integration,\n * following the same plugin pattern as @alfe.ai/openclaw-mobile and\n * @alfe.ai/openclaw-discord.\n *\n * Lifecycle:\n * - activate(api): start the sync engine + watcher based on config\n * - deactivate(api): stop the watcher, clean up resources\n * - configure(api, config): update scope/schedule at runtime\n *\n * Registers 'sync.now' and 'sync.status' gateway RPC methods.\n */\n\nimport { createSyncEngine } from './sync-engine.js';\nimport { startWatcher } from './watcher.js';\nimport { isInitialized, readConfig } from './config.js';\nimport type { SyncEngine, SyncResult } from './sync-engine.js';\nimport { createSharedSyncEngine } from './shared-sync.js';\nimport type { SharedSyncEngine, SharedScope } from './shared-sync.js';\nimport { resolveConfig as resolveAlfeConfig, DEFAULT_SOCKET_PATH, DEFAULT_WORKSPACE_PATH } from '@alfe.ai/config';\nimport { createRequire } from 'node:module';\nconst require = createRequire(import.meta.url);\nconst pkg = require('../package.json') as { version: string };\n\n// ── Constants ───────────────────────────────────────────────\nconst SYNC_CAPABILITIES = ['sync.push', 'sync.pull', 'sync.fullSync'] as const;\nconst SYNC_RELAY_RECONNECT_BASE_MS = 1000;\nconst SYNC_RELAY_RECONNECT_MAX_MS = 30000;\nconst SYNC_RELAY_DEBOUNCE_MS = 500;\n\n// ── Types ───────────────────────────────────────────────────\n\n/** Logger interface used by the plugin API */\ninterface PluginLogger {\n info(msg: string): void;\n warn(msg: string): void;\n error(msg: string): void;\n debug(msg: string): void;\n}\n\ninterface PluginServiceContext {\n config: Record<string, unknown>;\n workspaceDir?: string;\n stateDir: string;\n logger: PluginLogger;\n}\n\n/** Plugin API passed to activate/deactivate/configure */\ninterface PluginApi {\n logger: PluginLogger;\n registrationMode?: 'full' | 'setup-only' | 'setup-runtime' | 'cli-metadata';\n config?: SyncPluginConfig;\n registerGatewayMethod?: (name: string, handler: () => Promise<unknown>) => void;\n registerService?(service: {\n id: string;\n start: (ctx: PluginServiceContext) => void | Promise<void>;\n stop?: (ctx: PluginServiceContext) => void | Promise<void>;\n }): void;\n}\n\n/** IPC client interface for daemon communication */\ninterface IpcClient {\n on(event: string, handler: (...args: unknown[]) => void): void;\n request(method: string, params: unknown): Promise<{ ok: boolean; error?: { message: string } }>;\n start(): void;\n stop(): void;\n}\n\n/** WebSocket-like interface for the sync relay */\ninterface SyncRelaySocket {\n on(event: string, handler: (...args: never[]) => void): void;\n send(data: string): void;\n close(code?: number, reason?: string): void;\n}\n\n/** Parsed relay message */\ninterface RelayMessage {\n type: string;\n status?: string;\n agentId?: string;\n message?: string;\n filePath?: string;\n etag?: string;\n eventType?: string;\n}\n\nexport interface SyncPluginConfig {\n /** Workspace path to sync. Defaults to ~/.openclaw */\n workspacePath?: string;\n /** Sync scope — which data categories to sync */\n syncScope?: ('config' | 'conversations' | 'memory')[];\n /** Sync schedule — how often to auto-sync */\n syncSchedule?: 'realtime' | 'hourly' | 'daily' | 'weekly';\n /** IPC socket path override */\n socketPath?: string;\n /** Sync relay URL override (default: wss://sync.alfe.ai/ws) */\n syncRelayUrl?: string;\n /** Enable shared file sync for org/team/project scopes. Default: true */\n sharedSync?: boolean;\n}\n\n// ── Plugin State ────────────────────────────────────────────\n\nlet syncEngine: SyncEngine | null = null;\nlet sharedSyncEngine: SharedSyncEngine | null = null;\nlet stopWatcher: (() => Promise<void>) | null = null;\nlet daemonIpcClient: IpcClient | null = null;\nlet scheduledInterval: ReturnType<typeof setInterval> | null = null;\nlet currentConfig: SyncPluginConfig = {};\nlet lastSyncResult: SyncResult | null = null;\nlet syncRelayWs: SyncRelaySocket | null = null;\nlet syncRelayReconnectTimer: ReturnType<typeof setTimeout> | null = null;\nlet syncRelayReconnectAttempt = 0;\nlet syncRelayDebounceTimer: ReturnType<typeof setTimeout> | null = null;\nconst syncRelayPendingPaths =\n new Map<string, { etag?: string; eventType: 'created' | 'deleted' }>();\n\n// ── Schedule Helpers ────────────────────────────────────────\n\nconst SCHEDULE_INTERVALS_MS: Record<string, number> = {\n hourly: 60 * 60 * 1000,\n daily: 24 * 60 * 60 * 1000,\n weekly: 7 * 24 * 60 * 60 * 1000,\n};\n\nfunction clearSchedule() {\n if (scheduledInterval) {\n clearInterval(scheduledInterval);\n scheduledInterval = null;\n }\n}\n\nfunction setupSchedule(schedule: string, log: PluginLogger) {\n clearSchedule();\n\n if (schedule === 'realtime') {\n log.info('Sync schedule: realtime (file watcher active)');\n return;\n }\n\n const intervalMs = SCHEDULE_INTERVALS_MS[schedule];\n if (!intervalMs) {\n log.warn(`Unknown sync schedule: ${schedule}, defaulting to hourly`);\n setupSchedule('hourly', log);\n return;\n }\n\n log.info(`Sync schedule: ${schedule} (every ${String(intervalMs / 1000)}s)`);\n scheduledInterval = setInterval(() => {\n if (!syncEngine) return;\n const engine = syncEngine;\n void (async () => {\n try {\n log.info(`Scheduled sync (${schedule}) starting...`);\n lastSyncResult = await engine.fullSync({ quiet: true });\n log.info(\n `Scheduled sync complete: ${String(lastSyncResult.pushed)} pushed, ${String(lastSyncResult.pulled)} pulled`,\n );\n } catch (err: unknown) {\n log.error(`Scheduled sync failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n })();\n }, intervalMs);\n}\n\n// ── Daemon IPC ──────────────────────────────────────────────\n\nasync function connectToDaemon(socketPath: string, log: PluginLogger): Promise<IpcClient | null> {\n try {\n const modPath = '@alfe.ai/openclaw';\n const mod = await import(/* webpackIgnore: true */ modPath) as {\n IPCClient: new (socketPath: string, log: PluginLogger) => IpcClient;\n };\n const IPCClient = mod.IPCClient;\n const client = new IPCClient(socketPath, log);\n\n client.on('connected', () => {\n void (async () => {\n log.info('Connected to Alfe daemon — registering sync capabilities...');\n const response = await client.request('capability.register', {\n plugin: '@alfe.ai/openclaw-sync',\n capabilities: [...SYNC_CAPABILITIES],\n });\n if (response.ok) {\n log.info('Sync capabilities registered with daemon');\n } else {\n log.warn(`Failed to register sync capabilities: ${response.error?.message ?? 'unknown'}`);\n }\n })();\n });\n\n client.on('disconnected', (...args: unknown[]) => {\n const reason = typeof args[0] === 'string' ? args[0] : String(args[0]);\n log.warn(`Disconnected from Alfe daemon: ${reason}`);\n });\n\n client.on('message', (...args: unknown[]) => {\n const msg = args[0] as Record<string, unknown> | undefined;\n if (msg?.type === 'SYNC_NOW' || msg?.command === 'SYNC_NOW') {\n log.info('Received SYNC_NOW command — triggering immediate sync...');\n if (syncEngine) {\n const engine = syncEngine;\n void (async () => {\n try {\n lastSyncResult = await engine.fullSync({ quiet: true });\n log.info(\n `SYNC_NOW complete: ${String(lastSyncResult.pushed)} pushed, ${String(lastSyncResult.pulled)} pulled`,\n );\n } catch (err: unknown) {\n log.error(`SYNC_NOW failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n })();\n }\n }\n\n // Handle SHARED_SCOPES from gateway (via daemon)\n if (msg?.type === 'SHARED_SCOPES') {\n const scopes = msg.scopes as SharedScope[] | undefined;\n const engine = sharedSyncEngine;\n if (scopes && engine) {\n log.info(`Received SHARED_SCOPES update: ${String(scopes.length)} scope(s)`);\n void (async () => {\n try {\n await engine.updateScopes(scopes);\n } catch (err: unknown) {\n log.error(`SHARED_SCOPES update failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n })();\n }\n }\n });\n\n client.on('error', (...args: unknown[]) => {\n const err = args[0];\n log.debug(`Daemon IPC error: ${err instanceof Error ? err.message : String(err)}`);\n });\n\n client.start();\n return client;\n } catch {\n log.info('Alfe daemon not available — Sync plugin running standalone');\n return null;\n }\n}\n\n// ── Sync Relay Connection ───────────────────────────────────\n\nfunction clearSyncRelayReconnect() {\n if (syncRelayReconnectTimer) {\n clearTimeout(syncRelayReconnectTimer);\n syncRelayReconnectTimer = null;\n }\n}\n\nfunction clearSyncRelayDebounce() {\n if (syncRelayDebounceTimer) {\n clearTimeout(syncRelayDebounceTimer);\n syncRelayDebounceTimer = null;\n }\n}\n\nasync function processPendingNotifications(log: PluginLogger) {\n if (syncRelayPendingPaths.size === 0) return;\n\n const entries = new Map(syncRelayPendingPaths);\n syncRelayPendingPaths.clear();\n\n // Separate shared vs private notifications\n const sharedEntries = new Map<string, { etag?: string; eventType: 'created' | 'deleted' }>();\n const privateEntries = new Map<string, { etag?: string; eventType: 'created' | 'deleted' }>();\n\n for (const [filePath, info] of entries) {\n if (filePath.startsWith('shared/')) {\n sharedEntries.set(filePath, info);\n } else {\n privateEntries.set(filePath, info);\n }\n }\n\n // Handle shared file notifications\n if (sharedEntries.size > 0 && sharedSyncEngine) {\n for (const [filePath, info] of sharedEntries) {\n try {\n await sharedSyncEngine.handleNotification(filePath, info.eventType);\n } catch (err: unknown) {\n log.error(`Shared sync relay: failed ${info.eventType} ${filePath}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n }\n\n // Handle private file notifications\n if (privateEntries.size > 0 && syncEngine) {\n const engine = syncEngine;\n const toPull: string[] = [];\n const toDelete: string[] = [];\n\n for (const [filePath, info] of privateEntries) {\n if (info.eventType === 'deleted') {\n toDelete.push(filePath);\n } else {\n toPull.push(filePath);\n }\n }\n\n // Handle deletes\n for (const filePath of toDelete) {\n try {\n await engine.removeLocalFile(filePath, { quiet: true });\n log.debug(`Sync relay: deleted ${filePath}`);\n } catch (err: unknown) {\n log.error(`Sync relay: failed to delete ${filePath}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // Handle pulls\n if (toPull.length > 0) {\n try {\n const result = await engine.pullFiles(toPull, { quiet: true });\n if (result.pulled > 0) {\n log.info(`Sync relay: pulled ${String(result.pulled)} file(s)`);\n }\n if (result.errors > 0) {\n log.warn(`Sync relay: ${String(result.errors)} pull error(s)`);\n }\n } catch (err: unknown) {\n log.error(`Sync relay: pull failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n }\n}\n\nasync function connectToSyncRelay(\n relayUrl: string,\n token: string,\n agentId: string,\n log: PluginLogger,\n): Promise<SyncRelaySocket | null> {\n try {\n const { default: WebSocket } = await import('ws');\n\n const wsUrl = `${relayUrl}?token=${encodeURIComponent(token)}`;\n const ws = new WebSocket(wsUrl);\n\n ws.on('open', () => {\n log.info('Connected to Sync Relay');\n syncRelayReconnectAttempt = 0;\n\n // Subscribe to file changes for our agent\n ws.send(JSON.stringify({ type: 'SUBSCRIBE', agentId }));\n });\n\n ws.on('message', (data: Buffer | string) => {\n let message: RelayMessage;\n try {\n message = JSON.parse(data.toString()) as RelayMessage;\n } catch {\n return;\n }\n\n switch (message.type) {\n case 'SUBSCRIBE_ACK':\n if (message.status === 'ok') {\n log.info(`Subscribed to sync notifications for agent ${message.agentId ?? agentId}`);\n // On reconnect, trigger shared file full sync to catch up on missed notifications\n const sharedEngine = sharedSyncEngine;\n if (sharedEngine) {\n void (async () => {\n try {\n await sharedEngine.fullSync();\n log.info('Shared sync: reconnect full sync complete');\n } catch (err: unknown) {\n log.error(`Shared sync: reconnect full sync failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n })();\n }\n } else {\n log.warn(`Sync relay subscribe failed: ${message.message ?? 'unknown'}`);\n }\n break;\n\n case 'FILE_CHANGED':\n // Accumulate file paths, debounce before acting\n if (message.filePath) {\n syncRelayPendingPaths.set(message.filePath, {\n etag: message.etag,\n eventType: (message.eventType === 'deleted' ? 'deleted' : 'created'),\n });\n }\n\n clearSyncRelayDebounce();\n syncRelayDebounceTimer = setTimeout(() => {\n void processPendingNotifications(log);\n }, SYNC_RELAY_DEBOUNCE_MS);\n break;\n\n case 'PING':\n try {\n ws.send(JSON.stringify({ type: 'PONG' }));\n } catch {\n // Ignore\n }\n break;\n }\n });\n\n ws.on('close', (code: number) => {\n log.info(`Sync Relay disconnected (code=${String(code)})`);\n syncRelayWs = null;\n scheduleSyncRelayReconnect(relayUrl, token, agentId, log);\n });\n\n ws.on('error', (err: Error) => {\n log.debug(`Sync Relay error: ${err.message}`);\n });\n\n return ws as SyncRelaySocket;\n } catch (err: unknown) {\n log.debug(`Failed to connect to Sync Relay: ${err instanceof Error ? err.message : String(err)}`);\n scheduleSyncRelayReconnect(relayUrl, token, agentId, log);\n return null;\n }\n}\n\nfunction scheduleSyncRelayReconnect(\n relayUrl: string,\n token: string,\n agentId: string,\n log: PluginLogger,\n) {\n clearSyncRelayReconnect();\n\n const delay = Math.min(\n SYNC_RELAY_RECONNECT_BASE_MS * Math.pow(2, syncRelayReconnectAttempt),\n SYNC_RELAY_RECONNECT_MAX_MS,\n );\n syncRelayReconnectAttempt++;\n\n log.debug(`Reconnecting to Sync Relay in ${String(delay)}ms (attempt ${String(syncRelayReconnectAttempt)})`);\n\n syncRelayReconnectTimer = setTimeout(() => {\n void (async () => {\n syncRelayWs = await connectToSyncRelay(relayUrl, token, agentId, log);\n })();\n }, delay);\n}\n\nfunction disconnectSyncRelay() {\n clearSyncRelayReconnect();\n clearSyncRelayDebounce();\n syncRelayPendingPaths.clear();\n\n if (syncRelayWs) {\n try {\n syncRelayWs.send(JSON.stringify({ type: 'UNSUBSCRIBE' }));\n syncRelayWs.close(1000, 'Plugin deactivating');\n } catch {\n // Ignore\n }\n syncRelayWs = null;\n }\n}\n\n// ── Plugin Definition ───────────────────────────────────────\n\nconst plugin = {\n id: '@alfe.ai/openclaw-sync',\n name: 'Alfe Sync Plugin',\n description:\n 'Back up agent configuration, conversations, and memory to the cloud with scheduled or real-time sync.',\n version: pkg.version,\n\n activate(api: PluginApi) {\n const log = api.logger;\n\n // ── Parse config ────────────────────────────────────────\n const pluginConfig: SyncPluginConfig = api.config ?? {};\n currentConfig = pluginConfig;\n\n let alfeConfig: { workspacePath: string; socketPath: string } | null = null;\n try {\n alfeConfig = resolveAlfeConfig();\n } catch {\n // Config not available\n }\n\n const workspacePath =\n pluginConfig.workspacePath ??\n alfeConfig?.workspacePath ??\n DEFAULT_WORKSPACE_PATH;\n\n const syncScope = pluginConfig.syncScope ?? ['config', 'conversations', 'memory'];\n const syncSchedule = pluginConfig.syncSchedule ?? 'daily';\n const socketPath =\n pluginConfig.socketPath ??\n alfeConfig?.socketPath ??\n DEFAULT_SOCKET_PATH;\n\n // ── Service start/stop — extracted for registerService ──\n const startSyncService = async () => {\n if ((globalThis as Record<string, unknown>).__alfeSyncPluginActivated === true) {\n log.debug('Alfe Sync plugin already activated — skipping duplicate');\n return;\n }\n (globalThis as Record<string, unknown>).__alfeSyncPluginActivated = true;\n log.info('Alfe Sync plugin activating...');\n\n log.info(`Sync scope: ${syncScope.join(', ')}`);\n log.info(`Sync schedule: ${syncSchedule}`);\n log.info(`Workspace: ${workspacePath}`);\n\n // Initialize sync engine (if workspace is configured)\n if (isInitialized(workspacePath)) {\n try {\n syncEngine = await createSyncEngine(workspacePath);\n log.info('Sync engine initialized');\n } catch (err: unknown) {\n log.warn(`Failed to initialize sync engine: ${err instanceof Error ? err.message : String(err)}`);\n log.warn('Sync will be available once the workspace is configured (alfesync init)');\n }\n } else {\n log.info('Workspace not initialized for sync — run alfesync init to enable');\n }\n\n // Start file watcher for realtime mode\n if (syncSchedule === 'realtime' && syncEngine) {\n try {\n stopWatcher = await startWatcher({\n workspacePath,\n debounceMs: 2000,\n onChanges: async (paths) => {\n if (!syncEngine) return;\n log.debug(`Realtime sync: ${String(paths.length)} file(s) changed`);\n try {\n lastSyncResult = await syncEngine.push(paths, { quiet: true });\n } catch (err: unknown) {\n log.error(`Realtime push failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n },\n });\n log.info('File watcher started for realtime sync');\n } catch (err: unknown) {\n log.warn(`Failed to start file watcher: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // Setup scheduled sync\n if (syncSchedule !== 'realtime' && syncEngine) {\n setupSchedule(syncSchedule, log);\n }\n\n // Connect to Alfe daemon IPC\n daemonIpcClient = await connectToDaemon(socketPath, log);\n\n // Connect to Sync Relay for realtime notifications\n if (syncEngine) {\n try {\n const config = await readConfig(workspacePath);\n if (config) {\n const defaultRelayUrl =\n config.apiUrl.includes('dev.alfe.ai')\n ? 'wss://sync.dev.alfe.ai/ws'\n : 'wss://sync.alfe.ai/ws';\n const relayUrl =\n pluginConfig.syncRelayUrl ??\n defaultRelayUrl;\n\n syncRelayWs = await connectToSyncRelay(\n relayUrl,\n config.token,\n config.agentId,\n log,\n );\n }\n } catch (err: unknown) {\n log.debug(`Sync Relay connection skipped: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // Initialize shared sync engine\n if (syncEngine && (pluginConfig.sharedSync !== false)) {\n try {\n const sharedConfig = await readConfig(workspacePath);\n if (sharedConfig) {\n sharedSyncEngine = createSharedSyncEngine(\n {\n workspacePath,\n apiUrl: sharedConfig.apiUrl,\n token: sharedConfig.token,\n agentId: sharedConfig.agentId,\n },\n log,\n );\n log.info('Shared sync engine created — waiting for SHARED_SCOPES from gateway');\n }\n } catch (err: unknown) {\n log.debug(`Shared sync engine skipped: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n };\n\n const stopSyncService = async () => {\n (globalThis as Record<string, unknown>).__alfeSyncPluginActivated = false;\n\n clearSchedule();\n\n // Disconnect from Sync Relay\n disconnectSyncRelay();\n\n if (stopWatcher) {\n try {\n await stopWatcher();\n log.info('File watcher stopped');\n } catch (err: unknown) {\n log.debug(`Error stopping watcher: ${err instanceof Error ? err.message : String(err)}`);\n }\n stopWatcher = null;\n }\n\n if (daemonIpcClient) {\n try {\n daemonIpcClient.stop();\n log.info('Disconnected from Alfe daemon');\n } catch (err: unknown) {\n log.debug(`Error disconnecting from daemon: ${err instanceof Error ? err.message : String(err)}`);\n }\n daemonIpcClient = null;\n }\n\n syncEngine = null;\n sharedSyncEngine = null;\n lastSyncResult = null;\n currentConfig = {};\n\n log.info('Alfe Sync plugin deactivated');\n };\n\n // ── Register gateway RPCs (synchronous — available immediately) ──\n if (typeof api.registerGatewayMethod === 'function') {\n api.registerGatewayMethod('sync.now', async () => {\n if (!syncEngine) {\n return { ok: false, error: 'Sync engine not initialized — run alfesync init' };\n }\n try {\n lastSyncResult = await syncEngine.fullSync({ quiet: true });\n return { ok: true, result: lastSyncResult };\n } catch (err: unknown) {\n return { ok: false, error: err instanceof Error ? err.message : String(err) };\n }\n });\n log.info('Registered gateway RPC method: sync.now');\n\n api.registerGatewayMethod('sync.status', () => {\n return Promise.resolve({\n ok: true,\n initialized: !!syncEngine,\n schedule: currentConfig.syncSchedule ?? 'daily',\n scope: currentConfig.syncScope ?? ['config', 'conversations', 'memory'],\n lastResult: lastSyncResult,\n watcherActive: !!stopWatcher,\n });\n });\n log.info('Registered gateway RPC method: sync.status');\n }\n\n // ── Defer connections to gateway lifecycle via registerService ──\n if (api.registerService) {\n api.registerService({\n id: 'alfe-sync-engine',\n start: () => startSyncService(),\n stop: () => stopSyncService(),\n });\n } else {\n // Fallback for older OpenClaw versions without registerService\n void startSyncService().catch((err: unknown) => {\n log.error(`Sync plugin async init failed: ${err instanceof Error ? err.message : String(err)}`);\n });\n }\n\n log.info('Alfe Sync plugin activated');\n },\n\n async deactivate(api: PluginApi) {\n // Legacy fallback — cleanup when registerService is not available\n (globalThis as Record<string, unknown>).__alfeSyncPluginActivated = false;\n\n const log = api.logger;\n log.info('Alfe Sync plugin deactivating...');\n\n clearSchedule();\n\n // Disconnect from Sync Relay\n disconnectSyncRelay();\n\n if (stopWatcher) {\n try {\n await stopWatcher();\n log.info('File watcher stopped');\n } catch (err: unknown) {\n log.debug(`Error stopping watcher: ${err instanceof Error ? err.message : String(err)}`);\n }\n stopWatcher = null;\n }\n\n if (daemonIpcClient) {\n try {\n daemonIpcClient.stop();\n log.info('Disconnected from Alfe daemon');\n } catch (err: unknown) {\n log.debug(`Error disconnecting from daemon: ${err instanceof Error ? err.message : String(err)}`);\n }\n daemonIpcClient = null;\n }\n\n syncEngine = null;\n sharedSyncEngine = null;\n lastSyncResult = null;\n currentConfig = {};\n\n log.info('Alfe Sync plugin deactivated');\n },\n\n /**\n * Runtime config update — apply new scope/schedule without full restart.\n */\n async configure(api: PluginApi, config: SyncPluginConfig) {\n const log = api.logger;\n log.info('Reconfiguring Alfe Sync plugin...');\n\n currentConfig = { ...currentConfig, ...config };\n\n if (config.syncSchedule) {\n if (config.syncSchedule === 'realtime' && syncEngine && !stopWatcher) {\n let cfgForWorkspace: { workspacePath: string } | null = null;\n try { cfgForWorkspace = resolveAlfeConfig(); } catch { /* not available */ }\n const workspacePath =\n currentConfig.workspacePath ??\n cfgForWorkspace?.workspacePath ??\n '';\n\n stopWatcher = await startWatcher({\n workspacePath,\n debounceMs: 2000,\n onChanges: async (paths) => {\n if (!syncEngine) return;\n try {\n lastSyncResult = await syncEngine.push(paths, { quiet: true });\n } catch (err: unknown) {\n log.error(`Realtime push failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n },\n });\n clearSchedule();\n log.info('Switched to realtime sync');\n } else if (config.syncSchedule !== 'realtime') {\n if (stopWatcher) {\n await stopWatcher();\n stopWatcher = null;\n }\n setupSchedule(config.syncSchedule, log);\n }\n }\n\n if (config.syncScope) {\n log.info(`Updated sync scope: ${config.syncScope.join(', ')}`);\n }\n\n log.info('Alfe Sync plugin reconfigured');\n },\n};\n\nexport default plugin;\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/openclaw-sync",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "AlfeSync — agent workspace backup and sync skill for OpenClaw",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -27,6 +27,11 @@
|
|
|
27
27
|
"dist",
|
|
28
28
|
"openclaw.plugin.json"
|
|
29
29
|
],
|
|
30
|
+
"openclaw": {
|
|
31
|
+
"extensions": [
|
|
32
|
+
"./dist/plugin.js"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
30
35
|
"license": "UNLICENSED",
|
|
31
36
|
"scripts": {
|
|
32
37
|
"build": "tsdown",
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["resolveAlfeConfig"],"sources":["../src/watcher.ts","../src/shared-sync.ts","../src/plugin.ts"],"sourcesContent":["/**\n * AlfeSync watcher — recursive file watcher with debounce and ignore support.\n *\n * Uses chokidar to watch the workspace root, debounces per-file changes\n * by 2 seconds, and emits batches of changed paths.\n */\n\nimport { watch } from \"chokidar\";\nimport { relative } from \"node:path\";\nimport { loadIgnorePatterns, shouldIgnore } from \"./ignore.js\";\n\nexport interface WatcherOptions {\n workspacePath: string;\n /** Debounce delay per file in milliseconds. Default: 2000 */\n debounceMs?: number;\n /** Callback invoked with batches of changed relative paths */\n onChanges: (paths: string[]) => void | Promise<void>;\n}\n\n/**\n * Start watching a workspace for file changes.\n *\n * Returns a cleanup function to stop watching.\n */\nexport async function startWatcher(\n options: WatcherOptions,\n): Promise<() => Promise<void>> {\n const { workspacePath, debounceMs = 2000, onChanges } = options;\n const ignorePatterns = await loadIgnorePatterns(workspacePath);\n\n // Pending changes map: relativePath → timeout handle\n const pending = new Map<string, ReturnType<typeof setTimeout>>();\n // Batch accumulator for flushing\n let batchPaths = new Set<string>();\n let flushTimer: ReturnType<typeof setTimeout> | null = null;\n\n function scheduleBatch() {\n if (flushTimer) return;\n flushTimer = setTimeout(() => {\n flushTimer = null;\n if (batchPaths.size === 0) return;\n\n const paths = [...batchPaths];\n batchPaths = new Set();\n void onChanges(paths);\n }, debounceMs);\n }\n\n function handleChange(absolutePath: string) {\n const relativePath = relative(workspacePath, absolutePath);\n\n // Skip ignored files\n if (shouldIgnore(relativePath, ignorePatterns)) return;\n\n // Clear existing timer for this file\n const existingTimer = pending.get(relativePath);\n if (existingTimer) clearTimeout(existingTimer);\n\n // Debounce: wait before adding to batch\n const timer = setTimeout(() => {\n pending.delete(relativePath);\n batchPaths.add(relativePath);\n scheduleBatch();\n }, debounceMs);\n\n pending.set(relativePath, timer);\n }\n\n const watcher = watch(workspacePath, {\n persistent: true,\n ignoreInitial: true,\n followSymlinks: false,\n depth: undefined, // unlimited depth\n ignored: [\n \"**/node_modules/**\",\n \"**/.alfesync/**\",\n \"**/.git/**\",\n \"**/.sst/**\",\n ],\n });\n\n watcher.on(\"add\", handleChange);\n watcher.on(\"change\", handleChange);\n watcher.on(\"unlink\", handleChange);\n\n // Return cleanup function\n return async () => {\n // Clear all pending timers\n for (const timer of pending.values()) {\n clearTimeout(timer);\n }\n pending.clear();\n\n if (flushTimer) {\n clearTimeout(flushTimer);\n flushTimer = null;\n }\n\n await watcher.close();\n };\n}\n","/**\n * Shared file sync engine for org/team/project scoped files.\n *\n * Syncs shared files to a `shared/` directory in the agent's workspace,\n * organized by scope: shared/org/, shared/teams/{id}/, shared/projects/{id}/\n *\n * Uses the agent self-service API (/agents/org/...) with the agent's API key.\n */\n\nimport { join, dirname, normalize, sep } from \"node:path\";\nimport { mkdir, writeFile, unlink, rm } from \"node:fs/promises\";\n\nconst MAX_SHARED_FILE_SIZE = 100 * 1024 * 1024; // 100 MB\n\n/** Verify that resolvedPath stays within baseDir. Prevents path traversal. */\nfunction assertContained(baseDir: string, resolvedPath: string): void {\n const normalizedBase = normalize(baseDir) + sep;\n const normalizedPath = normalize(resolvedPath);\n if (!normalizedPath.startsWith(normalizedBase) && normalizedPath !== normalize(baseDir)) {\n throw new Error(`Path traversal blocked: ${resolvedPath} escapes ${baseDir}`);\n }\n}\n\n// ── Types ──────────────────────────────────────────────────\n\nexport interface SharedScope {\n scopeType: \"team\" | \"project\" | \"org\";\n scopeId: string;\n name: string;\n}\n\nexport interface SharedSyncConfig {\n workspacePath: string;\n apiUrl: string;\n token: string;\n agentId: string;\n}\n\ninterface SharedFileEntry {\n filePath: string;\n fileName: string;\n size: number;\n contentType?: string;\n}\n\ninterface PluginLogger {\n info(msg: string): void;\n warn(msg: string): void;\n error(msg: string): void;\n debug(msg: string): void;\n}\n\n// ── HTTP helpers ───────────────────────────────────────────\n\nasync function fetchJson<T>(url: string, token: string): Promise<T> {\n const response = await fetch(url, {\n headers: {\n \"Authorization\": `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n });\n if (!response.ok) {\n let errorBody = \"\";\n try {\n errorBody = await response.text();\n } catch {\n errorBody = \"(unable to read error body)\";\n }\n throw new Error(`HTTP ${String(response.status)}: ${errorBody}`);\n }\n return response.json() as Promise<T>;\n}\n\n// ── SharedSyncEngine ───────────────────────────────────────\n\nexport interface SharedSyncEngine {\n initialize(scopes: SharedScope[]): Promise<void>;\n updateScopes(scopes: SharedScope[]): Promise<void>;\n handleNotification(filePath: string, eventType: \"created\" | \"deleted\"): Promise<void>;\n fullSync(): Promise<void>;\n getScopes(): SharedScope[];\n}\n\nexport function createSharedSyncEngine(\n config: SharedSyncConfig,\n log: PluginLogger,\n): SharedSyncEngine {\n let activeScopes: SharedScope[] = [];\n const sharedDir = join(config.workspacePath, \"shared\");\n\n function scopeDir(scope: SharedScope): string {\n if (scope.scopeType === \"org\") return join(sharedDir, \"org\");\n const plural = scope.scopeType === \"team\" ? \"teams\" : \"projects\";\n return join(sharedDir, plural, scope.scopeId);\n }\n\n function apiBase(): string {\n return `${config.apiUrl}/agents/org`;\n }\n\n async function listRemoteFiles(scope: SharedScope): Promise<SharedFileEntry[]> {\n const url = `${apiBase()}/files/${scope.scopeType}/${scope.scopeId}`;\n const result = await fetchJson<{ files: SharedFileEntry[] }>(url, config.token);\n return result.files;\n }\n\n async function downloadFile(scope: SharedScope, filePath: string, localPath: string): Promise<void> {\n // Prevent path traversal — ensure localPath stays within scope directory\n assertContained(scopeDir(scope), localPath);\n\n const url = `${apiBase()}/files/${scope.scopeType}/${scope.scopeId}/${encodeURIComponent(filePath)}/download`;\n const result = await fetchJson<{ downloadUrl: string }>(url, config.token);\n\n const response = await fetch(result.downloadUrl);\n if (!response.ok) throw new Error(`Download failed: HTTP ${String(response.status)}`);\n\n const contentLength = parseInt(response.headers.get(\"content-length\") ?? \"0\", 10);\n if (contentLength > MAX_SHARED_FILE_SIZE) {\n throw new Error(`File too large: ${String(contentLength)} bytes exceeds ${String(MAX_SHARED_FILE_SIZE)} limit`);\n }\n\n const buffer = Buffer.from(await response.arrayBuffer());\n if (buffer.length > MAX_SHARED_FILE_SIZE) {\n throw new Error(`Downloaded file exceeds size limit: ${String(buffer.length)} bytes`);\n }\n\n await mkdir(dirname(localPath), { recursive: true });\n await writeFile(localPath, buffer);\n }\n\n async function syncScope(scope: SharedScope): Promise<void> {\n const dir = scopeDir(scope);\n await mkdir(dir, { recursive: true });\n\n try {\n const remoteFiles = await listRemoteFiles(scope);\n\n for (const file of remoteFiles) {\n const localPath = join(dir, file.filePath);\n try {\n await downloadFile(scope, file.filePath, localPath);\n log.debug(`Shared sync: downloaded ${scope.scopeType}/${scope.scopeId}/${file.filePath}`);\n } catch (err: unknown) {\n log.error(`Shared sync: failed to download ${file.filePath}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n } catch (err: unknown) {\n log.error(`Shared sync: failed to list files for ${scope.scopeType}/${scope.scopeId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n function parseScopedPath(filePath: string): { scope: SharedScope; relativePath: string } | null {\n // Reject any path traversal attempts\n if (filePath.includes(\"..\")) return null;\n\n // shared/org/{relativePath}\n const orgMatch = /^shared\\/org\\/(.+)$/.exec(filePath);\n if (orgMatch) {\n const scope = activeScopes.find((s) => s.scopeType === \"org\");\n if (scope) return { scope, relativePath: orgMatch[1] };\n }\n\n // shared/teams/{scopeId}/{relativePath}\n const teamMatch = /^shared\\/teams\\/([^/]+)\\/(.+)$/.exec(filePath);\n if (teamMatch) {\n const scope = activeScopes.find((s) => s.scopeType === \"team\" && s.scopeId === teamMatch[1]);\n if (scope) return { scope, relativePath: teamMatch[2] };\n }\n\n // shared/projects/{scopeId}/{relativePath}\n const projectMatch = /^shared\\/projects\\/([^/]+)\\/(.+)$/.exec(filePath);\n if (projectMatch) {\n const scope = activeScopes.find((s) => s.scopeType === \"project\" && s.scopeId === projectMatch[1]);\n if (scope) return { scope, relativePath: projectMatch[2] };\n }\n\n return null;\n }\n\n return {\n async initialize(scopes: SharedScope[]): Promise<void> {\n activeScopes = [...scopes];\n log.info(`Shared sync: initializing with ${String(scopes.length)} scope(s)`);\n\n await mkdir(sharedDir, { recursive: true });\n\n for (const scope of scopes) {\n await syncScope(scope);\n }\n\n log.info(\"Shared sync: initialization complete\");\n },\n\n async updateScopes(newScopes: SharedScope[]): Promise<void> {\n const oldIds = new Set(activeScopes.map((s) => `${s.scopeType}:${s.scopeId}`));\n const newIds = new Set(newScopes.map((s) => `${s.scopeType}:${s.scopeId}`));\n\n // Removed scopes — delete local directories\n for (const scope of activeScopes) {\n const key = `${scope.scopeType}:${scope.scopeId}`;\n if (!newIds.has(key)) {\n const dir = scopeDir(scope);\n try {\n await rm(dir, { recursive: true, force: true });\n log.info(`Shared sync: removed scope directory ${dir}`);\n } catch (err: unknown) {\n log.warn(`Shared sync: failed to remove ${dir}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n }\n\n // New scopes — create and sync\n for (const scope of newScopes) {\n const key = `${scope.scopeType}:${scope.scopeId}`;\n if (!oldIds.has(key)) {\n log.info(`Shared sync: new scope ${scope.scopeType}/${scope.scopeId} — syncing files`);\n await syncScope(scope);\n }\n }\n\n activeScopes = [...newScopes];\n },\n\n async handleNotification(filePath: string, eventType: \"created\" | \"deleted\"): Promise<void> {\n const parsed = parseScopedPath(filePath);\n if (!parsed) {\n log.debug(`Shared sync: ignoring notification for unknown path: ${filePath}`);\n return;\n }\n\n const dir = scopeDir(parsed.scope);\n const localPath = join(dir, parsed.relativePath);\n\n // Prevent path traversal — ensure localPath stays within scope directory\n try {\n assertContained(dir, localPath);\n } catch {\n log.warn(`Shared sync: path traversal blocked for ${filePath}`);\n return;\n }\n\n if (eventType === \"deleted\") {\n try {\n await unlink(localPath);\n log.debug(`Shared sync: deleted ${filePath}`);\n } catch {\n // Idempotent — file may not exist locally (out-of-order events)\n }\n return;\n }\n\n // eventType === \"created\"\n try {\n await downloadFile(parsed.scope, parsed.relativePath, localPath);\n log.debug(`Shared sync: pulled ${filePath}`);\n } catch (err: unknown) {\n log.error(`Shared sync: failed to pull ${filePath}: ${err instanceof Error ? err.message : String(err)}`);\n }\n },\n\n async fullSync(): Promise<void> {\n log.info(`Shared sync: full sync across ${String(activeScopes.length)} scope(s)`);\n for (const scope of activeScopes) {\n await syncScope(scope);\n }\n },\n\n getScopes(): SharedScope[] {\n return [...activeScopes];\n },\n };\n}\n","/**\n * @alfe.ai/openclaw-sync — OpenClaw Sync plugin.\n *\n * Wraps the existing sync engine as a lifecycle-managed integration,\n * following the same plugin pattern as @alfe.ai/openclaw-mobile and\n * @alfe.ai/openclaw-discord.\n *\n * Lifecycle:\n * - activate(api): start the sync engine + watcher based on config\n * - deactivate(api): stop the watcher, clean up resources\n * - configure(api, config): update scope/schedule at runtime\n *\n * Registers 'sync.now' and 'sync.status' gateway RPC methods.\n */\n\nimport { createSyncEngine } from './sync-engine.js';\nimport { startWatcher } from './watcher.js';\nimport { isInitialized, readConfig } from './config.js';\nimport type { SyncEngine, SyncResult } from './sync-engine.js';\nimport { createSharedSyncEngine } from './shared-sync.js';\nimport type { SharedSyncEngine, SharedScope } from './shared-sync.js';\nimport { resolveConfig as resolveAlfeConfig, DEFAULT_SOCKET_PATH, DEFAULT_WORKSPACE_PATH } from '@alfe.ai/config';\nimport { createRequire } from 'node:module';\nconst require = createRequire(import.meta.url);\nconst pkg = require('../package.json') as { version: string };\n\n// ── Constants ───────────────────────────────────────────────\nconst SYNC_CAPABILITIES = ['sync.push', 'sync.pull', 'sync.fullSync'] as const;\nconst SYNC_RELAY_RECONNECT_BASE_MS = 1000;\nconst SYNC_RELAY_RECONNECT_MAX_MS = 30000;\nconst SYNC_RELAY_DEBOUNCE_MS = 500;\n\n// ── Types ───────────────────────────────────────────────────\n\n/** Logger interface used by the plugin API */\ninterface PluginLogger {\n info(msg: string): void;\n warn(msg: string): void;\n error(msg: string): void;\n debug(msg: string): void;\n}\n\ninterface PluginServiceContext {\n config: Record<string, unknown>;\n workspaceDir?: string;\n stateDir: string;\n logger: PluginLogger;\n}\n\n/** Plugin API passed to activate/deactivate/configure */\ninterface PluginApi {\n logger: PluginLogger;\n registrationMode?: 'full' | 'setup-only' | 'setup-runtime' | 'cli-metadata';\n config?: SyncPluginConfig;\n registerGatewayMethod?: (name: string, handler: () => Promise<unknown>) => void;\n registerService?(service: {\n id: string;\n start: (ctx: PluginServiceContext) => void | Promise<void>;\n stop?: (ctx: PluginServiceContext) => void | Promise<void>;\n }): void;\n}\n\n/** IPC client interface for daemon communication */\ninterface IpcClient {\n on(event: string, handler: (...args: unknown[]) => void): void;\n request(method: string, params: unknown): Promise<{ ok: boolean; error?: { message: string } }>;\n start(): void;\n stop(): void;\n}\n\n/** WebSocket-like interface for the sync relay */\ninterface SyncRelaySocket {\n on(event: string, handler: (...args: never[]) => void): void;\n send(data: string): void;\n close(code?: number, reason?: string): void;\n}\n\n/** Parsed relay message */\ninterface RelayMessage {\n type: string;\n status?: string;\n agentId?: string;\n message?: string;\n filePath?: string;\n etag?: string;\n eventType?: string;\n}\n\nexport interface SyncPluginConfig {\n /** Workspace path to sync. Defaults to ~/.openclaw */\n workspacePath?: string;\n /** Sync scope — which data categories to sync */\n syncScope?: ('config' | 'conversations' | 'memory')[];\n /** Sync schedule — how often to auto-sync */\n syncSchedule?: 'realtime' | 'hourly' | 'daily' | 'weekly';\n /** IPC socket path override */\n socketPath?: string;\n /** Sync relay URL override (default: wss://sync.alfe.ai/ws) */\n syncRelayUrl?: string;\n /** Enable shared file sync for org/team/project scopes. Default: true */\n sharedSync?: boolean;\n}\n\n// ── Plugin State ────────────────────────────────────────────\n\nlet syncEngine: SyncEngine | null = null;\nlet sharedSyncEngine: SharedSyncEngine | null = null;\nlet stopWatcher: (() => Promise<void>) | null = null;\nlet daemonIpcClient: IpcClient | null = null;\nlet scheduledInterval: ReturnType<typeof setInterval> | null = null;\nlet currentConfig: SyncPluginConfig = {};\nlet lastSyncResult: SyncResult | null = null;\nlet syncRelayWs: SyncRelaySocket | null = null;\nlet syncRelayReconnectTimer: ReturnType<typeof setTimeout> | null = null;\nlet syncRelayReconnectAttempt = 0;\nlet syncRelayDebounceTimer: ReturnType<typeof setTimeout> | null = null;\nconst syncRelayPendingPaths =\n new Map<string, { etag?: string; eventType: 'created' | 'deleted' }>();\n\n// ── Schedule Helpers ────────────────────────────────────────\n\nconst SCHEDULE_INTERVALS_MS: Record<string, number> = {\n hourly: 60 * 60 * 1000,\n daily: 24 * 60 * 60 * 1000,\n weekly: 7 * 24 * 60 * 60 * 1000,\n};\n\nfunction clearSchedule() {\n if (scheduledInterval) {\n clearInterval(scheduledInterval);\n scheduledInterval = null;\n }\n}\n\nfunction setupSchedule(schedule: string, log: PluginLogger) {\n clearSchedule();\n\n if (schedule === 'realtime') {\n log.info('Sync schedule: realtime (file watcher active)');\n return;\n }\n\n const intervalMs = SCHEDULE_INTERVALS_MS[schedule];\n if (!intervalMs) {\n log.warn(`Unknown sync schedule: ${schedule}, defaulting to hourly`);\n setupSchedule('hourly', log);\n return;\n }\n\n log.info(`Sync schedule: ${schedule} (every ${String(intervalMs / 1000)}s)`);\n scheduledInterval = setInterval(() => {\n if (!syncEngine) return;\n const engine = syncEngine;\n void (async () => {\n try {\n log.info(`Scheduled sync (${schedule}) starting...`);\n lastSyncResult = await engine.fullSync({ quiet: true });\n log.info(\n `Scheduled sync complete: ${String(lastSyncResult.pushed)} pushed, ${String(lastSyncResult.pulled)} pulled`,\n );\n } catch (err: unknown) {\n log.error(`Scheduled sync failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n })();\n }, intervalMs);\n}\n\n// ── Daemon IPC ──────────────────────────────────────────────\n\nasync function connectToDaemon(socketPath: string, log: PluginLogger): Promise<IpcClient | null> {\n try {\n const modPath = '@alfe.ai/openclaw';\n const mod = await import(/* webpackIgnore: true */ modPath) as {\n IPCClient: new (socketPath: string, log: PluginLogger) => IpcClient;\n };\n const IPCClient = mod.IPCClient;\n const client = new IPCClient(socketPath, log);\n\n client.on('connected', () => {\n void (async () => {\n log.info('Connected to Alfe daemon — registering sync capabilities...');\n const response = await client.request('capability.register', {\n plugin: '@alfe.ai/openclaw-sync',\n capabilities: [...SYNC_CAPABILITIES],\n });\n if (response.ok) {\n log.info('Sync capabilities registered with daemon');\n } else {\n log.warn(`Failed to register sync capabilities: ${response.error?.message ?? 'unknown'}`);\n }\n })();\n });\n\n client.on('disconnected', (...args: unknown[]) => {\n const reason = typeof args[0] === 'string' ? args[0] : String(args[0]);\n log.warn(`Disconnected from Alfe daemon: ${reason}`);\n });\n\n client.on('message', (...args: unknown[]) => {\n const msg = args[0] as Record<string, unknown> | undefined;\n if (msg?.type === 'SYNC_NOW' || msg?.command === 'SYNC_NOW') {\n log.info('Received SYNC_NOW command — triggering immediate sync...');\n if (syncEngine) {\n const engine = syncEngine;\n void (async () => {\n try {\n lastSyncResult = await engine.fullSync({ quiet: true });\n log.info(\n `SYNC_NOW complete: ${String(lastSyncResult.pushed)} pushed, ${String(lastSyncResult.pulled)} pulled`,\n );\n } catch (err: unknown) {\n log.error(`SYNC_NOW failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n })();\n }\n }\n\n // Handle SHARED_SCOPES from gateway (via daemon)\n if (msg?.type === 'SHARED_SCOPES') {\n const scopes = msg.scopes as SharedScope[] | undefined;\n const engine = sharedSyncEngine;\n if (scopes && engine) {\n log.info(`Received SHARED_SCOPES update: ${String(scopes.length)} scope(s)`);\n void (async () => {\n try {\n await engine.updateScopes(scopes);\n } catch (err: unknown) {\n log.error(`SHARED_SCOPES update failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n })();\n }\n }\n });\n\n client.on('error', (...args: unknown[]) => {\n const err = args[0];\n log.debug(`Daemon IPC error: ${err instanceof Error ? err.message : String(err)}`);\n });\n\n client.start();\n return client;\n } catch {\n log.info('Alfe daemon not available — Sync plugin running standalone');\n return null;\n }\n}\n\n// ── Sync Relay Connection ───────────────────────────────────\n\nfunction clearSyncRelayReconnect() {\n if (syncRelayReconnectTimer) {\n clearTimeout(syncRelayReconnectTimer);\n syncRelayReconnectTimer = null;\n }\n}\n\nfunction clearSyncRelayDebounce() {\n if (syncRelayDebounceTimer) {\n clearTimeout(syncRelayDebounceTimer);\n syncRelayDebounceTimer = null;\n }\n}\n\nasync function processPendingNotifications(log: PluginLogger) {\n if (syncRelayPendingPaths.size === 0) return;\n\n const entries = new Map(syncRelayPendingPaths);\n syncRelayPendingPaths.clear();\n\n // Separate shared vs private notifications\n const sharedEntries = new Map<string, { etag?: string; eventType: 'created' | 'deleted' }>();\n const privateEntries = new Map<string, { etag?: string; eventType: 'created' | 'deleted' }>();\n\n for (const [filePath, info] of entries) {\n if (filePath.startsWith('shared/')) {\n sharedEntries.set(filePath, info);\n } else {\n privateEntries.set(filePath, info);\n }\n }\n\n // Handle shared file notifications\n if (sharedEntries.size > 0 && sharedSyncEngine) {\n for (const [filePath, info] of sharedEntries) {\n try {\n await sharedSyncEngine.handleNotification(filePath, info.eventType);\n } catch (err: unknown) {\n log.error(`Shared sync relay: failed ${info.eventType} ${filePath}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n }\n\n // Handle private file notifications\n if (privateEntries.size > 0 && syncEngine) {\n const engine = syncEngine;\n const toPull: string[] = [];\n const toDelete: string[] = [];\n\n for (const [filePath, info] of privateEntries) {\n if (info.eventType === 'deleted') {\n toDelete.push(filePath);\n } else {\n toPull.push(filePath);\n }\n }\n\n // Handle deletes\n for (const filePath of toDelete) {\n try {\n await engine.removeLocalFile(filePath, { quiet: true });\n log.debug(`Sync relay: deleted ${filePath}`);\n } catch (err: unknown) {\n log.error(`Sync relay: failed to delete ${filePath}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // Handle pulls\n if (toPull.length > 0) {\n try {\n const result = await engine.pullFiles(toPull, { quiet: true });\n if (result.pulled > 0) {\n log.info(`Sync relay: pulled ${String(result.pulled)} file(s)`);\n }\n if (result.errors > 0) {\n log.warn(`Sync relay: ${String(result.errors)} pull error(s)`);\n }\n } catch (err: unknown) {\n log.error(`Sync relay: pull failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n }\n}\n\nasync function connectToSyncRelay(\n relayUrl: string,\n token: string,\n agentId: string,\n log: PluginLogger,\n): Promise<SyncRelaySocket | null> {\n try {\n const { default: WebSocket } = await import('ws');\n\n const wsUrl = `${relayUrl}?token=${encodeURIComponent(token)}`;\n const ws = new WebSocket(wsUrl);\n\n ws.on('open', () => {\n log.info('Connected to Sync Relay');\n syncRelayReconnectAttempt = 0;\n\n // Subscribe to file changes for our agent\n ws.send(JSON.stringify({ type: 'SUBSCRIBE', agentId }));\n });\n\n ws.on('message', (data: Buffer | string) => {\n let message: RelayMessage;\n try {\n message = JSON.parse(data.toString()) as RelayMessage;\n } catch {\n return;\n }\n\n switch (message.type) {\n case 'SUBSCRIBE_ACK':\n if (message.status === 'ok') {\n log.info(`Subscribed to sync notifications for agent ${message.agentId ?? agentId}`);\n // On reconnect, trigger shared file full sync to catch up on missed notifications\n const sharedEngine = sharedSyncEngine;\n if (sharedEngine) {\n void (async () => {\n try {\n await sharedEngine.fullSync();\n log.info('Shared sync: reconnect full sync complete');\n } catch (err: unknown) {\n log.error(`Shared sync: reconnect full sync failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n })();\n }\n } else {\n log.warn(`Sync relay subscribe failed: ${message.message ?? 'unknown'}`);\n }\n break;\n\n case 'FILE_CHANGED':\n // Accumulate file paths, debounce before acting\n if (message.filePath) {\n syncRelayPendingPaths.set(message.filePath, {\n etag: message.etag,\n eventType: (message.eventType === 'deleted' ? 'deleted' : 'created'),\n });\n }\n\n clearSyncRelayDebounce();\n syncRelayDebounceTimer = setTimeout(() => {\n void processPendingNotifications(log);\n }, SYNC_RELAY_DEBOUNCE_MS);\n break;\n\n case 'PING':\n try {\n ws.send(JSON.stringify({ type: 'PONG' }));\n } catch {\n // Ignore\n }\n break;\n }\n });\n\n ws.on('close', (code: number) => {\n log.info(`Sync Relay disconnected (code=${String(code)})`);\n syncRelayWs = null;\n scheduleSyncRelayReconnect(relayUrl, token, agentId, log);\n });\n\n ws.on('error', (err: Error) => {\n log.debug(`Sync Relay error: ${err.message}`);\n });\n\n return ws as SyncRelaySocket;\n } catch (err: unknown) {\n log.debug(`Failed to connect to Sync Relay: ${err instanceof Error ? err.message : String(err)}`);\n scheduleSyncRelayReconnect(relayUrl, token, agentId, log);\n return null;\n }\n}\n\nfunction scheduleSyncRelayReconnect(\n relayUrl: string,\n token: string,\n agentId: string,\n log: PluginLogger,\n) {\n clearSyncRelayReconnect();\n\n const delay = Math.min(\n SYNC_RELAY_RECONNECT_BASE_MS * Math.pow(2, syncRelayReconnectAttempt),\n SYNC_RELAY_RECONNECT_MAX_MS,\n );\n syncRelayReconnectAttempt++;\n\n log.debug(`Reconnecting to Sync Relay in ${String(delay)}ms (attempt ${String(syncRelayReconnectAttempt)})`);\n\n syncRelayReconnectTimer = setTimeout(() => {\n void (async () => {\n syncRelayWs = await connectToSyncRelay(relayUrl, token, agentId, log);\n })();\n }, delay);\n}\n\nfunction disconnectSyncRelay() {\n clearSyncRelayReconnect();\n clearSyncRelayDebounce();\n syncRelayPendingPaths.clear();\n\n if (syncRelayWs) {\n try {\n syncRelayWs.send(JSON.stringify({ type: 'UNSUBSCRIBE' }));\n syncRelayWs.close(1000, 'Plugin deactivating');\n } catch {\n // Ignore\n }\n syncRelayWs = null;\n }\n}\n\n// ── Plugin Definition ───────────────────────────────────────\n\nconst plugin = {\n id: '@alfe.ai/openclaw-sync',\n name: 'Alfe Sync Plugin',\n description:\n 'Back up agent configuration, conversations, and memory to the cloud with scheduled or real-time sync.',\n version: pkg.version,\n\n activate(api: PluginApi) {\n const log = api.logger;\n\n // ── Parse config ────────────────────────────────────────\n const pluginConfig: SyncPluginConfig = api.config ?? {};\n currentConfig = pluginConfig;\n\n let alfeConfig: { workspacePath: string; socketPath: string } | null = null;\n try {\n alfeConfig = resolveAlfeConfig();\n } catch {\n // Config not available\n }\n\n const workspacePath =\n pluginConfig.workspacePath ??\n alfeConfig?.workspacePath ??\n DEFAULT_WORKSPACE_PATH;\n\n const syncScope = pluginConfig.syncScope ?? ['config', 'conversations', 'memory'];\n const syncSchedule = pluginConfig.syncSchedule ?? 'daily';\n const socketPath =\n pluginConfig.socketPath ??\n alfeConfig?.socketPath ??\n DEFAULT_SOCKET_PATH;\n\n // ── Service start/stop — extracted for registerService ──\n const startSyncService = async () => {\n if ((globalThis as Record<string, unknown>).__alfeSyncPluginActivated === true) {\n log.debug('Alfe Sync plugin already activated — skipping duplicate');\n return;\n }\n (globalThis as Record<string, unknown>).__alfeSyncPluginActivated = true;\n log.info('Alfe Sync plugin activating...');\n\n log.info(`Sync scope: ${syncScope.join(', ')}`);\n log.info(`Sync schedule: ${syncSchedule}`);\n log.info(`Workspace: ${workspacePath}`);\n\n // Initialize sync engine (if workspace is configured)\n if (isInitialized(workspacePath)) {\n try {\n syncEngine = await createSyncEngine(workspacePath);\n log.info('Sync engine initialized');\n } catch (err: unknown) {\n log.warn(`Failed to initialize sync engine: ${err instanceof Error ? err.message : String(err)}`);\n log.warn('Sync will be available once the workspace is configured (alfesync init)');\n }\n } else {\n log.info('Workspace not initialized for sync — run alfesync init to enable');\n }\n\n // Start file watcher for realtime mode\n if (syncSchedule === 'realtime' && syncEngine) {\n try {\n stopWatcher = await startWatcher({\n workspacePath,\n debounceMs: 2000,\n onChanges: async (paths) => {\n if (!syncEngine) return;\n log.debug(`Realtime sync: ${String(paths.length)} file(s) changed`);\n try {\n lastSyncResult = await syncEngine.push(paths, { quiet: true });\n } catch (err: unknown) {\n log.error(`Realtime push failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n },\n });\n log.info('File watcher started for realtime sync');\n } catch (err: unknown) {\n log.warn(`Failed to start file watcher: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // Setup scheduled sync\n if (syncSchedule !== 'realtime' && syncEngine) {\n setupSchedule(syncSchedule, log);\n }\n\n // Connect to Alfe daemon IPC\n daemonIpcClient = await connectToDaemon(socketPath, log);\n\n // Connect to Sync Relay for realtime notifications\n if (syncEngine) {\n try {\n const config = await readConfig(workspacePath);\n if (config) {\n const defaultRelayUrl =\n config.apiUrl.includes('dev.alfe.ai')\n ? 'wss://sync.dev.alfe.ai/ws'\n : 'wss://sync.alfe.ai/ws';\n const relayUrl =\n pluginConfig.syncRelayUrl ??\n defaultRelayUrl;\n\n syncRelayWs = await connectToSyncRelay(\n relayUrl,\n config.token,\n config.agentId,\n log,\n );\n }\n } catch (err: unknown) {\n log.debug(`Sync Relay connection skipped: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // Initialize shared sync engine\n if (syncEngine && (pluginConfig.sharedSync !== false)) {\n try {\n const sharedConfig = await readConfig(workspacePath);\n if (sharedConfig) {\n sharedSyncEngine = createSharedSyncEngine(\n {\n workspacePath,\n apiUrl: sharedConfig.apiUrl,\n token: sharedConfig.token,\n agentId: sharedConfig.agentId,\n },\n log,\n );\n log.info('Shared sync engine created — waiting for SHARED_SCOPES from gateway');\n }\n } catch (err: unknown) {\n log.debug(`Shared sync engine skipped: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n };\n\n const stopSyncService = async () => {\n (globalThis as Record<string, unknown>).__alfeSyncPluginActivated = false;\n\n clearSchedule();\n\n // Disconnect from Sync Relay\n disconnectSyncRelay();\n\n if (stopWatcher) {\n try {\n await stopWatcher();\n log.info('File watcher stopped');\n } catch (err: unknown) {\n log.debug(`Error stopping watcher: ${err instanceof Error ? err.message : String(err)}`);\n }\n stopWatcher = null;\n }\n\n if (daemonIpcClient) {\n try {\n daemonIpcClient.stop();\n log.info('Disconnected from Alfe daemon');\n } catch (err: unknown) {\n log.debug(`Error disconnecting from daemon: ${err instanceof Error ? err.message : String(err)}`);\n }\n daemonIpcClient = null;\n }\n\n syncEngine = null;\n sharedSyncEngine = null;\n lastSyncResult = null;\n currentConfig = {};\n\n log.info('Alfe Sync plugin deactivated');\n };\n\n // ── Register gateway RPCs (synchronous — available immediately) ──\n if (typeof api.registerGatewayMethod === 'function') {\n api.registerGatewayMethod('sync.now', async () => {\n if (!syncEngine) {\n return { ok: false, error: 'Sync engine not initialized — run alfesync init' };\n }\n try {\n lastSyncResult = await syncEngine.fullSync({ quiet: true });\n return { ok: true, result: lastSyncResult };\n } catch (err: unknown) {\n return { ok: false, error: err instanceof Error ? err.message : String(err) };\n }\n });\n log.info('Registered gateway RPC method: sync.now');\n\n api.registerGatewayMethod('sync.status', () => {\n return Promise.resolve({\n ok: true,\n initialized: !!syncEngine,\n schedule: currentConfig.syncSchedule ?? 'daily',\n scope: currentConfig.syncScope ?? ['config', 'conversations', 'memory'],\n lastResult: lastSyncResult,\n watcherActive: !!stopWatcher,\n });\n });\n log.info('Registered gateway RPC method: sync.status');\n }\n\n // ── Defer connections to gateway lifecycle via registerService ──\n if (api.registerService) {\n api.registerService({\n id: 'alfe-sync-engine',\n start: () => startSyncService(),\n stop: () => stopSyncService(),\n });\n } else {\n // Fallback for older OpenClaw versions without registerService\n void startSyncService().catch((err: unknown) => {\n log.error(`Sync plugin async init failed: ${err instanceof Error ? err.message : String(err)}`);\n });\n }\n\n log.info('Alfe Sync plugin activated');\n },\n\n async deactivate(api: PluginApi) {\n // Legacy fallback — cleanup when registerService is not available\n (globalThis as Record<string, unknown>).__alfeSyncPluginActivated = false;\n\n const log = api.logger;\n log.info('Alfe Sync plugin deactivating...');\n\n clearSchedule();\n\n // Disconnect from Sync Relay\n disconnectSyncRelay();\n\n if (stopWatcher) {\n try {\n await stopWatcher();\n log.info('File watcher stopped');\n } catch (err: unknown) {\n log.debug(`Error stopping watcher: ${err instanceof Error ? err.message : String(err)}`);\n }\n stopWatcher = null;\n }\n\n if (daemonIpcClient) {\n try {\n daemonIpcClient.stop();\n log.info('Disconnected from Alfe daemon');\n } catch (err: unknown) {\n log.debug(`Error disconnecting from daemon: ${err instanceof Error ? err.message : String(err)}`);\n }\n daemonIpcClient = null;\n }\n\n syncEngine = null;\n sharedSyncEngine = null;\n lastSyncResult = null;\n currentConfig = {};\n\n log.info('Alfe Sync plugin deactivated');\n },\n\n /**\n * Runtime config update — apply new scope/schedule without full restart.\n */\n async configure(api: PluginApi, config: SyncPluginConfig) {\n const log = api.logger;\n log.info('Reconfiguring Alfe Sync plugin...');\n\n currentConfig = { ...currentConfig, ...config };\n\n if (config.syncSchedule) {\n if (config.syncSchedule === 'realtime' && syncEngine && !stopWatcher) {\n let cfgForWorkspace: { workspacePath: string } | null = null;\n try { cfgForWorkspace = resolveAlfeConfig(); } catch { /* not available */ }\n const workspacePath =\n currentConfig.workspacePath ??\n cfgForWorkspace?.workspacePath ??\n '';\n\n stopWatcher = await startWatcher({\n workspacePath,\n debounceMs: 2000,\n onChanges: async (paths) => {\n if (!syncEngine) return;\n try {\n lastSyncResult = await syncEngine.push(paths, { quiet: true });\n } catch (err: unknown) {\n log.error(`Realtime push failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n },\n });\n clearSchedule();\n log.info('Switched to realtime sync');\n } else if (config.syncSchedule !== 'realtime') {\n if (stopWatcher) {\n await stopWatcher();\n stopWatcher = null;\n }\n setupSchedule(config.syncSchedule, log);\n }\n }\n\n if (config.syncScope) {\n log.info(`Updated sync scope: ${config.syncScope.join(', ')}`);\n }\n\n log.info('Alfe Sync plugin reconfigured');\n },\n};\n\nexport default plugin;\n"],"mappings":"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"}
|