// @generated by scripts/build-runtime.mjs; do not edit. // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader. import { SyncSetupReviewChangedError, updateSyncSetup } from "./chunk-4F3W4CSC.ts"; import { createSyncBackend, readSnapshotForHead } from "./chunk-FR6CVXAL.ts"; import { errorMessage, safeTerminalText } from "./chunk-W64NEHXT.ts"; import { loadConfig, loadPartialConfig, syncConfigReviewFingerprint } from "./chunk-APZVBSM6.ts"; import { compareSyncInclude, inspectRemoteSelection, sameSyncInclude } from "./chunk-YQ6UW7IF.ts"; // src/remote-selection-ui.ts import { defineMenu, runMenu } from "@narumitw/pi-tui-kit"; // src/cancellable-operation.ts import { BorderedLoader } from "@earendil-works/pi-coding-agent"; import { truncateToWidth } from "@earendil-works/pi-tui"; import { runCustomInteraction } from "@narumitw/pi-tui-kit"; // src/manager-helpers.ts async function requiredExistingBucket(ctx, example, signal) { const value = await ctx.ui.input("Existing bucket", `Example: ${example}`, { signal }); if (signal?.aborted) { throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError"); } if (value === void 0) return void 0; const normalized = value.trim(); if (!normalized) { ctx.ui.notify("Enter the name of an existing R2/S3 bucket, or cancel setup.", "warning"); return void 0; } return normalized; } async function requiredInput(ctx, title, placeholder, signal) { const value = await ctx.ui.input(title, placeholder, { signal }); if (signal?.aborted) { throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError"); } if (value === void 0) return void 0; const normalized = value.trim() || placeholder; return normalized.includes("<") || normalized.includes(">") ? void 0 : normalized; } function ownRecord(value) { return value && typeof value === "object" && !Array.isArray(value) ? value : void 0; } function safeTerminalText2(value) { return value.replace(/[\u0000-\u001f\u007f-\u009f]/gu, "?"); } function errorMessage2(error) { return error instanceof Error ? error.message : String(error); } // src/cancellable-operation.ts async function runCancellableOperation(ctx, message, route, runRoute, options = {}) { const { commitAware = false, cancelledMessage = "Check cancelled; no settings or files were changed.", target, signal } = options; if (ctx.mode !== "tui") { return await runRoute(route, signal, void 0, target) ?? { kind: "failed" }; } let commitStarted = false; let routeResult; const interaction = await runCustomInteraction(ctx, { signal, isCurrent: () => !signal?.aborted, create: ({ tui, theme, keybindings, signal: interactionSignal, complete }) => { const loader = new BorderedLoader(tui, theme, message, { cancellable: false }); const cancelHint = `${keybindingText(keybindings, "tui.select.cancel", "esc")} cancel`; const operation = runRoute( route, interactionSignal, commitAware ? () => commitStarted = true : void 0, target ).then( (result) => { routeResult = result; complete({}); }, (error) => complete({ error }) ); return { render(width) { const safeWidth = Math.max(1, width); const lines = loader.render(safeWidth); const bottomBorder = lines.at(-1); return [ ...lines.slice(0, -1), truncateToWidth(theme.fg("dim", cancelHint), safeWidth, ""), ...bottomBorder === void 0 ? [] : [bottomBorder] ]; }, invalidate: () => loader.invalidate(), handleInput(data) { if (!keybindings.matches(data, "tui.select.cancel")) return; if (commitStarted) { ctx.ui.notify( "Applying or publishing has started and cannot be cancelled safely.", "warning" ); return; } complete({ cancelled: true }); }, dispose: () => loader.dispose(), waitForPending: () => operation }; } }); if (interaction.kind === "error") throw interaction.error; if (interaction.kind !== "completed") return { kind: "closed" }; if (interaction.value.cancelled) { if (cancelledMessage) ctx.ui.notify(cancelledMessage, "info"); return { kind: "cancelled" }; } if (interaction.value.error) throw interaction.value.error; return routeResult ?? { kind: "failed" }; } function keybindingText(keybindings, binding, fallback) { const keys = keybindings.getKeys(binding).map(String).map((key) => { if (key === "return") return "enter"; if (key === "escape") return "esc"; return safeTerminalText2(key); }).filter(Boolean); return keys.join("/") || fallback; } // src/remote-selection-ui.ts var STATUS_KEY = "sync"; async function showRemoteSelectionReview(ctx, setupName, signal, factory = createSyncBackend, options = {}) { try { let decision = options.decision; if (!decision) { const inspected = await inspectConfiguredRemoteSelection(ctx, setupName, signal, factory); if (!inspected || signal?.aborted) return { kind: "stale" }; if (inspected.kind === "empty") { ctx.ui.notify("Remote storage has no snapshot or synced-content list yet.", "info"); return { kind: "back" }; } if (inspected.state.kind === "same") { ctx.ui.notify("Remote synced content already matches this sync setup.", "info"); return { kind: "back" }; } if (inspected.state.kind === "legacy") { if (ctx.mode !== "tui") { ctx.ui.notify(formatLegacySummary(inspected.config, inspected.state.discovered), "info"); return { kind: "back" }; } await showLegacyDiscovery(ctx, inspected.config, inspected.state.discovered, signal); return signal?.aborted ? { kind: "stale" } : { kind: "back" }; } decision = decisionFromState(inspected.config, inspected.state); } if (ctx.mode !== "tui") { ctx.ui.notify(formatRemoteSelectionSummary(decision), "warning"); return { kind: "back" }; } let currentDecision = decision; for (; ; ) { if (signal?.aborted) return { kind: "stale" }; const result = await showSelectionDifference( ctx, currentDecision, options.origin ?? "settings", options.runRoute, signal, factory, options ); if (result.kind !== "refresh") return result; const refreshed = await runWithOptionalStateAccess( options, () => inspectConfiguredRemoteSelection(ctx, currentDecision.setupName, signal, factory) ); if (!refreshed || signal?.aborted) return { kind: "stale" }; if (refreshed.kind === "empty") { ctx.ui.notify("Remote storage no longer has a snapshot or synced-content list.", "warning"); return { kind: "back" }; } if (refreshed.state.kind !== "different") { ctx.ui.notify( refreshed.state.kind === "same" ? "Remote synced content now matches this sync setup." : "The refreshed legacy snapshot has no authoritative synced-content list.", "info" ); options.onSelectionResolved?.(); return { kind: "done" }; } currentDecision = decisionFromState(refreshed.config, refreshed.state); } } catch (error) { if (signal?.aborted) return { kind: "stale" }; ctx.ui.notify(`Could not review synced content: ${errorMessage(error)}`, "error"); return { kind: "back" }; } finally { ctx.ui.setStatus(STATUS_KEY, void 0); } } async function showSelectionDifference(ctx, initialDecision, origin, runRoute, sessionSignal, factory, options) { let flowState = { decision: initialDecision, saved: false }; let continuationReview; let nextResult; let refreshRequested = false; const route = origin === "settings" ? "sync" : origin; const menu = defineMenu({ start: "choice", screens: { choice: ({ state }) => ({ kind: "actions", title: "Synced content differs", lines: selectionSummaryLines(state.decision), items: [ { id: "review", label: "Review all paths (recommended)", description: "Compare exact remote-only, device-only, and ordered lists.", to: "review" }, { id: "adopt", label: "Use remote content list", description: "Save the reviewed list on this device without pulling files.", action: "adopt" }, { id: "keep", label: "Keep this device's content list and update remote\u2026", description: "Open the existing exact force-push preview without skipping confirmation.", action: "keep" }, { id: "cancel", label: options.cancelLabel ?? "Cancel", action: "cancel" } ], hint: "back" }), review: ({ state }) => ({ kind: "review", title: `Review synced content \xB7 ${safeTerminalText(state.decision.setupName)}`, content: formatSelectionDifference(state.decision), format: { kind: "text" }, viewportSize: "adaptive", hint: "back" }), saved: ({ state }) => ({ kind: "actions", title: "Remote content list saved", lines: [ `Sync setup: ${safeTerminalText(state.decision.setupName)}`, "Only the included-content setting was saved.", "No files were pulled and sync state was not changed." ], items: [ ...runRoute ? [ { id: "continue", label: continueLabel(origin), description: "Start a fresh check and exact preview for this sync setup.", action: "continue" } ] : [], { id: "done", label: "Done", action: "done" } ], hint: "close" }) }, actions: { adopt: async ({ state, signal: actionSignal }) => { const signal = combineSignals(sessionSignal, actionSignal); try { const currentConfig = await loadConfig(state.decision.setupName); if (signal.aborted) return { kind: "close" }; assertLocalSelectionCurrent(currentConfig, state.decision); if (!currentConfig.include.includes("sessions") && state.decision.remoteInclude.includes("sessions")) { const acknowledged = await ctx.ui.confirm( "Use a content list that includes session conversations?", "Session JSONL may contain prompts, tool output, file paths, images, and secrets. This saves the list only; it does not pull files.", { signal } ); if (signal.aborted) return { kind: "close" }; if (!acknowledged) return { kind: "stay" }; } const review = await runWithOptionalStateAccess(options, async () => { const loaded = await loadAdoptionReview(state.decision, signal, factory); if (signal.aborted) throw signal.reason; await revalidateAndAdopt(loaded, state.decision, signal); return loaded; }); if (signal.aborted) return { kind: "close" }; options.onSelectionResolved?.(); continuationReview = { ...review.storageReview, setupName: state.decision.setupName, include: [...state.decision.remoteInclude], automatic: review.config.automatic, onSwitch: review.config.onSwitch }; flowState = { decision: state.decision, saved: true }; return { kind: "to", screen: "saved" }; } catch (error) { if (signal.aborted) return { kind: "close" }; if (isStaleReviewError(error)) { ctx.ui.notify(`${errorMessage(error)} Refreshing the comparison.`, "warning"); refreshRequested = true; return { kind: "close" }; } ctx.ui.notify(`Could not save the remote content list: ${errorMessage(error)}`, "error"); return { kind: "stay" }; } }, keep: async ({ state, signal: actionSignal }) => { if (!runRoute) { ctx.ui.notify("The reviewed update-remote route is unavailable.", "error"); return { kind: "stay" }; } const signal = combineSignals(sessionSignal, actionSignal); try { const latest = await loadConfig(state.decision.setupName); if (signal.aborted) return { kind: "close" }; assertLocalSelectionCurrent(latest, state.decision); const result = await runCancellableOperation( ctx, "Preparing this device's push preview\u2026", "push --force", runRoute, { commitAware: true, cancelledMessage: "Push preparation cancelled; no remote files were changed.", target: state.decision.setupName, signal } ); return handleNestedRouteResult(result, "push"); } catch (error) { if (signal.aborted) return { kind: "close" }; if (isStaleReviewError(error)) { ctx.ui.notify(`${errorMessage(error)} Refreshing the comparison.`, "warning"); refreshRequested = true; return { kind: "close" }; } ctx.ui.notify(`Could not prepare the remote update: ${errorMessage(error)}`, "error"); return { kind: "stay" }; } }, continue: async ({ state, signal: actionSignal }) => { if (!runRoute || !continuationReview) return { kind: "stay" }; const signal = combineSignals(sessionSignal, actionSignal); try { const latest = await loadPartialConfig(state.decision.setupName); if (signal.aborted) return { kind: "close" }; if (!sameContinuationReview(latest, continuationReview)) { throw new StaleRemoteSelectionReviewError( `Sync setup \u201C${safeTerminalText(state.decision.setupName)}\u201D changed after the remote content list was saved.` ); } const result = await runCancellableOperation( ctx, continueBusyLabel(origin), route, runRoute, { commitAware: true, cancelledMessage: continuationCancelledMessage(route), target: state.decision.setupName, signal } ); return handleNestedRouteResult(result, route); } catch (error) { if (signal.aborted) return { kind: "close" }; ctx.ui.notify(`Could not continue: ${errorMessage(error)}`, "error"); return { kind: "stay" }; } }, cancel: async () => ({ kind: "back" }), done: async () => { nextResult = { kind: "done" }; return { kind: "close" }; } } }); function handleNestedRouteResult(result, nestedRoute) { if (result.kind === "completed" && result.outcome === "applied") { options.onSelectionResolved?.(); } if (result.kind === "closed") { nextResult = { kind: "closed" }; return { kind: "close" }; } if (result.kind === "cancelled" || result.kind === "completed" && result.outcome === "cancelled" || result.kind === "failed") { return { kind: "stay" }; } nextResult = { kind: "route-result", result, route: nestedRoute }; return { kind: "close" }; } const menuResult = await runMenu(ctx, menu, { getState: () => flowState, signal: sessionSignal, isCurrent: () => !sessionSignal?.aborted, onError: (_menuCtx, error) => ctx.ui.notify(errorMessage(error), "error") }); if (sessionSignal?.aborted || menuResult.kind === "stale") return { kind: "stale" }; if (refreshRequested) return { kind: "refresh" }; if (nextResult) return nextResult; if (menuResult.kind === "closed") { return menuResult.reason === "back" ? { kind: "back" } : { kind: "closed" }; } return { kind: "closed" }; } async function loadAdoptionReview(decision, signal, factory) { const config = await loadConfig(decision.setupName); throwIfAborted(signal); assertLocalSelectionCurrent(config, decision); const partial = await loadPartialConfig(decision.setupName); throwIfAborted(signal); if (!sameSyncInclude(partial.include, decision.localInclude)) { throw new StaleRemoteSelectionReviewError( `Sync setup \u201C${safeTerminalText(decision.setupName)}\u201D changed while the comparison was open.` ); } const backend = await factory(config); throwIfAborted(signal); const reviewedHead = await backend.readHead(signal); throwIfAborted(signal); if (!reviewedHead) { throw new StaleRemoteSelectionReviewError( "Remote storage changed while the comparison was open." ); } const snapshot = await readSnapshotForHead(backend, reviewedHead, signal); throwIfAborted(signal); const state = inspectRemoteSelection(config.include, snapshot); if (state.kind !== "different" || !sameSyncInclude(state.include, decision.remoteInclude)) { throw new StaleRemoteSelectionReviewError( "Remote synced content changed while the comparison was open." ); } return { config, storageReview: partial, backend, reviewedHead }; } async function revalidateAndAdopt(review, decision, signal) { const currentHead = await review.backend.readHead(signal); throwIfAborted(signal); if (!currentHead || !review.backend.sameRevision(review.reviewedHead.revision, currentHead.revision)) { throw new StaleRemoteSelectionReviewError( "Remote storage changed while the comparison was open." ); } const currentSnapshot = await readSnapshotForHead(review.backend, currentHead, signal); throwIfAborted(signal); const currentState = inspectRemoteSelection(review.config.include, currentSnapshot); if (currentState.kind !== "different" || !sameSyncInclude(currentState.include, decision.remoteInclude)) { throw new StaleRemoteSelectionReviewError( "Remote synced content changed while the comparison was open." ); } await updateSyncSetup( decision.setupName, (setup) => ({ ...setup, sync: { ...setup.sync, include: [...decision.remoteInclude] } }), { expectedStorage: review.storageReview, expectedInclude: decision.localInclude, signal } ); } async function inspectConfiguredRemoteSelection(ctx, setupName, signal, factory) { const config = await loadConfig(setupName); if (signal?.aborted) return void 0; ctx.ui.setStatus(STATUS_KEY, `checking synced content for ${safeTerminalText(config.setupName)}`); const backend = await factory(config); if (signal?.aborted) return void 0; const head = await backend.readHead(signal); if (signal?.aborted) return void 0; if (!head) return { kind: "empty", config }; const snapshot = await readSnapshotForHead(backend, head, signal); if (signal?.aborted) return void 0; return { kind: "selection", config, state: inspectRemoteSelection(config.include, snapshot) }; } async function showLegacyDiscovery(ctx, config, discovered, signal) { const menu = defineMenu({ start: "choice", screens: { choice: () => ({ kind: "actions", title: `Compare synced content \xB7 ${safeTerminalText(config.setupName)}`, lines: [ "This legacy snapshot has no portable synced-content list.", "Discovered paths are partial and read-only; preserved files may not have been selected." ], items: [ { id: "review", label: "Review discovered paths", to: "review" }, { id: "back", label: "Back", action: "back" } ], hint: "close" }), review: () => ({ kind: "review", title: "Partial discovery from legacy snapshot", content: [ "Partial discovery only \u2014 not an authoritative selection.", "", ...discovered.length > 0 ? discovered.map((item) => `Discovered: ${safeTerminalText(item)}`) : ["No safe paths were discovered."], "", "Use Add custom path\u2026 in the local Included Content editor if needed." ].join("\n"), format: { kind: "text" }, viewportSize: "adaptive", hint: "back" }) }, actions: { back: async () => ({ kind: "close" }) } }); await runMenu(ctx, menu, { getState: () => void 0, signal, isCurrent: () => !signal?.aborted }); } function selectionSummaryLines(decision) { const comparison = compareSyncInclude(decision.localInclude, decision.remoteInclude); return [ `Sync setup: ${safeTerminalText(decision.setupName)}`, "Nothing changed. Review both lists before choosing what happens next.", ...comparison.remoteOnly.length === 0 && comparison.localOnly.length === 0 ? ["Only the ordering differs; membership is the same."] : [ `Remote-only paths: ${comparison.remoteOnly.length} \xB7 Device-only paths: ${comparison.localOnly.length}` ] ]; } function formatSelectionDifference(decision) { const comparison = compareSyncInclude(decision.localInclude, decision.remoteInclude); return [ ...comparison.remoteOnly.length === 0 && comparison.localOnly.length === 0 ? ["Only ordering differs; both lists contain the same paths.", ""] : [], "Remote-only paths:", ...comparison.remoteOnly.length > 0 ? comparison.remoteOnly.map((item) => `+ ${safeTerminalText(item)}`) : ["(none)"], "", "Device-only paths:", ...comparison.localOnly.length > 0 ? comparison.localOnly.map((item) => `- ${safeTerminalText(item)}`) : ["(none)"], "", "Remote ordered list:", ...decision.remoteInclude.length > 0 ? decision.remoteInclude.map((item, index) => `${index + 1}. ${safeTerminalText(item)}`) : ["(none)"], "", "This device's ordered list:", ...decision.localInclude.length > 0 ? decision.localInclude.map((item, index) => `${index + 1}. ${safeTerminalText(item)}`) : ["(none)"], "", "Using the remote list saves settings only and does not pull files." ].join("\n"); } function formatRemoteSelectionSummary(decision) { const comparison = compareSyncInclude(decision.localInclude, decision.remoteInclude); return [ `Synced content for \u201C${safeTerminalText(decision.setupName)}\u201D differs from this device.`, `Remote-only: ${safeList(comparison.remoteOnly)}`, `Device-only: ${safeList(comparison.localOnly)}`, ...comparison.remoteOnly.length === 0 && comparison.localOnly.length === 0 ? [ "Only ordering differs.", `Remote order: ${safeList(decision.remoteInclude)}`, `Device order: ${safeList(decision.localInclude)}` ] : [], "Run /sync in TUI to choose a content list; RPC review is read-only." ].join("\n"); } function formatLegacySummary(config, discovered) { return `Remote snapshot for \u201C${safeTerminalText(config.setupName)}\u201D has no portable synced-content list; ${discovered.length} safe path${discovered.length === 1 ? " was" : "s were"} discovered, but the result is partial and read-only.`; } function decisionFromState(config, state) { return { setupName: config.setupName, configIdentity: syncConfigReviewFingerprint(config), localInclude: [...config.include], remoteInclude: [...state.include] }; } function assertLocalSelectionCurrent(config, decision) { if (syncConfigReviewFingerprint(config) === decision.configIdentity && sameSyncInclude(config.include, decision.localInclude)) { return; } throw new StaleRemoteSelectionReviewError( `Sync setup \u201C${safeTerminalText(config.setupName)}\u201D changed while the comparison was open.` ); } function sameContinuationReview(left, right) { return left.setupName === right.setupName && left.connectionName === right.connectionName && left.storageKind === right.storageKind && left.storagePath === right.storagePath && left.bucket === right.bucket && left.branch === right.branch && sameSyncInclude(left.include, right.include); } function continueLabel(origin) { if (origin === "pull") return "Continue Pull now\u2026"; if (origin === "push") return "Continue Push now\u2026"; return "Continue Sync now\u2026"; } function continueBusyLabel(origin) { if (origin === "pull") return "Checking remote changes\u2026"; if (origin === "push") return "Preparing push preview\u2026"; return "Checking current sync setup\u2026"; } function continuationCancelledMessage(route) { if (route === "pull") return "Pull check cancelled; no local files were changed."; if (route === "push") return "Push preparation cancelled; no remote files were changed."; return "Sync check cancelled; no settings or files were changed."; } function safeList(values) { return values.length > 0 ? values.map(safeTerminalText).join(", ") : "none"; } var StaleRemoteSelectionReviewError = class extends Error { constructor(message) { super(message); this.name = "StaleRemoteSelectionReviewError"; } }; function isStaleReviewError(error) { return error instanceof StaleRemoteSelectionReviewError || error instanceof SyncSetupReviewChangedError; } function runWithOptionalStateAccess(options, task) { return options.withStateAccess ? options.withStateAccess(task) : task(); } function combineSignals(sessionSignal, actionSignal) { return sessionSignal ? AbortSignal.any([sessionSignal, actionSignal]) : actionSignal; } function throwIfAborted(signal) { if (!signal.aborted) return; throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError"); } export { requiredExistingBucket, requiredInput, ownRecord, safeTerminalText2 as safeTerminalText, errorMessage2 as errorMessage, runCancellableOperation, showRemoteSelectionReview }; //# sourceMappingURL=chunk-D3S54OXI.ts.map