@narumitw/pi-sync 0.53.0 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +6 -4
  2. package/dist/chunks/{chunk-I5IG4WV4.ts → chunk-5ZWQ33XL.ts} +39 -19
  3. package/dist/chunks/{chunk-I5IG4WV4.ts.map → chunk-5ZWQ33XL.ts.map} +2 -2
  4. package/dist/chunks/{chunk-QTCGWQP3.ts → chunk-QDPED3XJ.ts} +67 -8
  5. package/dist/chunks/{chunk-QTCGWQP3.ts.map → chunk-QDPED3XJ.ts.map} +3 -3
  6. package/dist/chunks/{chunk-FHNISD7A.ts → chunk-SDZCTCYQ.ts} +4 -4
  7. package/dist/chunks/{chunk-NGIJQSRE.ts → chunk-VHXBCEVF.ts} +2 -2
  8. package/dist/chunks/{chunk-BX3K7YW7.ts → chunk-WFKUKVCP.ts} +2 -2
  9. package/dist/chunks/{chunk-UETBKTDI.ts → chunk-YDVQYUHE.ts} +1 -1
  10. package/dist/chunks/{chunk-UETBKTDI.ts.map → chunk-YDVQYUHE.ts.map} +2 -2
  11. package/dist/chunks/{conflict-review-NK5CM6UA.ts → conflict-review-JANNPKKS.ts} +4 -4
  12. package/dist/chunks/{git-backend-HLGKUQJT.ts → git-backend-FD7AVSK6.ts} +2 -2
  13. package/dist/chunks/{manager-result-dispatcher-35TM3ZR3.ts → manager-result-dispatcher-6MQKFCWB.ts} +4 -4
  14. package/dist/chunks/{manager-ui-6AYMNXUP.ts → manager-ui-IURS4WXY.ts} +675 -610
  15. package/dist/chunks/manager-ui-IURS4WXY.ts.map +7 -0
  16. package/dist/chunks/{remote-selection-ui-BB5SX6P5.ts → remote-selection-ui-6LJJ4IMQ.ts} +3 -3
  17. package/dist/chunks/{s3-backend-O2AS3TH6.ts → s3-backend-AQGV6LFT.ts} +18 -4
  18. package/dist/chunks/s3-backend-AQGV6LFT.ts.map +7 -0
  19. package/dist/chunks/{sync-inspection-RLIHUYDV.ts → sync-inspection-K45QVLHB.ts} +3 -3
  20. package/dist/chunks/{sync-operations-RBUOUJWL.ts → sync-operations-OWTE3ZL2.ts} +18 -12
  21. package/dist/chunks/{sync-operations-RBUOUJWL.ts.map → sync-operations-OWTE3ZL2.ts.map} +2 -2
  22. package/dist/chunks/{webdav-backend-ETQGSI6I.ts → webdav-backend-TBILVEQK.ts} +2 -2
  23. package/dist/index.ts +33 -8
  24. package/dist/index.ts.map +3 -3
  25. package/docs/settings.md +11 -3
  26. package/package.json +4 -4
  27. package/src/backends/s3/s3-backend.ts +22 -1
  28. package/src/backends/s3/s3-client.ts +3 -1
  29. package/src/backends/sync-backend.ts +2 -0
  30. package/src/commands/command-execution.ts +3 -0
  31. package/src/commands/command-handler.ts +8 -1
  32. package/src/sync/merge-transfer-error.ts +72 -0
  33. package/src/sync/merged-sync.ts +8 -5
  34. package/src/sync/sync-mutations.ts +9 -7
  35. package/src/sync/sync-queries.ts +1 -0
  36. package/src/ui/cancellable-operation.ts +47 -17
  37. package/src/ui/manager-attention.ts +3 -5
  38. package/src/ui/manager-state.ts +32 -15
  39. package/src/ui/manager-ui.ts +59 -87
  40. package/src/ui/operation-dialogs.ts +29 -0
  41. package/src/ui/settings-ui.ts +140 -46
  42. package/src/ui/setup/setup-actions.ts +2 -2
  43. package/dist/chunks/manager-ui-6AYMNXUP.ts.map +0 -7
  44. package/dist/chunks/s3-backend-O2AS3TH6.ts.map +0 -7
  45. /package/dist/chunks/{chunk-FHNISD7A.ts.map → chunk-SDZCTCYQ.ts.map} +0 -0
  46. /package/dist/chunks/{chunk-NGIJQSRE.ts.map → chunk-VHXBCEVF.ts.map} +0 -0
  47. /package/dist/chunks/{chunk-BX3K7YW7.ts.map → chunk-WFKUKVCP.ts.map} +0 -0
  48. /package/dist/chunks/{conflict-review-NK5CM6UA.ts.map → conflict-review-JANNPKKS.ts.map} +0 -0
  49. /package/dist/chunks/{git-backend-HLGKUQJT.ts.map → git-backend-FD7AVSK6.ts.map} +0 -0
  50. /package/dist/chunks/{manager-result-dispatcher-35TM3ZR3.ts.map → manager-result-dispatcher-6MQKFCWB.ts.map} +0 -0
  51. /package/dist/chunks/{remote-selection-ui-BB5SX6P5.ts.map → remote-selection-ui-6LJJ4IMQ.ts.map} +0 -0
  52. /package/dist/chunks/{sync-inspection-RLIHUYDV.ts.map → sync-inspection-K45QVLHB.ts.map} +0 -0
  53. /package/dist/chunks/{webdav-backend-ETQGSI6I.ts.map → webdav-backend-TBILVEQK.ts.map} +0 -0
package/dist/index.ts.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/commands/command-execution.ts", "../src/commands/command-handler.ts", "../src/settings/config-errors.ts", "../src/sync/automatic-sync.ts", "../src/sync/startup-check.ts", "../src/sync/sync-loaders.ts", "../src/sync-extension.ts"],
4
- "sourcesContent": ["import type { ExtensionCommandContext, ExtensionContext } from \"@earendil-works/pi-coding-agent\";\nimport { loadConfig, sessionTokenWarnings, syncSessionsWarnings } from \"../settings/config.js\";\nimport { activeLocalConfigPath, createLocalConfigDocument, localConfigPath } from \"../settings/config-file.js\";\nimport { localConfigTemplate, readLocalConfigObject } from \"../settings/settings-store.js\";\nimport type { AnySyncConfig } from \"../settings/settings-types.js\";\nimport { unlock, withLock } from \"../state/lock.js\";\nimport { migrateLegacyStateDirectory, stateDirectoryMigrationNotice } from \"../state/state-directory.js\";\nimport { throwIfAborted } from \"../sync/signals.js\";\nimport { syncErrorGuidance } from \"../sync/sync-error-guidance.js\";\nimport { isSyncDecisionRequiredError, SetupPullRequiresUiError } from \"../sync/sync-errors.js\";\nimport type { SyncLoaders } from \"../sync/sync-loaders.js\";\nimport { RemoteSelectionMismatchError } from \"../sync/sync-policy.js\";\nimport { automaticSyncSummary } from \"../ui/automatic-sync-summary.js\";\nimport type { RunRouteResult } from \"../ui/cancellable-operation.js\";\nimport { setSyncStatus } from \"../ui/sync-status.js\";\nimport { safeTerminalText } from \"../ui/terminal-text.js\";\nimport { parseOptions, resolveSyncCommand, splitArgs, usage, validateCommandOptions } from \"./command.js\";\nimport type { CommandOptions } from \"./command-types.js\";\n\nexport async function executeRecoveryCommand(\n rawArgs: string,\n ctx: ExtensionContext,\n signal: AbortSignal | undefined,\n loaders: SyncLoaders,\n onCommit?: () => void,\n setup?: string,\n): Promise<RunRouteResult> {\n try {\n const [subcommand, ...rest] = splitArgs(rawArgs);\n if (subcommand !== \"sync\" && subcommand !== \"pull\" && subcommand !== \"push\") {\n throw new Error(`Unsupported sync recovery route: ${subcommand ?? \"missing\"}`);\n }\n const options = parseOptions(rest);\n if (setup !== undefined) options.setup = setup;\n if (signal) options.signal = signal;\n if (onCommit) options.onCommit = onCommit;\n options.reload = false;\n options.auto = false;\n validateCommandOptions(subcommand, options);\n const operations = await loaders.operations();\n throwIfAborted(options.signal);\n if (subcommand === \"push\") {\n const outcome = await withLock(\"push\", () => operations.push(ctx, options));\n return { kind: \"completed\", ...(outcome ? { outcome } : {}) };\n }\n if (subcommand === \"pull\") {\n const outcome = await withLock(\"pull\", () => operations.pull(ctx, options));\n return { kind: \"completed\", ...(outcome ? { outcome } : {}) };\n }\n await withLock(\"sync\", () => operations.syncBoth(ctx, options));\n return { kind: \"completed\" };\n } catch (error) {\n if (signal?.aborted) return { kind: \"failed\" };\n setSyncStatus(ctx, undefined);\n if (error instanceof RemoteSelectionMismatchError) {\n return { kind: \"remote-selection-required\", decision: error.decision };\n }\n if (isSyncDecisionRequiredError(error)) {\n return { kind: \"decision-required\", decision: error.decision };\n }\n ctx.ui.notify(syncErrorGuidance(error), \"error\");\n return { kind: \"failed\" };\n }\n}\n\nexport async function executeCommand(\n rawArgs: string,\n ctx: ExtensionCommandContext,\n signal: AbortSignal | undefined,\n loaders: SyncLoaders,\n onCommit?: () => void,\n setup?: string,\n): Promise<RunRouteResult> {\n try {\n const command = await resolveSyncCommand(rawArgs, ctx);\n if (signal?.aborted || !command) return { kind: \"completed\" };\n const { subcommand, rest } = command;\n const options = parseOptions(rest);\n if (setup !== undefined) options.setup = setup;\n if (signal) options.signal = signal;\n if (onCommit) options.onCommit = onCommit;\n validateCommandOptions(subcommand, options);\n\n switch (subcommand) {\n case \"help\":\n ctx.ui.notify(usage(), \"info\");\n return { kind: \"completed\" };\n case \"use\": {\n const { useSyncSetup } = await loaders.setupSwitch();\n throwIfAborted(options.signal);\n await useSyncSetup(\n ctx,\n options.args[0] ?? \"\",\n async (selectedSetup) => {\n const operations = await loaders.operations();\n throwIfAborted(options.signal);\n return withLock(\"pull\", () => operations.pull(ctx, { ...options, setup: selectedSetup }));\n },\n undefined,\n options.signal,\n );\n return { kind: \"completed\" };\n }\n case \"init\":\n await initConfig(ctx, signal);\n return { kind: \"completed\" };\n case \"config\":\n await showConfig(ctx, options);\n return { kind: \"completed\" };\n case \"files\": {\n const { showFileSelection } = await import(\"../ui/file-selection.js\");\n throwIfAborted(options.signal);\n await showFileSelection(ctx, options.setup, options.signal);\n return { kind: \"completed\" };\n }\n case \"status\": {\n const operations = await loaders.operations();\n throwIfAborted(options.signal);\n await operations.status(ctx, options);\n return { kind: \"completed\" };\n }\n case \"diff\": {\n const operations = await loaders.operations();\n throwIfAborted(options.signal);\n await operations.diff(ctx, options);\n return { kind: \"completed\" };\n }\n case \"conflicts\": {\n const { showConflicts } = await import(\"../sync/conflict-review.js\");\n throwIfAborted(options.signal);\n await withLock(\"sync\", () => showConflicts(ctx, options));\n return { kind: \"completed\" };\n }\n case \"doctor\": {\n const operations = await loaders.operations();\n throwIfAborted(options.signal);\n await operations.doctor(ctx, options);\n return { kind: \"completed\" };\n }\n case \"push\": {\n const operations = await loaders.operations();\n throwIfAborted(options.signal);\n const outcome = await withLock(\"push\", () => operations.push(ctx, options));\n return { kind: \"completed\", ...(outcome ? { outcome } : {}) };\n }\n case \"pull\": {\n const operations = await loaders.operations();\n throwIfAborted(options.signal);\n const outcome = await withLock(\"pull\", () => operations.pull(ctx, options));\n return { kind: \"completed\", ...(outcome ? { outcome } : {}) };\n }\n case \"sync\": {\n const operations = await loaders.operations();\n throwIfAborted(options.signal);\n await withLock(\"sync\", () => operations.syncBoth(ctx, options));\n return { kind: \"completed\" };\n }\n case \"history\": {\n const operations = await loaders.operations();\n throwIfAborted(options.signal);\n await operations.history(ctx, options);\n return { kind: \"completed\" };\n }\n case \"rollback\": {\n const operations = await loaders.operations();\n throwIfAborted(options.signal);\n await withLock(\"rollback\", () => operations.rollback(ctx, options));\n return { kind: \"completed\" };\n }\n case \"migrate-state\":\n await migrateStateDirectory(ctx, options);\n return { kind: \"completed\" };\n case \"unlock\":\n await unlock(ctx, options);\n return { kind: \"completed\" };\n default:\n ctx.ui.notify(`Unknown /sync command: ${subcommand}\\n\\n${usage()}`, \"warning\");\n return { kind: \"failed\" };\n }\n } catch (error) {\n if (signal?.aborted) return { kind: \"failed\" };\n setSyncStatus(ctx, undefined);\n if (error instanceof SetupPullRequiresUiError) throw error;\n if (error instanceof RemoteSelectionMismatchError) {\n return { kind: \"remote-selection-required\", decision: error.decision };\n }\n if (isSyncDecisionRequiredError(error)) {\n return { kind: \"decision-required\", decision: error.decision };\n }\n ctx.ui.notify(syncErrorGuidance(error), \"error\");\n return { kind: \"failed\" };\n }\n}\n\nasync function migrateStateDirectory(ctx: ExtensionCommandContext, options: CommandOptions) {\n const notice = stateDirectoryMigrationNotice();\n if (!notice) {\n ctx.ui.notify(\"pi-sync already uses the canonical pi-sync/ state directory.\", \"info\");\n return;\n }\n if (\n !options.yes &&\n !(await ctx.ui.confirm(\n \"Migrate pi-sync state directory\",\n \"Confirm that every other Pi process is closed. pi-sync will atomically rename .pisync/ to pi-sync/ without merging or deleting either root.\",\n { signal: options.signal },\n ))\n ) {\n ctx.ui.notify(\"pi-sync state migration cancelled.\", \"info\");\n return;\n }\n throwIfAborted(options.signal);\n const result = await migrateLegacyStateDirectory();\n throwIfAborted(options.signal);\n if (result.status === \"ready\") {\n ctx.ui.notify(\"pi-sync already uses the canonical pi-sync/ state directory.\", \"info\");\n return;\n }\n ctx.ui.notify(result.message, result.status === \"migrated\" ? \"info\" : \"warning\");\n}\n\nasync function initConfig(ctx: ExtensionCommandContext, signal?: AbortSignal) {\n const configPath = localConfigPath();\n if (await readLocalConfigObject()) {\n ctx.ui.notify(`Config already exists: ${await activeLocalConfigPath()}`, \"info\");\n return;\n }\n\n if (ctx.mode === \"tui\") {\n const { showSetupWizard } = await import(\"../ui/setup/setup-wizard.js\");\n throwIfAborted(signal);\n await showSetupWizard(ctx, signal);\n return;\n }\n await createLocalConfigDocument(localConfigTemplate());\n ctx.ui.notify(`Created ${configPath}. Add a storage connection and sync setup before syncing.`, \"info\");\n}\n\nasync function showConfig(ctx: ExtensionCommandContext, options: CommandOptions) {\n const config = await loadConfig(options.setup);\n const warnings = [\n ...(config.backend.type === \"s3\" ? sessionTokenWarnings(config.backend.profile) : []),\n ...syncSessionsWarnings(config),\n ];\n const storageLines = configStorageLines(config);\n ctx.ui.notify(\n [\n \"pi-sync config:\",\n `sync setup: ${config.setupName}`,\n `storage connection: ${config.connectionName}`,\n ...storageLines,\n `storage path: ${config.storagePath}`,\n `automatic sync: ${automaticSyncSummary(config.automatic)}`,\n `settings field merge: ${config.mergeSettings ? \"On (experimental)\" : \"Off\"}`,\n `machine-local settings.json fields: ${(config.localFields ?? []).map(safeTerminalText).join(\", \") || \"none\"}`,\n `automatic transfer at startup: ${config.automaticTransfer ? \"On \u2014 selected-file upload/replace/delete; no reload\" : \"Off\"}`,\n `included content: ${config.include.join(\", \") || \"none\"}`,\n `sessions: ${config.include.includes(\"sessions\") ? \"included\" : \"not included\"}`,\n `settings file: ${localConfigPath()}`,\n ...warnings,\n ].join(\"\\n\"),\n warnings.length > 0 ? \"warning\" : \"info\",\n );\n}\n\nfunction configStorageLines(config: AnySyncConfig) {\n switch (config.backend.type) {\n case \"git\":\n return [\n \"kind: git\",\n `remote: ${displayGitRemote(config.backend.profile.remote)}`,\n \"authentication: existing Git/SSH credentials (not stored)\",\n `branch: ${config.backend.destination.branch}`,\n ];\n case \"webdav\":\n return [\n \"kind: webdav\",\n `url: ${displayWebDavUrl(config.backend.profile.url, config.backend.profile.username)}`,\n \"username: configured (value hidden)\",\n \"password: configured\",\n ];\n case \"s3\":\n return [\n \"kind: s3\",\n `endpoint: ${config.backend.profile.endpoint}`,\n `bucket: ${config.backend.destination.bucket}`,\n `region: ${config.backend.profile.region}`,\n \"access key id: configured\",\n \"secret access key: configured\",\n `session token: ${config.backend.profile.sessionToken ? \"configured\" : \"not configured\"}`,\n ];\n }\n}\n\nfunction displayGitRemote(value: string | undefined) {\n if (!value) return \"missing\";\n try {\n const url = new URL(value);\n url.username = \"\";\n url.password = \"\";\n url.search = \"\";\n url.hash = \"\";\n return url.toString();\n } catch {\n return value.replace(/^(?:[^@\\s]+@)?(?<host>[^:]+):.+$/u, \"$<host>:\u2026\");\n }\n}\n\nfunction displayWebDavUrl(value: string | undefined, username: string | undefined) {\n if (!value) return \"missing\";\n try {\n const url = new URL(value);\n url.username = \"\";\n url.password = \"\";\n url.search = \"\";\n url.hash = \"\";\n return username ? `${url.origin}/\u2026` : `${url.origin}${url.pathname}`;\n } catch {\n return \"invalid (value hidden)\";\n }\n}\n", "import type { ExtensionCommandContext, ExtensionContext } from \"@earendil-works/pi-coding-agent\";\nimport { loadConfig } from \"../settings/config.js\";\nimport { readStateForConfig, syncStateFingerprint } from \"../state/sync-state-store.js\";\nimport { combineSignals } from \"../sync/signals.js\";\nimport { errorMessage } from \"../sync/sync-errors.js\";\nimport type { SyncLoaders } from \"../sync/sync-loaders.js\";\nimport { formatRemoteSelectionMismatch } from \"../sync/sync-policy.js\";\nimport type { RunRouteResult } from \"../ui/cancellable-operation.js\";\nimport {\n observationMatchesConfig,\n type SyncAttentionController,\n type SyncAttentionOrigin,\n syncAttentionMatchesConfig,\n} from \"../ui/sync-attention.js\";\nimport { setSyncStatus } from \"../ui/sync-status.js\";\nimport { parseOptions, splitArgs } from \"./command.js\";\nimport { executeCommand, executeRecoveryCommand } from \"./command-execution.js\";\n\nexport async function handleCommand(\n rawArgs: string,\n ctx: ExtensionCommandContext,\n sessionSignal: AbortSignal,\n loaders: SyncLoaders,\n attention: SyncAttentionController,\n) {\n await reconcileObservation(attention, sessionSignal);\n if (sessionSignal.aborted) return;\n const run = (route: string, signal?: AbortSignal, onCommit?: () => void, target?: string) =>\n executeCommand(\n route,\n ctx,\n combineSignals(sessionSignal, signal),\n loaders,\n observationCommitCallback(attention, sessionSignal, onCommit),\n target,\n );\n if (!rawArgs.trim()) {\n try {\n const { showSyncManager } = await import(\"../ui/manager-ui.js\");\n if (sessionSignal.aborted) return;\n await showSyncManager(ctx, run, sessionSignal, {\n getAttention: () => attention.current(),\n getObservation: () => attention.observation(),\n onObservationInvalidated: () => attention.clearObservation(),\n onSelectionResolved: (expected) => {\n if (attention.current() === expected) attention.clear(ctx);\n },\n });\n } catch (error) {\n if (sessionSignal.aborted) return;\n setSyncStatus(ctx, undefined);\n ctx.ui.notify(errorMessage(error), \"error\");\n }\n await reconcileObservation(attention, sessionSignal);\n if (!sessionSignal.aborted) await attention.publish(ctx, sessionSignal);\n return;\n }\n const result = await run(rawArgs);\n if (result.kind === \"decision-required\") {\n ctx.ui.notify(result.decision.directMessage, \"error\");\n } else if (result.kind === \"remote-selection-required\") {\n const origin = directSelectionOrigin(rawArgs);\n if (origin) attention.set(result.decision, origin);\n const deterministic = splitArgs(rawArgs).some((arg) => arg === \"--yes\" || arg === \"-y\");\n if (origin && ctx.mode === \"tui\" && !deterministic) {\n await resolveSelectionAttention(ctx, attention, sessionSignal, loaders);\n } else {\n ctx.ui.notify(\n formatRemoteSelectionMismatch(\n result.decision.setupName,\n result.decision.localInclude,\n result.decision.remoteInclude,\n ),\n \"error\",\n );\n }\n }\n await clearAttentionAfterCompletedOperation(rawArgs, result, ctx, attention, sessionSignal);\n await reconcileSelectionAttention(ctx, attention, sessionSignal);\n await reconcileObservation(attention, sessionSignal);\n if (!sessionSignal.aborted) await attention.publish(ctx, sessionSignal);\n}\n\nfunction observationCommitCallback(\n attention: SyncAttentionController,\n sessionSignal: AbortSignal,\n onCommit?: () => void,\n) {\n const observed = attention.observation();\n return () => {\n // Opening or cancelling a review changes nothing. A commit may change data\n // even if later publication/baseline cleanup fails; never restore old hints.\n if (!sessionSignal.aborted && attention.observation() === observed) attention.clearObservation();\n onCommit?.();\n };\n}\n\nasync function reconcileObservation(attention: SyncAttentionController, signal: AbortSignal) {\n const observed = attention.observation();\n if (!observed || signal.aborted) return;\n try {\n const config = await loadConfig();\n if (signal.aborted || attention.observation() !== observed) return;\n if (!observationMatchesConfig(observed, config)) {\n attention.clearObservation();\n return;\n }\n const state = await readStateForConfig(config);\n if (signal.aborted || attention.observation() !== observed) return;\n if (syncStateFingerprint(state) !== observed.inspection.stateIdentity) attention.clearObservation();\n } catch {\n if (!signal.aborted && attention.observation() === observed) attention.clearObservation();\n }\n}\n\nasync function clearAttentionAfterCompletedOperation(\n rawArgs: string,\n result: RunRouteResult,\n ctx: ExtensionContext,\n attention: SyncAttentionController,\n signal: AbortSignal,\n) {\n if (result.kind !== \"completed\" || result.outcome === \"cancelled\" || signal.aborted) return;\n const [command, ...rest] = splitArgs(rawArgs);\n if (command !== \"sync\" && command !== \"pull\" && command !== \"push\") return;\n const current = attention.current();\n if (!current) return;\n try {\n const options = parseOptions(rest);\n const setupName = options.setup ?? (await loadConfig()).setupName;\n if (signal.aborted || attention.current() !== current) return;\n if (current.decision.setupName === setupName) attention.clear(ctx);\n } catch {\n // Attention reconciliation below owns malformed or concurrently changed settings.\n }\n}\n\nasync function reconcileSelectionAttention(\n ctx: ExtensionContext,\n attention: SyncAttentionController,\n signal: AbortSignal,\n) {\n const current = attention.current();\n if (!current || signal.aborted) return;\n try {\n const config = await loadConfig(current.decision.setupName);\n if (signal.aborted || attention.current() !== current) return;\n if (!syncAttentionMatchesConfig(current, config)) attention.clear(ctx);\n } catch {\n if (!signal.aborted && attention.current() === current) attention.clear(ctx);\n }\n}\n\nfunction directSelectionOrigin(rawArgs: string): SyncAttentionOrigin | undefined {\n const command = splitArgs(rawArgs)[0];\n return command === \"sync\" || command === \"pull\" || command === \"push\" ? command : undefined;\n}\n\nexport async function resolveSelectionAttention(\n ctx: ExtensionContext,\n attention: SyncAttentionController,\n signal: AbortSignal,\n loaders: SyncLoaders,\n options: {\n cancelLabel?: string;\n withStateAccess?: <T>(task: () => Promise<T>) => Promise<T>;\n } = {},\n) {\n const current = attention.current();\n if (!current || signal.aborted) return;\n const { dispatchManagerResult } = await import(\"../ui/manager-result-dispatcher.js\");\n if (signal.aborted || attention.current() !== current) return;\n await dispatchManagerResult(\n ctx,\n { kind: \"remote-selection-required\", decision: current.decision },\n current.origin,\n (route, actionSignal, onCommit, target) => {\n const execute = () =>\n executeRecoveryCommand(\n route,\n ctx,\n combineSignals(signal, actionSignal),\n loaders,\n observationCommitCallback(attention, signal, onCommit),\n target,\n );\n return options.withStateAccess ? options.withStateAccess(execute) : execute();\n },\n signal,\n {\n cancelLabel: options.cancelLabel,\n withStateAccess: options.withStateAccess,\n onSelectionResolved: () => {\n if (attention.current() === current) attention.clear(ctx);\n },\n },\n );\n}\n", "export function isMissingConfigError(error: unknown) {\n return (\n error instanceof Error &&\n (error.message.startsWith(\"Missing pi-sync settings.\") || error.message === \"No sync setups are configured.\")\n );\n}\n", "import type { ExtensionContext } from \"@earendil-works/pi-coding-agent\";\nimport { setSyncSetupCompletions } from \"../commands/command.js\";\nimport type { CommandOptions } from \"../commands/command-types.js\";\nimport { loadConfig, loadPartialConfig } from \"../settings/config.js\";\nimport { isMissingConfigError } from \"../settings/config-errors.js\";\nimport { consumeLocalConfigMigrationNotice } from \"../settings/config-file.js\";\nimport { readLocalConfigObject } from \"../settings/settings-store.js\";\nimport { sessionDirFromContext, snapshotOptionsForContext } from \"../snapshot/session-paths.js\";\nimport { recoverSnapshotTransactionsOnStartup } from \"../snapshot/snapshot-transaction.js\";\nimport { withLock } from \"../state/lock.js\";\nimport { stateDirectoryMigrationNotice } from \"../state/state-directory.js\";\nimport { ensureStateDir, readStateForConfig } from \"../state/sync-state-store.js\";\nimport { configureSyncStatus, setSyncStatus } from \"../ui/sync-status.js\";\nimport { safeTerminalText } from \"../ui/terminal-text.js\";\nimport { throwIfAborted } from \"./signals.js\";\nimport { errorMessage } from \"./sync-errors.js\";\nimport type { SyncLoaders } from \"./sync-loaders.js\";\nimport { captureMutationOwner } from \"./sync-local.js\";\n\nconst AUTO_SYNC_OPTIONS: CommandOptions = {\n yes: true,\n force: false,\n stale: false,\n silent: true,\n reload: false,\n auto: true,\n args: [],\n};\n\nexport async function startSession(ctx: ExtensionContext, signal: AbortSignal) {\n throwIfAborted(signal);\n const stateNotice = stateDirectoryMigrationNotice();\n if (stateNotice && ctx.hasUI) ctx.ui.notify(stateNotice, \"warning\");\n // Recovery can restore managed files: finish it before Pi accepts user edits.\n // Unlike remote inspection, it must never be detached behind startup.\n const validateMutation = captureMutationOwner(ctx, signal);\n const currentFile = ctx.sessionManager.getSessionFile?.();\n await recoverSnapshotTransactionsOnStartup({\n signal,\n sessionDir: sessionDirFromContext(ctx),\n protectedTargets: currentFile ? [currentFile] : [],\n validateMutation,\n });\n throwIfAborted(signal);\n try {\n const settings = await readLocalConfigObject();\n throwIfAborted(signal);\n const names = settings ? Object.keys(settings.syncSetups).sort((left, right) => left.localeCompare(right)) : [];\n setSyncSetupCompletions(names);\n configureSyncStatus(ctx, settings?.showStatus ?? true);\n } catch {\n if (signal.aborted) return;\n setSyncSetupCompletions([]);\n }\n const migrationNotice = consumeLocalConfigMigrationNotice();\n if (migrationNotice && ctx.hasUI) ctx.ui.notify(migrationNotice, \"warning\");\n throwIfAborted(signal);\n if (!ctx.hasUI) return;\n try {\n const config = await loadConfig();\n throwIfAborted(signal);\n configureSyncStatus(ctx, config.showStatus);\n return config.automatic || config.automaticTransfer ? config : undefined;\n } catch (error) {\n throwIfAborted(signal);\n if (!isMissingConfigError(error)) {\n ctx.ui.notify(`pi-sync startup check skipped: ${safeTerminalText(errorMessage(error))}`, \"warning\");\n }\n }\n}\n\nexport async function autoPushSessions(ctx: ExtensionContext, signal: AbortSignal, loaders: SyncLoaders) {\n try {\n const partial = await loadPartialConfig();\n throwIfAborted(signal);\n if (!partial.automatic) return;\n if (!partial.include.includes(\"sessions\")) return;\n await ensureStateDir();\n throwIfAborted(signal);\n const config = await loadConfig();\n throwIfAborted(signal);\n if (!config.include.includes(\"sessions\")) return;\n const [operations, snapshotModule, syncStateModule] = await Promise.all([\n loaders.operations(),\n loaders.snapshot(),\n loaders.syncState(),\n ]);\n throwIfAborted(signal);\n await withLock(\"auto-session-push\", async () => {\n throwIfAborted(signal);\n const state = await readStateForConfig(config);\n throwIfAborted(signal);\n const local = await snapshotModule.createSnapshot(config.snapshotIdentity, {\n ...snapshotOptionsForContext(ctx, config),\n signal,\n });\n throwIfAborted(signal);\n if (!syncStateModule.hasLocalChanges(local, state, config)) return;\n await operations.push(ctx, { ...AUTO_SYNC_OPTIONS, signal }, { config, state, local });\n });\n } catch (error) {\n if (signal.aborted || isMissingConfigError(error)) return;\n setSyncStatus(ctx, undefined);\n ctx.ui.notify(`pi-sync session push skipped: ${errorMessage(error)}`, \"warning\");\n }\n}\n", "import type { ExtensionContext } from \"@earendil-works/pi-coding-agent\";\nimport { loadConfigForCheck, syncCheckConfigFingerprint } from \"../settings/config.js\";\nimport { isMissingConfigError } from \"../settings/config-errors.js\";\nimport type { AnySyncConfig } from \"../settings/settings-types.js\";\nimport { snapshotOptionsForContext } from \"../snapshot/session-paths.js\";\nimport { withLock } from \"../state/lock.js\";\nimport { withStateDirectoryAccess } from \"../state/state-directory.js\";\nimport { readStateForConfig, syncStateFingerprint } from \"../state/sync-state-store.js\";\nimport type { SyncAttentionController } from \"../ui/sync-attention.js\";\nimport { setSyncStatus } from \"../ui/sync-status.js\";\nimport { safeTerminalText } from \"../ui/terminal-text.js\";\nimport { combineSignals, throwIfAborted } from \"./signals.js\";\nimport { errorMessage } from \"./sync-errors.js\";\nimport type { SyncLoaders } from \"./sync-loaders.js\";\n\n/** One owned task per extension session, never keyed by the shared headless UI. */\nexport function createStartupCheck(loaders: SyncLoaders, attention: SyncAttentionController, timeoutMs = 30_000) {\n let active:\n | {\n controller: AbortController;\n promise: Promise<void>;\n owner: ExtensionContext[\"sessionManager\"];\n transfer: boolean;\n }\n | undefined;\n\n let pending: { ctx: ExtensionContext; signal: AbortSignal; config: AnySyncConfig } | undefined;\n\n const controllerApi = {\n async interrupt(ctx: ExtensionContext) {\n const task = active;\n if (!task?.transfer || task.owner !== ctx.sessionManager) return;\n task.controller.abort(new DOMException(\"Agent became busy\", \"AbortError\"));\n await task.promise;\n },\n settled(ctx: ExtensionContext) {\n const queued = pending;\n if (!queued || queued.ctx.sessionManager !== ctx.sessionManager || queued.signal.aborted || !ctx.isIdle()) return;\n pending = undefined;\n controllerApi.start(ctx, queued.signal, queued.config);\n },\n start(ctx: ExtensionContext, sessionSignal: AbortSignal, initialConfig?: AnySyncConfig) {\n if (!ctx.hasUI || sessionSignal.aborted) return;\n if (initialConfig?.automaticTransfer && !ctx.isIdle()) {\n pending = { ctx, signal: sessionSignal, config: initialConfig };\n return;\n }\n if (active) throw new Error(\"Previous startup check has not been drained.\");\n const controller = new AbortController();\n const signal = combineSignals(sessionSignal, controller.signal);\n const task = {\n controller,\n owner: ctx.sessionManager,\n promise: Promise.resolve(),\n transfer: Boolean(initialConfig?.automaticTransfer),\n };\n active = task;\n const isCurrent = () => !sessionSignal.aborted && active === task && task.owner === ctx.sessionManager;\n const timer = setTimeout(\n () => controller.abort(new DOMException(\"Startup check timed out\", \"TimeoutError\")),\n timeoutMs,\n );\n let config: AnySyncConfig | undefined;\n let checking = false;\n // Handle every detached rejection here; Pi's awaited event runner cannot catch it.\n task.promise = Promise.resolve()\n .then(async () => {\n try {\n throwIfAborted(signal);\n config = initialConfig ?? (await loadConfigForCheck());\n task.transfer = Boolean(config.automaticTransfer);\n throwIfAborted(signal);\n if (!config.automatic && !config.automaticTransfer) return;\n const captured = config;\n const identity = syncCheckConfigFingerprint(captured);\n checking = true;\n setSyncStatus(ctx, \"sync ...\");\n const { inspectSync } = await loaders.inspection();\n throwIfAborted(signal);\n const currentConfig = await loadConfigForCheck();\n throwIfAborted(signal);\n if (syncCheckConfigFingerprint(currentConfig) !== identity) return;\n // Lock order: state migration guard \u2192 sync operation \u2192 backend cache.\n // Read heads may write only private cache data. Never call doctor/syncBoth.\n const inspection = await withStateDirectoryAccess(() => {\n throwIfAborted(signal);\n return withLock(\"startup-check\", () => {\n throwIfAborted(signal);\n return inspectSync(captured, snapshotOptionsForContext(ctx, captured), signal);\n });\n });\n throwIfAborted(signal);\n const latest = await loadConfigForCheck();\n throwIfAborted(signal);\n if (syncCheckConfigFingerprint(latest) !== identity) return;\n const state = await withStateDirectoryAccess(() => readStateForConfig(latest));\n throwIfAborted(signal);\n if (syncStateFingerprint(state) !== inspection.stateIdentity || !isCurrent()) return;\n attention.observe({\n setupName: captured.setupName,\n configIdentity: identity,\n checkedAt: new Date().toISOString(),\n inspection,\n automaticTransfer: captured.automaticTransfer,\n });\n if (\n captured.automaticTransfer &&\n !inspection.firstSync &&\n inspection.head &&\n inspection.selectionState?.kind === \"same\"\n ) {\n const operations = await loaders.operations();\n throwIfAborted(signal);\n if (!isCurrent()) return;\n if (!ctx.isIdle()) {\n pending = { ctx, signal: sessionSignal, config: captured };\n return;\n }\n await withStateDirectoryAccess(() =>\n withLock(\"startup-transfer\", () =>\n operations.syncBoth(ctx, {\n args: [],\n yes: true,\n force: false,\n stale: false,\n silent: true,\n reload: false,\n auto: true,\n signal,\n onCommit: () => attention.clearObservation(),\n }),\n ),\n );\n throwIfAborted(signal);\n if (!isCurrent()) return;\n attention.clearObservation();\n return;\n }\n attention.observe({\n setupName: captured.setupName,\n configIdentity: identity,\n checkedAt: new Date().toISOString(),\n inspection,\n automaticTransfer: captured.automaticTransfer,\n });\n if (ctx.mode === \"rpc\") attention.notifyObservation(ctx);\n } catch (error) {\n if (!isCurrent() || sessionSignal.aborted || isMissingConfigError(error)) return;\n if (signal.aborted && controller.signal.reason?.name !== \"TimeoutError\") return;\n // A settings edit invalidates failures as well as successful observations.\n if (config) {\n const latest = await loadConfigForCheck().catch(() => undefined);\n if (!isCurrent() || (signal.aborted && controller.signal.reason?.name !== \"TimeoutError\")) return;\n if (!latest || syncCheckConfigFingerprint(latest) !== syncCheckConfigFingerprint(config)) return;\n }\n ctx.ui.notify(\n config?.automaticTransfer\n ? `pi-sync automatic transfer stopped: ${safeTerminalText(errorMessage(error))}. Run /sync to review or reconcile retained recovery evidence. No resources were reloaded.`\n : `pi-sync startup check skipped: ${safeTerminalText(errorMessage(error))}. Run /sync status to retry. No startup transfer was performed.`,\n \"warning\",\n );\n } finally {\n clearTimeout(timer);\n if (isCurrent() && checking) {\n if (!signal.aborted || controller.signal.reason?.name === \"TimeoutError\") {\n const latest = await loadConfigForCheck().catch(() => undefined);\n if (isCurrent()) {\n if (signal.aborted && controller.signal.reason?.name !== \"TimeoutError\")\n setSyncStatus(ctx, undefined);\n else if (\n config &&\n latest &&\n syncCheckConfigFingerprint(latest) === syncCheckConfigFingerprint(config)\n )\n await attention.publish(ctx, sessionSignal);\n else attention.reset(ctx);\n }\n } else setSyncStatus(ctx, undefined);\n }\n if (active === task) active = undefined;\n }\n })\n .catch(() => {\n // UI teardown can itself throw. No unhandled rejection or retained timer.\n clearTimeout(timer);\n if (active === task) active = undefined;\n });\n },\n async stop() {\n pending = undefined;\n const task = active;\n if (!task) return;\n task.controller.abort(new DOMException(\"Startup check cancelled\", \"AbortError\"));\n // Drain the actual work, including child/ref cleanup, before foreground guards.\n await task.promise;\n },\n };\n return controllerApi;\n}\n", "type SetupSwitchModule = Pick<typeof import(\"./setup-switch.js\"), \"useSyncSetup\">;\ntype SnapshotModule = Pick<typeof import(\"../snapshot/snapshot.js\"), \"createSnapshot\">;\ntype SyncStateModule = Pick<typeof import(\"./sync-state.js\"), \"hasLocalChanges\">;\ntype SyncOperations = typeof import(\"./sync-operations.js\");\n\ntype SyncInspectionModule = Pick<typeof import(\"./sync-inspection.js\"), \"inspectSync\">;\n\nexport interface SyncDependencies {\n loadSyncInspection(): Promise<SyncInspectionModule>;\n loadSetupSwitch(): Promise<SetupSwitchModule>;\n loadSnapshot(): Promise<SnapshotModule>;\n loadSyncState(): Promise<SyncStateModule>;\n loadSyncOperations(): Promise<SyncOperations>;\n}\n\nexport interface SyncLoaders {\n inspection(): Promise<SyncInspectionModule>;\n setupSwitch(): Promise<SetupSwitchModule>;\n snapshot(): Promise<SnapshotModule>;\n syncState(): Promise<SyncStateModule>;\n operations(): Promise<SyncOperations>;\n}\n\nexport function createSyncLoaders(dependencies: Partial<SyncDependencies>): SyncLoaders {\n const loaders: SyncLoaders = {\n inspection: cachedModuleLoader(dependencies.loadSyncInspection ?? (() => import(\"./sync-inspection.js\"))),\n setupSwitch: cachedModuleLoader(dependencies.loadSetupSwitch ?? (() => import(\"./setup-switch.js\"))),\n snapshot: cachedModuleLoader(dependencies.loadSnapshot ?? (() => import(\"../snapshot/snapshot.js\"))),\n syncState: cachedModuleLoader(dependencies.loadSyncState ?? (() => import(\"./sync-state.js\"))),\n operations: cachedModuleLoader(dependencies.loadSyncOperations ?? (() => import(\"./sync-operations.js\"))),\n };\n return loaders;\n}\n\nfunction cachedModuleLoader<Module>(load: () => Promise<Module>): () => Promise<Module> {\n let pending: Promise<Module> | undefined;\n return () => {\n if (!pending) {\n pending = load().catch((error) => {\n pending = undefined;\n throw error;\n });\n }\n return pending;\n };\n}\n", "import type { ExtensionAPI } from \"@earendil-works/pi-coding-agent\";\nimport { completeSyncArguments, splitArgs } from \"./commands/command.js\";\nimport { handleCommand } from \"./commands/command-handler.js\";\nimport type { AnySyncConfig } from \"./settings/settings-types.js\";\nimport { withStateDirectoryAccess } from \"./state/state-directory.js\";\nimport { autoPushSessions, startSession } from \"./sync/automatic-sync.js\";\nimport { combineSignals } from \"./sync/signals.js\";\nimport { createStartupCheck } from \"./sync/startup-check.js\";\nimport { errorMessage } from \"./sync/sync-errors.js\";\nimport { createSyncLoaders, type SyncDependencies } from \"./sync/sync-loaders.js\";\nimport { createSyncAttentionController } from \"./ui/sync-attention.js\";\nimport { setSyncStatus } from \"./ui/sync-status.js\";\nimport { safeTerminalText } from \"./ui/terminal-text.js\";\n\nexport default function sync(pi: ExtensionAPI, dependencies: Partial<SyncDependencies> = {}) {\n const loaders = createSyncLoaders(dependencies);\n const attention = createSyncAttentionController();\n const check = createStartupCheck(loaders, attention);\n let sessionAbort = new AbortController();\n let shutdownAbort: AbortController | undefined;\n let initialization = Promise.resolve<AnySyncConfig | false | undefined>(undefined);\n\n pi.registerCommand(\"sync\", {\n description: \"Sync Pi settings through Git, WebDAV, R2, or S3-compatible storage\",\n getArgumentCompletions: completeSyncArguments,\n handler: async (args, ctx) => {\n if (!ctx.hasUI) {\n throw new Error(\"/sync requires TUI or RPC mode so results and safety prompts are observable.\");\n }\n const signal = sessionAbort.signal;\n const ready = await initialization;\n if (signal.aborted) return;\n await check.stop();\n if (signal.aborted) return;\n const command = splitArgs(args)[0];\n if (ready === false && command !== \"help\" && command !== \"unlock\" && command !== \"migrate-state\") {\n ctx.ui.notify(\n \"pi-sync recovery required. Repair the startup error, then /reload before syncing. Use /sync help for recovery guidance.\",\n \"error\",\n );\n return;\n }\n const run = () => handleCommand(args, ctx, signal, loaders, attention);\n // Drain background cache/child cleanup before entering any foreground guard.\n // Help must remain reachable when the state roots themselves need repair.\n if (command === \"migrate-state\" || command === \"help\") await run();\n else await withStateDirectoryAccess(run);\n },\n });\n\n pi.on(\"session_start\", async (_event, ctx) => {\n shutdownAbort?.abort(new DOMException(\"Session replaced\", \"AbortError\"));\n shutdownAbort = undefined;\n sessionAbort.abort(new DOMException(\"Session replaced\", \"AbortError\"));\n sessionAbort = new AbortController();\n const signal = sessionAbort.signal;\n attention.reset(ctx);\n const previous = initialization;\n initialization = (async () => {\n await previous;\n if (signal.aborted) return false;\n await check.stop();\n if (signal.aborted) return false;\n try {\n const config = await withStateDirectoryAccess(() => startSession(ctx, signal));\n return signal.aborted ? false : config;\n } catch (error) {\n if (!signal.aborted && ctx.hasUI) {\n ctx.ui.notify(`pi-sync recovery required: ${safeTerminalText(errorMessage(error))}`, \"error\");\n }\n return false;\n }\n })();\n const ready = await initialization;\n if (ready && !signal.aborted) check.start(ctx, signal, ready);\n });\n\n pi.on(\"agent_start\", async (_event, ctx) => {\n await check.interrupt(ctx);\n });\n\n pi.on(\"agent_settled\", (_event, ctx) => {\n if (!sessionAbort.signal.aborted) check.settled(ctx);\n });\n\n pi.on(\"session_shutdown\", async (event, ctx) => {\n sessionAbort.abort(new DOMException(\"Session shut down\", \"AbortError\"));\n attention.reset(ctx);\n shutdownAbort?.abort(new DOMException(\"Session shut down again\", \"AbortError\"));\n const controller = new AbortController();\n shutdownAbort = controller;\n const signal = combineSignals(controller.signal, AbortSignal.timeout(30_000));\n const reason = typeof event === \"object\" && event ? (event as { reason?: string }).reason : undefined;\n try {\n const ready = await initialization;\n if (signal.aborted) return;\n await check.stop();\n if (signal.aborted) return;\n if (ready !== false && reason !== \"reload\") {\n await withStateDirectoryAccess(async () => {\n if (signal.aborted) return;\n await autoPushSessions(ctx, signal, loaders);\n });\n }\n } catch (error) {\n if (!signal.aborted) {\n ctx.ui.notify(`pi-sync session push skipped: ${errorMessage(error)}`, \"warning\");\n }\n } finally {\n if (shutdownAbort === controller) shutdownAbort = undefined;\n }\n if (signal.aborted) return;\n setSyncStatus(ctx, undefined);\n });\n}\n\nexport type { SyncDependencies } from \"./sync/sync-loaders.js\";\n"],
5
- "mappings": "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",
3
+ "sources": ["../src/ui/operation-dialogs.ts", "../src/commands/command-execution.ts", "../src/commands/command-handler.ts", "../src/settings/config-errors.ts", "../src/sync/automatic-sync.ts", "../src/sync/startup-check.ts", "../src/sync/sync-loaders.ts", "../src/sync-extension.ts"],
4
+ "sourcesContent": ["import type { ExtensionContext } from \"@earendil-works/pi-coding-agent\";\n\n/** Track History's Pi-owned selector/confirmation lifetime without inspecting renderer internals. */\nexport function trackOperationDialogs<Context extends ExtensionContext>(\n ctx: Context,\n onDialog?: (active: boolean) => void,\n): Context {\n if (!onDialog) return ctx;\n const runDialog = async <Value>(task: () => Promise<Value>): Promise<Value> => {\n onDialog(true);\n try {\n return await task();\n } finally {\n onDialog(false);\n }\n };\n const ui: ExtensionContext[\"ui\"] = {\n ...ctx.ui,\n select: (...args) => runDialog(() => ctx.ui.select(...args)),\n confirm: (...args) => runDialog(() => ctx.ui.confirm(...args)),\n };\n // Forward live context properties: copying the context would hide session\n // replacement from mutation-owner revalidation after a dialog settles.\n return new Proxy(ctx, {\n get(target, key, receiver) {\n return key === \"ui\" ? ui : Reflect.get(target, key, receiver);\n },\n });\n}\n", "import type { ExtensionCommandContext, ExtensionContext } from \"@earendil-works/pi-coding-agent\";\nimport { loadConfig, sessionTokenWarnings, syncSessionsWarnings } from \"../settings/config.js\";\nimport { activeLocalConfigPath, createLocalConfigDocument, localConfigPath } from \"../settings/config-file.js\";\nimport { localConfigTemplate, readLocalConfigObject } from \"../settings/settings-store.js\";\nimport type { AnySyncConfig } from \"../settings/settings-types.js\";\nimport { unlock, withLock } from \"../state/lock.js\";\nimport { migrateLegacyStateDirectory, stateDirectoryMigrationNotice } from \"../state/state-directory.js\";\nimport { throwIfAborted } from \"../sync/signals.js\";\nimport { syncErrorGuidance } from \"../sync/sync-error-guidance.js\";\nimport { isSyncDecisionRequiredError, SetupPullRequiresUiError } from \"../sync/sync-errors.js\";\nimport type { SyncLoaders } from \"../sync/sync-loaders.js\";\nimport { RemoteSelectionMismatchError } from \"../sync/sync-policy.js\";\nimport { automaticSyncSummary } from \"../ui/automatic-sync-summary.js\";\nimport type { RunRouteResult } from \"../ui/cancellable-operation.js\";\nimport { trackOperationDialogs } from \"../ui/operation-dialogs.js\";\nimport { setSyncStatus } from \"../ui/sync-status.js\";\nimport { safeTerminalText } from \"../ui/terminal-text.js\";\nimport { parseOptions, resolveSyncCommand, splitArgs, usage, validateCommandOptions } from \"./command.js\";\nimport type { CommandOptions } from \"./command-types.js\";\n\nexport async function executeRecoveryCommand(\n rawArgs: string,\n ctx: ExtensionContext,\n signal: AbortSignal | undefined,\n loaders: SyncLoaders,\n onCommit?: () => void,\n setup?: string,\n): Promise<RunRouteResult> {\n try {\n const [subcommand, ...rest] = splitArgs(rawArgs);\n if (subcommand !== \"sync\" && subcommand !== \"pull\" && subcommand !== \"push\") {\n throw new Error(`Unsupported sync recovery route: ${subcommand ?? \"missing\"}`);\n }\n const options = parseOptions(rest);\n if (setup !== undefined) options.setup = setup;\n if (signal) options.signal = signal;\n if (onCommit) options.onCommit = onCommit;\n options.reload = false;\n options.auto = false;\n validateCommandOptions(subcommand, options);\n const operations = await loaders.operations();\n throwIfAborted(options.signal);\n if (subcommand === \"push\") {\n const outcome = await withLock(\"push\", () => operations.push(ctx, options));\n return { kind: \"completed\", ...(outcome ? { outcome } : {}) };\n }\n if (subcommand === \"pull\") {\n const outcome = await withLock(\"pull\", () => operations.pull(ctx, options));\n return { kind: \"completed\", ...(outcome ? { outcome } : {}) };\n }\n await withLock(\"sync\", () => operations.syncBoth(ctx, options));\n return { kind: \"completed\" };\n } catch (error) {\n if (signal?.aborted) return { kind: \"failed\" };\n setSyncStatus(ctx, undefined);\n if (error instanceof RemoteSelectionMismatchError) {\n return { kind: \"remote-selection-required\", decision: error.decision };\n }\n if (isSyncDecisionRequiredError(error)) {\n return { kind: \"decision-required\", decision: error.decision };\n }\n ctx.ui.notify(syncErrorGuidance(error), \"error\");\n return { kind: \"failed\" };\n }\n}\n\nexport async function executeCommand(\n rawArgs: string,\n ctx: ExtensionCommandContext,\n signal: AbortSignal | undefined,\n loaders: SyncLoaders,\n onCommit?: () => void,\n setup?: string,\n onDialog?: (active: boolean) => void,\n): Promise<RunRouteResult> {\n ctx = trackOperationDialogs(ctx, onDialog);\n try {\n const command = await resolveSyncCommand(rawArgs, ctx);\n if (signal?.aborted || !command) return { kind: \"completed\" };\n const { subcommand, rest } = command;\n const options = parseOptions(rest);\n if (setup !== undefined) options.setup = setup;\n if (signal) options.signal = signal;\n if (onCommit) options.onCommit = onCommit;\n validateCommandOptions(subcommand, options);\n\n switch (subcommand) {\n case \"help\":\n ctx.ui.notify(usage(), \"info\");\n return { kind: \"completed\" };\n case \"use\": {\n const { useSyncSetup } = await loaders.setupSwitch();\n throwIfAborted(options.signal);\n await useSyncSetup(\n ctx,\n options.args[0] ?? \"\",\n async (selectedSetup) => {\n const operations = await loaders.operations();\n throwIfAborted(options.signal);\n return withLock(\"pull\", () => operations.pull(ctx, { ...options, setup: selectedSetup }));\n },\n undefined,\n options.signal,\n );\n return { kind: \"completed\" };\n }\n case \"init\":\n await initConfig(ctx, signal);\n return { kind: \"completed\" };\n case \"config\":\n await showConfig(ctx, options);\n return { kind: \"completed\" };\n case \"files\": {\n const { showFileSelection } = await import(\"../ui/file-selection.js\");\n throwIfAborted(options.signal);\n await showFileSelection(ctx, options.setup, options.signal);\n return { kind: \"completed\" };\n }\n case \"status\": {\n const operations = await loaders.operations();\n throwIfAborted(options.signal);\n await operations.status(ctx, options);\n return { kind: \"completed\" };\n }\n case \"diff\": {\n const operations = await loaders.operations();\n throwIfAborted(options.signal);\n await operations.diff(ctx, options);\n return { kind: \"completed\" };\n }\n case \"conflicts\": {\n const { showConflicts } = await import(\"../sync/conflict-review.js\");\n throwIfAborted(options.signal);\n await withLock(\"sync\", () => showConflicts(ctx, options));\n return { kind: \"completed\" };\n }\n case \"doctor\": {\n const operations = await loaders.operations();\n throwIfAborted(options.signal);\n await operations.doctor(ctx, options);\n return { kind: \"completed\" };\n }\n case \"push\": {\n const operations = await loaders.operations();\n throwIfAborted(options.signal);\n const outcome = await withLock(\"push\", () => operations.push(ctx, options));\n return { kind: \"completed\", ...(outcome ? { outcome } : {}) };\n }\n case \"pull\": {\n const operations = await loaders.operations();\n throwIfAborted(options.signal);\n const outcome = await withLock(\"pull\", () => operations.pull(ctx, options));\n return { kind: \"completed\", ...(outcome ? { outcome } : {}) };\n }\n case \"sync\": {\n const operations = await loaders.operations();\n throwIfAborted(options.signal);\n await withLock(\"sync\", () => operations.syncBoth(ctx, options));\n return { kind: \"completed\" };\n }\n case \"history\": {\n const operations = await loaders.operations();\n throwIfAborted(options.signal);\n await operations.history(ctx, options);\n return { kind: \"completed\" };\n }\n case \"rollback\": {\n const operations = await loaders.operations();\n throwIfAborted(options.signal);\n await withLock(\"rollback\", () => operations.rollback(ctx, options));\n return { kind: \"completed\" };\n }\n case \"migrate-state\":\n await migrateStateDirectory(ctx, options);\n return { kind: \"completed\" };\n case \"unlock\":\n await unlock(ctx, options);\n return { kind: \"completed\" };\n default:\n ctx.ui.notify(`Unknown /sync command: ${subcommand}\\n\\n${usage()}`, \"warning\");\n return { kind: \"failed\" };\n }\n } catch (error) {\n if (signal?.aborted) return { kind: \"failed\" };\n setSyncStatus(ctx, undefined);\n if (error instanceof SetupPullRequiresUiError) throw error;\n if (error instanceof RemoteSelectionMismatchError) {\n return { kind: \"remote-selection-required\", decision: error.decision };\n }\n if (isSyncDecisionRequiredError(error)) {\n return { kind: \"decision-required\", decision: error.decision };\n }\n ctx.ui.notify(syncErrorGuidance(error), \"error\");\n return { kind: \"failed\" };\n }\n}\n\nasync function migrateStateDirectory(ctx: ExtensionCommandContext, options: CommandOptions) {\n const notice = stateDirectoryMigrationNotice();\n if (!notice) {\n ctx.ui.notify(\"pi-sync already uses the canonical pi-sync/ state directory.\", \"info\");\n return;\n }\n if (\n !options.yes &&\n !(await ctx.ui.confirm(\n \"Migrate pi-sync state directory\",\n \"Confirm that every other Pi process is closed. pi-sync will atomically rename .pisync/ to pi-sync/ without merging or deleting either root.\",\n { signal: options.signal },\n ))\n ) {\n ctx.ui.notify(\"pi-sync state migration cancelled.\", \"info\");\n return;\n }\n throwIfAborted(options.signal);\n const result = await migrateLegacyStateDirectory();\n throwIfAborted(options.signal);\n if (result.status === \"ready\") {\n ctx.ui.notify(\"pi-sync already uses the canonical pi-sync/ state directory.\", \"info\");\n return;\n }\n ctx.ui.notify(result.message, result.status === \"migrated\" ? \"info\" : \"warning\");\n}\n\nasync function initConfig(ctx: ExtensionCommandContext, signal?: AbortSignal) {\n const configPath = localConfigPath();\n if (await readLocalConfigObject()) {\n ctx.ui.notify(`Config already exists: ${await activeLocalConfigPath()}`, \"info\");\n return;\n }\n\n if (ctx.mode === \"tui\") {\n const { showSetupWizard } = await import(\"../ui/setup/setup-wizard.js\");\n throwIfAborted(signal);\n await showSetupWizard(ctx, signal);\n return;\n }\n await createLocalConfigDocument(localConfigTemplate());\n ctx.ui.notify(`Created ${configPath}. Add a storage connection and sync setup before syncing.`, \"info\");\n}\n\nasync function showConfig(ctx: ExtensionCommandContext, options: CommandOptions) {\n const config = await loadConfig(options.setup);\n const warnings = [\n ...(config.backend.type === \"s3\" ? sessionTokenWarnings(config.backend.profile) : []),\n ...syncSessionsWarnings(config),\n ];\n const storageLines = configStorageLines(config);\n ctx.ui.notify(\n [\n \"pi-sync config:\",\n `sync setup: ${config.setupName}`,\n `storage connection: ${config.connectionName}`,\n ...storageLines,\n `storage path: ${config.storagePath}`,\n `automatic sync: ${automaticSyncSummary(config.automatic)}`,\n `settings field merge: ${config.mergeSettings ? \"On (experimental)\" : \"Off\"}`,\n `machine-local settings.json fields: ${(config.localFields ?? []).map(safeTerminalText).join(\", \") || \"none\"}`,\n `automatic transfer at startup: ${config.automaticTransfer ? \"On \u2014 selected-file upload/replace/delete; no reload\" : \"Off\"}`,\n `included content: ${config.include.join(\", \") || \"none\"}`,\n `sessions: ${config.include.includes(\"sessions\") ? \"included\" : \"not included\"}`,\n `settings file: ${localConfigPath()}`,\n ...warnings,\n ].join(\"\\n\"),\n warnings.length > 0 ? \"warning\" : \"info\",\n );\n}\n\nfunction configStorageLines(config: AnySyncConfig) {\n switch (config.backend.type) {\n case \"git\":\n return [\n \"kind: git\",\n `remote: ${displayGitRemote(config.backend.profile.remote)}`,\n \"authentication: existing Git/SSH credentials (not stored)\",\n `branch: ${config.backend.destination.branch}`,\n ];\n case \"webdav\":\n return [\n \"kind: webdav\",\n `url: ${displayWebDavUrl(config.backend.profile.url, config.backend.profile.username)}`,\n \"username: configured (value hidden)\",\n \"password: configured\",\n ];\n case \"s3\":\n return [\n \"kind: s3\",\n `endpoint: ${config.backend.profile.endpoint}`,\n `bucket: ${config.backend.destination.bucket}`,\n `region: ${config.backend.profile.region}`,\n \"access key id: configured\",\n \"secret access key: configured\",\n `session token: ${config.backend.profile.sessionToken ? \"configured\" : \"not configured\"}`,\n ];\n }\n}\n\nfunction displayGitRemote(value: string | undefined) {\n if (!value) return \"missing\";\n try {\n const url = new URL(value);\n url.username = \"\";\n url.password = \"\";\n url.search = \"\";\n url.hash = \"\";\n return url.toString();\n } catch {\n return value.replace(/^(?:[^@\\s]+@)?(?<host>[^:]+):.+$/u, \"$<host>:\u2026\");\n }\n}\n\nfunction displayWebDavUrl(value: string | undefined, username: string | undefined) {\n if (!value) return \"missing\";\n try {\n const url = new URL(value);\n url.username = \"\";\n url.password = \"\";\n url.search = \"\";\n url.hash = \"\";\n return username ? `${url.origin}/\u2026` : `${url.origin}${url.pathname}`;\n } catch {\n return \"invalid (value hidden)\";\n }\n}\n", "import type { ExtensionCommandContext, ExtensionContext } from \"@earendil-works/pi-coding-agent\";\nimport { loadConfig } from \"../settings/config.js\";\nimport { readStateForConfig, syncStateFingerprint } from \"../state/sync-state-store.js\";\nimport { combineSignals } from \"../sync/signals.js\";\nimport { errorMessage } from \"../sync/sync-errors.js\";\nimport type { SyncLoaders } from \"../sync/sync-loaders.js\";\nimport { formatRemoteSelectionMismatch } from \"../sync/sync-policy.js\";\nimport type { RunRouteResult } from \"../ui/cancellable-operation.js\";\nimport {\n observationMatchesConfig,\n type SyncAttentionController,\n type SyncAttentionOrigin,\n syncAttentionMatchesConfig,\n} from \"../ui/sync-attention.js\";\nimport { setSyncStatus } from \"../ui/sync-status.js\";\nimport { parseOptions, splitArgs } from \"./command.js\";\nimport { executeCommand, executeRecoveryCommand } from \"./command-execution.js\";\n\nexport async function handleCommand(\n rawArgs: string,\n ctx: ExtensionCommandContext,\n sessionSignal: AbortSignal,\n loaders: SyncLoaders,\n attention: SyncAttentionController,\n) {\n await reconcileObservation(attention, sessionSignal);\n if (sessionSignal.aborted) return;\n const run = (\n route: string,\n signal?: AbortSignal,\n onCommit?: () => void,\n target?: string,\n onDialog?: (active: boolean) => void,\n ) =>\n executeCommand(\n route,\n ctx,\n combineSignals(sessionSignal, signal),\n loaders,\n observationCommitCallback(attention, sessionSignal, onCommit),\n target,\n onDialog,\n );\n if (!rawArgs.trim()) {\n try {\n const { showSyncManager } = await import(\"../ui/manager-ui.js\");\n if (sessionSignal.aborted) return;\n await showSyncManager(ctx, run, sessionSignal, {\n getAttention: () => attention.current(),\n getObservation: () => attention.observation(),\n onObservationInvalidated: () => attention.clearObservation(),\n onSelectionResolved: (expected) => {\n if (attention.current() === expected) attention.clear(ctx);\n },\n });\n } catch (error) {\n if (sessionSignal.aborted) return;\n setSyncStatus(ctx, undefined);\n ctx.ui.notify(errorMessage(error), \"error\");\n }\n await reconcileObservation(attention, sessionSignal);\n if (!sessionSignal.aborted) await attention.publish(ctx, sessionSignal);\n return;\n }\n const result = await run(rawArgs);\n if (result.kind === \"decision-required\") {\n ctx.ui.notify(result.decision.directMessage, \"error\");\n } else if (result.kind === \"remote-selection-required\") {\n const origin = directSelectionOrigin(rawArgs);\n if (origin) attention.set(result.decision, origin);\n const deterministic = splitArgs(rawArgs).some((arg) => arg === \"--yes\" || arg === \"-y\");\n if (origin && ctx.mode === \"tui\" && !deterministic) {\n await resolveSelectionAttention(ctx, attention, sessionSignal, loaders);\n } else {\n ctx.ui.notify(\n formatRemoteSelectionMismatch(\n result.decision.setupName,\n result.decision.localInclude,\n result.decision.remoteInclude,\n ),\n \"error\",\n );\n }\n }\n await clearAttentionAfterCompletedOperation(rawArgs, result, ctx, attention, sessionSignal);\n await reconcileSelectionAttention(ctx, attention, sessionSignal);\n await reconcileObservation(attention, sessionSignal);\n if (!sessionSignal.aborted) await attention.publish(ctx, sessionSignal);\n}\n\nfunction observationCommitCallback(\n attention: SyncAttentionController,\n sessionSignal: AbortSignal,\n onCommit?: () => void,\n) {\n const observed = attention.observation();\n return () => {\n // Opening or cancelling a review changes nothing. A commit may change data\n // even if later publication/baseline cleanup fails; never restore old hints.\n if (!sessionSignal.aborted && attention.observation() === observed) attention.clearObservation();\n onCommit?.();\n };\n}\n\nasync function reconcileObservation(attention: SyncAttentionController, signal: AbortSignal) {\n const observed = attention.observation();\n if (!observed || signal.aborted) return;\n try {\n const config = await loadConfig();\n if (signal.aborted || attention.observation() !== observed) return;\n if (!observationMatchesConfig(observed, config)) {\n attention.clearObservation();\n return;\n }\n const state = await readStateForConfig(config);\n if (signal.aborted || attention.observation() !== observed) return;\n if (syncStateFingerprint(state) !== observed.inspection.stateIdentity) attention.clearObservation();\n } catch {\n if (!signal.aborted && attention.observation() === observed) attention.clearObservation();\n }\n}\n\nasync function clearAttentionAfterCompletedOperation(\n rawArgs: string,\n result: RunRouteResult,\n ctx: ExtensionContext,\n attention: SyncAttentionController,\n signal: AbortSignal,\n) {\n if (result.kind !== \"completed\" || result.outcome === \"cancelled\" || signal.aborted) return;\n const [command, ...rest] = splitArgs(rawArgs);\n if (command !== \"sync\" && command !== \"pull\" && command !== \"push\") return;\n const current = attention.current();\n if (!current) return;\n try {\n const options = parseOptions(rest);\n const setupName = options.setup ?? (await loadConfig()).setupName;\n if (signal.aborted || attention.current() !== current) return;\n if (current.decision.setupName === setupName) attention.clear(ctx);\n } catch {\n // Attention reconciliation below owns malformed or concurrently changed settings.\n }\n}\n\nasync function reconcileSelectionAttention(\n ctx: ExtensionContext,\n attention: SyncAttentionController,\n signal: AbortSignal,\n) {\n const current = attention.current();\n if (!current || signal.aborted) return;\n try {\n const config = await loadConfig(current.decision.setupName);\n if (signal.aborted || attention.current() !== current) return;\n if (!syncAttentionMatchesConfig(current, config)) attention.clear(ctx);\n } catch {\n if (!signal.aborted && attention.current() === current) attention.clear(ctx);\n }\n}\n\nfunction directSelectionOrigin(rawArgs: string): SyncAttentionOrigin | undefined {\n const command = splitArgs(rawArgs)[0];\n return command === \"sync\" || command === \"pull\" || command === \"push\" ? command : undefined;\n}\n\nexport async function resolveSelectionAttention(\n ctx: ExtensionContext,\n attention: SyncAttentionController,\n signal: AbortSignal,\n loaders: SyncLoaders,\n options: {\n cancelLabel?: string;\n withStateAccess?: <T>(task: () => Promise<T>) => Promise<T>;\n } = {},\n) {\n const current = attention.current();\n if (!current || signal.aborted) return;\n const { dispatchManagerResult } = await import(\"../ui/manager-result-dispatcher.js\");\n if (signal.aborted || attention.current() !== current) return;\n await dispatchManagerResult(\n ctx,\n { kind: \"remote-selection-required\", decision: current.decision },\n current.origin,\n (route, actionSignal, onCommit, target) => {\n const execute = () =>\n executeRecoveryCommand(\n route,\n ctx,\n combineSignals(signal, actionSignal),\n loaders,\n observationCommitCallback(attention, signal, onCommit),\n target,\n );\n return options.withStateAccess ? options.withStateAccess(execute) : execute();\n },\n signal,\n {\n cancelLabel: options.cancelLabel,\n withStateAccess: options.withStateAccess,\n onSelectionResolved: () => {\n if (attention.current() === current) attention.clear(ctx);\n },\n },\n );\n}\n", "export function isMissingConfigError(error: unknown) {\n return (\n error instanceof Error &&\n (error.message.startsWith(\"Missing pi-sync settings.\") || error.message === \"No sync setups are configured.\")\n );\n}\n", "import type { ExtensionContext } from \"@earendil-works/pi-coding-agent\";\nimport { setSyncSetupCompletions } from \"../commands/command.js\";\nimport type { CommandOptions } from \"../commands/command-types.js\";\nimport { loadConfig, loadPartialConfig } from \"../settings/config.js\";\nimport { isMissingConfigError } from \"../settings/config-errors.js\";\nimport { consumeLocalConfigMigrationNotice } from \"../settings/config-file.js\";\nimport { readLocalConfigObject } from \"../settings/settings-store.js\";\nimport { sessionDirFromContext, snapshotOptionsForContext } from \"../snapshot/session-paths.js\";\nimport { recoverSnapshotTransactionsOnStartup } from \"../snapshot/snapshot-transaction.js\";\nimport { withLock } from \"../state/lock.js\";\nimport { stateDirectoryMigrationNotice } from \"../state/state-directory.js\";\nimport { ensureStateDir, readStateForConfig } from \"../state/sync-state-store.js\";\nimport { configureSyncStatus, setSyncStatus } from \"../ui/sync-status.js\";\nimport { safeTerminalText } from \"../ui/terminal-text.js\";\nimport { throwIfAborted } from \"./signals.js\";\nimport { errorMessage } from \"./sync-errors.js\";\nimport type { SyncLoaders } from \"./sync-loaders.js\";\nimport { captureMutationOwner } from \"./sync-local.js\";\n\nconst AUTO_SYNC_OPTIONS: CommandOptions = {\n yes: true,\n force: false,\n stale: false,\n silent: true,\n reload: false,\n auto: true,\n args: [],\n};\n\nexport async function startSession(ctx: ExtensionContext, signal: AbortSignal) {\n throwIfAborted(signal);\n const stateNotice = stateDirectoryMigrationNotice();\n if (stateNotice && ctx.hasUI) ctx.ui.notify(stateNotice, \"warning\");\n // Recovery can restore managed files: finish it before Pi accepts user edits.\n // Unlike remote inspection, it must never be detached behind startup.\n const validateMutation = captureMutationOwner(ctx, signal);\n const currentFile = ctx.sessionManager.getSessionFile?.();\n await recoverSnapshotTransactionsOnStartup({\n signal,\n sessionDir: sessionDirFromContext(ctx),\n protectedTargets: currentFile ? [currentFile] : [],\n validateMutation,\n });\n throwIfAborted(signal);\n try {\n const settings = await readLocalConfigObject();\n throwIfAborted(signal);\n const names = settings ? Object.keys(settings.syncSetups).sort((left, right) => left.localeCompare(right)) : [];\n setSyncSetupCompletions(names);\n configureSyncStatus(ctx, settings?.showStatus ?? true);\n } catch {\n if (signal.aborted) return;\n setSyncSetupCompletions([]);\n }\n const migrationNotice = consumeLocalConfigMigrationNotice();\n if (migrationNotice && ctx.hasUI) ctx.ui.notify(migrationNotice, \"warning\");\n throwIfAborted(signal);\n if (!ctx.hasUI) return;\n try {\n const config = await loadConfig();\n throwIfAborted(signal);\n configureSyncStatus(ctx, config.showStatus);\n return config.automatic || config.automaticTransfer ? config : undefined;\n } catch (error) {\n throwIfAborted(signal);\n if (!isMissingConfigError(error)) {\n ctx.ui.notify(`pi-sync startup check skipped: ${safeTerminalText(errorMessage(error))}`, \"warning\");\n }\n }\n}\n\nexport async function autoPushSessions(ctx: ExtensionContext, signal: AbortSignal, loaders: SyncLoaders) {\n try {\n const partial = await loadPartialConfig();\n throwIfAborted(signal);\n if (!partial.automatic) return;\n if (!partial.include.includes(\"sessions\")) return;\n await ensureStateDir();\n throwIfAborted(signal);\n const config = await loadConfig();\n throwIfAborted(signal);\n if (!config.include.includes(\"sessions\")) return;\n const [operations, snapshotModule, syncStateModule] = await Promise.all([\n loaders.operations(),\n loaders.snapshot(),\n loaders.syncState(),\n ]);\n throwIfAborted(signal);\n await withLock(\"auto-session-push\", async () => {\n throwIfAborted(signal);\n const state = await readStateForConfig(config);\n throwIfAborted(signal);\n const local = await snapshotModule.createSnapshot(config.snapshotIdentity, {\n ...snapshotOptionsForContext(ctx, config),\n signal,\n });\n throwIfAborted(signal);\n if (!syncStateModule.hasLocalChanges(local, state, config)) return;\n await operations.push(ctx, { ...AUTO_SYNC_OPTIONS, signal }, { config, state, local });\n });\n } catch (error) {\n if (signal.aborted || isMissingConfigError(error)) return;\n setSyncStatus(ctx, undefined);\n ctx.ui.notify(`pi-sync session push skipped: ${errorMessage(error)}`, \"warning\");\n }\n}\n", "import type { ExtensionContext } from \"@earendil-works/pi-coding-agent\";\nimport { loadConfigForCheck, syncCheckConfigFingerprint } from \"../settings/config.js\";\nimport { isMissingConfigError } from \"../settings/config-errors.js\";\nimport type { AnySyncConfig } from \"../settings/settings-types.js\";\nimport { snapshotOptionsForContext } from \"../snapshot/session-paths.js\";\nimport { withLock } from \"../state/lock.js\";\nimport { withStateDirectoryAccess } from \"../state/state-directory.js\";\nimport { readStateForConfig, syncStateFingerprint } from \"../state/sync-state-store.js\";\nimport type { SyncAttentionController } from \"../ui/sync-attention.js\";\nimport { setSyncStatus } from \"../ui/sync-status.js\";\nimport { safeTerminalText } from \"../ui/terminal-text.js\";\nimport { combineSignals, throwIfAborted } from \"./signals.js\";\nimport { errorMessage } from \"./sync-errors.js\";\nimport type { SyncLoaders } from \"./sync-loaders.js\";\n\n/** One owned task per extension session, never keyed by the shared headless UI. */\nexport function createStartupCheck(loaders: SyncLoaders, attention: SyncAttentionController, timeoutMs = 30_000) {\n let active:\n | {\n controller: AbortController;\n promise: Promise<void>;\n owner: ExtensionContext[\"sessionManager\"];\n transfer: boolean;\n }\n | undefined;\n\n let pending: { ctx: ExtensionContext; signal: AbortSignal; config: AnySyncConfig } | undefined;\n\n const controllerApi = {\n async interrupt(ctx: ExtensionContext) {\n const task = active;\n if (!task?.transfer || task.owner !== ctx.sessionManager) return;\n task.controller.abort(new DOMException(\"Agent became busy\", \"AbortError\"));\n await task.promise;\n },\n settled(ctx: ExtensionContext) {\n const queued = pending;\n if (!queued || queued.ctx.sessionManager !== ctx.sessionManager || queued.signal.aborted || !ctx.isIdle()) return;\n pending = undefined;\n controllerApi.start(ctx, queued.signal, queued.config);\n },\n start(ctx: ExtensionContext, sessionSignal: AbortSignal, initialConfig?: AnySyncConfig) {\n if (!ctx.hasUI || sessionSignal.aborted) return;\n if (initialConfig?.automaticTransfer && !ctx.isIdle()) {\n pending = { ctx, signal: sessionSignal, config: initialConfig };\n return;\n }\n if (active) throw new Error(\"Previous startup check has not been drained.\");\n const controller = new AbortController();\n const signal = combineSignals(sessionSignal, controller.signal);\n const task = {\n controller,\n owner: ctx.sessionManager,\n promise: Promise.resolve(),\n transfer: Boolean(initialConfig?.automaticTransfer),\n };\n active = task;\n const isCurrent = () => !sessionSignal.aborted && active === task && task.owner === ctx.sessionManager;\n const timer = setTimeout(\n () => controller.abort(new DOMException(\"Startup check timed out\", \"TimeoutError\")),\n timeoutMs,\n );\n let config: AnySyncConfig | undefined;\n let checking = false;\n // Handle every detached rejection here; Pi's awaited event runner cannot catch it.\n task.promise = Promise.resolve()\n .then(async () => {\n try {\n throwIfAborted(signal);\n config = initialConfig ?? (await loadConfigForCheck());\n task.transfer = Boolean(config.automaticTransfer);\n throwIfAborted(signal);\n if (!config.automatic && !config.automaticTransfer) return;\n const captured = config;\n const identity = syncCheckConfigFingerprint(captured);\n checking = true;\n setSyncStatus(ctx, \"sync ...\");\n const { inspectSync } = await loaders.inspection();\n throwIfAborted(signal);\n const currentConfig = await loadConfigForCheck();\n throwIfAborted(signal);\n if (syncCheckConfigFingerprint(currentConfig) !== identity) return;\n // Lock order: state migration guard \u2192 sync operation \u2192 backend cache.\n // Read heads may write only private cache data. Never call doctor/syncBoth.\n const inspection = await withStateDirectoryAccess(() => {\n throwIfAborted(signal);\n return withLock(\"startup-check\", () => {\n throwIfAborted(signal);\n return inspectSync(captured, snapshotOptionsForContext(ctx, captured), signal);\n });\n });\n throwIfAborted(signal);\n const latest = await loadConfigForCheck();\n throwIfAborted(signal);\n if (syncCheckConfigFingerprint(latest) !== identity) return;\n const state = await withStateDirectoryAccess(() => readStateForConfig(latest));\n throwIfAborted(signal);\n if (syncStateFingerprint(state) !== inspection.stateIdentity || !isCurrent()) return;\n attention.observe({\n setupName: captured.setupName,\n configIdentity: identity,\n checkedAt: new Date().toISOString(),\n inspection,\n automaticTransfer: captured.automaticTransfer,\n });\n if (\n captured.automaticTransfer &&\n !inspection.firstSync &&\n inspection.head &&\n inspection.selectionState?.kind === \"same\"\n ) {\n const operations = await loaders.operations();\n throwIfAborted(signal);\n if (!isCurrent()) return;\n if (!ctx.isIdle()) {\n pending = { ctx, signal: sessionSignal, config: captured };\n return;\n }\n await withStateDirectoryAccess(() =>\n withLock(\"startup-transfer\", () =>\n operations.syncBoth(ctx, {\n args: [],\n yes: true,\n force: false,\n stale: false,\n silent: true,\n reload: false,\n auto: true,\n signal,\n onCommit: () => attention.clearObservation(),\n }),\n ),\n );\n throwIfAborted(signal);\n if (!isCurrent()) return;\n attention.clearObservation();\n return;\n }\n attention.observe({\n setupName: captured.setupName,\n configIdentity: identity,\n checkedAt: new Date().toISOString(),\n inspection,\n automaticTransfer: captured.automaticTransfer,\n });\n if (ctx.mode === \"rpc\") attention.notifyObservation(ctx);\n } catch (error) {\n if (!isCurrent() || sessionSignal.aborted || isMissingConfigError(error)) return;\n if (signal.aborted && controller.signal.reason?.name !== \"TimeoutError\") return;\n // A settings edit invalidates failures as well as successful observations.\n if (config) {\n const latest = await loadConfigForCheck().catch(() => undefined);\n if (!isCurrent() || (signal.aborted && controller.signal.reason?.name !== \"TimeoutError\")) return;\n if (!latest || syncCheckConfigFingerprint(latest) !== syncCheckConfigFingerprint(config)) return;\n }\n ctx.ui.notify(\n config?.automaticTransfer\n ? `pi-sync automatic transfer stopped: ${safeTerminalText(errorMessage(error))}. Run /sync to review or reconcile retained recovery evidence. No resources were reloaded.`\n : `pi-sync startup check skipped: ${safeTerminalText(errorMessage(error))}. Run /sync status to retry. No startup transfer was performed.`,\n \"warning\",\n );\n } finally {\n clearTimeout(timer);\n if (isCurrent() && checking) {\n if (!signal.aborted || controller.signal.reason?.name === \"TimeoutError\") {\n const latest = await loadConfigForCheck().catch(() => undefined);\n if (isCurrent()) {\n if (signal.aborted && controller.signal.reason?.name !== \"TimeoutError\")\n setSyncStatus(ctx, undefined);\n else if (\n config &&\n latest &&\n syncCheckConfigFingerprint(latest) === syncCheckConfigFingerprint(config)\n )\n await attention.publish(ctx, sessionSignal);\n else attention.reset(ctx);\n }\n } else setSyncStatus(ctx, undefined);\n }\n if (active === task) active = undefined;\n }\n })\n .catch(() => {\n // UI teardown can itself throw. No unhandled rejection or retained timer.\n clearTimeout(timer);\n if (active === task) active = undefined;\n });\n },\n async stop() {\n pending = undefined;\n const task = active;\n if (!task) return;\n task.controller.abort(new DOMException(\"Startup check cancelled\", \"AbortError\"));\n // Drain the actual work, including child/ref cleanup, before foreground guards.\n await task.promise;\n },\n };\n return controllerApi;\n}\n", "type SetupSwitchModule = Pick<typeof import(\"./setup-switch.js\"), \"useSyncSetup\">;\ntype SnapshotModule = Pick<typeof import(\"../snapshot/snapshot.js\"), \"createSnapshot\">;\ntype SyncStateModule = Pick<typeof import(\"./sync-state.js\"), \"hasLocalChanges\">;\ntype SyncOperations = typeof import(\"./sync-operations.js\");\n\ntype SyncInspectionModule = Pick<typeof import(\"./sync-inspection.js\"), \"inspectSync\">;\n\nexport interface SyncDependencies {\n loadSyncInspection(): Promise<SyncInspectionModule>;\n loadSetupSwitch(): Promise<SetupSwitchModule>;\n loadSnapshot(): Promise<SnapshotModule>;\n loadSyncState(): Promise<SyncStateModule>;\n loadSyncOperations(): Promise<SyncOperations>;\n}\n\nexport interface SyncLoaders {\n inspection(): Promise<SyncInspectionModule>;\n setupSwitch(): Promise<SetupSwitchModule>;\n snapshot(): Promise<SnapshotModule>;\n syncState(): Promise<SyncStateModule>;\n operations(): Promise<SyncOperations>;\n}\n\nexport function createSyncLoaders(dependencies: Partial<SyncDependencies>): SyncLoaders {\n const loaders: SyncLoaders = {\n inspection: cachedModuleLoader(dependencies.loadSyncInspection ?? (() => import(\"./sync-inspection.js\"))),\n setupSwitch: cachedModuleLoader(dependencies.loadSetupSwitch ?? (() => import(\"./setup-switch.js\"))),\n snapshot: cachedModuleLoader(dependencies.loadSnapshot ?? (() => import(\"../snapshot/snapshot.js\"))),\n syncState: cachedModuleLoader(dependencies.loadSyncState ?? (() => import(\"./sync-state.js\"))),\n operations: cachedModuleLoader(dependencies.loadSyncOperations ?? (() => import(\"./sync-operations.js\"))),\n };\n return loaders;\n}\n\nfunction cachedModuleLoader<Module>(load: () => Promise<Module>): () => Promise<Module> {\n let pending: Promise<Module> | undefined;\n return () => {\n if (!pending) {\n pending = load().catch((error) => {\n pending = undefined;\n throw error;\n });\n }\n return pending;\n };\n}\n", "import type { ExtensionAPI } from \"@earendil-works/pi-coding-agent\";\nimport { completeSyncArguments, splitArgs } from \"./commands/command.js\";\nimport { handleCommand } from \"./commands/command-handler.js\";\nimport type { AnySyncConfig } from \"./settings/settings-types.js\";\nimport { withStateDirectoryAccess } from \"./state/state-directory.js\";\nimport { autoPushSessions, startSession } from \"./sync/automatic-sync.js\";\nimport { combineSignals } from \"./sync/signals.js\";\nimport { createStartupCheck } from \"./sync/startup-check.js\";\nimport { errorMessage } from \"./sync/sync-errors.js\";\nimport { createSyncLoaders, type SyncDependencies } from \"./sync/sync-loaders.js\";\nimport { createSyncAttentionController } from \"./ui/sync-attention.js\";\nimport { setSyncStatus } from \"./ui/sync-status.js\";\nimport { safeTerminalText } from \"./ui/terminal-text.js\";\n\nexport default function sync(pi: ExtensionAPI, dependencies: Partial<SyncDependencies> = {}) {\n const loaders = createSyncLoaders(dependencies);\n const attention = createSyncAttentionController();\n const check = createStartupCheck(loaders, attention);\n let sessionAbort = new AbortController();\n let shutdownAbort: AbortController | undefined;\n let initialization = Promise.resolve<AnySyncConfig | false | undefined>(undefined);\n\n pi.registerCommand(\"sync\", {\n description: \"Sync Pi settings through Git, WebDAV, R2, or S3-compatible storage\",\n getArgumentCompletions: completeSyncArguments,\n handler: async (args, ctx) => {\n if (!ctx.hasUI) {\n throw new Error(\"/sync requires TUI or RPC mode so results and safety prompts are observable.\");\n }\n const signal = sessionAbort.signal;\n const ready = await initialization;\n if (signal.aborted) return;\n await check.stop();\n if (signal.aborted) return;\n const command = splitArgs(args)[0];\n if (ready === false && command !== \"help\" && command !== \"unlock\" && command !== \"migrate-state\") {\n ctx.ui.notify(\n \"pi-sync recovery required. Repair the startup error, then /reload before syncing. Use /sync help for recovery guidance.\",\n \"error\",\n );\n return;\n }\n const run = () => handleCommand(args, ctx, signal, loaders, attention);\n // Drain background cache/child cleanup before entering any foreground guard.\n // Help must remain reachable when the state roots themselves need repair.\n if (command === \"migrate-state\" || command === \"help\") await run();\n else await withStateDirectoryAccess(run);\n },\n });\n\n pi.on(\"session_start\", async (_event, ctx) => {\n shutdownAbort?.abort(new DOMException(\"Session replaced\", \"AbortError\"));\n shutdownAbort = undefined;\n sessionAbort.abort(new DOMException(\"Session replaced\", \"AbortError\"));\n sessionAbort = new AbortController();\n const signal = sessionAbort.signal;\n attention.reset(ctx);\n const previous = initialization;\n initialization = (async () => {\n await previous;\n if (signal.aborted) return false;\n await check.stop();\n if (signal.aborted) return false;\n try {\n const config = await withStateDirectoryAccess(() => startSession(ctx, signal));\n return signal.aborted ? false : config;\n } catch (error) {\n if (!signal.aborted && ctx.hasUI) {\n ctx.ui.notify(`pi-sync recovery required: ${safeTerminalText(errorMessage(error))}`, \"error\");\n }\n return false;\n }\n })();\n const ready = await initialization;\n if (ready && !signal.aborted) check.start(ctx, signal, ready);\n });\n\n pi.on(\"agent_start\", async (_event, ctx) => {\n await check.interrupt(ctx);\n });\n\n pi.on(\"agent_settled\", (_event, ctx) => {\n if (!sessionAbort.signal.aborted) check.settled(ctx);\n });\n\n pi.on(\"session_shutdown\", async (event, ctx) => {\n sessionAbort.abort(new DOMException(\"Session shut down\", \"AbortError\"));\n attention.reset(ctx);\n shutdownAbort?.abort(new DOMException(\"Session shut down again\", \"AbortError\"));\n const controller = new AbortController();\n shutdownAbort = controller;\n const signal = combineSignals(controller.signal, AbortSignal.timeout(30_000));\n const reason = typeof event === \"object\" && event ? (event as { reason?: string }).reason : undefined;\n try {\n const ready = await initialization;\n if (signal.aborted) return;\n await check.stop();\n if (signal.aborted) return;\n if (ready !== false && reason !== \"reload\") {\n await withStateDirectoryAccess(async () => {\n if (signal.aborted) return;\n await autoPushSessions(ctx, signal, loaders);\n });\n }\n } catch (error) {\n if (!signal.aborted) {\n ctx.ui.notify(`pi-sync session push skipped: ${errorMessage(error)}`, \"warning\");\n }\n } finally {\n if (shutdownAbort === controller) shutdownAbort = undefined;\n }\n if (signal.aborted) return;\n setSyncStatus(ctx, undefined);\n });\n}\n\nexport type { SyncDependencies } from \"./sync/sync-loaders.js\";\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGO,SAAS,sBACd,KACA,UACS;AACT,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,YAAY,OAAc,SAA+C;AAC7E,aAAS,IAAI;AACb,QAAI;AACF,aAAO,MAAM,KAAK;AAAA,IACpB,UAAE;AACA,eAAS,KAAK;AAAA,IAChB;AAAA,EACF;AACA,QAAM,KAA6B;AAAA,IACjC,GAAG,IAAI;AAAA,IACP,QAAQ,IAAI,SAAS,UAAU,MAAM,IAAI,GAAG,OAAO,GAAG,IAAI,CAAC;AAAA,IAC3D,SAAS,IAAI,SAAS,UAAU,MAAM,IAAI,GAAG,QAAQ,GAAG,IAAI,CAAC;AAAA,EAC/D;AAGA,SAAO,IAAI,MAAM,KAAK;AAAA,IACpB,IAAI,QAAQ,KAAK,UAAU;AACzB,aAAO,QAAQ,OAAO,KAAK,QAAQ,IAAI,QAAQ,KAAK,QAAQ;AAAA,IAC9D;AAAA,EACF,CAAC;AACH;;;ACRA,eAAsB,uBACpB,SACA,KACA,QACA,SACA,UACA,OACyB;AACzB,MAAI;AACF,UAAM,CAAC,YAAY,GAAG,IAAI,IAAI,UAAU,OAAO;AAC/C,QAAI,eAAe,UAAU,eAAe,UAAU,eAAe,QAAQ;AAC3E,YAAM,IAAI,MAAM,oCAAoC,cAAc,SAAS,EAAE;AAAA,IAC/E;AACA,UAAM,UAAU,aAAa,IAAI;AACjC,QAAI,UAAU,OAAW,SAAQ,QAAQ;AACzC,QAAI,OAAQ,SAAQ,SAAS;AAC7B,QAAI,SAAU,SAAQ,WAAW;AACjC,YAAQ,SAAS;AACjB,YAAQ,OAAO;AACf,2BAAuB,YAAY,OAAO;AAC1C,UAAM,aAAa,MAAM,QAAQ,WAAW;AAC5C,mBAAe,QAAQ,MAAM;AAC7B,QAAI,eAAe,QAAQ;AACzB,YAAM,UAAU,MAAM,SAAS,QAAQ,MAAM,WAAW,KAAK,KAAK,OAAO,CAAC;AAC1E,aAAO,EAAE,MAAM,aAAa,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC,EAAG;AAAA,IAC9D;AACA,QAAI,eAAe,QAAQ;AACzB,YAAM,UAAU,MAAM,SAAS,QAAQ,MAAM,WAAW,KAAK,KAAK,OAAO,CAAC;AAC1E,aAAO,EAAE,MAAM,aAAa,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC,EAAG;AAAA,IAC9D;AACA,UAAM,SAAS,QAAQ,MAAM,WAAW,SAAS,KAAK,OAAO,CAAC;AAC9D,WAAO,EAAE,MAAM,YAAY;AAAA,EAC7B,SAAS,OAAO;AACd,QAAI,QAAQ,QAAS,QAAO,EAAE,MAAM,SAAS;AAC7C,kBAAc,KAAK,MAAS;AAC5B,QAAI,iBAAiB,8BAA8B;AACjD,aAAO,EAAE,MAAM,6BAA6B,UAAU,MAAM,SAAS;AAAA,IACvE;AACA,QAAI,4BAA4B,KAAK,GAAG;AACtC,aAAO,EAAE,MAAM,qBAAqB,UAAU,MAAM,SAAS;AAAA,IAC/D;AACA,QAAI,GAAG,OAAO,kBAAkB,KAAK,GAAG,OAAO;AAC/C,WAAO,EAAE,MAAM,SAAS;AAAA,EAC1B;AACF;AAEA,eAAsB,eACpB,SACA,KACA,QACA,SACA,UACA,OACA,UACyB;AACzB,QAAM,sBAAsB,KAAK,QAAQ;AACzC,MAAI;AACF,UAAM,UAAU,MAAM,mBAAmB,SAAS,GAAG;AACrD,QAAI,QAAQ,WAAW,CAAC,QAAS,QAAO,EAAE,MAAM,YAAY;AAC5D,UAAM,EAAE,YAAY,KAAK,IAAI;AAC7B,UAAM,UAAU,aAAa,IAAI;AACjC,QAAI,UAAU,OAAW,SAAQ,QAAQ;AACzC,QAAI,OAAQ,SAAQ,SAAS;AAC7B,QAAI,SAAU,SAAQ,WAAW;AACjC,2BAAuB,YAAY,OAAO;AAE1C,YAAQ,YAAY;AAAA,MAClB,KAAK;AACH,YAAI,GAAG,OAAO,MAAM,GAAG,MAAM;AAC7B,eAAO,EAAE,MAAM,YAAY;AAAA,MAC7B,KAAK,OAAO;AACV,cAAM,EAAE,aAAa,IAAI,MAAM,QAAQ,YAAY;AACnD,uBAAe,QAAQ,MAAM;AAC7B,cAAM;AAAA,UACJ;AAAA,UACA,QAAQ,KAAK,CAAC,KAAK;AAAA,UACnB,OAAO,kBAAkB;AACvB,kBAAM,aAAa,MAAM,QAAQ,WAAW;AAC5C,2BAAe,QAAQ,MAAM;AAC7B,mBAAO,SAAS,QAAQ,MAAM,WAAW,KAAK,KAAK,EAAE,GAAG,SAAS,OAAO,cAAc,CAAC,CAAC;AAAA,UAC1F;AAAA,UACA;AAAA,UACA,QAAQ;AAAA,QACV;AACA,eAAO,EAAE,MAAM,YAAY;AAAA,MAC7B;AAAA,MACA,KAAK;AACH,cAAM,WAAW,KAAK,MAAM;AAC5B,eAAO,EAAE,MAAM,YAAY;AAAA,MAC7B,KAAK;AACH,cAAM,WAAW,KAAK,OAAO;AAC7B,eAAO,EAAE,MAAM,YAAY;AAAA,MAC7B,KAAK,SAAS;AACZ,cAAM,EAAE,kBAAkB,IAAI,MAAM,OAAO,qCAAyB;AACpE,uBAAe,QAAQ,MAAM;AAC7B,cAAM,kBAAkB,KAAK,QAAQ,OAAO,QAAQ,MAAM;AAC1D,eAAO,EAAE,MAAM,YAAY;AAAA,MAC7B;AAAA,MACA,KAAK,UAAU;AACb,cAAM,aAAa,MAAM,QAAQ,WAAW;AAC5C,uBAAe,QAAQ,MAAM;AAC7B,cAAM,WAAW,OAAO,KAAK,OAAO;AACpC,eAAO,EAAE,MAAM,YAAY;AAAA,MAC7B;AAAA,MACA,KAAK,QAAQ;AACX,cAAM,aAAa,MAAM,QAAQ,WAAW;AAC5C,uBAAe,QAAQ,MAAM;AAC7B,cAAM,WAAW,KAAK,KAAK,OAAO;AAClC,eAAO,EAAE,MAAM,YAAY;AAAA,MAC7B;AAAA,MACA,KAAK,aAAa;AAChB,cAAM,EAAE,cAAc,IAAI,MAAM,OAAO,sCAA4B;AACnE,uBAAe,QAAQ,MAAM;AAC7B,cAAM,SAAS,QAAQ,MAAM,cAAc,KAAK,OAAO,CAAC;AACxD,eAAO,EAAE,MAAM,YAAY;AAAA,MAC7B;AAAA,MACA,KAAK,UAAU;AACb,cAAM,aAAa,MAAM,QAAQ,WAAW;AAC5C,uBAAe,QAAQ,MAAM;AAC7B,cAAM,WAAW,OAAO,KAAK,OAAO;AACpC,eAAO,EAAE,MAAM,YAAY;AAAA,MAC7B;AAAA,MACA,KAAK,QAAQ;AACX,cAAM,aAAa,MAAM,QAAQ,WAAW;AAC5C,uBAAe,QAAQ,MAAM;AAC7B,cAAM,UAAU,MAAM,SAAS,QAAQ,MAAM,WAAW,KAAK,KAAK,OAAO,CAAC;AAC1E,eAAO,EAAE,MAAM,aAAa,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC,EAAG;AAAA,MAC9D;AAAA,MACA,KAAK,QAAQ;AACX,cAAM,aAAa,MAAM,QAAQ,WAAW;AAC5C,uBAAe,QAAQ,MAAM;AAC7B,cAAM,UAAU,MAAM,SAAS,QAAQ,MAAM,WAAW,KAAK,KAAK,OAAO,CAAC;AAC1E,eAAO,EAAE,MAAM,aAAa,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC,EAAG;AAAA,MAC9D;AAAA,MACA,KAAK,QAAQ;AACX,cAAM,aAAa,MAAM,QAAQ,WAAW;AAC5C,uBAAe,QAAQ,MAAM;AAC7B,cAAM,SAAS,QAAQ,MAAM,WAAW,SAAS,KAAK,OAAO,CAAC;AAC9D,eAAO,EAAE,MAAM,YAAY;AAAA,MAC7B;AAAA,MACA,KAAK,WAAW;AACd,cAAM,aAAa,MAAM,QAAQ,WAAW;AAC5C,uBAAe,QAAQ,MAAM;AAC7B,cAAM,WAAW,QAAQ,KAAK,OAAO;AACrC,eAAO,EAAE,MAAM,YAAY;AAAA,MAC7B;AAAA,MACA,KAAK,YAAY;AACf,cAAM,aAAa,MAAM,QAAQ,WAAW;AAC5C,uBAAe,QAAQ,MAAM;AAC7B,cAAM,SAAS,YAAY,MAAM,WAAW,SAAS,KAAK,OAAO,CAAC;AAClE,eAAO,EAAE,MAAM,YAAY;AAAA,MAC7B;AAAA,MACA,KAAK;AACH,cAAM,sBAAsB,KAAK,OAAO;AACxC,eAAO,EAAE,MAAM,YAAY;AAAA,MAC7B,KAAK;AACH,cAAM,OAAO,KAAK,OAAO;AACzB,eAAO,EAAE,MAAM,YAAY;AAAA,MAC7B;AACE,YAAI,GAAG,OAAO,0BAA0B,UAAU;AAAA;AAAA,EAAO,MAAM,CAAC,IAAI,SAAS;AAC7E,eAAO,EAAE,MAAM,SAAS;AAAA,IAC5B;AAAA,EACF,SAAS,OAAO;AACd,QAAI,QAAQ,QAAS,QAAO,EAAE,MAAM,SAAS;AAC7C,kBAAc,KAAK,MAAS;AAC5B,QAAI,iBAAiB,yBAA0B,OAAM;AACrD,QAAI,iBAAiB,8BAA8B;AACjD,aAAO,EAAE,MAAM,6BAA6B,UAAU,MAAM,SAAS;AAAA,IACvE;AACA,QAAI,4BAA4B,KAAK,GAAG;AACtC,aAAO,EAAE,MAAM,qBAAqB,UAAU,MAAM,SAAS;AAAA,IAC/D;AACA,QAAI,GAAG,OAAO,kBAAkB,KAAK,GAAG,OAAO;AAC/C,WAAO,EAAE,MAAM,SAAS;AAAA,EAC1B;AACF;AAEA,eAAe,sBAAsB,KAA8B,SAAyB;AAC1F,QAAM,SAAS,8BAA8B;AAC7C,MAAI,CAAC,QAAQ;AACX,QAAI,GAAG,OAAO,gEAAgE,MAAM;AACpF;AAAA,EACF;AACA,MACE,CAAC,QAAQ,OACT,CAAE,MAAM,IAAI,GAAG;AAAA,IACb;AAAA,IACA;AAAA,IACA,EAAE,QAAQ,QAAQ,OAAO;AAAA,EAC3B,GACA;AACA,QAAI,GAAG,OAAO,sCAAsC,MAAM;AAC1D;AAAA,EACF;AACA,iBAAe,QAAQ,MAAM;AAC7B,QAAM,SAAS,MAAM,4BAA4B;AACjD,iBAAe,QAAQ,MAAM;AAC7B,MAAI,OAAO,WAAW,SAAS;AAC7B,QAAI,GAAG,OAAO,gEAAgE,MAAM;AACpF;AAAA,EACF;AACA,MAAI,GAAG,OAAO,OAAO,SAAS,OAAO,WAAW,aAAa,SAAS,SAAS;AACjF;AAEA,eAAe,WAAW,KAA8B,QAAsB;AAC5E,QAAM,aAAa,gBAAgB;AACnC,MAAI,MAAM,sBAAsB,GAAG;AACjC,QAAI,GAAG,OAAO,0BAA0B,MAAM,sBAAsB,CAAC,IAAI,MAAM;AAC/E;AAAA,EACF;AAEA,MAAI,IAAI,SAAS,OAAO;AACtB,UAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,mCAA6B;AACtE,mBAAe,MAAM;AACrB,UAAM,gBAAgB,KAAK,MAAM;AACjC;AAAA,EACF;AACA,QAAM,0BAA0B,oBAAoB,CAAC;AACrD,MAAI,GAAG,OAAO,WAAW,UAAU,6DAA6D,MAAM;AACxG;AAEA,eAAe,WAAW,KAA8B,SAAyB;AAC/E,QAAM,SAAS,MAAM,WAAW,QAAQ,KAAK;AAC7C,QAAM,WAAW;AAAA,IACf,GAAI,OAAO,QAAQ,SAAS,OAAO,qBAAqB,OAAO,QAAQ,OAAO,IAAI,CAAC;AAAA,IACnF,GAAG,qBAAqB,MAAM;AAAA,EAChC;AACA,QAAM,eAAe,mBAAmB,MAAM;AAC9C,MAAI,GAAG;AAAA,IACL;AAAA,MACE;AAAA,MACA,eAAe,OAAO,SAAS;AAAA,MAC/B,uBAAuB,OAAO,cAAc;AAAA,MAC5C,GAAG;AAAA,MACH,iBAAiB,OAAO,WAAW;AAAA,MACnC,mBAAmB,qBAAqB,OAAO,SAAS,CAAC;AAAA,MACzD,yBAAyB,OAAO,gBAAgB,sBAAsB,KAAK;AAAA,MAC3E,wCAAwC,OAAO,eAAe,CAAC,GAAG,IAAI,gBAAgB,EAAE,KAAK,IAAI,KAAK,MAAM;AAAA,MAC5G,kCAAkC,OAAO,oBAAoB,6DAAwD,KAAK;AAAA,MAC1H,qBAAqB,OAAO,QAAQ,KAAK,IAAI,KAAK,MAAM;AAAA,MACxD,aAAa,OAAO,QAAQ,SAAS,UAAU,IAAI,aAAa,cAAc;AAAA,MAC9E,kBAAkB,gBAAgB,CAAC;AAAA,MACnC,GAAG;AAAA,IACL,EAAE,KAAK,IAAI;AAAA,IACX,SAAS,SAAS,IAAI,YAAY;AAAA,EACpC;AACF;AAEA,SAAS,mBAAmB,QAAuB;AACjD,UAAQ,OAAO,QAAQ,MAAM;AAAA,IAC3B,KAAK;AACH,aAAO;AAAA,QACL;AAAA,QACA,WAAW,iBAAiB,OAAO,QAAQ,QAAQ,MAAM,CAAC;AAAA,QAC1D;AAAA,QACA,WAAW,OAAO,QAAQ,YAAY,MAAM;AAAA,MAC9C;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL;AAAA,QACA,QAAQ,iBAAiB,OAAO,QAAQ,QAAQ,KAAK,OAAO,QAAQ,QAAQ,QAAQ,CAAC;AAAA,QACrF;AAAA,QACA;AAAA,MACF;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL;AAAA,QACA,aAAa,OAAO,QAAQ,QAAQ,QAAQ;AAAA,QAC5C,WAAW,OAAO,QAAQ,YAAY,MAAM;AAAA,QAC5C,WAAW,OAAO,QAAQ,QAAQ,MAAM;AAAA,QACxC;AAAA,QACA;AAAA,QACA,kBAAkB,OAAO,QAAQ,QAAQ,eAAe,eAAe,gBAAgB;AAAA,MACzF;AAAA,EACJ;AACF;AAEA,SAAS,iBAAiB,OAA2B;AACnD,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,KAAK;AACzB,QAAI,WAAW;AACf,QAAI,WAAW;AACf,QAAI,SAAS;AACb,QAAI,OAAO;AACX,WAAO,IAAI,SAAS;AAAA,EACtB,QAAQ;AACN,WAAO,MAAM,QAAQ,qCAAqC,gBAAW;AAAA,EACvE;AACF;AAEA,SAAS,iBAAiB,OAA2B,UAA8B;AACjF,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,KAAK;AACzB,QAAI,WAAW;AACf,QAAI,WAAW;AACf,QAAI,SAAS;AACb,QAAI,OAAO;AACX,WAAO,WAAW,GAAG,IAAI,MAAM,YAAO,GAAG,IAAI,MAAM,GAAG,IAAI,QAAQ;AAAA,EACpE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACjTA,eAAsB,cACpB,SACA,KACA,eACA,SACA,WACA;AACA,QAAM,qBAAqB,WAAW,aAAa;AACnD,MAAI,cAAc,QAAS;AAC3B,QAAM,MAAM,CACV,OACA,QACA,UACA,QACA,aAEA;AAAA,IACE;AAAA,IACA;AAAA,IACA,eAAe,eAAe,MAAM;AAAA,IACpC;AAAA,IACA,0BAA0B,WAAW,eAAe,QAAQ;AAAA,IAC5D;AAAA,IACA;AAAA,EACF;AACF,MAAI,CAAC,QAAQ,KAAK,GAAG;AACnB,QAAI;AACF,YAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,iCAAqB;AAC9D,UAAI,cAAc,QAAS;AAC3B,YAAM,gBAAgB,KAAK,KAAK,eAAe;AAAA,QAC7C,cAAc,MAAM,UAAU,QAAQ;AAAA,QACtC,gBAAgB,MAAM,UAAU,YAAY;AAAA,QAC5C,0BAA0B,MAAM,UAAU,iBAAiB;AAAA,QAC3D,qBAAqB,CAAC,aAAa;AACjC,cAAI,UAAU,QAAQ,MAAM,SAAU,WAAU,MAAM,GAAG;AAAA,QAC3D;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,UAAI,cAAc,QAAS;AAC3B,oBAAc,KAAK,MAAS;AAC5B,UAAI,GAAG,OAAO,aAAa,KAAK,GAAG,OAAO;AAAA,IAC5C;AACA,UAAM,qBAAqB,WAAW,aAAa;AACnD,QAAI,CAAC,cAAc,QAAS,OAAM,UAAU,QAAQ,KAAK,aAAa;AACtE;AAAA,EACF;AACA,QAAM,SAAS,MAAM,IAAI,OAAO;AAChC,MAAI,OAAO,SAAS,qBAAqB;AACvC,QAAI,GAAG,OAAO,OAAO,SAAS,eAAe,OAAO;AAAA,EACtD,WAAW,OAAO,SAAS,6BAA6B;AACtD,UAAM,SAAS,sBAAsB,OAAO;AAC5C,QAAI,OAAQ,WAAU,IAAI,OAAO,UAAU,MAAM;AACjD,UAAM,gBAAgB,UAAU,OAAO,EAAE,KAAK,CAAC,QAAQ,QAAQ,WAAW,QAAQ,IAAI;AACtF,QAAI,UAAU,IAAI,SAAS,SAAS,CAAC,eAAe;AAClD,YAAM,0BAA0B,KAAK,WAAW,eAAe,OAAO;AAAA,IACxE,OAAO;AACL,UAAI,GAAG;AAAA,QACL;AAAA,UACE,OAAO,SAAS;AAAA,UAChB,OAAO,SAAS;AAAA,UAChB,OAAO,SAAS;AAAA,QAClB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,QAAM,sCAAsC,SAAS,QAAQ,KAAK,WAAW,aAAa;AAC1F,QAAM,4BAA4B,KAAK,WAAW,aAAa;AAC/D,QAAM,qBAAqB,WAAW,aAAa;AACnD,MAAI,CAAC,cAAc,QAAS,OAAM,UAAU,QAAQ,KAAK,aAAa;AACxE;AAEA,SAAS,0BACP,WACA,eACA,UACA;AACA,QAAM,WAAW,UAAU,YAAY;AACvC,SAAO,MAAM;AAGX,QAAI,CAAC,cAAc,WAAW,UAAU,YAAY,MAAM,SAAU,WAAU,iBAAiB;AAC/F,eAAW;AAAA,EACb;AACF;AAEA,eAAe,qBAAqB,WAAoC,QAAqB;AAC3F,QAAM,WAAW,UAAU,YAAY;AACvC,MAAI,CAAC,YAAY,OAAO,QAAS;AACjC,MAAI;AACF,UAAM,SAAS,MAAM,WAAW;AAChC,QAAI,OAAO,WAAW,UAAU,YAAY,MAAM,SAAU;AAC5D,QAAI,CAAC,yBAAyB,UAAU,MAAM,GAAG;AAC/C,gBAAU,iBAAiB;AAC3B;AAAA,IACF;AACA,UAAM,QAAQ,MAAM,mBAAmB,MAAM;AAC7C,QAAI,OAAO,WAAW,UAAU,YAAY,MAAM,SAAU;AAC5D,QAAI,qBAAqB,KAAK,MAAM,SAAS,WAAW,cAAe,WAAU,iBAAiB;AAAA,EACpG,QAAQ;AACN,QAAI,CAAC,OAAO,WAAW,UAAU,YAAY,MAAM,SAAU,WAAU,iBAAiB;AAAA,EAC1F;AACF;AAEA,eAAe,sCACb,SACA,QACA,KACA,WACA,QACA;AACA,MAAI,OAAO,SAAS,eAAe,OAAO,YAAY,eAAe,OAAO,QAAS;AACrF,QAAM,CAAC,SAAS,GAAG,IAAI,IAAI,UAAU,OAAO;AAC5C,MAAI,YAAY,UAAU,YAAY,UAAU,YAAY,OAAQ;AACpE,QAAM,UAAU,UAAU,QAAQ;AAClC,MAAI,CAAC,QAAS;AACd,MAAI;AACF,UAAM,UAAU,aAAa,IAAI;AACjC,UAAM,YAAY,QAAQ,UAAU,MAAM,WAAW,GAAG;AACxD,QAAI,OAAO,WAAW,UAAU,QAAQ,MAAM,QAAS;AACvD,QAAI,QAAQ,SAAS,cAAc,UAAW,WAAU,MAAM,GAAG;AAAA,EACnE,QAAQ;AAAA,EAER;AACF;AAEA,eAAe,4BACb,KACA,WACA,QACA;AACA,QAAM,UAAU,UAAU,QAAQ;AAClC,MAAI,CAAC,WAAW,OAAO,QAAS;AAChC,MAAI;AACF,UAAM,SAAS,MAAM,WAAW,QAAQ,SAAS,SAAS;AAC1D,QAAI,OAAO,WAAW,UAAU,QAAQ,MAAM,QAAS;AACvD,QAAI,CAAC,2BAA2B,SAAS,MAAM,EAAG,WAAU,MAAM,GAAG;AAAA,EACvE,QAAQ;AACN,QAAI,CAAC,OAAO,WAAW,UAAU,QAAQ,MAAM,QAAS,WAAU,MAAM,GAAG;AAAA,EAC7E;AACF;AAEA,SAAS,sBAAsB,SAAkD;AAC/E,QAAM,UAAU,UAAU,OAAO,EAAE,CAAC;AACpC,SAAO,YAAY,UAAU,YAAY,UAAU,YAAY,SAAS,UAAU;AACpF;AAEA,eAAsB,0BACpB,KACA,WACA,QACA,SACA,UAGI,CAAC,GACL;AACA,QAAM,UAAU,UAAU,QAAQ;AAClC,MAAI,CAAC,WAAW,OAAO,QAAS;AAChC,QAAM,EAAE,sBAAsB,IAAI,MAAM,OAAO,gDAAoC;AACnF,MAAI,OAAO,WAAW,UAAU,QAAQ,MAAM,QAAS;AACvD,QAAM;AAAA,IACJ;AAAA,IACA,EAAE,MAAM,6BAA6B,UAAU,QAAQ,SAAS;AAAA,IAChE,QAAQ;AAAA,IACR,CAAC,OAAO,cAAc,UAAU,WAAW;AACzC,YAAM,UAAU,MACd;AAAA,QACE;AAAA,QACA;AAAA,QACA,eAAe,QAAQ,YAAY;AAAA,QACnC;AAAA,QACA,0BAA0B,WAAW,QAAQ,QAAQ;AAAA,QACrD;AAAA,MACF;AACF,aAAO,QAAQ,kBAAkB,QAAQ,gBAAgB,OAAO,IAAI,QAAQ;AAAA,IAC9E;AAAA,IACA;AAAA,IACA;AAAA,MACE,aAAa,QAAQ;AAAA,MACrB,iBAAiB,QAAQ;AAAA,MACzB,qBAAqB,MAAM;AACzB,YAAI,UAAU,QAAQ,MAAM,QAAS,WAAU,MAAM,GAAG;AAAA,MAC1D;AAAA,IACF;AAAA,EACF;AACF;;;AC5MO,SAAS,qBAAqB,OAAgB;AACnD,SACE,iBAAiB,UAChB,MAAM,QAAQ,WAAW,2BAA2B,KAAK,MAAM,YAAY;AAEhF;;;ACcA,IAAM,oBAAoC;AAAA,EACxC,KAAK;AAAA,EACL,OAAO;AAAA,EACP,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,MAAM,CAAC;AACT;AAEA,eAAsB,aAAa,KAAuB,QAAqB;AAC7E,iBAAe,MAAM;AACrB,QAAM,cAAc,8BAA8B;AAClD,MAAI,eAAe,IAAI,MAAO,KAAI,GAAG,OAAO,aAAa,SAAS;AAGlE,QAAM,mBAAmB,qBAAqB,KAAK,MAAM;AACzD,QAAM,cAAc,IAAI,eAAe,iBAAiB;AACxD,QAAM,qCAAqC;AAAA,IACzC;AAAA,IACA,YAAY,sBAAsB,GAAG;AAAA,IACrC,kBAAkB,cAAc,CAAC,WAAW,IAAI,CAAC;AAAA,IACjD;AAAA,EACF,CAAC;AACD,iBAAe,MAAM;AACrB,MAAI;AACF,UAAM,WAAW,MAAM,sBAAsB;AAC7C,mBAAe,MAAM;AACrB,UAAM,QAAQ,WAAW,OAAO,KAAK,SAAS,UAAU,EAAE,KAAK,CAAC,MAAM,UAAU,KAAK,cAAc,KAAK,CAAC,IAAI,CAAC;AAC9G,4BAAwB,KAAK;AAC7B,wBAAoB,KAAK,UAAU,cAAc,IAAI;AAAA,EACvD,QAAQ;AACN,QAAI,OAAO,QAAS;AACpB,4BAAwB,CAAC,CAAC;AAAA,EAC5B;AACA,QAAM,kBAAkB,kCAAkC;AAC1D,MAAI,mBAAmB,IAAI,MAAO,KAAI,GAAG,OAAO,iBAAiB,SAAS;AAC1E,iBAAe,MAAM;AACrB,MAAI,CAAC,IAAI,MAAO;AAChB,MAAI;AACF,UAAM,SAAS,MAAM,WAAW;AAChC,mBAAe,MAAM;AACrB,wBAAoB,KAAK,OAAO,UAAU;AAC1C,WAAO,OAAO,aAAa,OAAO,oBAAoB,SAAS;AAAA,EACjE,SAAS,OAAO;AACd,mBAAe,MAAM;AACrB,QAAI,CAAC,qBAAqB,KAAK,GAAG;AAChC,UAAI,GAAG,OAAO,kCAAkC,iBAAiB,aAAa,KAAK,CAAC,CAAC,IAAI,SAAS;AAAA,IACpG;AAAA,EACF;AACF;AAEA,eAAsB,iBAAiB,KAAuB,QAAqB,SAAsB;AACvG,MAAI;AACF,UAAM,UAAU,MAAM,kBAAkB;AACxC,mBAAe,MAAM;AACrB,QAAI,CAAC,QAAQ,UAAW;AACxB,QAAI,CAAC,QAAQ,QAAQ,SAAS,UAAU,EAAG;AAC3C,UAAM,eAAe;AACrB,mBAAe,MAAM;AACrB,UAAM,SAAS,MAAM,WAAW;AAChC,mBAAe,MAAM;AACrB,QAAI,CAAC,OAAO,QAAQ,SAAS,UAAU,EAAG;AAC1C,UAAM,CAAC,YAAY,gBAAgB,eAAe,IAAI,MAAM,QAAQ,IAAI;AAAA,MACtE,QAAQ,WAAW;AAAA,MACnB,QAAQ,SAAS;AAAA,MACjB,QAAQ,UAAU;AAAA,IACpB,CAAC;AACD,mBAAe,MAAM;AACrB,UAAM,SAAS,qBAAqB,YAAY;AAC9C,qBAAe,MAAM;AACrB,YAAM,QAAQ,MAAM,mBAAmB,MAAM;AAC7C,qBAAe,MAAM;AACrB,YAAM,QAAQ,MAAM,eAAe,eAAe,OAAO,kBAAkB;AAAA,QACzE,GAAG,0BAA0B,KAAK,MAAM;AAAA,QACxC;AAAA,MACF,CAAC;AACD,qBAAe,MAAM;AACrB,UAAI,CAAC,gBAAgB,gBAAgB,OAAO,OAAO,MAAM,EAAG;AAC5D,YAAM,WAAW,KAAK,KAAK,EAAE,GAAG,mBAAmB,OAAO,GAAG,EAAE,QAAQ,OAAO,MAAM,CAAC;AAAA,IACvF,CAAC;AAAA,EACH,SAAS,OAAO;AACd,QAAI,OAAO,WAAW,qBAAqB,KAAK,EAAG;AACnD,kBAAc,KAAK,MAAS;AAC5B,QAAI,GAAG,OAAO,iCAAiC,aAAa,KAAK,CAAC,IAAI,SAAS;AAAA,EACjF;AACF;;;ACzFO,SAAS,mBAAmB,SAAsB,WAAoC,YAAY,KAAQ;AAC/G,MAAI;AASJ,MAAI;AAEJ,QAAM,gBAAgB;AAAA,IACpB,MAAM,UAAU,KAAuB;AACrC,YAAM,OAAO;AACb,UAAI,CAAC,MAAM,YAAY,KAAK,UAAU,IAAI,eAAgB;AAC1D,WAAK,WAAW,MAAM,IAAI,aAAa,qBAAqB,YAAY,CAAC;AACzE,YAAM,KAAK;AAAA,IACb;AAAA,IACA,QAAQ,KAAuB;AAC7B,YAAM,SAAS;AACf,UAAI,CAAC,UAAU,OAAO,IAAI,mBAAmB,IAAI,kBAAkB,OAAO,OAAO,WAAW,CAAC,IAAI,OAAO,EAAG;AAC3G,gBAAU;AACV,oBAAc,MAAM,KAAK,OAAO,QAAQ,OAAO,MAAM;AAAA,IACvD;AAAA,IACA,MAAM,KAAuB,eAA4B,eAA+B;AACtF,UAAI,CAAC,IAAI,SAAS,cAAc,QAAS;AACzC,UAAI,eAAe,qBAAqB,CAAC,IAAI,OAAO,GAAG;AACrD,kBAAU,EAAE,KAAK,QAAQ,eAAe,QAAQ,cAAc;AAC9D;AAAA,MACF;AACA,UAAI,OAAQ,OAAM,IAAI,MAAM,8CAA8C;AAC1E,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,SAAS,eAAe,eAAe,WAAW,MAAM;AAC9D,YAAM,OAAO;AAAA,QACX;AAAA,QACA,OAAO,IAAI;AAAA,QACX,SAAS,QAAQ,QAAQ;AAAA,QACzB,UAAU,QAAQ,eAAe,iBAAiB;AAAA,MACpD;AACA,eAAS;AACT,YAAM,YAAY,MAAM,CAAC,cAAc,WAAW,WAAW,QAAQ,KAAK,UAAU,IAAI;AACxF,YAAM,QAAQ;AAAA,QACZ,MAAM,WAAW,MAAM,IAAI,aAAa,2BAA2B,cAAc,CAAC;AAAA,QAClF;AAAA,MACF;AACA,UAAI;AACJ,UAAI,WAAW;AAEf,WAAK,UAAU,QAAQ,QAAQ,EAC5B,KAAK,YAAY;AAChB,YAAI;AACF,yBAAe,MAAM;AACrB,mBAAS,iBAAkB,MAAM,mBAAmB;AACpD,eAAK,WAAW,QAAQ,OAAO,iBAAiB;AAChD,yBAAe,MAAM;AACrB,cAAI,CAAC,OAAO,aAAa,CAAC,OAAO,kBAAmB;AACpD,gBAAM,WAAW;AACjB,gBAAM,WAAW,2BAA2B,QAAQ;AACpD,qBAAW;AACX,wBAAc,KAAK,UAAU;AAC7B,gBAAM,EAAE,YAAY,IAAI,MAAM,QAAQ,WAAW;AACjD,yBAAe,MAAM;AACrB,gBAAM,gBAAgB,MAAM,mBAAmB;AAC/C,yBAAe,MAAM;AACrB,cAAI,2BAA2B,aAAa,MAAM,SAAU;AAG5D,gBAAM,aAAa,MAAM,yBAAyB,MAAM;AACtD,2BAAe,MAAM;AACrB,mBAAO,SAAS,iBAAiB,MAAM;AACrC,6BAAe,MAAM;AACrB,qBAAO,YAAY,UAAU,0BAA0B,KAAK,QAAQ,GAAG,MAAM;AAAA,YAC/E,CAAC;AAAA,UACH,CAAC;AACD,yBAAe,MAAM;AACrB,gBAAM,SAAS,MAAM,mBAAmB;AACxC,yBAAe,MAAM;AACrB,cAAI,2BAA2B,MAAM,MAAM,SAAU;AACrD,gBAAM,QAAQ,MAAM,yBAAyB,MAAM,mBAAmB,MAAM,CAAC;AAC7E,yBAAe,MAAM;AACrB,cAAI,qBAAqB,KAAK,MAAM,WAAW,iBAAiB,CAAC,UAAU,EAAG;AAC9E,oBAAU,QAAQ;AAAA,YAChB,WAAW,SAAS;AAAA,YACpB,gBAAgB;AAAA,YAChB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,YAClC;AAAA,YACA,mBAAmB,SAAS;AAAA,UAC9B,CAAC;AACD,cACE,SAAS,qBACT,CAAC,WAAW,aACZ,WAAW,QACX,WAAW,gBAAgB,SAAS,QACpC;AACA,kBAAM,aAAa,MAAM,QAAQ,WAAW;AAC5C,2BAAe,MAAM;AACrB,gBAAI,CAAC,UAAU,EAAG;AAClB,gBAAI,CAAC,IAAI,OAAO,GAAG;AACjB,wBAAU,EAAE,KAAK,QAAQ,eAAe,QAAQ,SAAS;AACzD;AAAA,YACF;AACA,kBAAM;AAAA,cAAyB,MAC7B;AAAA,gBAAS;AAAA,gBAAoB,MAC3B,WAAW,SAAS,KAAK;AAAA,kBACvB,MAAM,CAAC;AAAA,kBACP,KAAK;AAAA,kBACL,OAAO;AAAA,kBACP,OAAO;AAAA,kBACP,QAAQ;AAAA,kBACR,QAAQ;AAAA,kBACR,MAAM;AAAA,kBACN;AAAA,kBACA,UAAU,MAAM,UAAU,iBAAiB;AAAA,gBAC7C,CAAC;AAAA,cACH;AAAA,YACF;AACA,2BAAe,MAAM;AACrB,gBAAI,CAAC,UAAU,EAAG;AAClB,sBAAU,iBAAiB;AAC3B;AAAA,UACF;AACA,oBAAU,QAAQ;AAAA,YAChB,WAAW,SAAS;AAAA,YACpB,gBAAgB;AAAA,YAChB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,YAClC;AAAA,YACA,mBAAmB,SAAS;AAAA,UAC9B,CAAC;AACD,cAAI,IAAI,SAAS,MAAO,WAAU,kBAAkB,GAAG;AAAA,QACzD,SAAS,OAAO;AACd,cAAI,CAAC,UAAU,KAAK,cAAc,WAAW,qBAAqB,KAAK,EAAG;AAC1E,cAAI,OAAO,WAAW,WAAW,OAAO,QAAQ,SAAS,eAAgB;AAEzE,cAAI,QAAQ;AACV,kBAAM,SAAS,MAAM,mBAAmB,EAAE,MAAM,MAAM,MAAS;AAC/D,gBAAI,CAAC,UAAU,KAAM,OAAO,WAAW,WAAW,OAAO,QAAQ,SAAS,eAAiB;AAC3F,gBAAI,CAAC,UAAU,2BAA2B,MAAM,MAAM,2BAA2B,MAAM,EAAG;AAAA,UAC5F;AACA,cAAI,GAAG;AAAA,YACL,QAAQ,oBACJ,uCAAuC,iBAAiB,aAAa,KAAK,CAAC,CAAC,+FAC5E,kCAAkC,iBAAiB,aAAa,KAAK,CAAC,CAAC;AAAA,YAC3E;AAAA,UACF;AAAA,QACF,UAAE;AACA,uBAAa,KAAK;AAClB,cAAI,UAAU,KAAK,UAAU;AAC3B,gBAAI,CAAC,OAAO,WAAW,WAAW,OAAO,QAAQ,SAAS,gBAAgB;AACxE,oBAAM,SAAS,MAAM,mBAAmB,EAAE,MAAM,MAAM,MAAS;AAC/D,kBAAI,UAAU,GAAG;AACf,oBAAI,OAAO,WAAW,WAAW,OAAO,QAAQ,SAAS;AACvD,gCAAc,KAAK,MAAS;AAAA,yBAE5B,UACA,UACA,2BAA2B,MAAM,MAAM,2BAA2B,MAAM;AAExE,wBAAM,UAAU,QAAQ,KAAK,aAAa;AAAA,oBACvC,WAAU,MAAM,GAAG;AAAA,cAC1B;AAAA,YACF,MAAO,eAAc,KAAK,MAAS;AAAA,UACrC;AACA,cAAI,WAAW,KAAM,UAAS;AAAA,QAChC;AAAA,MACF,CAAC,EACA,MAAM,MAAM;AAEX,qBAAa,KAAK;AAClB,YAAI,WAAW,KAAM,UAAS;AAAA,MAChC,CAAC;AAAA,IACL;AAAA,IACA,MAAM,OAAO;AACX,gBAAU;AACV,YAAM,OAAO;AACb,UAAI,CAAC,KAAM;AACX,WAAK,WAAW,MAAM,IAAI,aAAa,2BAA2B,YAAY,CAAC;AAE/E,YAAM,KAAK;AAAA,IACb;AAAA,EACF;AACA,SAAO;AACT;;;AC/KO,SAAS,kBAAkB,cAAsD;AACtF,QAAM,UAAuB;AAAA,IAC3B,YAAY,mBAAmB,aAAa,uBAAuB,MAAM,OAAO,sCAAsB,EAAE;AAAA,IACxG,aAAa,mBAAmB,aAAa,oBAAoB,MAAM,OAAO,mCAAmB,EAAE;AAAA,IACnG,UAAU,mBAAmB,aAAa,iBAAiB,MAAM,OAAO,+BAAyB,EAAE;AAAA,IACnG,WAAW,mBAAmB,aAAa,kBAAkB,MAAM,OAAO,iCAAiB,EAAE;AAAA,IAC7F,YAAY,mBAAmB,aAAa,uBAAuB,MAAM,OAAO,sCAAsB,EAAE;AAAA,EAC1G;AACA,SAAO;AACT;AAEA,SAAS,mBAA2B,MAAoD;AACtF,MAAI;AACJ,SAAO,MAAM;AACX,QAAI,CAAC,SAAS;AACZ,gBAAU,KAAK,EAAE,MAAM,CAAC,UAAU;AAChC,kBAAU;AACV,cAAM;AAAA,MACR,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AACF;;;AC/Be,SAAR,KAAsB,IAAkB,eAA0C,CAAC,GAAG;AAC3F,QAAM,UAAU,kBAAkB,YAAY;AAC9C,QAAM,YAAY,8BAA8B;AAChD,QAAM,QAAQ,mBAAmB,SAAS,SAAS;AACnD,MAAI,eAAe,IAAI,gBAAgB;AACvC,MAAI;AACJ,MAAI,iBAAiB,QAAQ,QAA2C,MAAS;AAEjF,KAAG,gBAAgB,QAAQ;AAAA,IACzB,aAAa;AAAA,IACb,wBAAwB;AAAA,IACxB,SAAS,OAAO,MAAM,QAAQ;AAC5B,UAAI,CAAC,IAAI,OAAO;AACd,cAAM,IAAI,MAAM,8EAA8E;AAAA,MAChG;AACA,YAAM,SAAS,aAAa;AAC5B,YAAM,QAAQ,MAAM;AACpB,UAAI,OAAO,QAAS;AACpB,YAAM,MAAM,KAAK;AACjB,UAAI,OAAO,QAAS;AACpB,YAAM,UAAU,UAAU,IAAI,EAAE,CAAC;AACjC,UAAI,UAAU,SAAS,YAAY,UAAU,YAAY,YAAY,YAAY,iBAAiB;AAChG,YAAI,GAAG;AAAA,UACL;AAAA,UACA;AAAA,QACF;AACA;AAAA,MACF;AACA,YAAM,MAAM,MAAM,cAAc,MAAM,KAAK,QAAQ,SAAS,SAAS;AAGrE,UAAI,YAAY,mBAAmB,YAAY,OAAQ,OAAM,IAAI;AAAA,UAC5D,OAAM,yBAAyB,GAAG;AAAA,IACzC;AAAA,EACF,CAAC;AAED,KAAG,GAAG,iBAAiB,OAAO,QAAQ,QAAQ;AAC5C,mBAAe,MAAM,IAAI,aAAa,oBAAoB,YAAY,CAAC;AACvE,oBAAgB;AAChB,iBAAa,MAAM,IAAI,aAAa,oBAAoB,YAAY,CAAC;AACrE,mBAAe,IAAI,gBAAgB;AACnC,UAAM,SAAS,aAAa;AAC5B,cAAU,MAAM,GAAG;AACnB,UAAM,WAAW;AACjB,sBAAkB,YAAY;AAC5B,YAAM;AACN,UAAI,OAAO,QAAS,QAAO;AAC3B,YAAM,MAAM,KAAK;AACjB,UAAI,OAAO,QAAS,QAAO;AAC3B,UAAI;AACF,cAAM,SAAS,MAAM,yBAAyB,MAAM,aAAa,KAAK,MAAM,CAAC;AAC7E,eAAO,OAAO,UAAU,QAAQ;AAAA,MAClC,SAAS,OAAO;AACd,YAAI,CAAC,OAAO,WAAW,IAAI,OAAO;AAChC,cAAI,GAAG,OAAO,8BAA8B,iBAAiB,aAAa,KAAK,CAAC,CAAC,IAAI,OAAO;AAAA,QAC9F;AACA,eAAO;AAAA,MACT;AAAA,IACF,GAAG;AACH,UAAM,QAAQ,MAAM;AACpB,QAAI,SAAS,CAAC,OAAO,QAAS,OAAM,MAAM,KAAK,QAAQ,KAAK;AAAA,EAC9D,CAAC;AAED,KAAG,GAAG,eAAe,OAAO,QAAQ,QAAQ;AAC1C,UAAM,MAAM,UAAU,GAAG;AAAA,EAC3B,CAAC;AAED,KAAG,GAAG,iBAAiB,CAAC,QAAQ,QAAQ;AACtC,QAAI,CAAC,aAAa,OAAO,QAAS,OAAM,QAAQ,GAAG;AAAA,EACrD,CAAC;AAED,KAAG,GAAG,oBAAoB,OAAO,OAAO,QAAQ;AAC9C,iBAAa,MAAM,IAAI,aAAa,qBAAqB,YAAY,CAAC;AACtE,cAAU,MAAM,GAAG;AACnB,mBAAe,MAAM,IAAI,aAAa,2BAA2B,YAAY,CAAC;AAC9E,UAAM,aAAa,IAAI,gBAAgB;AACvC,oBAAgB;AAChB,UAAM,SAAS,eAAe,WAAW,QAAQ,YAAY,QAAQ,GAAM,CAAC;AAC5E,UAAM,SAAS,OAAO,UAAU,YAAY,QAAS,MAA8B,SAAS;AAC5F,QAAI;AACF,YAAM,QAAQ,MAAM;AACpB,UAAI,OAAO,QAAS;AACpB,YAAM,MAAM,KAAK;AACjB,UAAI,OAAO,QAAS;AACpB,UAAI,UAAU,SAAS,WAAW,UAAU;AAC1C,cAAM,yBAAyB,YAAY;AACzC,cAAI,OAAO,QAAS;AACpB,gBAAM,iBAAiB,KAAK,QAAQ,OAAO;AAAA,QAC7C,CAAC;AAAA,MACH;AAAA,IACF,SAAS,OAAO;AACd,UAAI,CAAC,OAAO,SAAS;AACnB,YAAI,GAAG,OAAO,iCAAiC,aAAa,KAAK,CAAC,IAAI,SAAS;AAAA,MACjF;AAAA,IACF,UAAE;AACA,UAAI,kBAAkB,WAAY,iBAAgB;AAAA,IACpD;AACA,QAAI,OAAO,QAAS;AACpB,kBAAc,KAAK,MAAS;AAAA,EAC9B,CAAC;AACH;",
6
6
  "names": []
7
7
  }
package/docs/settings.md CHANGED
@@ -29,6 +29,14 @@ A private `pi-sync.local.json` containing a valid version 3 document is copied b
29
29
  The old file remains as a recovery copy.
30
30
  If both paths exist, `pi-sync.json` wins and the legacy file remains untouched.
31
31
 
32
+ ### Editing in the manager
33
+
34
+ Open `/sync` → **Settings** for one searchable list ordered by content, destination, automation, merge, and global preferences; these are task groups, not submenus. **Included content** and **Compare synced content** are adjacent. **Storage location** edits the current setup's coordinates, while **Manage sync setups** and **Manage storage connections** retain the catalog and affected-setup reviews. Returning from catalog management refreshes the rows; switching or removing the owning setup closes its old Settings screen.
35
+
36
+ Simple choices save immediately, preserve the selected row and search during refresh, and restore their previous value if saving fails. Escape closes the screen without undoing successful saves. Complex values retain their input/review flows. This navigation change does not alter schema, defaults, automatic-sync behavior, or experimental opt-ins.
37
+
38
+ RPC reports the manual settings path and offers the existing setup/connection catalog dialogs, not the TUI preference or masked-credential editors. Print/JSON do not support `/sync`; use the private settings file directly. Main-menu summaries remain cached observations; `/sync status`, `/sync diff`, and `/sync help` remain available through the visible command hints.
39
+
32
40
  ### Complete version 3 example
33
41
 
34
42
  ```json
@@ -157,9 +165,9 @@ When `true`, pushes skip the local secret scan; other safety checks and confirma
157
165
  Enable it only after reviewing the destination and selected content because files containing secrets can be uploaded.
158
166
  `/sync doctor` still scans and reports possible secrets regardless of this setting.
159
167
 
160
- ### Check setup
168
+ ### Diagnostics
161
169
 
162
- **More… → Check setup** and `/sync doctor` retain the same route. S3/R2 reads the selected setup's latest pointer with a ten-second deadline and bounded error details; it never writes, deletes, lists buckets, or changes settings. HTTP success does not validate snapshot contents or write access. `NoSuchKey` indicates no snapshot at the path; `NoSuchBucket` indicates a missing bucket; other 404 responses cannot distinguish the two. Authentication and transport failures give credentials/permissions or address/network guidance. Git reads remote snapshots and checks its local cache; write access is not tested. WebDAV retains its isolated conditional-write/cleanup probe and repair of a missing active-snapshot history entry.
170
+ **/sync → Diagnostics** and `/sync doctor` use the same existing route. S3/R2 reads the selected setup's latest pointer with a ten-second deadline and bounded error details; it never writes, deletes, lists buckets, or changes settings. HTTP success does not validate snapshot contents or write access. `NoSuchKey` indicates no snapshot at the path; `NoSuchBucket` indicates a missing bucket; other 404 responses cannot distinguish the two. Authentication and transport failures give credentials/permissions or address/network guidance. Git reads remote snapshots and checks its local cache; write access is not tested. WebDAV retains its isolated conditional-write/cleanup probe and repair of a missing active-snapshot history entry.
163
171
 
164
172
  ### Included content
165
173
 
@@ -232,7 +240,7 @@ Same-session content merge accepts only complete validated Pi v3 histories with
232
240
 
233
241
  Partial progress stores per-path accepted hashes separately from the observed remote head. Withheld local versions remain local; the complete published snapshot retains the observed remote versions, never omission/deletion. Their previous verified ancestors survive staging and restart. Resource and collision groups defer together when independence cannot be proved; partially conflicted JSON and sessions remain atomic. Publication/apply/baseline is journaled, and a changed head replans only proven pre-commit attempts; unknown outcomes preserve evidence. Older codecs refuse version-3 partial snapshots and older settings readers refuse version 5, so do not collapse state for downgrade.
234
242
 
235
- `/sync conflicts` (also **History & recovery → Review unresolved conflicts**) shows private versions only when requested, in TUI or paginated RPC. It rejects confirmation/force bypass flags, reviews an entire group, then separately asks for local or remote authority. It revalidates the reviewed artifact fingerprint, group versions, baseline, configuration, ownership and current observed head before apply; stale/cancelled review performs no transfer. The manager always reports unresolved groups and the observed head separately from Last applied. Repeated unchanged conflicts retain one stable opaque identity rather than creating more evidence or modal reviews.
243
+ `/sync conflicts` (also the conditional main **Review unresolved conflicts** action) shows private versions only when requested, in TUI or paginated RPC. It rejects confirmation/force bypass flags, reviews an entire group, then separately asks for local or remote authority. It revalidates the reviewed artifact fingerprint, group versions, baseline, configuration, ownership and current observed head before apply; stale/cancelled review performs no transfer. The manager always reports unresolved groups and the observed head separately from Last applied. Repeated unchanged conflicts retain one stable opaque identity rather than creating more evidence or modal reviews.
236
244
 
237
245
  Artifacts are private immutable records below denied state storage with a 192 MiB/record cap; accepted ancestor records use the same bound, and actual content strategies have smaller limits. Unresolved and unknown/corrupted evidence is never automatically removed. Completed cleanup retains the newest 32 **proven-common, unreferenced** artifacts; pinned files remain even if other groups in the same record completed. Total evidence can exceed those bounds across unresolved/unknown records: inspect it manually with Pi closed rather than deleting pending evidence. This is not remote-history erasure.
238
246
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-sync",
3
- "version": "0.53.0",
3
+ "version": "0.54.0",
4
4
  "description": "Pi extension that syncs Pi configuration through Git, WebDAV, Cloudflare R2, or S3-compatible storage.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,13 +40,13 @@
40
40
  "typecheck": "tsc --noEmit"
41
41
  },
42
42
  "dependencies": {
43
- "@narumitw/pi-tui-kit": "^0.65.1",
43
+ "@narumitw/pi-tui-kit": "^0.65.4",
44
44
  "fast-xml-parser": "^5.11.2",
45
45
  "proper-lockfile": "^4.1.2"
46
46
  },
47
47
  "devDependencies": {
48
- "@earendil-works/pi-coding-agent": "1.0.2",
49
- "@earendil-works/pi-tui": "1.0.2",
48
+ "@earendil-works/pi-coding-agent": "1.1.0",
49
+ "@earendil-works/pi-tui": "1.1.0",
50
50
  "@types/proper-lockfile": "^4.1.4",
51
51
  "esbuild": "0.28.2",
52
52
  "typescript": "7.0.2"
@@ -29,6 +29,7 @@ export class S3SyncBackend implements SyncBackend {
29
29
  readonly destination: string;
30
30
  readonly capability = "conditional-required" as const;
31
31
  private readonly checksums = new Map<string, string>();
32
+ private recoveryHead?: { current: string; legacy: string };
32
33
 
33
34
  constructor(
34
35
  private readonly config: ResolvedS3Backend,
@@ -43,7 +44,18 @@ export class S3SyncBackend implements SyncBackend {
43
44
  return left === right;
44
45
  }
45
46
 
47
+ matchesUncommittedRecoveryHead(current: RemoteHead, recorded: RemoteHead) {
48
+ return (
49
+ this.sameRevision(current.revision, recorded.revision) ||
50
+ (current.snapshotId === recorded.snapshotId &&
51
+ current.snapshotRef === recorded.snapshotRef &&
52
+ current.revision === this.recoveryHead?.current &&
53
+ recorded.revision === this.recoveryHead?.legacy)
54
+ );
55
+ }
56
+
46
57
  async readHead(signal?: AbortSignal): Promise<RemoteHead | undefined> {
58
+ this.recoveryHead = undefined;
47
59
  const object = await new S3Client(this.config, signal).getJson<LatestPointer>(latestKey(this.config));
48
60
  throwIfAborted(signal);
49
61
  if (object.missing) return undefined;
@@ -53,7 +65,16 @@ export class S3SyncBackend implements SyncBackend {
53
65
  this.config.destination.namespace,
54
66
  );
55
67
  this.registerChecksum(pointer.snapshot, pointer.sha256);
56
- return remoteHead(pointer, this.identity, object.etag);
68
+ const head = remoteHead(pointer, this.identity, object.etag);
69
+ if (object.etag && /^"[^"\r\n]+"$/u.test(object.etag)) {
70
+ // Old clients hashed R2's compression-weakened ETag. Bind the alias to the
71
+ // exact current pointer and strong ETag, solely for inactive-journal retirement.
72
+ this.recoveryHead = {
73
+ current: head.revision,
74
+ legacy: remoteHead(pointer, this.identity, `W/${object.etag}`).revision,
75
+ };
76
+ }
77
+ return head;
57
78
  }
58
79
 
59
80
  async readSnapshot(reference: string, signal?: AbortSignal): Promise<Snapshot> {
@@ -188,7 +188,9 @@ export class S3Client {
188
188
  method,
189
189
  url,
190
190
  body,
191
- extraHeaders,
191
+ // Transfer compression can weaken an object's ETag (R2 JSON responses).
192
+ // Read the original representation for exact checksums and strong If-Match tokens.
193
+ extraHeaders: method === "GET" ? { ...extraHeaders, "accept-encoding": "identity" } : extraHeaders,
192
194
  accessKeyId: this.config.profile.accessKeyId,
193
195
  secretAccessKey: this.config.profile.secretAccessKey,
194
196
  sessionToken,
@@ -52,6 +52,8 @@ export interface SyncBackend {
52
52
  readonly capability: PublicationCapability;
53
53
  /** Compare opaque revisions produced by this backend identity. */
54
54
  sameRevision(left: string, right: string): boolean;
55
+ /** Recovery-only equivalence for retiring an inactive candidate; never authorizes publication or apply. */
56
+ matchesUncommittedRecoveryHead?(current: RemoteHead, recorded: RemoteHead): boolean;
55
57
  readHead(signal?: AbortSignal): Promise<RemoteHead | undefined>;
56
58
  readSnapshot(reference: string, signal?: AbortSignal): Promise<Snapshot>;
57
59
  publishSnapshot(
@@ -12,6 +12,7 @@ import type { SyncLoaders } from "../sync/sync-loaders.js";
12
12
  import { RemoteSelectionMismatchError } from "../sync/sync-policy.js";
13
13
  import { automaticSyncSummary } from "../ui/automatic-sync-summary.js";
14
14
  import type { RunRouteResult } from "../ui/cancellable-operation.js";
15
+ import { trackOperationDialogs } from "../ui/operation-dialogs.js";
15
16
  import { setSyncStatus } from "../ui/sync-status.js";
16
17
  import { safeTerminalText } from "../ui/terminal-text.js";
17
18
  import { parseOptions, resolveSyncCommand, splitArgs, usage, validateCommandOptions } from "./command.js";
@@ -70,7 +71,9 @@ export async function executeCommand(
70
71
  loaders: SyncLoaders,
71
72
  onCommit?: () => void,
72
73
  setup?: string,
74
+ onDialog?: (active: boolean) => void,
73
75
  ): Promise<RunRouteResult> {
76
+ ctx = trackOperationDialogs(ctx, onDialog);
74
77
  try {
75
78
  const command = await resolveSyncCommand(rawArgs, ctx);
76
79
  if (signal?.aborted || !command) return { kind: "completed" };
@@ -25,7 +25,13 @@ export async function handleCommand(
25
25
  ) {
26
26
  await reconcileObservation(attention, sessionSignal);
27
27
  if (sessionSignal.aborted) return;
28
- const run = (route: string, signal?: AbortSignal, onCommit?: () => void, target?: string) =>
28
+ const run = (
29
+ route: string,
30
+ signal?: AbortSignal,
31
+ onCommit?: () => void,
32
+ target?: string,
33
+ onDialog?: (active: boolean) => void,
34
+ ) =>
29
35
  executeCommand(
30
36
  route,
31
37
  ctx,
@@ -33,6 +39,7 @@ export async function handleCommand(
33
39
  loaders,
34
40
  observationCommitCallback(attention, sessionSignal, onCommit),
35
41
  target,
42
+ onDialog,
36
43
  );
37
44
  if (!rawArgs.trim()) {
38
45
  try {
@@ -0,0 +1,72 @@
1
+ import type { ResolvedSyncBackend } from "../backends/backend-types.js";
2
+ import { safeTerminalText } from "../ui/terminal-text.js";
3
+
4
+ const MAX_CAUSES = 4;
5
+ const MAX_DETAIL_LENGTH = 600;
6
+
7
+ /** Only the sanitized message is displayed; retain the original cause for outcome handling. */
8
+ export function mergeTransferError(error: unknown, backend: ResolvedSyncBackend): Error {
9
+ const messages: string[] = [];
10
+ const seen = new Set<Error>();
11
+ let current = error;
12
+ while (current instanceof Error && !seen.has(current) && seen.size < MAX_CAUSES) {
13
+ seen.add(current);
14
+ const code = (current as NodeJS.ErrnoException).code;
15
+ const status = (current as Error & { status?: unknown }).status;
16
+ const metadata = [
17
+ typeof code === "string" && /^[A-Z][A-Z0-9_]{1,63}$/u.test(code) ? code : undefined,
18
+ typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599
19
+ ? `HTTP ${status}`
20
+ : undefined,
21
+ ].filter(Boolean);
22
+ const message = `${current.message}${metadata.length ? ` (${metadata.join(", ")})` : ""}`;
23
+ if (message && !messages.includes(message)) messages.push(message);
24
+ current = current.cause;
25
+ }
26
+ let detail = messages.join(" → ") || "No error details available.";
27
+ // Redact before truncation or terminal escaping, including overlapping and encoded credentials.
28
+ for (const secret of credentialVariants(backend)) detail = detail.replaceAll(secret, "[REDACTED]");
29
+ detail = detail.replace(/\b(Bearer|Basic)\s+[^\s<>"']+/giu, "$1 [REDACTED]");
30
+ detail = safeTerminalText(detail);
31
+ if (detail.length > MAX_DETAIL_LENGTH) detail = `${detail.slice(0, MAX_DETAIL_LENGTH)}… (details truncated)`;
32
+ return new Error(
33
+ `Merged transfer interrupted; journal and backup retained. Run /sync sync to reconcile before further mutations. Transfer failure: ${detail}`,
34
+ { cause: error },
35
+ );
36
+ }
37
+
38
+ function credentialVariants(backend: ResolvedSyncBackend): string[] {
39
+ const secrets: string[] = [];
40
+ let address: string;
41
+ switch (backend.type) {
42
+ case "s3":
43
+ secrets.push(backend.profile.accessKeyId, backend.profile.secretAccessKey, backend.profile.sessionToken ?? "");
44
+ address = backend.profile.endpoint;
45
+ break;
46
+ case "webdav":
47
+ secrets.push(
48
+ backend.profile.username,
49
+ backend.profile.password,
50
+ Buffer.from(`${backend.profile.username}:${backend.profile.password}`).toString("base64"),
51
+ );
52
+ address = backend.profile.url;
53
+ break;
54
+ case "git":
55
+ address = backend.profile.remote;
56
+ break;
57
+ }
58
+ try {
59
+ const url = new URL(address);
60
+ secrets.push(url.username, url.password, ...url.searchParams.values());
61
+ for (const value of [url.username, url.password]) secrets.push(decodeURIComponent(value));
62
+ } catch {
63
+ // Git SSH shorthand is not a URL; configured Git remotes cannot contain credentials.
64
+ }
65
+ return [
66
+ ...new Set(
67
+ secrets
68
+ .filter(Boolean)
69
+ .flatMap((value) => [value, encodeURIComponent(value), JSON.stringify(value).slice(1, -1)]),
70
+ ),
71
+ ].sort((left, right) => right.length - left.length);
72
+ }
@@ -52,6 +52,7 @@ import {
52
52
  readMergeJournal,
53
53
  writeMergeJournal,
54
54
  } from "./merge-journal.js";
55
+ import { mergeTransferError } from "./merge-transfer-error.js";
55
56
  import { acceptedMergeFiles, type PartialProgress, progressState } from "./partial-progress.js";
56
57
  import {
57
58
  formatRemoteSelectionStatus,
@@ -497,10 +498,7 @@ export async function mergeSync(
497
498
  }
498
499
  // Cancellation before publication is safe only if no backend attempt began.
499
500
  // Every uncertain/after-commit outcome retains evidence and is never blindly retried.
500
- throw new Error(
501
- "Merged transfer interrupted; journal and backup retained. Run /sync sync to reconcile before further mutations.",
502
- { cause: error },
503
- );
501
+ throw mergeTransferError(error, config.backend);
504
502
  }
505
503
  const completed = await completeJournal(ctx, config, backend, journal, validate, options.signal, options.auto);
506
504
  if (!completed) {
@@ -557,7 +555,12 @@ async function completeJournal(
557
555
  }
558
556
  const snapshotOptions = { ...snapshotOptionsForContext(ctx, config), sessionDir: sessionRoot, signal };
559
557
  const head = await backend.readHead(signal);
560
- if (!journal.committedHead && head && backend.sameRevision(head.revision, journal.expectedHead.revision)) {
558
+ if (
559
+ !journal.committedHead &&
560
+ head &&
561
+ (backend.sameRevision(head.revision, journal.expectedHead.revision) ||
562
+ backend.matchesUncommittedRecoveryHead?.(head, journal.expectedHead))
563
+ ) {
561
564
  await validate();
562
565
  const local = await createSnapshot(config.snapshotIdentity, snapshotOptions);
563
566
  if (