@alfe.ai/openclaw-sync 0.0.15 → 0.0.16
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/cli/index.cjs +28 -74
- package/dist/cli/index.js +29 -75
- package/dist/cli/index.js.map +1 -1
- package/dist/ignore.cjs +2 -2
- package/dist/ignore.js +2 -2
- package/dist/index.cjs +3 -4
- package/dist/index.d.cts +49 -22
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +49 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/plugin.d.cts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin2.cjs +23 -25
- package/dist/plugin2.js +24 -26
- package/dist/plugin2.js.map +1 -1
- package/dist/sync-engine.cjs +123 -80
- package/dist/sync-engine.js +108 -59
- package/dist/sync-engine.js.map +1 -1
- package/package.json +1 -1
package/dist/plugin2.js.map
CHANGED
|
@@ -1 +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":";;;;;;;;;;;;;;;;;;;AAwBA,eAAsB,aACpB,SAC8B;CAC9B,MAAM,EAAE,eAAe,aAAa,KAAM,cAAc;CACxD,MAAM,iBAAiB,MAAM,mBAAmB,cAAc;CAG9D,MAAM,0BAAU,IAAI,KAA4C;CAEhE,IAAI,6BAAa,IAAI,KAAa;CAClC,IAAI,aAAmD;CAEvD,SAAS,gBAAgB;AACvB,MAAI,WAAY;AAChB,eAAa,iBAAiB;AAC5B,gBAAa;AACb,OAAI,WAAW,SAAS,EAAG;GAE3B,MAAM,QAAQ,CAAC,GAAG,WAAW;AAC7B,gCAAa,IAAI,KAAK;AACjB,aAAU,MAAM;KACpB,WAAW;;CAGhB,SAAS,aAAa,cAAsB;EAC1C,MAAM,eAAe,SAAS,eAAe,aAAa;AAG1D,MAAI,aAAa,cAAc,eAAe,CAAE;EAGhD,MAAM,gBAAgB,QAAQ,IAAI,aAAa;AAC/C,MAAI,cAAe,cAAa,cAAc;EAG9C,MAAM,QAAQ,iBAAiB;AAC7B,WAAQ,OAAO,aAAa;AAC5B,cAAW,IAAI,aAAa;AAC5B,kBAAe;KACd,WAAW;AAEd,UAAQ,IAAI,cAAc,MAAM;;CAGlC,MAAM,UAAU,MAAM,eAAe;EACnC,YAAY;EACZ,eAAe;EACf,gBAAgB;EAChB,OAAO,KAAA;EACP,SAAS;GACP;GACA;GACA;GACA;GACD;EACF,CAAC;AAEF,SAAQ,GAAG,OAAO,aAAa;AAC/B,SAAQ,GAAG,UAAU,aAAa;AAClC,SAAQ,GAAG,UAAU,aAAa;AAGlC,QAAO,YAAY;AAEjB,OAAK,MAAM,SAAS,QAAQ,QAAQ,CAClC,cAAa,MAAM;AAErB,UAAQ,OAAO;AAEf,MAAI,YAAY;AACd,gBAAa,WAAW;AACxB,gBAAa;;AAGf,QAAM,QAAQ,OAAO;;;;;;;;;;;;;ACtFzB,MAAM,uBAAuB,MAAM,OAAO;;AAG1C,SAAS,gBAAgB,SAAiB,cAA4B;CACpE,MAAM,iBAAiB,UAAU,QAAQ,GAAG;CAC5C,MAAM,iBAAiB,UAAU,aAAa;AAC9C,KAAI,CAAC,eAAe,WAAW,eAAe,IAAI,mBAAmB,UAAU,QAAQ,CACrF,OAAM,IAAI,MAAM,2BAA2B,aAAa,WAAW,UAAU;;AAmCjF,eAAe,UAAa,KAAa,OAA2B;CAClE,MAAM,WAAW,MAAM,MAAM,KAAK,EAChC,SAAS;EACP,iBAAiB,UAAU;EAC3B,gBAAgB;EACjB,EACF,CAAC;AACF,KAAI,CAAC,SAAS,IAAI;EAChB,IAAI,YAAY;AAChB,MAAI;AACF,eAAY,MAAM,SAAS,MAAM;UAC3B;AACN,eAAY;;AAEd,QAAM,IAAI,MAAM,QAAQ,OAAO,SAAS,OAAO,CAAC,IAAI,YAAY;;AAElE,QAAO,SAAS,MAAM;;AAaxB,SAAgB,uBACd,QACA,KACkB;CAClB,IAAI,eAA8B,EAAE;CACpC,MAAM,YAAY,KAAK,OAAO,eAAe,SAAS;CAEtD,SAAS,SAAS,OAA4B;AAC5C,MAAI,MAAM,cAAc,MAAO,QAAO,KAAK,WAAW,MAAM;AAE5D,SAAO,KAAK,WADG,MAAM,cAAc,SAAS,UAAU,YACvB,MAAM,QAAQ;;CAG/C,SAAS,UAAkB;AACzB,SAAO,GAAG,OAAO,OAAO;;CAG1B,eAAe,gBAAgB,OAAgD;AAG7E,UADe,MAAM,UADT,GAAG,SAAS,CAAC,SAAS,MAAM,UAAU,GAAG,MAAM,WACO,OAAO,MAAM,EACjE;;CAGhB,eAAe,aAAa,OAAoB,UAAkB,WAAkC;AAElG,kBAAgB,SAAS,MAAM,EAAE,UAAU;EAG3C,MAAM,SAAS,MAAM,UADT,GAAG,SAAS,CAAC,SAAS,MAAM,UAAU,GAAG,MAAM,QAAQ,GAAG,mBAAmB,SAAS,CAAC,YACtC,OAAO,MAAM;EAE1E,MAAM,WAAW,MAAM,MAAM,OAAO,YAAY;AAChD,MAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,yBAAyB,OAAO,SAAS,OAAO,GAAG;EAErF,MAAM,gBAAgB,SAAS,SAAS,QAAQ,IAAI,iBAAiB,IAAI,KAAK,GAAG;AACjF,MAAI,gBAAgB,qBAClB,OAAM,IAAI,MAAM,mBAAmB,OAAO,cAAc,CAAC,iBAAiB,OAAO,qBAAqB,CAAC,QAAQ;EAGjH,MAAM,SAAS,OAAO,KAAK,MAAM,SAAS,aAAa,CAAC;AACxD,MAAI,OAAO,SAAS,qBAClB,OAAM,IAAI,MAAM,uCAAuC,OAAO,OAAO,OAAO,CAAC,QAAQ;AAGvF,QAAM,MAAM,QAAQ,UAAU,EAAE,EAAE,WAAW,MAAM,CAAC;AACpD,QAAM,UAAU,WAAW,OAAO;;CAGpC,eAAe,UAAU,OAAmC;EAC1D,MAAM,MAAM,SAAS,MAAM;AAC3B,QAAM,MAAM,KAAK,EAAE,WAAW,MAAM,CAAC;AAErC,MAAI;GACF,MAAM,cAAc,MAAM,gBAAgB,MAAM;AAEhD,QAAK,MAAM,QAAQ,aAAa;IAC9B,MAAM,YAAY,KAAK,KAAK,KAAK,SAAS;AAC1C,QAAI;AACF,WAAM,aAAa,OAAO,KAAK,UAAU,UAAU;AACnD,SAAI,MAAM,2BAA2B,MAAM,UAAU,GAAG,MAAM,QAAQ,GAAG,KAAK,WAAW;aAClF,KAAc;AACrB,SAAI,MAAM,mCAAmC,KAAK,SAAS,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;;WAG/G,KAAc;AACrB,OAAI,MAAM,yCAAyC,MAAM,UAAU,GAAG,MAAM,QAAQ,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;;CAI/I,SAAS,gBAAgB,UAAuE;AAE9F,MAAI,SAAS,SAAS,KAAK,CAAE,QAAO;EAGpC,MAAM,WAAW,sBAAsB,KAAK,SAAS;AACrD,MAAI,UAAU;GACZ,MAAM,QAAQ,aAAa,MAAM,MAAM,EAAE,cAAc,MAAM;AAC7D,OAAI,MAAO,QAAO;IAAE;IAAO,cAAc,SAAS;IAAI;;EAIxD,MAAM,YAAY,iCAAiC,KAAK,SAAS;AACjE,MAAI,WAAW;GACb,MAAM,QAAQ,aAAa,MAAM,MAAM,EAAE,cAAc,UAAU,EAAE,YAAY,UAAU,GAAG;AAC5F,OAAI,MAAO,QAAO;IAAE;IAAO,cAAc,UAAU;IAAI;;EAIzD,MAAM,eAAe,oCAAoC,KAAK,SAAS;AACvE,MAAI,cAAc;GAChB,MAAM,QAAQ,aAAa,MAAM,MAAM,EAAE,cAAc,aAAa,EAAE,YAAY,aAAa,GAAG;AAClG,OAAI,MAAO,QAAO;IAAE;IAAO,cAAc,aAAa;IAAI;;AAG5D,SAAO;;AAGT,QAAO;EACL,MAAM,WAAW,QAAsC;AACrD,kBAAe,CAAC,GAAG,OAAO;AAC1B,OAAI,KAAK,kCAAkC,OAAO,OAAO,OAAO,CAAC,WAAW;AAE5E,SAAM,MAAM,WAAW,EAAE,WAAW,MAAM,CAAC;AAE3C,QAAK,MAAM,SAAS,OAClB,OAAM,UAAU,MAAM;AAGxB,OAAI,KAAK,uCAAuC;;EAGlD,MAAM,aAAa,WAAyC;GAC1D,MAAM,SAAS,IAAI,IAAI,aAAa,KAAK,MAAM,GAAG,EAAE,UAAU,GAAG,EAAE,UAAU,CAAC;GAC9E,MAAM,SAAS,IAAI,IAAI,UAAU,KAAK,MAAM,GAAG,EAAE,UAAU,GAAG,EAAE,UAAU,CAAC;AAG3E,QAAK,MAAM,SAAS,cAAc;IAChC,MAAM,MAAM,GAAG,MAAM,UAAU,GAAG,MAAM;AACxC,QAAI,CAAC,OAAO,IAAI,IAAI,EAAE;KACpB,MAAM,MAAM,SAAS,MAAM;AAC3B,SAAI;AACF,YAAM,GAAG,KAAK;OAAE,WAAW;OAAM,OAAO;OAAM,CAAC;AAC/C,UAAI,KAAK,wCAAwC,MAAM;cAChD,KAAc;AACrB,UAAI,KAAK,iCAAiC,IAAI,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;;;AAM3G,QAAK,MAAM,SAAS,WAAW;IAC7B,MAAM,MAAM,GAAG,MAAM,UAAU,GAAG,MAAM;AACxC,QAAI,CAAC,OAAO,IAAI,IAAI,EAAE;AACpB,SAAI,KAAK,0BAA0B,MAAM,UAAU,GAAG,MAAM,QAAQ,kBAAkB;AACtF,WAAM,UAAU,MAAM;;;AAI1B,kBAAe,CAAC,GAAG,UAAU;;EAG/B,MAAM,mBAAmB,UAAkB,WAAiD;GAC1F,MAAM,SAAS,gBAAgB,SAAS;AACxC,OAAI,CAAC,QAAQ;AACX,QAAI,MAAM,wDAAwD,WAAW;AAC7E;;GAGF,MAAM,MAAM,SAAS,OAAO,MAAM;GAClC,MAAM,YAAY,KAAK,KAAK,OAAO,aAAa;AAGhD,OAAI;AACF,oBAAgB,KAAK,UAAU;WACzB;AACN,QAAI,KAAK,2CAA2C,WAAW;AAC/D;;AAGF,OAAI,cAAc,WAAW;AAC3B,QAAI;AACF,WAAM,OAAO,UAAU;AACvB,SAAI,MAAM,wBAAwB,WAAW;YACvC;AAGR;;AAIF,OAAI;AACF,UAAM,aAAa,OAAO,OAAO,OAAO,cAAc,UAAU;AAChE,QAAI,MAAM,uBAAuB,WAAW;YACrC,KAAc;AACrB,QAAI,MAAM,+BAA+B,SAAS,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;;EAI7G,MAAM,WAA0B;AAC9B,OAAI,KAAK,iCAAiC,OAAO,aAAa,OAAO,CAAC,WAAW;AACjF,QAAK,MAAM,SAAS,aAClB,OAAM,UAAU,MAAM;;EAI1B,YAA2B;AACzB,UAAO,CAAC,GAAG,aAAa;;EAE3B;;;;;;;;;;;;;;;;;;ACtPH,MAAM,MADU,cAAc,OAAO,KAAK,IAAI,CAC1B,kBAAkB;AAGtC,MAAM,oBAAoB;CAAC;CAAa;CAAa;CAAgB;AACrE,MAAM,+BAA+B;AACrC,MAAM,8BAA8B;AACpC,MAAM,yBAAyB;AA2E/B,IAAI,aAAgC;AACpC,IAAI,mBAA4C;AAChD,IAAI,cAA4C;AAChD,IAAI,kBAAoC;AACxC,IAAI,oBAA2D;AAC/D,IAAI,gBAAkC,EAAE;AACxC,IAAI,iBAAoC;AACxC,IAAI,cAAsC;AAC1C,IAAI,0BAAgE;AACpE,IAAI,4BAA4B;AAChC,IAAI,yBAA+D;AACnE,MAAM,wCACJ,IAAI,KAAkE;AAIxE,MAAM,wBAAgD;CACpD,QAAQ,OAAU;CAClB,OAAO,OAAU,KAAK;CACtB,QAAQ,QAAc,KAAK;CAC5B;AAED,SAAS,gBAAgB;AACvB,KAAI,mBAAmB;AACrB,gBAAc,kBAAkB;AAChC,sBAAoB;;;AAIxB,SAAS,cAAc,UAAkB,KAAmB;AAC1D,gBAAe;AAEf,KAAI,aAAa,YAAY;AAC3B,MAAI,KAAK,gDAAgD;AACzD;;CAGF,MAAM,aAAa,sBAAsB;AACzC,KAAI,CAAC,YAAY;AACf,MAAI,KAAK,0BAA0B,SAAS,wBAAwB;AACpE,gBAAc,UAAU,IAAI;AAC5B;;AAGF,KAAI,KAAK,kBAAkB,SAAS,UAAU,OAAO,aAAa,IAAK,CAAC,IAAI;AAC5E,qBAAoB,kBAAkB;AACpC,MAAI,CAAC,WAAY;EACjB,MAAM,SAAS;AACf,GAAM,YAAY;AAChB,OAAI;AACF,QAAI,KAAK,mBAAmB,SAAS,eAAe;AACpD,qBAAiB,MAAM,OAAO,SAAS,EAAE,OAAO,MAAM,CAAC;AACvD,QAAI,KACF,4BAA4B,OAAO,eAAe,OAAO,CAAC,WAAW,OAAO,eAAe,OAAO,CAAC,SACpG;YACM,KAAc;AACrB,QAAI,MAAM,0BAA0B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;MAEvF;IACH,WAAW;;AAKhB,eAAe,gBAAgB,YAAoB,KAA8C;AAC/F,KAAI;EAKF,MAAM,aAHM,MAAM,OADF,sBAIM;EACtB,MAAM,SAAS,IAAI,UAAU,YAAY,IAAI;AAE7C,SAAO,GAAG,mBAAmB;AAC3B,IAAM,YAAY;AAChB,QAAI,KAAK,8DAA8D;IACvE,MAAM,WAAW,MAAM,OAAO,QAAQ,uBAAuB;KAC3D,QAAQ;KACR,cAAc,CAAC,GAAG,kBAAkB;KACrC,CAAC;AACF,QAAI,SAAS,GACX,KAAI,KAAK,2CAA2C;QAEpD,KAAI,KAAK,yCAAyC,SAAS,OAAO,WAAW,YAAY;OAEzF;IACJ;AAEF,SAAO,GAAG,iBAAiB,GAAG,SAAoB;GAChD,MAAM,SAAS,OAAO,KAAK,OAAO,WAAW,KAAK,KAAK,OAAO,KAAK,GAAG;AACtE,OAAI,KAAK,kCAAkC,SAAS;IACpD;AAEF,SAAO,GAAG,YAAY,GAAG,SAAoB;GAC3C,MAAM,MAAM,KAAK;AACjB,OAAI,KAAK,SAAS,cAAc,KAAK,YAAY,YAAY;AAC3D,QAAI,KAAK,2DAA2D;AACpE,QAAI,YAAY;KACd,MAAM,SAAS;AACf,MAAM,YAAY;AAChB,UAAI;AACF,wBAAiB,MAAM,OAAO,SAAS,EAAE,OAAO,MAAM,CAAC;AACvD,WAAI,KACF,sBAAsB,OAAO,eAAe,OAAO,CAAC,WAAW,OAAO,eAAe,OAAO,CAAC,SAC9F;eACM,KAAc;AACrB,WAAI,MAAM,oBAAoB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;SAEjF;;;AAKR,OAAI,KAAK,SAAS,iBAAiB;IACjC,MAAM,SAAS,IAAI;IACnB,MAAM,SAAS;AACf,QAAI,UAAU,QAAQ;AACpB,SAAI,KAAK,kCAAkC,OAAO,OAAO,OAAO,CAAC,WAAW;AAC5E,MAAM,YAAY;AAChB,UAAI;AACF,aAAM,OAAO,aAAa,OAAO;eAC1B,KAAc;AACrB,WAAI,MAAM,gCAAgC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;SAE7F;;;IAGR;AAEF,SAAO,GAAG,UAAU,GAAG,SAAoB;GACzC,MAAM,MAAM,KAAK;AACjB,OAAI,MAAM,qBAAqB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;IAClF;AAEF,SAAO,OAAO;AACd,SAAO;SACD;AACN,MAAI,KAAK,6DAA6D;AACtE,SAAO;;;AAMX,SAAS,0BAA0B;AACjC,KAAI,yBAAyB;AAC3B,eAAa,wBAAwB;AACrC,4BAA0B;;;AAI9B,SAAS,yBAAyB;AAChC,KAAI,wBAAwB;AAC1B,eAAa,uBAAuB;AACpC,2BAAyB;;;AAI7B,eAAe,4BAA4B,KAAmB;AAC5D,KAAI,sBAAsB,SAAS,EAAG;CAEtC,MAAM,UAAU,IAAI,IAAI,sBAAsB;AAC9C,uBAAsB,OAAO;CAG7B,MAAM,gCAAgB,IAAI,KAAkE;CAC5F,MAAM,iCAAiB,IAAI,KAAkE;AAE7F,MAAK,MAAM,CAAC,UAAU,SAAS,QAC7B,KAAI,SAAS,WAAW,UAAU,CAChC,eAAc,IAAI,UAAU,KAAK;KAEjC,gBAAe,IAAI,UAAU,KAAK;AAKtC,KAAI,cAAc,OAAO,KAAK,iBAC5B,MAAK,MAAM,CAAC,UAAU,SAAS,cAC7B,KAAI;AACF,QAAM,iBAAiB,mBAAmB,UAAU,KAAK,UAAU;UAC5D,KAAc;AACrB,MAAI,MAAM,6BAA6B,KAAK,UAAU,GAAG,SAAS,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;AAM/H,KAAI,eAAe,OAAO,KAAK,YAAY;EACzC,MAAM,SAAS;EACf,MAAM,SAAmB,EAAE;EAC3B,MAAM,WAAqB,EAAE;AAE7B,OAAK,MAAM,CAAC,UAAU,SAAS,eAC7B,KAAI,KAAK,cAAc,UACrB,UAAS,KAAK,SAAS;MAEvB,QAAO,KAAK,SAAS;AAKzB,OAAK,MAAM,YAAY,SACrB,KAAI;AACF,SAAM,OAAO,gBAAgB,UAAU,EAAE,OAAO,MAAM,CAAC;AACvD,OAAI,MAAM,uBAAuB,WAAW;WACrC,KAAc;AACrB,OAAI,MAAM,gCAAgC,SAAS,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;AAK9G,MAAI,OAAO,SAAS,EAClB,KAAI;GACF,MAAM,SAAS,MAAM,OAAO,UAAU,QAAQ,EAAE,OAAO,MAAM,CAAC;AAC9D,OAAI,OAAO,SAAS,EAClB,KAAI,KAAK,sBAAsB,OAAO,OAAO,OAAO,CAAC,UAAU;AAEjE,OAAI,OAAO,SAAS,EAClB,KAAI,KAAK,eAAe,OAAO,OAAO,OAAO,CAAC,gBAAgB;WAEzD,KAAc;AACrB,OAAI,MAAM,4BAA4B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;;;AAMjG,eAAe,mBACb,UACA,OACA,SACA,KACiC;AACjC,KAAI;EACF,MAAM,EAAE,SAAS,cAAc,MAAM,OAAO;EAG5C,MAAM,KAAK,IAAI,UADD,GAAG,SAAS,SAAS,mBAAmB,MAAM,GAC7B;AAE/B,KAAG,GAAG,cAAc;AAClB,OAAI,KAAK,0BAA0B;AACnC,+BAA4B;AAG5B,MAAG,KAAK,KAAK,UAAU;IAAE,MAAM;IAAa;IAAS,CAAC,CAAC;IACvD;AAEF,KAAG,GAAG,YAAY,SAA0B;GAC1C,IAAI;AACJ,OAAI;AACF,cAAU,KAAK,MAAM,KAAK,UAAU,CAAC;WAC/B;AACN;;AAGF,WAAQ,QAAQ,MAAhB;IACE,KAAK;AACH,SAAI,QAAQ,WAAW,MAAM;AAC3B,UAAI,KAAK,8CAA8C,QAAQ,WAAW,UAAU;MAEpF,MAAM,eAAe;AACrB,UAAI,aACF,EAAM,YAAY;AAChB,WAAI;AACF,cAAM,aAAa,UAAU;AAC7B,YAAI,KAAK,4CAA4C;gBAC9C,KAAc;AACrB,YAAI,MAAM,4CAA4C,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;UAEzG;WAGN,KAAI,KAAK,gCAAgC,QAAQ,WAAW,YAAY;AAE1E;IAEF,KAAK;AAEH,SAAI,QAAQ,SACV,uBAAsB,IAAI,QAAQ,UAAU;MAC1C,MAAM,QAAQ;MACd,WAAY,QAAQ,cAAc,YAAY,YAAY;MAC3D,CAAC;AAGJ,6BAAwB;AACxB,8BAAyB,iBAAiB;AACnC,kCAA4B,IAAI;QACpC,uBAAuB;AAC1B;IAEF,KAAK;AACH,SAAI;AACF,SAAG,KAAK,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAC,CAAC;aACnC;AAGR;;IAEJ;AAEF,KAAG,GAAG,UAAU,SAAiB;AAC/B,OAAI,KAAK,iCAAiC,OAAO,KAAK,CAAC,GAAG;AAC1D,iBAAc;AACd,8BAA2B,UAAU,OAAO,SAAS,IAAI;IACzD;AAEF,KAAG,GAAG,UAAU,QAAe;AAC7B,OAAI,MAAM,qBAAqB,IAAI,UAAU;IAC7C;AAEF,SAAO;UACA,KAAc;AACrB,MAAI,MAAM,oCAAoC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;AACjG,6BAA2B,UAAU,OAAO,SAAS,IAAI;AACzD,SAAO;;;AAIX,SAAS,2BACP,UACA,OACA,SACA,KACA;AACA,0BAAyB;CAEzB,MAAM,QAAQ,KAAK,IACjB,+BAA+B,KAAK,IAAI,GAAG,0BAA0B,EACrE,4BACD;AACD;AAEA,KAAI,MAAM,iCAAiC,OAAO,MAAM,CAAC,cAAc,OAAO,0BAA0B,CAAC,GAAG;AAE5G,2BAA0B,iBAAiB;AACzC,GAAM,YAAY;AAChB,iBAAc,MAAM,mBAAmB,UAAU,OAAO,SAAS,IAAI;MACnE;IACH,MAAM;;AAGX,SAAS,sBAAsB;AAC7B,0BAAyB;AACzB,yBAAwB;AACxB,uBAAsB,OAAO;AAE7B,KAAI,aAAa;AACf,MAAI;AACF,eAAY,KAAK,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC,CAAC;AACzD,eAAY,MAAM,KAAM,sBAAsB;UACxC;AAGR,gBAAc;;;AAMlB,MAAM,SAAS;CACb,IAAI;CACJ,MAAM;CACN,aACE;CACF,SAAS,IAAI;CAEb,SAAS,KAAgB;EACvB,MAAM,MAAM,IAAI;EAGhB,MAAM,eAAiC,IAAI,UAAU,EAAE;AACvD,kBAAgB;EAEhB,IAAI,aAAmE;AACvE,MAAI;AACF,gBAAaA,eAAmB;UAC1B;EAIR,MAAM,gBACJ,aAAa,iBACb,YAAY,iBACZ;EAEF,MAAM,YAAY,aAAa,aAAa;GAAC;GAAU;GAAiB;GAAS;EACjF,MAAM,eAAe,aAAa,gBAAgB;EAClD,MAAM,aACJ,aAAa,cACb,YAAY,cACZ;EAGF,MAAM,mBAAmB,YAAY;AACnC,OAAK,WAAuC,8BAA8B,MAAM;AAC9E,QAAI,MAAM,0DAA0D;AACpE;;AAED,cAAuC,4BAA4B;AACpE,OAAI,KAAK,iCAAiC;AAE1C,OAAI,KAAK,eAAe,UAAU,KAAK,KAAK,GAAG;AAC/C,OAAI,KAAK,kBAAkB,eAAe;AAC1C,OAAI,KAAK,cAAc,gBAAgB;AAGvC,OAAI,cAAc,cAAc,CAC9B,KAAI;AACF,iBAAa,MAAM,iBAAiB,cAAc;AAClD,QAAI,KAAK,0BAA0B;YAC5B,KAAc;AACrB,QAAI,KAAK,qCAAqC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;AACjG,QAAI,KAAK,0EAA0E;;OAGrF,KAAI,KAAK,mEAAmE;AAI9E,OAAI,iBAAiB,cAAc,WACjC,KAAI;AACF,kBAAc,MAAM,aAAa;KAC/B;KACA,YAAY;KACZ,WAAW,OAAO,UAAU;AAC1B,UAAI,CAAC,WAAY;AACjB,UAAI,MAAM,kBAAkB,OAAO,MAAM,OAAO,CAAC,kBAAkB;AACnE,UAAI;AACF,wBAAiB,MAAM,WAAW,KAAK,OAAO,EAAE,OAAO,MAAM,CAAC;eACvD,KAAc;AACrB,WAAI,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;;KAG3F,CAAC;AACF,QAAI,KAAK,yCAAyC;YAC3C,KAAc;AACrB,QAAI,KAAK,iCAAiC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;AAKjG,OAAI,iBAAiB,cAAc,WACjC,eAAc,cAAc,IAAI;AAIlC,qBAAkB,MAAM,gBAAgB,YAAY,IAAI;AAGxD,OAAI,WACF,KAAI;IACF,MAAM,SAAS,MAAM,WAAW,cAAc;AAC9C,QAAI,QAAQ;KACV,MAAM,kBACJ,OAAO,OAAO,SAAS,cAAc,GACjC,8BACA;AAKN,mBAAc,MAAM,mBAHlB,aAAa,gBACb,iBAIA,OAAO,OACP,OAAO,SACP,IACD;;YAEI,KAAc;AACrB,QAAI,MAAM,kCAAkC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;AAKnG,OAAI,cAAe,aAAa,eAAe,MAC7C,KAAI;IACF,MAAM,eAAe,MAAM,WAAW,cAAc;AACpD,QAAI,cAAc;AAChB,wBAAmB,uBACjB;MACE;MACA,QAAQ,aAAa;MACrB,OAAO,aAAa;MACpB,SAAS,aAAa;MACvB,EACD,IACD;AACD,SAAI,KAAK,sEAAsE;;YAE1E,KAAc;AACrB,QAAI,MAAM,+BAA+B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;;EAKlG,MAAM,kBAAkB,YAAY;AACjC,cAAuC,4BAA4B;AAEpE,kBAAe;AAGf,wBAAqB;AAErB,OAAI,aAAa;AACf,QAAI;AACF,WAAM,aAAa;AACnB,SAAI,KAAK,uBAAuB;aACzB,KAAc;AACrB,SAAI,MAAM,2BAA2B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;AAE1F,kBAAc;;AAGhB,OAAI,iBAAiB;AACnB,QAAI;AACF,qBAAgB,MAAM;AACtB,SAAI,KAAK,gCAAgC;aAClC,KAAc;AACrB,SAAI,MAAM,oCAAoC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;AAEnG,sBAAkB;;AAGpB,gBAAa;AACb,sBAAmB;AACnB,oBAAiB;AACjB,mBAAgB,EAAE;AAElB,OAAI,KAAK,+BAA+B;;AAI1C,MAAI,OAAO,IAAI,0BAA0B,YAAY;AACnD,OAAI,sBAAsB,YAAY,YAAY;AAChD,QAAI,CAAC,WACH,QAAO;KAAE,IAAI;KAAO,OAAO;KAAmD;AAEhF,QAAI;AACF,sBAAiB,MAAM,WAAW,SAAS,EAAE,OAAO,MAAM,CAAC;AAC3D,YAAO;MAAE,IAAI;MAAM,QAAQ;MAAgB;aACpC,KAAc;AACrB,YAAO;MAAE,IAAI;MAAO,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;MAAE;;KAE/E;AACF,OAAI,KAAK,0CAA0C;AAEnD,OAAI,sBAAsB,qBAAqB;AAC7C,WAAO,QAAQ,QAAQ;KACrB,IAAI;KACJ,aAAa,CAAC,CAAC;KACf,UAAU,cAAc,gBAAgB;KACxC,OAAO,cAAc,aAAa;MAAC;MAAU;MAAiB;MAAS;KACvE,YAAY;KACZ,eAAe,CAAC,CAAC;KAClB,CAAC;KACF;AACF,OAAI,KAAK,6CAA6C;;AAIxD,MAAI,IAAI,gBACN,KAAI,gBAAgB;GAClB,IAAI;GACJ,aAAa,kBAAkB;GAC/B,YAAY,iBAAiB;GAC9B,CAAC;MAGG,mBAAkB,CAAC,OAAO,QAAiB;AAC9C,OAAI,MAAM,kCAAkC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;IAC/F;AAGJ,MAAI,KAAK,6BAA6B;;CAGxC,MAAM,WAAW,KAAgB;AAE9B,aAAuC,4BAA4B;EAEpE,MAAM,MAAM,IAAI;AAChB,MAAI,KAAK,mCAAmC;AAE5C,iBAAe;AAGf,uBAAqB;AAErB,MAAI,aAAa;AACf,OAAI;AACF,UAAM,aAAa;AACnB,QAAI,KAAK,uBAAuB;YACzB,KAAc;AACrB,QAAI,MAAM,2BAA2B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;AAE1F,iBAAc;;AAGhB,MAAI,iBAAiB;AACnB,OAAI;AACF,oBAAgB,MAAM;AACtB,QAAI,KAAK,gCAAgC;YAClC,KAAc;AACrB,QAAI,MAAM,oCAAoC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;AAEnG,qBAAkB;;AAGpB,eAAa;AACb,qBAAmB;AACnB,mBAAiB;AACjB,kBAAgB,EAAE;AAElB,MAAI,KAAK,+BAA+B;;CAM1C,MAAM,UAAU,KAAgB,QAA0B;EACxD,MAAM,MAAM,IAAI;AAChB,MAAI,KAAK,oCAAoC;AAE7C,kBAAgB;GAAE,GAAG;GAAe,GAAG;GAAQ;AAE/C,MAAI,OAAO;OACL,OAAO,iBAAiB,cAAc,cAAc,CAAC,aAAa;IACpE,IAAI,kBAAoD;AACxD,QAAI;AAAE,uBAAkBA,eAAmB;YAAU;AAMrD,kBAAc,MAAM,aAAa;KAC/B,eALA,cAAc,iBACd,iBAAiB,iBACjB;KAIA,YAAY;KACZ,WAAW,OAAO,UAAU;AAC1B,UAAI,CAAC,WAAY;AACjB,UAAI;AACF,wBAAiB,MAAM,WAAW,KAAK,OAAO,EAAE,OAAO,MAAM,CAAC;eACvD,KAAc;AACrB,WAAI,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;;KAG3F,CAAC;AACF,mBAAe;AACf,QAAI,KAAK,4BAA4B;cAC5B,OAAO,iBAAiB,YAAY;AAC7C,QAAI,aAAa;AACf,WAAM,aAAa;AACnB,mBAAc;;AAEhB,kBAAc,OAAO,cAAc,IAAI;;;AAI3C,MAAI,OAAO,UACT,KAAI,KAAK,uBAAuB,OAAO,UAAU,KAAK,KAAK,GAAG;AAGhE,MAAI,KAAK,gCAAgC;;CAE5C"}
|
|
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, resolveSyncConfig } 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. Credentials come from `~/.alfe/config.toml`\n // (the existing `alfe login` config) — no separate sync init step.\n if (isInitialized()) {\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 }\n } else {\n log.info('Sync skipped — no Alfe config found. Run `alfe login` 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 + shared sync engine. Both need the same\n // resolved sync config (token, agentId, apiUrl), so resolve once.\n if (syncEngine) {\n const config = await resolveSyncConfig();\n if (config) {\n try {\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 = pluginConfig.syncRelayUrl ?? defaultRelayUrl;\n\n syncRelayWs = await connectToSyncRelay(\n relayUrl,\n config.token,\n config.agentId,\n log,\n );\n } catch (err: unknown) {\n log.debug(`Sync Relay connection skipped: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n if (pluginConfig.sharedSync !== false) {\n try {\n sharedSyncEngine = createSharedSyncEngine(\n {\n workspacePath,\n apiUrl: config.apiUrl,\n token: config.token,\n agentId: config.agentId,\n },\n log,\n );\n log.info('Shared sync engine created — waiting for SHARED_SCOPES from gateway');\n } catch (err: unknown) {\n log.debug(`Shared sync engine skipped: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\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/dist/sync-engine.cjs
CHANGED
|
@@ -1,96 +1,141 @@
|
|
|
1
1
|
const require_ignore = require("./ignore.cjs");
|
|
2
|
-
let node_fs_promises = require("node:fs/promises");
|
|
3
|
-
let node_fs = require("node:fs");
|
|
4
2
|
let node_path = require("node:path");
|
|
3
|
+
let node_os = require("node:os");
|
|
4
|
+
let node_fs = require("node:fs");
|
|
5
|
+
let _alfe_ai_config = require("@alfe.ai/config");
|
|
6
|
+
let node_fs_promises = require("node:fs/promises");
|
|
5
7
|
let node_crypto = require("node:crypto");
|
|
6
8
|
let _auriclabs_logger = require("@auriclabs/logger");
|
|
7
9
|
//#region src/config.ts
|
|
8
10
|
/**
|
|
9
|
-
* AlfeSync configuration
|
|
11
|
+
* AlfeSync configuration.
|
|
12
|
+
*
|
|
13
|
+
* Derives credentials from `@alfe.ai/config` (~/.alfe/config.toml) so the
|
|
14
|
+
* sync plugin works on any agent that's already logged in — no separate
|
|
15
|
+
* `.alfesync/config.json` or manual `alfesync init` required.
|
|
16
|
+
*
|
|
17
|
+
* The only field not directly in @alfe.ai/config is `agentId`, which is
|
|
18
|
+
* resolved once at startup by calling POST /auth/validate (same pattern
|
|
19
|
+
* the gateway daemon uses to bootstrap its own identity).
|
|
10
20
|
*/
|
|
11
|
-
const
|
|
12
|
-
const CONFIG_FILE = "config.json";
|
|
21
|
+
const SYNC_STATE_DIR = (0, node_path.join)((0, node_path.join)((0, node_os.homedir)(), ".alfe"), "sync");
|
|
13
22
|
/**
|
|
14
|
-
*
|
|
23
|
+
* In-memory cache — agentId is stable for process lifetime.
|
|
24
|
+
*
|
|
25
|
+
* We cache the *Promise* (not just the resolved value) so that concurrent
|
|
26
|
+
* callers during plugin activation share a single `/auth/validate` round-trip
|
|
27
|
+
* instead of each firing their own.
|
|
15
28
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
29
|
+
let cachedSyncConfigPromise = null;
|
|
30
|
+
/**
|
|
31
|
+
* Local on-disk state directory (manifest, etc.) — separate from credentials.
|
|
32
|
+
* Lives under ~/.alfe/sync/ so sync state stays alongside the rest of Alfe's
|
|
33
|
+
* agent state, not scattered across the workspace.
|
|
34
|
+
*/
|
|
35
|
+
function syncStateDir() {
|
|
36
|
+
return SYNC_STATE_DIR;
|
|
18
37
|
}
|
|
19
38
|
/**
|
|
20
|
-
*
|
|
39
|
+
* True if the agent has logged in (~/.alfe/config.toml exists).
|
|
40
|
+
* Sync needs no separate initialization — login is enough.
|
|
21
41
|
*/
|
|
22
|
-
function
|
|
23
|
-
return (0,
|
|
42
|
+
function isInitialized() {
|
|
43
|
+
return (0, _alfe_ai_config.configExists)();
|
|
24
44
|
}
|
|
25
45
|
/**
|
|
26
|
-
*
|
|
46
|
+
* Invalidate the in-memory config cache. Call after login changes.
|
|
27
47
|
*/
|
|
28
|
-
function
|
|
29
|
-
|
|
48
|
+
function invalidateSyncConfigCache() {
|
|
49
|
+
cachedSyncConfigPromise = null;
|
|
30
50
|
}
|
|
31
51
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
52
|
+
* Resolve the full sync config — apiKey + apiUrl from @alfe.ai/config,
|
|
53
|
+
* agentId/orgId fetched once from /auth/validate.
|
|
54
|
+
*
|
|
55
|
+
* Returns null if the agent isn't logged in or validation fails.
|
|
56
|
+
* Callers that require config should use requireConfig().
|
|
57
|
+
*
|
|
58
|
+
* Concurrent callers share a single in-flight promise; failures are
|
|
59
|
+
* not cached so a transient API blip doesn't poison the process.
|
|
34
60
|
*/
|
|
35
|
-
async function
|
|
36
|
-
|
|
37
|
-
|
|
61
|
+
async function resolveSyncConfig() {
|
|
62
|
+
if (cachedSyncConfigPromise) return cachedSyncConfigPromise;
|
|
63
|
+
const promise = doResolveSyncConfig();
|
|
64
|
+
cachedSyncConfigPromise = promise;
|
|
65
|
+
const result = await promise;
|
|
66
|
+
if (result === null) cachedSyncConfigPromise = null;
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
async function doResolveSyncConfig() {
|
|
70
|
+
if (!(0, _alfe_ai_config.configExists)()) return null;
|
|
71
|
+
let alfe;
|
|
38
72
|
try {
|
|
39
|
-
|
|
40
|
-
const parsed = JSON.parse(raw);
|
|
41
|
-
if (!parsed.agentId || !parsed.token || !parsed.apiUrl) return null;
|
|
42
|
-
return {
|
|
43
|
-
...parsed,
|
|
44
|
-
workspacePath
|
|
45
|
-
};
|
|
73
|
+
alfe = (0, _alfe_ai_config.resolveConfig)();
|
|
46
74
|
} catch {
|
|
47
75
|
return null;
|
|
48
76
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const data = {
|
|
58
|
-
agentId: config.agentId,
|
|
59
|
-
orgId: config.orgId,
|
|
60
|
-
token: config.token,
|
|
61
|
-
workspacePath: config.workspacePath,
|
|
62
|
-
apiUrl: config.apiUrl
|
|
77
|
+
const validated = await validateToken(alfe.apiUrl, alfe.apiKey);
|
|
78
|
+
if (!validated?.agentId || !validated.tenantId) return null;
|
|
79
|
+
return {
|
|
80
|
+
agentId: validated.agentId,
|
|
81
|
+
orgId: validated.tenantId,
|
|
82
|
+
token: alfe.apiKey,
|
|
83
|
+
workspacePath: alfe.workspacePath,
|
|
84
|
+
apiUrl: alfe.apiUrl
|
|
63
85
|
};
|
|
64
|
-
await (0, node_fs_promises.writeFile)(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
65
86
|
}
|
|
66
87
|
/**
|
|
67
|
-
*
|
|
88
|
+
* Resolve config or throw with a clear message. Use in CLI entry points.
|
|
68
89
|
*/
|
|
69
|
-
async function requireConfig(
|
|
70
|
-
|
|
71
|
-
|
|
90
|
+
async function requireConfig() {
|
|
91
|
+
if (!(0, _alfe_ai_config.configExists)()) throw new Error("Alfe not configured — run `alfe login` first.");
|
|
92
|
+
const config = await resolveSyncConfig();
|
|
93
|
+
if (!config) throw new Error("Failed to resolve sync config — token may be invalid or the API is unreachable.");
|
|
72
94
|
return config;
|
|
73
95
|
}
|
|
96
|
+
async function validateToken(apiUrl, token) {
|
|
97
|
+
try {
|
|
98
|
+
const res = await fetch(`${apiUrl}/auth/validate`, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: { "Content-Type": "application/json" },
|
|
101
|
+
body: JSON.stringify({ token })
|
|
102
|
+
});
|
|
103
|
+
if (!res.ok) return null;
|
|
104
|
+
return await res.json();
|
|
105
|
+
} catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
74
109
|
//#endregion
|
|
75
110
|
//#region src/manifest.ts
|
|
76
111
|
/**
|
|
77
|
-
* AlfeSync manifest — local file manifest at
|
|
112
|
+
* AlfeSync manifest — local file manifest at `~/.alfe/sync/manifest.json`.
|
|
78
113
|
*
|
|
79
114
|
* Tracks file hashes, sizes, sync timestamps, and storage classes
|
|
80
|
-
* to enable efficient diff-based syncing.
|
|
115
|
+
* to enable efficient diff-based syncing. Lives under `~/.alfe/sync/`
|
|
116
|
+
* so workspace directories stay clean.
|
|
117
|
+
*
|
|
118
|
+
* `workspacePath` is accepted by every function for consistency with the
|
|
119
|
+
* rest of the package, but the manifest itself is workspace-independent
|
|
120
|
+
* (one agent, one workspace, one manifest).
|
|
81
121
|
*/
|
|
82
122
|
const MANIFEST_FILE = "manifest.json";
|
|
83
123
|
/**
|
|
84
|
-
* Resolve the manifest file path
|
|
124
|
+
* Resolve the manifest file path. Lives under `~/.alfe/sync/`, independent
|
|
125
|
+
* of the workspace path — one agent has one manifest.
|
|
85
126
|
*/
|
|
86
|
-
function manifestPath(
|
|
87
|
-
return (0, node_path.join)(
|
|
127
|
+
function manifestPath() {
|
|
128
|
+
return (0, node_path.join)(syncStateDir(), MANIFEST_FILE);
|
|
88
129
|
}
|
|
89
130
|
/**
|
|
90
131
|
* Read the local manifest. Returns empty manifest if not found.
|
|
132
|
+
*
|
|
133
|
+
* `workspacePath` is accepted for call-site symmetry with the rest of the
|
|
134
|
+
* package but is not used — the manifest path is resolved from
|
|
135
|
+
* `~/.alfe/sync/` regardless of which workspace the call comes from.
|
|
91
136
|
*/
|
|
92
137
|
async function readManifest(workspacePath) {
|
|
93
|
-
const path = manifestPath(
|
|
138
|
+
const path = manifestPath();
|
|
94
139
|
if (!(0, node_fs.existsSync)(path)) return { files: {} };
|
|
95
140
|
try {
|
|
96
141
|
const raw = await (0, node_fs_promises.readFile)(path, "utf-8");
|
|
@@ -100,11 +145,13 @@ async function readManifest(workspacePath) {
|
|
|
100
145
|
}
|
|
101
146
|
}
|
|
102
147
|
/**
|
|
103
|
-
* Write the local manifest.
|
|
148
|
+
* Write the local manifest. `workspacePath` is accepted for call-site
|
|
149
|
+
* symmetry but unused (see `readManifest`).
|
|
104
150
|
*/
|
|
105
151
|
async function writeManifest(workspacePath, manifest) {
|
|
106
|
-
|
|
107
|
-
await (0, node_fs_promises.
|
|
152
|
+
const path = manifestPath();
|
|
153
|
+
await (0, node_fs_promises.mkdir)(syncStateDir(), { recursive: true });
|
|
154
|
+
await (0, node_fs_promises.writeFile)(path, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
108
155
|
}
|
|
109
156
|
/**
|
|
110
157
|
* Update a single file entry in the local manifest.
|
|
@@ -448,10 +495,12 @@ function formatBytes(bytes) {
|
|
|
448
495
|
*/
|
|
449
496
|
const log = (0, _auriclabs_logger.createLogger)("SyncEngine");
|
|
450
497
|
/**
|
|
451
|
-
* Create a sync engine
|
|
498
|
+
* Create a sync engine. Workspace path defaults to `config.workspacePath`
|
|
499
|
+
* from the resolved Alfe config, but can be overridden (e.g. for tests).
|
|
452
500
|
*/
|
|
453
|
-
async function createSyncEngine(
|
|
454
|
-
const config = await requireConfig(
|
|
501
|
+
async function createSyncEngine(workspacePathOverride) {
|
|
502
|
+
const config = await requireConfig();
|
|
503
|
+
const workspacePath = workspacePathOverride ?? config.workspacePath;
|
|
455
504
|
const client = createApiClient({
|
|
456
505
|
apiUrl: config.apiUrl,
|
|
457
506
|
token: config.token,
|
|
@@ -664,7 +713,7 @@ async function detectLocalChanges(workspacePath, ignorePatterns) {
|
|
|
664
713
|
* Directories to always skip during walks.
|
|
665
714
|
*/
|
|
666
715
|
function shouldSkipDir(name) {
|
|
667
|
-
return name === "node_modules" || name === ".git" || name === ".sst" || name === ".
|
|
716
|
+
return name === "node_modules" || name === ".git" || name === ".sst" || name === ".build" || name === "dist";
|
|
668
717
|
}
|
|
669
718
|
//#endregion
|
|
670
719
|
Object.defineProperty(exports, "computeFileHash", {
|
|
@@ -673,18 +722,6 @@ Object.defineProperty(exports, "computeFileHash", {
|
|
|
673
722
|
return computeFileHash;
|
|
674
723
|
}
|
|
675
724
|
});
|
|
676
|
-
Object.defineProperty(exports, "configDir", {
|
|
677
|
-
enumerable: true,
|
|
678
|
-
get: function() {
|
|
679
|
-
return configDir;
|
|
680
|
-
}
|
|
681
|
-
});
|
|
682
|
-
Object.defineProperty(exports, "configPath", {
|
|
683
|
-
enumerable: true,
|
|
684
|
-
get: function() {
|
|
685
|
-
return configPath;
|
|
686
|
-
}
|
|
687
|
-
});
|
|
688
725
|
Object.defineProperty(exports, "createApiClient", {
|
|
689
726
|
enumerable: true,
|
|
690
727
|
get: function() {
|
|
@@ -709,16 +746,16 @@ Object.defineProperty(exports, "downloadFiles", {
|
|
|
709
746
|
return downloadFiles;
|
|
710
747
|
}
|
|
711
748
|
});
|
|
712
|
-
Object.defineProperty(exports, "
|
|
749
|
+
Object.defineProperty(exports, "invalidateSyncConfigCache", {
|
|
713
750
|
enumerable: true,
|
|
714
751
|
get: function() {
|
|
715
|
-
return
|
|
752
|
+
return invalidateSyncConfigCache;
|
|
716
753
|
}
|
|
717
754
|
});
|
|
718
|
-
Object.defineProperty(exports, "
|
|
755
|
+
Object.defineProperty(exports, "isInitialized", {
|
|
719
756
|
enumerable: true,
|
|
720
757
|
get: function() {
|
|
721
|
-
return
|
|
758
|
+
return isInitialized;
|
|
722
759
|
}
|
|
723
760
|
});
|
|
724
761
|
Object.defineProperty(exports, "readManifest", {
|
|
@@ -739,22 +776,28 @@ Object.defineProperty(exports, "requireConfig", {
|
|
|
739
776
|
return requireConfig;
|
|
740
777
|
}
|
|
741
778
|
});
|
|
742
|
-
Object.defineProperty(exports, "
|
|
779
|
+
Object.defineProperty(exports, "resolveSyncConfig", {
|
|
743
780
|
enumerable: true,
|
|
744
781
|
get: function() {
|
|
745
|
-
return
|
|
782
|
+
return resolveSyncConfig;
|
|
746
783
|
}
|
|
747
784
|
});
|
|
748
|
-
Object.defineProperty(exports, "
|
|
785
|
+
Object.defineProperty(exports, "syncStateDir", {
|
|
749
786
|
enumerable: true,
|
|
750
787
|
get: function() {
|
|
751
|
-
return
|
|
788
|
+
return syncStateDir;
|
|
752
789
|
}
|
|
753
790
|
});
|
|
754
|
-
Object.defineProperty(exports, "
|
|
791
|
+
Object.defineProperty(exports, "updateManifestEntry", {
|
|
755
792
|
enumerable: true,
|
|
756
793
|
get: function() {
|
|
757
|
-
return
|
|
794
|
+
return updateManifestEntry;
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
Object.defineProperty(exports, "uploadFiles", {
|
|
798
|
+
enumerable: true,
|
|
799
|
+
get: function() {
|
|
800
|
+
return uploadFiles;
|
|
758
801
|
}
|
|
759
802
|
});
|
|
760
803
|
Object.defineProperty(exports, "writeManifest", {
|