@narumitw/pi-sync 0.57.12 → 0.58.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.
- package/CHANGELOG.md +6 -0
- package/README.md +2 -0
- package/dist/chunks/{agent-sync-P6AWQXGK.ts → agent-sync-2R4GG2MF.ts} +3 -3
- package/dist/chunks/{chunk-ZDDBZH72.ts → chunk-JYILGR3K.ts} +2 -2
- package/dist/chunks/{chunk-OKPPZA4D.ts → chunk-MG66XM5U.ts} +3 -3
- package/dist/chunks/{chunk-OKPPZA4D.ts.map → chunk-MG66XM5U.ts.map} +2 -2
- package/dist/chunks/{chunk-Q23JJW7E.ts → chunk-MW6KUQYA.ts} +7 -5
- package/dist/chunks/chunk-MW6KUQYA.ts.map +7 -0
- package/dist/chunks/{chunk-ZC3IHFNM.ts → chunk-WQMYV4DW.ts} +3 -3
- package/dist/chunks/chunk-WQMYV4DW.ts.map +7 -0
- package/dist/chunks/chunk-YIPK23JW.ts +171 -0
- package/dist/chunks/chunk-YIPK23JW.ts.map +7 -0
- package/dist/chunks/{command-handler-VKLZBRLO.ts → command-handler-KCB6MCWS.ts} +6 -6
- package/dist/chunks/{continuous-sync-TTOSJCQU.ts → continuous-sync-FAFNLEUW.ts} +18 -10
- package/dist/chunks/continuous-sync-FAFNLEUW.ts.map +7 -0
- package/dist/chunks/{manager-result-dispatcher-6W52NM7F.ts → manager-result-dispatcher-XCYPW7RC.ts} +4 -4
- package/dist/chunks/{manager-ui-INVMAFEN.ts → manager-ui-FMZXKPX2.ts} +8 -8
- package/dist/chunks/{remote-selection-ui-TD4C4W3J.ts → remote-selection-ui-MYM2YCPF.ts} +3 -3
- package/dist/chunks/{sync-operations-TMGQHBF5.ts → sync-operations-45APGOL3.ts} +6 -6
- package/dist/chunks/sync-operations-45APGOL3.ts.map +7 -0
- package/dist/index.ts +78 -63
- package/dist/index.ts.map +2 -2
- package/docs/tui-contract.md +37 -0
- package/package.json +1 -1
- package/skills/configure-pi-sync/references/configuration.md +13 -1
- package/src/sync/agent-sync.ts +1 -1
- package/src/sync/continuous-sync.ts +15 -6
- package/src/sync/startup-check.ts +1 -1
- package/src/sync/sync-mutations.ts +2 -2
- package/src/sync/sync-queries.ts +2 -2
- package/src/sync-extension.ts +71 -57
- package/src/ui/remote-selection-ui.ts +1 -1
- package/src/ui/sync-attention.ts +5 -3
- package/src/ui/sync-status.ts +214 -3
- package/dist/chunks/chunk-K3RIEL6J.ts +0 -20
- package/dist/chunks/chunk-K3RIEL6J.ts.map +0 -7
- package/dist/chunks/chunk-Q23JJW7E.ts.map +0 -7
- package/dist/chunks/chunk-ZC3IHFNM.ts.map +0 -7
- package/dist/chunks/continuous-sync-TTOSJCQU.ts.map +0 -7
- package/dist/chunks/sync-operations-TMGQHBF5.ts.map +0 -7
- /package/dist/chunks/{agent-sync-P6AWQXGK.ts.map → agent-sync-2R4GG2MF.ts.map} +0 -0
- /package/dist/chunks/{chunk-ZDDBZH72.ts.map → chunk-JYILGR3K.ts.map} +0 -0
- /package/dist/chunks/{command-handler-VKLZBRLO.ts.map → command-handler-KCB6MCWS.ts.map} +0 -0
- /package/dist/chunks/{manager-result-dispatcher-6W52NM7F.ts.map → manager-result-dispatcher-XCYPW7RC.ts.map} +0 -0
- /package/dist/chunks/{manager-ui-INVMAFEN.ts.map → manager-ui-FMZXKPX2.ts.map} +0 -0
- /package/dist/chunks/{remote-selection-ui-TD4C4W3J.ts.map → remote-selection-ui-MYM2YCPF.ts.map} +0 -0
package/dist/index.ts.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/settings/config-errors.ts", "../src/sync/automatic-sync.ts", "../src/sync/startup-check.ts", "../src/sync/sync-loaders.ts", "../src/ui/startup-timings-entry.ts", "../src/sync-extension.ts"],
|
|
4
|
-
"sourcesContent": ["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 type { AnySyncConfig } from \"../settings/settings-types.js\";\nimport { sessionDirFromContext, snapshotOptionsForContext } from \"../snapshot/session-paths.js\";\nimport { pendingTransactionEntries } from \"../snapshot/snapshot-transaction-directory.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 { captureMutationOwner } from \"./mutation-owner.js\";\nimport { throwIfAborted } from \"./signals.js\";\nimport { createStartupTimings, type StartupTimingPublisher } from \"./startup-timings.js\";\nimport { errorMessage } from \"./sync-errors.js\";\nimport type { SyncLoaders } from \"./sync-loaders.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(\n ctx: ExtensionContext,\n signal: AbortSignal,\n publish?: StartupTimingPublisher,\n): Promise<{ debug: boolean; config?: AnySyncConfig } | undefined> {\n const timings = createStartupTimings(publish);\n let debug = false;\n let outcome: \"completed\" | \"stopped\" = \"stopped\";\n try {\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 timings.measure(\"transaction recovery\", async () => {\n const pending = await pendingTransactionEntries();\n validateMutation();\n if (!pending.some((entry) => entry.isDirectory())) return;\n // Normal startup needs only the directory probe, not the recovery/snapshot graph.\n const { recoverSnapshotTransactionsOnStartup } = await import(\"../snapshot/snapshot-transaction.js\");\n validateMutation();\n await recoverSnapshotTransactionsOnStartup({\n signal,\n sessionDir: sessionDirFromContext(ctx),\n protectedTargets: currentFile ? [currentFile] : [],\n validateMutation,\n });\n });\n throwIfAborted(signal);\n try {\n const settings = await timings.measure(\"settings read\", () => readLocalConfigObject());\n throwIfAborted(signal);\n debug = settings?.debug ?? false;\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 { debug };\n try {\n const config = await timings.measure(\"config resolution\", () => loadConfig());\n throwIfAborted(signal);\n configureSyncStatus(ctx, config.showStatus);\n outcome = \"completed\";\n return { debug, config };\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 return { debug };\n } finally {\n timings.publish(ctx, debug, signal, \"local initialization\", outcome);\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 { startupTransferEnabled } from \"./agent-policy.js\";\nimport { combineSignals, throwIfAborted } from \"./signals.js\";\nimport { createStartupTimings, type StartupTimingPublisher } from \"./startup-timings.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(\n loaders: SyncLoaders,\n attention: SyncAttentionController,\n timeoutMs = 30_000,\n publish?: StartupTimingPublisher,\n) {\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 && startupTransferEnabled(initialConfig) && !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 timings = createStartupTimings(publish);\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 && startupTransferEnabled(initialConfig)),\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 timer.unref();\n let config: AnySyncConfig | undefined;\n let checking = false;\n let stopped = 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 = startupTransferEnabled(config);\n throwIfAborted(signal);\n if (!config.automatic && !startupTransferEnabled(config)) return;\n const captured = config;\n const identity = syncCheckConfigFingerprint(captured);\n checking = true;\n setSyncStatus(ctx, \"sync ...\");\n const { inspectSync } = await timings.measure(\"inspection import\", () => 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 timings.measure(\"inspection and guards\", () =>\n withStateDirectoryAccess(() => {\n throwIfAborted(signal);\n return withLock(\"startup-check\", () => {\n throwIfAborted(signal);\n return inspectSync(captured, snapshotOptionsForContext(ctx, captured), signal, undefined, timings);\n });\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: startupTransferEnabled(captured),\n });\n if (\n startupTransferEnabled(captured) &&\n !inspection.firstSync &&\n inspection.head &&\n inspection.selectionState?.kind === \"same\"\n ) {\n const [operations, { withAgentSyncLock }] = await timings.measure(\"operations import\", () =>\n Promise.all([loaders.operations(), import(\"./agent-sync.js\")]),\n );\n throwIfAborted(signal);\n if (!isCurrent()) return;\n if (!ctx.isIdle()) {\n pending = { ctx, signal: sessionSignal, config: captured };\n return;\n }\n await timings.measure(\"automatic transfer\", () =>\n withStateDirectoryAccess(() =>\n withAgentSyncLock(\n ctx,\n {\n args: [],\n yes: true,\n force: false,\n stale: false,\n silent: true,\n reload: false,\n auto: true,\n signal,\n startupTimings: timings,\n onCommit: () => attention.clearObservation(),\n },\n (options) => operations.syncBoth(ctx, options),\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: startupTransferEnabled(captured),\n });\n if (ctx.mode === \"rpc\") attention.notifyObservation(ctx);\n } catch (error) {\n stopped = true;\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 && startupTransferEnabled(config)\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 (isCurrent()) {\n timings.publish(\n ctx,\n config?.debug ?? false,\n sessionSignal,\n \"background startup\",\n signal.aborted || stopped ? \"stopped\" : \"completed\",\n );\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\");\ntype CommandHandlerModule = Pick<typeof import(\"../commands/command-handler.js\"), \"handleCommand\">;\n\ntype SyncInspectionModule = Pick<typeof import(\"./sync-inspection.js\"), \"inspectSync\">;\n\nexport interface SyncDependencies {\n loadCommandHandler(): Promise<CommandHandlerModule>;\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 commands(): Promise<CommandHandlerModule>;\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 commands: cachedModuleLoader(dependencies.loadCommandHandler ?? (() => import(\"../commands/command-handler.js\"))),\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 { Text, truncateToWidth } from \"@earendil-works/pi-tui\";\nimport type { StartupTimingPublisher } from \"../sync/startup-timings.js\";\nimport { safeTerminalText } from \"./terminal-text.js\";\n\nexport const STARTUP_TIMING_ENTRY = \"pi-sync-startup-timings\";\n\nexport interface StartupTimingEntry {\n version: 1;\n report: string;\n}\n\nexport function registerStartupTimings(pi: ExtensionAPI): StartupTimingPublisher {\n pi.registerEntryRenderer<StartupTimingEntry>(STARTUP_TIMING_ENTRY, (entry, _options, theme) => {\n const data = entry.data;\n if (data?.version !== 1 || typeof data.report !== \"string\") return undefined;\n const text = new Text(\n data.report\n .split(\"\\n\")\n .map((line, index) => theme.fg(index === 0 ? \"accent\" : \"muted\", safeTerminalText(line)))\n .join(\"\\n\"),\n 0,\n 0,\n );\n return {\n render: (width) => (width > 0 ? text.render(width).map((line) => truncateToWidth(line, width)) : []),\n invalidate: () => text.invalidate(),\n };\n });\n return (ctx, report) => {\n // Pi coalesces consecutive info notifications, including the final /reload notice.\n // Custom entries remain visible without adding messages to model context.\n if (ctx.mode === \"tui\") pi.appendEntry<StartupTimingEntry>(STARTUP_TIMING_ENTRY, { version: 1, report });\n else ctx.ui.notify(report, \"info\");\n };\n}\n", "import type { ExtensionAPI, ExtensionContext } from \"@earendil-works/pi-coding-agent\";\nimport { completeSyncArguments, splitArgs } from \"./commands/command.js\";\nimport type { AnySyncConfig } from \"./settings/settings-types.js\";\nimport { withStateDirectoryAccess } from \"./state/state-directory.js\";\nimport { startupTransferEnabled } from \"./sync/agent-policy.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 { registerStartupTimings } from \"./ui/startup-timings-entry.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 publishTimings = registerStartupTimings(pi);\n const check = createStartupCheck(loaders, attention, 30_000, publishTimings);\n let sessionAbort = new AbortController();\n let shutdownAbort: AbortController | undefined;\n let initialization = Promise.resolve<AnySyncConfig | false | undefined>(undefined);\n let continuous: ReturnType<typeof import(\"./sync/continuous-sync.js\")[\"createContinuousSync\"]> | undefined;\n let owner: ExtensionContext[\"sessionManager\"] | undefined;\n let continuousPossible = false;\n let startupDebug = false;\n const foreground = new Map<ExtensionContext[\"sessionManager\"], number>();\n async function resumeContinuous(ctx: ExtensionContext, signal: AbortSignal, retry?: { setup?: string }) {\n const isCurrent = () => !signal.aborted && !foreground.has(ctx.sessionManager) && owner === ctx.sessionManager;\n if (!isCurrent() || !ctx.hasUI) return;\n try {\n const { loadConfigForCheck } = await import(\"./settings/config.js\");\n if (!isCurrent()) return;\n const config = await loadConfigForCheck();\n if (!isCurrent()) return;\n continuousPossible = Boolean(config.continuous && config.agentMode);\n if (!continuousPossible) {\n await continuous?.stop(ctx.sessionManager);\n return;\n }\n const { createContinuousSync } = await import(\"./sync/continuous-sync.js\");\n if (!isCurrent()) return;\n continuous ??= createContinuousSync(loaders, undefined, publishTimings);\n continuous.start(\n ctx,\n signal,\n Boolean(retry && (retry.setup === undefined || retry.setup === config.setupName)),\n startupDebug,\n );\n } catch {\n /* Existing initialization/command paths report settings errors. */\n }\n }\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 manager = ctx.sessionManager;\n const command = splitArgs(args)[0];\n let retry: { setup?: string } | undefined;\n let mayResume = false;\n foreground.set(manager, (foreground.get(manager) ?? 0) + 1);\n try {\n const ready = await initialization;\n mayResume = ready !== false;\n if (signal.aborted) return;\n await continuous?.stop(manager);\n if (signal.aborted) return;\n await check.stop();\n if (signal.aborted) return;\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 commands = await loaders.commands().catch((error) => {\n if (!signal.aborted) {\n ctx.ui.notify(`pi-sync command unavailable: ${safeTerminalText(errorMessage(error))}`, \"error\");\n }\n });\n if (signal.aborted || !commands) return;\n const run = () =>\n commands.handleCommand(args, ctx, signal, loaders, attention, (setup) => {\n retry = { setup };\n });\n // Drain background children before foreground guards. Help remains reachable during repair.\n if (command === \"migrate-state\" || command === \"help\") await run();\n else await withStateDirectoryAccess(run);\n } finally {\n const count = (foreground.get(manager) ?? 1) - 1;\n if (count) foreground.set(manager, count);\n else foreground.delete(manager);\n if (mayResume) await resumeContinuous(ctx, signal, retry);\n }\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 owner = ctx.sessionManager;\n continuousPossible = false;\n startupDebug = false;\n attention.reset(ctx);\n const previous = initialization;\n initialization = (async () => {\n await previous;\n if (signal.aborted) return false;\n await continuous?.stop();\n if (signal.aborted) return false;\n await check.stop();\n if (signal.aborted) return false;\n try {\n const startup = await withStateDirectoryAccess(() => startSession(ctx, signal, publishTimings));\n if (signal.aborted) return false;\n startupDebug = startup?.debug ?? false;\n return startup?.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) {\n if (ready.continuous && ready.agentMode) await resumeContinuous(ctx, signal);\n else if (ready.automatic || startupTransferEnabled(ready)) check.start(ctx, signal, ready);\n }\n });\n\n pi.on(\"agent_start\", async (_event, ctx) => {\n await continuous?.stop(ctx.sessionManager);\n await check.interrupt(ctx);\n });\n\n pi.on(\"agent_settled\", async (_event, ctx) => {\n const signal = sessionAbort.signal;\n if (!signal.aborted) {\n check.settled(ctx);\n if (continuousPossible) await resumeContinuous(ctx, signal);\n }\n });\n\n pi.on(\"session_shutdown\", async (event, ctx) => {\n if (continuous && owner && owner !== ctx.sessionManager) return;\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 continuous?.stop(ctx.sessionManager);\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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",
|
|
4
|
+
"sourcesContent": ["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 type { AnySyncConfig } from \"../settings/settings-types.js\";\nimport { sessionDirFromContext, snapshotOptionsForContext } from \"../snapshot/session-paths.js\";\nimport { pendingTransactionEntries } from \"../snapshot/snapshot-transaction-directory.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 { captureMutationOwner } from \"./mutation-owner.js\";\nimport { throwIfAborted } from \"./signals.js\";\nimport { createStartupTimings, type StartupTimingPublisher } from \"./startup-timings.js\";\nimport { errorMessage } from \"./sync-errors.js\";\nimport type { SyncLoaders } from \"./sync-loaders.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(\n ctx: ExtensionContext,\n signal: AbortSignal,\n publish?: StartupTimingPublisher,\n): Promise<{ debug: boolean; config?: AnySyncConfig } | undefined> {\n const timings = createStartupTimings(publish);\n let debug = false;\n let outcome: \"completed\" | \"stopped\" = \"stopped\";\n try {\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 timings.measure(\"transaction recovery\", async () => {\n const pending = await pendingTransactionEntries();\n validateMutation();\n if (!pending.some((entry) => entry.isDirectory())) return;\n // Normal startup needs only the directory probe, not the recovery/snapshot graph.\n const { recoverSnapshotTransactionsOnStartup } = await import(\"../snapshot/snapshot-transaction.js\");\n validateMutation();\n await recoverSnapshotTransactionsOnStartup({\n signal,\n sessionDir: sessionDirFromContext(ctx),\n protectedTargets: currentFile ? [currentFile] : [],\n validateMutation,\n });\n });\n throwIfAborted(signal);\n try {\n const settings = await timings.measure(\"settings read\", () => readLocalConfigObject());\n throwIfAborted(signal);\n debug = settings?.debug ?? false;\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 { debug };\n try {\n const config = await timings.measure(\"config resolution\", () => loadConfig());\n throwIfAborted(signal);\n configureSyncStatus(ctx, config.showStatus);\n outcome = \"completed\";\n return { debug, config };\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 return { debug };\n } finally {\n timings.publish(ctx, debug, signal, \"local initialization\", outcome);\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 { startupTransferEnabled } from \"./agent-policy.js\";\nimport { combineSignals, throwIfAborted } from \"./signals.js\";\nimport { createStartupTimings, type StartupTimingPublisher } from \"./startup-timings.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(\n loaders: SyncLoaders,\n attention: SyncAttentionController,\n timeoutMs = 30_000,\n publish?: StartupTimingPublisher,\n) {\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 && startupTransferEnabled(initialConfig) && !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 timings = createStartupTimings(publish);\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 && startupTransferEnabled(initialConfig)),\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 timer.unref();\n let config: AnySyncConfig | undefined;\n let checking = false;\n let stopped = 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 = startupTransferEnabled(config);\n throwIfAborted(signal);\n if (!config.automatic && !startupTransferEnabled(config)) return;\n const captured = config;\n const identity = syncCheckConfigFingerprint(captured);\n checking = true;\n setSyncStatus(ctx, \"sync ...\", { activity: \"checking\" });\n const { inspectSync } = await timings.measure(\"inspection import\", () => 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 timings.measure(\"inspection and guards\", () =>\n withStateDirectoryAccess(() => {\n throwIfAborted(signal);\n return withLock(\"startup-check\", () => {\n throwIfAborted(signal);\n return inspectSync(captured, snapshotOptionsForContext(ctx, captured), signal, undefined, timings);\n });\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: startupTransferEnabled(captured),\n });\n if (\n startupTransferEnabled(captured) &&\n !inspection.firstSync &&\n inspection.head &&\n inspection.selectionState?.kind === \"same\"\n ) {\n const [operations, { withAgentSyncLock }] = await timings.measure(\"operations import\", () =>\n Promise.all([loaders.operations(), import(\"./agent-sync.js\")]),\n );\n throwIfAborted(signal);\n if (!isCurrent()) return;\n if (!ctx.isIdle()) {\n pending = { ctx, signal: sessionSignal, config: captured };\n return;\n }\n await timings.measure(\"automatic transfer\", () =>\n withStateDirectoryAccess(() =>\n withAgentSyncLock(\n ctx,\n {\n args: [],\n yes: true,\n force: false,\n stale: false,\n silent: true,\n reload: false,\n auto: true,\n signal,\n startupTimings: timings,\n onCommit: () => attention.clearObservation(),\n },\n (options) => operations.syncBoth(ctx, options),\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: startupTransferEnabled(captured),\n });\n if (ctx.mode === \"rpc\") attention.notifyObservation(ctx);\n } catch (error) {\n stopped = true;\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 && startupTransferEnabled(config)\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 (isCurrent()) {\n timings.publish(\n ctx,\n config?.debug ?? false,\n sessionSignal,\n \"background startup\",\n signal.aborted || stopped ? \"stopped\" : \"completed\",\n );\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\");\ntype CommandHandlerModule = Pick<typeof import(\"../commands/command-handler.js\"), \"handleCommand\">;\n\ntype SyncInspectionModule = Pick<typeof import(\"./sync-inspection.js\"), \"inspectSync\">;\n\nexport interface SyncDependencies {\n loadCommandHandler(): Promise<CommandHandlerModule>;\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 commands(): Promise<CommandHandlerModule>;\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 commands: cachedModuleLoader(dependencies.loadCommandHandler ?? (() => import(\"../commands/command-handler.js\"))),\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 { Text, truncateToWidth } from \"@earendil-works/pi-tui\";\nimport type { StartupTimingPublisher } from \"../sync/startup-timings.js\";\nimport { safeTerminalText } from \"./terminal-text.js\";\n\nexport const STARTUP_TIMING_ENTRY = \"pi-sync-startup-timings\";\n\nexport interface StartupTimingEntry {\n version: 1;\n report: string;\n}\n\nexport function registerStartupTimings(pi: ExtensionAPI): StartupTimingPublisher {\n pi.registerEntryRenderer<StartupTimingEntry>(STARTUP_TIMING_ENTRY, (entry, _options, theme) => {\n const data = entry.data;\n if (data?.version !== 1 || typeof data.report !== \"string\") return undefined;\n const text = new Text(\n data.report\n .split(\"\\n\")\n .map((line, index) => theme.fg(index === 0 ? \"accent\" : \"muted\", safeTerminalText(line)))\n .join(\"\\n\"),\n 0,\n 0,\n );\n return {\n render: (width) => (width > 0 ? text.render(width).map((line) => truncateToWidth(line, width)) : []),\n invalidate: () => text.invalidate(),\n };\n });\n return (ctx, report) => {\n // Pi coalesces consecutive info notifications, including the final /reload notice.\n // Custom entries remain visible without adding messages to model context.\n if (ctx.mode === \"tui\") pi.appendEntry<StartupTimingEntry>(STARTUP_TIMING_ENTRY, { version: 1, report });\n else ctx.ui.notify(report, \"info\");\n };\n}\n", "import type { ExtensionAPI, ExtensionContext } from \"@earendil-works/pi-coding-agent\";\nimport { completeSyncArguments, splitArgs } from \"./commands/command.js\";\nimport type { AnySyncConfig } from \"./settings/settings-types.js\";\nimport { withStateDirectoryAccess } from \"./state/state-directory.js\";\nimport { startupTransferEnabled } from \"./sync/agent-policy.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 { registerStartupTimings } from \"./ui/startup-timings-entry.js\";\nimport { createSyncAttentionController } from \"./ui/sync-attention.js\";\nimport { createSyncStatusPublisher, 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 status = createSyncStatusPublisher(pi.events);\n const publishTimings = registerStartupTimings(pi);\n const check = createStartupCheck(loaders, attention, 30_000, publishTimings);\n let sessionAbort = new AbortController();\n let shutdownAbort: AbortController | undefined;\n let initialization = Promise.resolve<AnySyncConfig | false | undefined>(undefined);\n let continuous: ReturnType<typeof import(\"./sync/continuous-sync.js\")[\"createContinuousSync\"]> | undefined;\n let owner: ExtensionContext[\"sessionManager\"] | undefined;\n let continuousPossible = false;\n let startupDebug = false;\n const foreground = new Map<ExtensionContext[\"sessionManager\"], number>();\n async function resumeContinuous(ctx: ExtensionContext, signal: AbortSignal, retry?: { setup?: string }) {\n const isCurrent = () => !signal.aborted && !foreground.has(ctx.sessionManager) && owner === ctx.sessionManager;\n if (!isCurrent() || !ctx.hasUI) return;\n try {\n const { loadConfigForCheck } = await import(\"./settings/config.js\");\n if (!isCurrent()) return;\n const config = await loadConfigForCheck();\n if (!isCurrent()) return;\n continuousPossible = Boolean(config.continuous && config.agentMode);\n if (!continuousPossible) {\n await continuous?.stop(ctx.sessionManager);\n return;\n }\n const { createContinuousSync } = await import(\"./sync/continuous-sync.js\");\n if (!isCurrent()) return;\n continuous ??= createContinuousSync(loaders, undefined, publishTimings);\n continuous.start(\n ctx,\n signal,\n Boolean(retry && (retry.setup === undefined || retry.setup === config.setupName)),\n startupDebug,\n );\n } catch {\n /* Existing initialization/command paths report settings errors. */\n }\n }\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 status.bind(ctx);\n const signal = sessionAbort.signal;\n const manager = ctx.sessionManager;\n const command = splitArgs(args)[0];\n let retry: { setup?: string } | undefined;\n let mayResume = false;\n foreground.set(manager, (foreground.get(manager) ?? 0) + 1);\n try {\n const ready = await initialization;\n mayResume = ready !== false;\n if (signal.aborted) return;\n await continuous?.stop(manager);\n if (signal.aborted) return;\n await check.stop();\n if (signal.aborted) return;\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 commands = await loaders.commands().catch((error) => {\n if (!signal.aborted) {\n ctx.ui.notify(`pi-sync command unavailable: ${safeTerminalText(errorMessage(error))}`, \"error\");\n }\n });\n if (signal.aborted || !commands) return;\n const run = () =>\n commands.handleCommand(args, ctx, signal, loaders, attention, (setup) => {\n retry = { setup };\n });\n // Drain background children before foreground guards. Help remains reachable during repair.\n if (command === \"migrate-state\" || command === \"help\") await run();\n else await withStateDirectoryAccess(run);\n } finally {\n const count = (foreground.get(manager) ?? 1) - 1;\n if (count) foreground.set(manager, count);\n else foreground.delete(manager);\n if (mayResume) await resumeContinuous(ctx, signal, retry);\n }\n },\n });\n\n pi.on(\"session_start\", async (_event, ctx) => {\n const releaseStatus = status.start(ctx);\n try {\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 owner = ctx.sessionManager;\n continuousPossible = false;\n startupDebug = false;\n attention.reset(ctx);\n const previous = initialization;\n initialization = (async () => {\n await previous;\n if (signal.aborted) return false;\n await continuous?.stop();\n if (signal.aborted) return false;\n await check.stop();\n if (signal.aborted) return false;\n try {\n const startup = await withStateDirectoryAccess(() => startSession(ctx, signal, publishTimings));\n if (signal.aborted) return false;\n startupDebug = startup?.debug ?? false;\n return startup?.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 === false) releaseStatus();\n if (ready && !signal.aborted) {\n if (ready.continuous && ready.agentMode) await resumeContinuous(ctx, signal);\n else if (ready.automatic || startupTransferEnabled(ready)) check.start(ctx, signal, ready);\n }\n } catch (error) {\n releaseStatus();\n throw error;\n }\n });\n\n pi.on(\"agent_start\", async (_event, ctx) => {\n await continuous?.stop(ctx.sessionManager);\n await check.interrupt(ctx);\n });\n\n pi.on(\"agent_settled\", async (_event, ctx) => {\n const signal = sessionAbort.signal;\n if (!signal.aborted) {\n check.settled(ctx);\n if (continuousPossible) await resumeContinuous(ctx, signal);\n }\n });\n\n pi.on(\"session_shutdown\", async (event, ctx) => {\n const releaseStatus = status.cleanupFor(ctx);\n try {\n if (continuous && owner && owner !== ctx.sessionManager) return;\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 continuous?.stop(ctx.sessionManager);\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 } finally {\n releaseStatus();\n }\n });\n}\n\nexport type { SyncDependencies } from \"./sync/sync-loaders.js\";\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["loadConfigForCheck"]
|
|
7
7
|
}
|
package/docs/tui-contract.md
CHANGED
|
@@ -62,6 +62,43 @@ TUI Pull and Push reviews reuse the existing scrollable safe-default confirmatio
|
|
|
62
62
|
|
|
63
63
|
This change does not touch model prompts, tool exposure, shared-terminal handoff, settings formats, credentials, or default sync policies. Existing startup/session signals and Kit/custom-interaction disposal own new UI calls. Any awaited review is followed by owner validation, and each mutating operation revalidates its mutable inputs before its existing commit boundary. Status and differences actions use existing routes; manager opening/refresh remains observational and does not contact the remote. Diagnostics discloses its WebDAV write/repair behavior before execution.
|
|
64
64
|
|
|
65
|
+
## Quiet footer presentation
|
|
66
|
+
|
|
67
|
+
Sync publishes optional semantic metadata alongside its unchanged raw `sync` status. A compatible footer hides normal standby, including another local monitoring owner, without stopping monitoring or changing election. Pi Statusline owns glyph selection, reason formatting, and visual suppression through the [extension-neutral v1 contract](https://github.com/narumiruna/pi-extensions/blob/main/packages/pi-statusline/docs/extension-status-protocol.md). Native footers, RPC observers, and older or absent consumers retain the original readable raw value.
|
|
68
|
+
|
|
69
|
+
The producer inventory is:
|
|
70
|
+
|
|
71
|
+
| Source | Existing raw status / branch | Semantic activity |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
| `continuous-sync.ts` | Waiting for changes; another local owner | `idle` |
|
|
74
|
+
| `continuous-sync.ts` | Checking; syncing | `checking`; `working` |
|
|
75
|
+
| `continuous-sync.ts` | Scheduled retry; blocked; private-state/settings repair | `retry`; `blocked`, with a safe short reason |
|
|
76
|
+
| `startup-check.ts` | `sync ...` | `checking` |
|
|
77
|
+
| `sync-queries.ts`; `remote-selection-ui.ts` | Setup and selected-content inspection | `checking` |
|
|
78
|
+
| `sync-mutations.ts` | Push; pull | `pushing`; `pulling` |
|
|
79
|
+
| `agent-sync.ts` | Model resolution | `resolving` |
|
|
80
|
+
| `sync-attention.ts` | Local change; remote change; review | `local-change`; `remote-change`; `review` |
|
|
81
|
+
| Status helper, command cleanup, automatic sync, and lifecycle cleanup | Clear; unannotated raw status; Show sync status Off | Withdraw optional metadata; raw behavior stays compatible |
|
|
82
|
+
|
|
83
|
+
Failure classification stays in Sync. `transient`, `contention`, `stale`, `authentication`, `storage`, `unsafe`, `model`, `budget`, and `unknown` produce `temp`, `busy`, `stale`, `auth`, `storage`, `review`, `model`, `limit`, and `unknown`. Repair uses `repair`. A candidate's exhausted attempt count remains a model block; only the rolling request window uses `limit`. No short label grants permission, resets accounting, or replaces the existing recovery guidance.
|
|
84
|
+
|
|
85
|
+
The metadata request path is read-only. Publication and snapshot responses use public `sessionManager` identity, not the UI object. Old cleanup must not release a replacement publisher, including a reload with the same session manager. New subscribers request a snapshot after subscribing; late snapshots cannot replace later live activity or withdrawal. Missing or invalid metadata falls back to raw text rather than hiding an unknown state.
|
|
86
|
+
|
|
87
|
+
## Quiet footer verification
|
|
88
|
+
|
|
89
|
+
The 2026-11-08 display integration was audited against `docs/extension-conventions.md`, `docs/extension-settings.md`, and `docs/readme-conventions.md`. Producer changes add semantic annotations to existing raw statuses; authorization, scheduler election, request accounting, provider calls, resolver prompts, settings writers, widgets, and notifications remain unchanged. Independent complete-diff and follow-up reviews covered correctness, security, lifecycle, compatibility, regressions, and scope.
|
|
90
|
+
|
|
91
|
+
Verification evidence:
|
|
92
|
+
|
|
93
|
+
- Build prerequisites and test compilation passed. The focused Sync, Statusline, public-contract, and Jiti suites passed: 209 files, 3,772 tests, and 19 existing skips. Cases cover every activity/reason, raw fallback, Show sync status, mixed publishers, icons, widths, terminal controls, headless session isolation, reload, disposal, and reentrant updates/cleanup. Reviews produced 25 failing regressions before their fixes; those cases now pass without weaker acceptance criteria or longer test timeouts.
|
|
94
|
+
- Separate root `npm run check` and `npm test` gates passed: 942 files, 20,732 tests, 34 existing skips, and no unhandled errors. The fenced-code-aware heading audit passed for all 37 active package READMEs. Biome retained its existing informational advisories.
|
|
95
|
+
- `npm run package:pack -- sync` and `npm run package:pack -- statusline` passed. Actual temporary tarballs contained 330 Sync entries and 38 Statusline entries, including public docs and declared source/runtime paths, without tests or plans. Package-owned validators checked exact static and dynamic relative imports across all 82 Sync and three Statusline generated runtime files.
|
|
96
|
+
- Finite offline CLI smokes loaded source and extracted packages with Sync alone, Statusline alone, and both together. All six runs exercised lazy help through Pi's Jiti loader, reported zero network attempts and model runs, and shut down with clean EOF. `scripts/smoke-extension-status.mjs` blocks fetch/socket use; the extracted-package fixture supplies declared dependencies and Pi peers from the existing workspace, not a fresh registry installation.
|
|
97
|
+
|
|
98
|
+
Validation uses canonical temporary paths. The full suite was restarted after review fixes; no path guard, permission check, retry rule, or test deadline was relaxed. Both packages have minor Changesets for the optional public protocol addition; manifests, lockfiles, and historical changelogs were not edited for this change.
|
|
99
|
+
|
|
100
|
+
Physical-terminal font appearance, live model quality, production backends, a fresh registry install, Windows, and Android remain unverified. A maintainer can inspect marker spacing and distinct transfer/change arrows with the target terminal and font; this is not a new release gate. Live provider/backend checks require their own authorized environment. No publication, version tag, release dispatch, production transfer, or live-provider request was performed.
|
|
101
|
+
|
|
65
102
|
## Settings wording verification
|
|
66
103
|
|
|
67
104
|
The 2026-11-08 wording update uses **Autonomous sync**, removes Settings maturity markers at the user's request, and keeps `sync.agentMode`, saved consent, defaults, opt-ins, and risk warnings unchanged. A semantic diff audit checked all 16 changed runtime files against the base plus approved prose substitutions; authorization, settings storage, resolver prompts, cancellation, disposal, session replacement, and shutdown logic are unchanged. Review followed `docs/extension-conventions.md`, `docs/extension-settings.md`, and `docs/readme-conventions.md`, plus the skill editing rules. An independent complete-diff review found no issues.
|
package/package.json
CHANGED
|
@@ -229,6 +229,17 @@ Set it through **/sync → Settings → Show sync status**; changes are saved an
|
|
|
229
229
|
When `false`, pi-sync clears and suppresses status text for background checks, manual and automatic transfers, and review attention.
|
|
230
230
|
TUI review widgets and TUI/RPC notifications remain available, and lifecycle cleanup still clears any stale status owned by pi-sync.
|
|
231
231
|
|
|
232
|
+
Pi Sync also publishes optional semantic metadata through the [extension-neutral status protocol](https://github.com/narumiruna/pi-extensions/blob/main/packages/pi-statusline/docs/extension-status-protocol.md).
|
|
233
|
+
Pi Statusline uses that metadata for quiet display: waiting for changes and another local monitoring owner are hidden, while active work uses short static markers.
|
|
234
|
+
The existing configured icon identifies Sync without an extra duplicate label.
|
|
235
|
+
Native Pi footers, RPC observers, and older or absent metadata consumers still receive the original readable status value.
|
|
236
|
+
This is not permission to sync, change retry policy, or erase recovery evidence.
|
|
237
|
+
|
|
238
|
+
Retry and blocked states keep a short reason: `temp` (temporary service/network failure), `busy` (lock contention), `stale` (changed evidence), `auth` (authentication), `storage` (local storage), `review` (safety barrier), `model` (resolver/model failure or exhausted candidate attempts), `limit` (request-window allowance), or `unknown` (unclassified failure).
|
|
239
|
+
Settings or private-state repair uses `repair`.
|
|
240
|
+
A request-window limit is not a currency budget; exhausted attempts for one candidate remain a model block, not a timer-based allowance reset.
|
|
241
|
+
Use the existing manager and **Check remote status** guidance to inspect recovery requirements rather than treating a short marker as a complete diagnosis.
|
|
242
|
+
|
|
232
243
|
### Debug startup timings
|
|
233
244
|
|
|
234
245
|
The top-level user setting `debug` accepts only `true` or `false` and defaults to `false`.
|
|
@@ -554,7 +565,8 @@ Keep the directory as private evidence; do not delete it to repair a transfer.
|
|
|
554
565
|
|
|
555
566
|
#### Status, recovery and downgrade
|
|
556
567
|
|
|
557
|
-
The existing status key shows waiting, checking/syncing, scheduled retry, another local owner, or a stable blocked reason.
|
|
568
|
+
The existing raw status key shows waiting, checking/syncing, scheduled retry, another local owner, or a stable blocked reason.
|
|
569
|
+
A compatible Statusline uses the [quiet presentation described above](#status-display); normal hidden standby does not mean monitoring or another owner's work has stopped.
|
|
558
570
|
**Check remote status** and the manager show advisory ownership, the last successful background check/transfer and safe next actions.
|
|
559
571
|
Status also shows normalized rolling-window usage, the next window allowance, exhausted-candidate counts, serialized accounting bytes/capacity and on-disk bytes.
|
|
560
572
|
Window allowance is not dispatch permission: candidate, capacity, ownership and safety checks still apply.
|
package/src/sync/agent-sync.ts
CHANGED
|
@@ -57,7 +57,7 @@ export async function withAgentSyncLock<T>(
|
|
|
57
57
|
signal?.throwIfAborted();
|
|
58
58
|
});
|
|
59
59
|
await validate();
|
|
60
|
-
setSyncStatus(ctx, "sync resolving");
|
|
60
|
+
setSyncStatus(ctx, "sync resolving", { activity: "resolving" });
|
|
61
61
|
const cached = await withLock("agent-cache-read", async () => {
|
|
62
62
|
await assertOwner();
|
|
63
63
|
options.signal?.throwIfAborted();
|
|
@@ -5,7 +5,7 @@ import { snapshotOptionsForContext } from "../snapshot/session-paths.js";
|
|
|
5
5
|
import { withLock } from "../state/lock.js";
|
|
6
6
|
import { withStateDirectoryAccess } from "../state/state-directory.js";
|
|
7
7
|
import { readStateForConfig } from "../state/sync-state-store.js";
|
|
8
|
-
import { setSyncStatus } from "../ui/sync-status.js";
|
|
8
|
+
import { setSyncStatus, syncStatusFailureReason } from "../ui/sync-status.js";
|
|
9
9
|
import { withAgentSyncLock } from "./agent-sync.js";
|
|
10
10
|
import { ContinuousError, continuousFailure } from "./continuous-errors.js";
|
|
11
11
|
import { acquireContinuousLease, type ContinuousLease } from "./continuous-lease.js";
|
|
@@ -96,7 +96,10 @@ export function createContinuousSync(
|
|
|
96
96
|
if (signal.aborted) return;
|
|
97
97
|
try {
|
|
98
98
|
validateContext();
|
|
99
|
-
setSyncStatus(ctx, "sync blocked: private state or settings need repair"
|
|
99
|
+
setSyncStatus(ctx, "sync blocked: private state or settings need repair", {
|
|
100
|
+
activity: "blocked",
|
|
101
|
+
reason: "repair",
|
|
102
|
+
});
|
|
100
103
|
if (!repairNotices.has(ctx.sessionManager)) {
|
|
101
104
|
repairNotices.add(ctx.sessionManager);
|
|
102
105
|
ctx.ui.notify(
|
|
@@ -153,7 +156,7 @@ export function createContinuousSync(
|
|
|
153
156
|
if (lease) {
|
|
154
157
|
await monitor(ctx, config, binding, lease, signal, explicitRetry, startup);
|
|
155
158
|
explicitRetry = false;
|
|
156
|
-
} else setSyncStatus(ctx, "sync waiting: another local owner");
|
|
159
|
+
} else setSyncStatus(ctx, "sync waiting: another local owner", { activity: "idle" });
|
|
157
160
|
} finally {
|
|
158
161
|
await lease?.release();
|
|
159
162
|
}
|
|
@@ -162,7 +165,10 @@ export function createContinuousSync(
|
|
|
162
165
|
if (signal.aborted) return;
|
|
163
166
|
const failure = continuousFailure(error);
|
|
164
167
|
if (!failure.retry && !(error instanceof Error && error.name === "AbortError")) throw error;
|
|
165
|
-
setSyncStatus(ctx, `sync retry scheduled: ${failure.kind}
|
|
168
|
+
setSyncStatus(ctx, `sync retry scheduled: ${failure.kind}`, {
|
|
169
|
+
activity: "retry",
|
|
170
|
+
reason: syncStatusFailureReason(failure.kind),
|
|
171
|
+
});
|
|
166
172
|
}
|
|
167
173
|
validateOwner();
|
|
168
174
|
if (!continuousEnabled(await loadConfigForCheck())) return;
|
|
@@ -355,6 +361,9 @@ export function createContinuousSync(
|
|
|
355
361
|
: state.phase === "retry"
|
|
356
362
|
? `sync retry scheduled: ${state.error}`
|
|
357
363
|
: "sync waiting for changes",
|
|
364
|
+
state.phase === "blocked" || state.phase === "retry"
|
|
365
|
+
? { activity: state.phase, reason: syncStatusFailureReason(state.error) }
|
|
366
|
+
: { activity: "idle" },
|
|
358
367
|
);
|
|
359
368
|
if (state.phase === "retry") pollDue = Math.min(pollDue, state.next);
|
|
360
369
|
const next = Math.max(
|
|
@@ -400,7 +409,7 @@ export function createContinuousSync(
|
|
|
400
409
|
["authentication", "storage", "unknown"].includes(current.error ?? "")
|
|
401
410
|
)
|
|
402
411
|
return;
|
|
403
|
-
setSyncStatus(ctx, "sync checking");
|
|
412
|
+
setSyncStatus(ctx, "sync checking", { activity: "checking" });
|
|
404
413
|
const inspectionModule = await timings.measure("inspection import", () => loaders.inspection());
|
|
405
414
|
validate();
|
|
406
415
|
const observed = await timings.measure("inspection and guards", () =>
|
|
@@ -470,7 +479,7 @@ export function createContinuousSync(
|
|
|
470
479
|
if (observed.journal || observed.inspection.localChanged || observed.inspection.remoteChanged) {
|
|
471
480
|
const operations = await timings.measure("operations import", () => loaders.operations());
|
|
472
481
|
validate();
|
|
473
|
-
setSyncStatus(ctx, "sync syncing");
|
|
482
|
+
setSyncStatus(ctx, "sync syncing", { activity: "working" });
|
|
474
483
|
const result = await timings.measure("automatic transfer", () =>
|
|
475
484
|
withStateDirectoryAccess(() =>
|
|
476
485
|
withAgentSyncLock(
|
|
@@ -84,7 +84,7 @@ export function createStartupCheck(
|
|
|
84
84
|
const captured = config;
|
|
85
85
|
const identity = syncCheckConfigFingerprint(captured);
|
|
86
86
|
checking = true;
|
|
87
|
-
setSyncStatus(ctx, "sync ...");
|
|
87
|
+
setSyncStatus(ctx, "sync ...", { activity: "checking" });
|
|
88
88
|
const { inspectSync } = await timings.measure("inspection import", () => loaders.inspection());
|
|
89
89
|
throwIfAborted(signal);
|
|
90
90
|
const currentConfig = await loadConfigForCheck();
|
|
@@ -118,7 +118,7 @@ export async function push(
|
|
|
118
118
|
const state = input?.state ?? (await readStateForConfig(config));
|
|
119
119
|
validateMutation();
|
|
120
120
|
requireResolvedDirectionalProgress(state, options);
|
|
121
|
-
setSyncStatus(ctx, `pushing ${config.setupName}
|
|
121
|
+
setSyncStatus(ctx, `pushing ${config.setupName}`, { activity: "pushing" });
|
|
122
122
|
const backend = input?.backend ?? (await factory(config));
|
|
123
123
|
throwIfAborted(options.signal);
|
|
124
124
|
const localRaw =
|
|
@@ -272,7 +272,7 @@ export async function pull(
|
|
|
272
272
|
const config = await loadConfig(options.setup);
|
|
273
273
|
throwIfAborted(options.signal);
|
|
274
274
|
await requireNoMergeJournal(config, options);
|
|
275
|
-
setSyncStatus(ctx, `pulling ${config.setupName}
|
|
275
|
+
setSyncStatus(ctx, `pulling ${config.setupName}`, { activity: "pulling" });
|
|
276
276
|
const backend = await factory(config);
|
|
277
277
|
const state = await readStateForConfig(config);
|
|
278
278
|
validateMutation();
|
package/src/sync/sync-queries.ts
CHANGED
|
@@ -27,7 +27,7 @@ export async function status(
|
|
|
27
27
|
) {
|
|
28
28
|
const config = await loadConfig(options.setup);
|
|
29
29
|
throwIfAborted(options.signal);
|
|
30
|
-
setSyncStatus(ctx, `checking ${config.setupName}
|
|
30
|
+
setSyncStatus(ctx, `checking ${config.setupName}`, { activity: "checking" });
|
|
31
31
|
const { head, selectionState, localChanged, remoteChanged, localFiles, destination, capability } = await inspectSync(
|
|
32
32
|
config,
|
|
33
33
|
snapshotOptionsForContext(ctx, config),
|
|
@@ -70,7 +70,7 @@ export async function diff(
|
|
|
70
70
|
) {
|
|
71
71
|
const config = await loadConfig(options.setup);
|
|
72
72
|
throwIfAborted(options.signal);
|
|
73
|
-
setSyncStatus(ctx, `checking ${config.setupName}
|
|
73
|
+
setSyncStatus(ctx, `checking ${config.setupName}`, { activity: "checking" });
|
|
74
74
|
const backend = await factory(config);
|
|
75
75
|
const local = portableSnapshot(
|
|
76
76
|
await createSnapshot(config.snapshotIdentity, snapshotOptionsForContext(ctx, config)),
|
package/src/sync-extension.ts
CHANGED
|
@@ -10,12 +10,13 @@ import { errorMessage } from "./sync/sync-errors.js";
|
|
|
10
10
|
import { createSyncLoaders, type SyncDependencies } from "./sync/sync-loaders.js";
|
|
11
11
|
import { registerStartupTimings } from "./ui/startup-timings-entry.js";
|
|
12
12
|
import { createSyncAttentionController } from "./ui/sync-attention.js";
|
|
13
|
-
import { setSyncStatus } from "./ui/sync-status.js";
|
|
13
|
+
import { createSyncStatusPublisher, setSyncStatus } from "./ui/sync-status.js";
|
|
14
14
|
import { safeTerminalText } from "./ui/terminal-text.js";
|
|
15
15
|
|
|
16
16
|
export default function sync(pi: ExtensionAPI, dependencies: Partial<SyncDependencies> = {}) {
|
|
17
17
|
const loaders = createSyncLoaders(dependencies);
|
|
18
18
|
const attention = createSyncAttentionController();
|
|
19
|
+
const status = createSyncStatusPublisher(pi.events);
|
|
19
20
|
const publishTimings = registerStartupTimings(pi);
|
|
20
21
|
const check = createStartupCheck(loaders, attention, 30_000, publishTimings);
|
|
21
22
|
let sessionAbort = new AbortController();
|
|
@@ -60,6 +61,7 @@ export default function sync(pi: ExtensionAPI, dependencies: Partial<SyncDepende
|
|
|
60
61
|
if (!ctx.hasUI) {
|
|
61
62
|
throw new Error("/sync requires TUI or RPC mode so results and safety prompts are observable.");
|
|
62
63
|
}
|
|
64
|
+
status.bind(ctx);
|
|
63
65
|
const signal = sessionAbort.signal;
|
|
64
66
|
const manager = ctx.sessionManager;
|
|
65
67
|
const command = splitArgs(args)[0];
|
|
@@ -104,39 +106,46 @@ export default function sync(pi: ExtensionAPI, dependencies: Partial<SyncDepende
|
|
|
104
106
|
});
|
|
105
107
|
|
|
106
108
|
pi.on("session_start", async (_event, ctx) => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (signal.aborted) return false;
|
|
122
|
-
await check.stop();
|
|
123
|
-
if (signal.aborted) return false;
|
|
124
|
-
try {
|
|
125
|
-
const startup = await withStateDirectoryAccess(() => startSession(ctx, signal, publishTimings));
|
|
109
|
+
const releaseStatus = status.start(ctx);
|
|
110
|
+
try {
|
|
111
|
+
shutdownAbort?.abort(new DOMException("Session replaced", "AbortError"));
|
|
112
|
+
shutdownAbort = undefined;
|
|
113
|
+
sessionAbort.abort(new DOMException("Session replaced", "AbortError"));
|
|
114
|
+
sessionAbort = new AbortController();
|
|
115
|
+
const signal = sessionAbort.signal;
|
|
116
|
+
owner = ctx.sessionManager;
|
|
117
|
+
continuousPossible = false;
|
|
118
|
+
startupDebug = false;
|
|
119
|
+
attention.reset(ctx);
|
|
120
|
+
const previous = initialization;
|
|
121
|
+
initialization = (async () => {
|
|
122
|
+
await previous;
|
|
126
123
|
if (signal.aborted) return false;
|
|
127
|
-
|
|
128
|
-
return
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
|
|
124
|
+
await continuous?.stop();
|
|
125
|
+
if (signal.aborted) return false;
|
|
126
|
+
await check.stop();
|
|
127
|
+
if (signal.aborted) return false;
|
|
128
|
+
try {
|
|
129
|
+
const startup = await withStateDirectoryAccess(() => startSession(ctx, signal, publishTimings));
|
|
130
|
+
if (signal.aborted) return false;
|
|
131
|
+
startupDebug = startup?.debug ?? false;
|
|
132
|
+
return startup?.config;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (!signal.aborted && ctx.hasUI) {
|
|
135
|
+
ctx.ui.notify(`pi-sync recovery required: ${safeTerminalText(errorMessage(error))}`, "error");
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
132
138
|
}
|
|
133
|
-
|
|
139
|
+
})();
|
|
140
|
+
const ready = await initialization;
|
|
141
|
+
if (ready === false) releaseStatus();
|
|
142
|
+
if (ready && !signal.aborted) {
|
|
143
|
+
if (ready.continuous && ready.agentMode) await resumeContinuous(ctx, signal);
|
|
144
|
+
else if (ready.automatic || startupTransferEnabled(ready)) check.start(ctx, signal, ready);
|
|
134
145
|
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (ready.continuous && ready.agentMode) await resumeContinuous(ctx, signal);
|
|
139
|
-
else if (ready.automatic || startupTransferEnabled(ready)) check.start(ctx, signal, ready);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
releaseStatus();
|
|
148
|
+
throw error;
|
|
140
149
|
}
|
|
141
150
|
});
|
|
142
151
|
|
|
@@ -154,36 +163,41 @@ export default function sync(pi: ExtensionAPI, dependencies: Partial<SyncDepende
|
|
|
154
163
|
});
|
|
155
164
|
|
|
156
165
|
pi.on("session_shutdown", async (event, ctx) => {
|
|
157
|
-
|
|
158
|
-
sessionAbort.abort(new DOMException("Session shut down", "AbortError"));
|
|
159
|
-
attention.reset(ctx);
|
|
160
|
-
shutdownAbort?.abort(new DOMException("Session shut down again", "AbortError"));
|
|
161
|
-
const controller = new AbortController();
|
|
162
|
-
shutdownAbort = controller;
|
|
163
|
-
const signal = combineSignals(controller.signal, AbortSignal.timeout(30_000));
|
|
164
|
-
const reason = typeof event === "object" && event ? (event as { reason?: string }).reason : undefined;
|
|
166
|
+
const releaseStatus = status.cleanupFor(ctx);
|
|
165
167
|
try {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
168
|
+
if (continuous && owner && owner !== ctx.sessionManager) return;
|
|
169
|
+
sessionAbort.abort(new DOMException("Session shut down", "AbortError"));
|
|
170
|
+
attention.reset(ctx);
|
|
171
|
+
shutdownAbort?.abort(new DOMException("Session shut down again", "AbortError"));
|
|
172
|
+
const controller = new AbortController();
|
|
173
|
+
shutdownAbort = controller;
|
|
174
|
+
const signal = combineSignals(controller.signal, AbortSignal.timeout(30_000));
|
|
175
|
+
const reason = typeof event === "object" && event ? (event as { reason?: string }).reason : undefined;
|
|
176
|
+
try {
|
|
177
|
+
const ready = await initialization;
|
|
178
|
+
if (signal.aborted) return;
|
|
179
|
+
await continuous?.stop(ctx.sessionManager);
|
|
180
|
+
if (signal.aborted) return;
|
|
181
|
+
await check.stop();
|
|
182
|
+
if (signal.aborted) return;
|
|
183
|
+
if (ready !== false && reason !== "reload") {
|
|
184
|
+
await withStateDirectoryAccess(async () => {
|
|
185
|
+
if (signal.aborted) return;
|
|
186
|
+
await autoPushSessions(ctx, signal, loaders);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if (!signal.aborted) {
|
|
191
|
+
ctx.ui.notify(`pi-sync session push skipped: ${errorMessage(error)}`, "warning");
|
|
192
|
+
}
|
|
193
|
+
} finally {
|
|
194
|
+
if (shutdownAbort === controller) shutdownAbort = undefined;
|
|
181
195
|
}
|
|
196
|
+
if (signal.aborted) return;
|
|
197
|
+
setSyncStatus(ctx, undefined);
|
|
182
198
|
} finally {
|
|
183
|
-
|
|
199
|
+
releaseStatus();
|
|
184
200
|
}
|
|
185
|
-
if (signal.aborted) return;
|
|
186
|
-
setSyncStatus(ctx, undefined);
|
|
187
201
|
});
|
|
188
202
|
}
|
|
189
203
|
|
|
@@ -489,7 +489,7 @@ async function inspectConfiguredRemoteSelection(
|
|
|
489
489
|
const config = await loadConfig(setupName);
|
|
490
490
|
if (signal?.aborted) return undefined;
|
|
491
491
|
validateOwner();
|
|
492
|
-
setSyncStatus(ctx, `checking synced content for ${safeTerminalText(config.setupName)}
|
|
492
|
+
setSyncStatus(ctx, `checking synced content for ${safeTerminalText(config.setupName)}`, { activity: "checking" });
|
|
493
493
|
const backend = await factory(config);
|
|
494
494
|
if (signal?.aborted) return undefined;
|
|
495
495
|
validateOwner();
|