@narumitw/pi-sync 0.57.10 → 0.57.12
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 +14 -0
- package/README.md +32 -22
- package/dist/chunks/{agent-candidate-EYB35AGL.ts → agent-candidate-U2NU3GUX.ts} +11 -11
- package/dist/chunks/{agent-plan-KO43GTOR.ts → agent-plan-3YXDIJXO.ts} +25 -22
- package/dist/chunks/agent-plan-3YXDIJXO.ts.map +7 -0
- package/dist/chunks/{agent-refusal-retry-C2HQROTP.ts → agent-refusal-retry-476YGNU5.ts} +10 -10
- package/dist/chunks/{agent-runner-WMSCKSCB.ts → agent-runner-FCRV2Z75.ts} +11 -10
- package/dist/chunks/agent-runner-FCRV2Z75.ts.map +7 -0
- package/dist/chunks/{agent-store-UT52DS7B.ts → agent-store-7GINKTS7.ts} +8 -8
- package/dist/chunks/{agent-store-UT52DS7B.ts.map → agent-store-7GINKTS7.ts.map} +2 -2
- package/dist/chunks/{agent-sync-VM3TXWA3.ts → agent-sync-P6AWQXGK.ts} +6 -6
- package/dist/chunks/{chunk-JXWTEJS5.ts → chunk-45COPAK7.ts} +10 -3
- package/dist/chunks/chunk-45COPAK7.ts.map +7 -0
- package/dist/chunks/{chunk-DOJRPBQU.ts → chunk-5TSCBBPP.ts} +4 -4
- package/dist/chunks/{chunk-CDQGSZL3.ts → chunk-6LD5KX55.ts} +6 -6
- package/dist/chunks/{chunk-MDUORMCK.ts → chunk-6O3EDBRL.ts} +2 -2
- package/dist/chunks/{chunk-AAPAHANE.ts → chunk-7A35ZAVO.ts} +320 -219
- package/dist/chunks/chunk-7A35ZAVO.ts.map +7 -0
- package/dist/chunks/{chunk-STVQQ5KG.ts → chunk-7ZIYU5QS.ts} +6 -6
- package/dist/chunks/{chunk-AO6EDUA6.ts → chunk-AKMV47YC.ts} +3 -3
- package/dist/chunks/{chunk-66GAUVDL.ts → chunk-BEST45H2.ts} +6 -6
- package/dist/chunks/{chunk-DO55XUNU.ts → chunk-CBLTWXAU.ts} +15 -5
- package/dist/chunks/chunk-CBLTWXAU.ts.map +7 -0
- package/dist/chunks/{chunk-IMMMZUFC.ts → chunk-CQD2TJHE.ts} +7 -7
- package/dist/chunks/{chunk-IMMMZUFC.ts.map → chunk-CQD2TJHE.ts.map} +2 -2
- package/dist/chunks/{chunk-73MDIVYJ.ts → chunk-DN4IA7MR.ts} +6 -6
- package/dist/chunks/{chunk-PHANBTVM.ts → chunk-ESKOPQVB.ts} +4 -4
- package/dist/chunks/{chunk-YZ5BQNRZ.ts → chunk-FHE2DCA2.ts} +2 -2
- package/dist/chunks/{chunk-YZ5BQNRZ.ts.map → chunk-FHE2DCA2.ts.map} +2 -2
- package/dist/chunks/{chunk-MRIPZGPR.ts → chunk-FLHCOJCD.ts} +3 -3
- package/dist/chunks/{chunk-5WJQ6XOW.ts → chunk-GNYGNHFF.ts} +2 -2
- package/dist/chunks/{chunk-DFJGTVPX.ts → chunk-KYTT7KV4.ts} +2 -2
- package/dist/chunks/{chunk-UGSSZWQ6.ts → chunk-MKSMJPVA.ts} +2 -2
- package/dist/chunks/{chunk-2VSN3MFX.ts → chunk-MMQIEDZ6.ts} +33 -27
- package/dist/chunks/chunk-MMQIEDZ6.ts.map +7 -0
- package/dist/chunks/{chunk-RAMHGOX2.ts → chunk-OJARL2TG.ts} +4 -4
- package/dist/chunks/{chunk-BJXSCGQK.ts → chunk-OKPPZA4D.ts} +148 -89
- package/dist/chunks/chunk-OKPPZA4D.ts.map +7 -0
- package/dist/chunks/{chunk-RX3WGWVL.ts → chunk-OVBAW7KV.ts} +177 -130
- package/dist/chunks/chunk-OVBAW7KV.ts.map +7 -0
- package/dist/chunks/{chunk-U77DWNRH.ts → chunk-Q23JJW7E.ts} +3 -3
- package/dist/chunks/{chunk-NLWS55B6.ts → chunk-QPC5HT3A.ts} +2 -2
- package/dist/chunks/{chunk-NLWS55B6.ts.map → chunk-QPC5HT3A.ts.map} +1 -1
- package/dist/chunks/{chunk-4RLATUP4.ts → chunk-S5OCYVPS.ts} +7 -7
- package/dist/chunks/{chunk-4RLATUP4.ts.map → chunk-S5OCYVPS.ts.map} +2 -2
- package/dist/chunks/{chunk-QWV3XKNJ.ts → chunk-TIMWN2RH.ts} +5 -5
- package/dist/chunks/{chunk-Z5O2TLNF.ts → chunk-TZC7GDJE.ts} +7 -7
- package/dist/chunks/{chunk-UCD7TQDT.ts → chunk-U6GXGY5G.ts} +5 -5
- package/dist/chunks/{chunk-PWA3YN5Z.ts → chunk-XEODG2PE.ts} +3 -3
- package/dist/chunks/{chunk-TO2GMX3K.ts → chunk-XJHQPB3E.ts} +2 -2
- package/dist/chunks/{chunk-JY2RVOUL.ts → chunk-Y4UVXOLQ.ts} +5 -5
- package/dist/chunks/{chunk-ODF2M4RF.ts → chunk-ZC3IHFNM.ts} +8 -8
- package/dist/chunks/{chunk-ODF2M4RF.ts.map → chunk-ZC3IHFNM.ts.map} +2 -2
- package/dist/chunks/{chunk-M5IV3KL2.ts → chunk-ZDDBZH72.ts} +3 -3
- package/dist/chunks/{command-handler-FO6F2APH.ts → command-handler-VKLZBRLO.ts} +26 -21
- package/dist/chunks/command-handler-VKLZBRLO.ts.map +7 -0
- package/dist/chunks/{config-B37W7FXW.ts → config-HYG7G5EM.ts} +6 -4
- package/dist/chunks/{conflict-artifacts-6XGSSU5S.ts → conflict-artifacts-J6WSDCWP.ts} +12 -12
- package/dist/chunks/{conflict-review-BQSHSJB3.ts → conflict-review-GXA6Y7KY.ts} +23 -23
- package/dist/chunks/{continuous-record-4DQ2EAZS.ts → continuous-record-EPHGR2DU.ts} +6 -6
- package/dist/chunks/{continuous-sync-ZT3UEQLK.ts → continuous-sync-TTOSJCQU.ts} +18 -18
- package/dist/chunks/{file-selection-FBLKM25V.ts → file-selection-IELTXSGZ.ts} +6 -7
- package/dist/chunks/{file-selection-FBLKM25V.ts.map → file-selection-IELTXSGZ.ts.map} +1 -1
- package/dist/chunks/{git-backend-7PCWECRN.ts → git-backend-KHD36W27.ts} +6 -7
- package/dist/chunks/{git-backend-7PCWECRN.ts.map → git-backend-KHD36W27.ts.map} +1 -1
- package/dist/chunks/{local-fields-B6JWLPAE.ts → local-fields-JEKAPZKD.ts} +6 -7
- package/dist/chunks/manager-result-dispatcher-6W52NM7F.ts +32 -0
- package/dist/chunks/{manager-ui-DGRTG76I.ts → manager-ui-INVMAFEN.ts} +245 -224
- package/dist/chunks/manager-ui-INVMAFEN.ts.map +7 -0
- package/dist/chunks/{merge-baseline-store-HL3VLMAM.ts → merge-baseline-store-65GKUMXI.ts} +6 -6
- package/dist/chunks/{remote-selection-ui-OHMIQ32X.ts → remote-selection-ui-TD4C4W3J.ts} +16 -12
- package/dist/chunks/{s3-backend-CJXBEWWU.ts → s3-backend-L24UZ6IK.ts} +12 -10
- package/dist/chunks/s3-backend-L24UZ6IK.ts.map +7 -0
- package/dist/chunks/{setup-switch-66GWMBNH.ts → setup-switch-KLGESLQX.ts} +6 -6
- package/dist/chunks/{setup-wizard-OHZXJVQZ.ts → setup-wizard-ZQAK35BP.ts} +7 -7
- package/dist/chunks/{snapshot-VRHNAUUH.ts → snapshot-RGNDYT6A.ts} +5 -6
- package/dist/chunks/{snapshot-transaction-EUGA4VX6.ts → snapshot-transaction-26BQO36S.ts} +8 -8
- package/dist/chunks/{sync-inspection-4WFQEJOE.ts → sync-inspection-MOMLGUTD.ts} +11 -11
- package/dist/chunks/{sync-operations-X4AOXMBT.ts → sync-operations-TMGQHBF5.ts} +26 -26
- package/dist/chunks/{sync-operations-X4AOXMBT.ts.map → sync-operations-TMGQHBF5.ts.map} +2 -2
- package/dist/chunks/{sync-state-SWLSMK4G.ts → sync-state-7L4NT7W2.ts} +7 -8
- package/dist/chunks/{webdav-backend-2Q5ZEBST.ts → webdav-backend-3ZCACILP.ts} +7 -8
- package/dist/chunks/{webdav-backend-2Q5ZEBST.ts.map → webdav-backend-3ZCACILP.ts.map} +1 -1
- package/dist/index.ts +20 -20
- package/docs/agent-continuous-hardening.md +5 -5
- package/docs/agent-mode-verification.md +3 -3
- package/docs/agent-quality-verification.md +3 -3
- package/docs/continuous-sync-verification.md +2 -2
- package/docs/settings.md +9 -5
- package/docs/tui-contract.md +20 -3
- package/package.json +3 -3
- package/skills/configure-pi-sync/SKILL.md +3 -1
- package/skills/configure-pi-sync/references/configuration.md +56 -38
- package/src/backends/s3/s3-client.ts +8 -1
- package/src/commands/command-execution.ts +11 -6
- package/src/commands/command.ts +1 -1
- package/src/paths.ts +6 -1
- package/src/settings/config-file.ts +205 -118
- package/src/settings/config.ts +1 -1
- package/src/settings/settings-management.ts +15 -1
- package/src/settings/settings-store.ts +0 -14
- package/src/settings/settings-types.ts +1 -1
- package/src/snapshot/session-paths.ts +15 -2
- package/src/snapshot/snapshot-mutation-queues.ts +142 -0
- package/src/snapshot/snapshot-transaction.ts +122 -125
- package/src/sync/agent-candidate.ts +3 -3
- package/src/sync/agent-plan.ts +11 -8
- package/src/sync/agent-runner.ts +10 -9
- package/src/sync/agent-store.ts +1 -1
- package/src/sync/agent-sync.ts +2 -2
- package/src/sync/continuous-record.ts +1 -1
- package/src/sync/merge-apply.ts +6 -0
- package/src/sync/merged-sync.ts +4 -4
- package/src/sync/sync-mutations.ts +1 -1
- package/src/ui/agent-mode-setting.ts +6 -6
- package/src/ui/cancellable-operation.ts +36 -13
- package/src/ui/manager-state.ts +4 -2
- package/src/ui/manager-ui.ts +76 -24
- package/src/ui/remote-selection-ui.ts +131 -82
- package/src/ui/settings-ui.ts +21 -56
- package/src/ui/setup/setup-switcher.ts +2 -2
- package/src/ui/sync-setups-ui.ts +2 -2
- package/dist/chunks/agent-plan-KO43GTOR.ts.map +0 -7
- package/dist/chunks/agent-runner-WMSCKSCB.ts.map +0 -7
- package/dist/chunks/chunk-2VSN3MFX.ts.map +0 -7
- package/dist/chunks/chunk-AAPAHANE.ts.map +0 -7
- package/dist/chunks/chunk-BJXSCGQK.ts.map +0 -7
- package/dist/chunks/chunk-DO55XUNU.ts.map +0 -7
- package/dist/chunks/chunk-JXWTEJS5.ts.map +0 -7
- package/dist/chunks/chunk-RX3WGWVL.ts.map +0 -7
- package/dist/chunks/command-handler-FO6F2APH.ts.map +0 -7
- package/dist/chunks/manager-result-dispatcher-NODX3KKH.ts +0 -28
- package/dist/chunks/manager-ui-DGRTG76I.ts.map +0 -7
- package/dist/chunks/s3-backend-CJXBEWWU.ts.map +0 -7
- /package/dist/chunks/{agent-candidate-EYB35AGL.ts.map → agent-candidate-U2NU3GUX.ts.map} +0 -0
- /package/dist/chunks/{agent-refusal-retry-C2HQROTP.ts.map → agent-refusal-retry-476YGNU5.ts.map} +0 -0
- /package/dist/chunks/{agent-sync-VM3TXWA3.ts.map → agent-sync-P6AWQXGK.ts.map} +0 -0
- /package/dist/chunks/{chunk-DOJRPBQU.ts.map → chunk-5TSCBBPP.ts.map} +0 -0
- /package/dist/chunks/{chunk-CDQGSZL3.ts.map → chunk-6LD5KX55.ts.map} +0 -0
- /package/dist/chunks/{chunk-MDUORMCK.ts.map → chunk-6O3EDBRL.ts.map} +0 -0
- /package/dist/chunks/{chunk-STVQQ5KG.ts.map → chunk-7ZIYU5QS.ts.map} +0 -0
- /package/dist/chunks/{chunk-AO6EDUA6.ts.map → chunk-AKMV47YC.ts.map} +0 -0
- /package/dist/chunks/{chunk-66GAUVDL.ts.map → chunk-BEST45H2.ts.map} +0 -0
- /package/dist/chunks/{chunk-73MDIVYJ.ts.map → chunk-DN4IA7MR.ts.map} +0 -0
- /package/dist/chunks/{chunk-PHANBTVM.ts.map → chunk-ESKOPQVB.ts.map} +0 -0
- /package/dist/chunks/{chunk-MRIPZGPR.ts.map → chunk-FLHCOJCD.ts.map} +0 -0
- /package/dist/chunks/{chunk-5WJQ6XOW.ts.map → chunk-GNYGNHFF.ts.map} +0 -0
- /package/dist/chunks/{chunk-DFJGTVPX.ts.map → chunk-KYTT7KV4.ts.map} +0 -0
- /package/dist/chunks/{chunk-UGSSZWQ6.ts.map → chunk-MKSMJPVA.ts.map} +0 -0
- /package/dist/chunks/{chunk-RAMHGOX2.ts.map → chunk-OJARL2TG.ts.map} +0 -0
- /package/dist/chunks/{chunk-U77DWNRH.ts.map → chunk-Q23JJW7E.ts.map} +0 -0
- /package/dist/chunks/{chunk-QWV3XKNJ.ts.map → chunk-TIMWN2RH.ts.map} +0 -0
- /package/dist/chunks/{chunk-Z5O2TLNF.ts.map → chunk-TZC7GDJE.ts.map} +0 -0
- /package/dist/chunks/{chunk-UCD7TQDT.ts.map → chunk-U6GXGY5G.ts.map} +0 -0
- /package/dist/chunks/{chunk-PWA3YN5Z.ts.map → chunk-XEODG2PE.ts.map} +0 -0
- /package/dist/chunks/{chunk-TO2GMX3K.ts.map → chunk-XJHQPB3E.ts.map} +0 -0
- /package/dist/chunks/{chunk-JY2RVOUL.ts.map → chunk-Y4UVXOLQ.ts.map} +0 -0
- /package/dist/chunks/{chunk-M5IV3KL2.ts.map → chunk-ZDDBZH72.ts.map} +0 -0
- /package/dist/chunks/{config-B37W7FXW.ts.map → config-HYG7G5EM.ts.map} +0 -0
- /package/dist/chunks/{conflict-artifacts-6XGSSU5S.ts.map → conflict-artifacts-J6WSDCWP.ts.map} +0 -0
- /package/dist/chunks/{conflict-review-BQSHSJB3.ts.map → conflict-review-GXA6Y7KY.ts.map} +0 -0
- /package/dist/chunks/{continuous-record-4DQ2EAZS.ts.map → continuous-record-EPHGR2DU.ts.map} +0 -0
- /package/dist/chunks/{continuous-sync-ZT3UEQLK.ts.map → continuous-sync-TTOSJCQU.ts.map} +0 -0
- /package/dist/chunks/{local-fields-B6JWLPAE.ts.map → local-fields-JEKAPZKD.ts.map} +0 -0
- /package/dist/chunks/{manager-result-dispatcher-NODX3KKH.ts.map → manager-result-dispatcher-6W52NM7F.ts.map} +0 -0
- /package/dist/chunks/{merge-baseline-store-HL3VLMAM.ts.map → merge-baseline-store-65GKUMXI.ts.map} +0 -0
- /package/dist/chunks/{remote-selection-ui-OHMIQ32X.ts.map → remote-selection-ui-TD4C4W3J.ts.map} +0 -0
- /package/dist/chunks/{setup-switch-66GWMBNH.ts.map → setup-switch-KLGESLQX.ts.map} +0 -0
- /package/dist/chunks/{setup-wizard-OHZXJVQZ.ts.map → setup-wizard-ZQAK35BP.ts.map} +0 -0
- /package/dist/chunks/{snapshot-VRHNAUUH.ts.map → snapshot-RGNDYT6A.ts.map} +0 -0
- /package/dist/chunks/{snapshot-transaction-EUGA4VX6.ts.map → snapshot-transaction-26BQO36S.ts.map} +0 -0
- /package/dist/chunks/{sync-inspection-4WFQEJOE.ts.map → sync-inspection-MOMLGUTD.ts.map} +0 -0
- /package/dist/chunks/{sync-state-SWLSMK4G.ts.map → sync-state-7L4NT7W2.ts.map} +0 -0
|
@@ -210,7 +210,7 @@ function hasReservationCapacity(record: ContinuousRecord, now: number): boolean
|
|
|
210
210
|
/** Read-only status: no directory creation, lease, mutation, request reservation or provider call. */
|
|
211
211
|
export async function continuousStatusLines(config: AnySyncConfig, clock: () => number = Date.now): Promise<string[]> {
|
|
212
212
|
if (!config.continuous) return ["Continuous sync: Off"];
|
|
213
|
-
if (!config.agentMode) return ["Continuous sync: paused; enable
|
|
213
|
+
if (!config.agentMode) return ["Continuous sync: paused; enable Autonomous sync first"];
|
|
214
214
|
try {
|
|
215
215
|
const binding = await continuousBinding(config);
|
|
216
216
|
const document = await readContinuousPrivateDocument(`${binding}.json`);
|
package/src/sync/merge-apply.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
|
|
5
5
|
import { agentDir } from "../snapshot/session-paths.js";
|
|
6
6
|
import { createSnapshot, snapshotTarget } from "../snapshot/snapshot.js";
|
|
7
7
|
import { preflightSnapshotApply, preflightSnapshotMutations } from "../snapshot/snapshot-apply.js";
|
|
8
|
+
import { assertCanonicalMutationTargets } from "../snapshot/snapshot-mutation-queues.js";
|
|
8
9
|
import { sessionStorageRoot } from "../snapshot/snapshot-paths.js";
|
|
9
10
|
import type { Snapshot, SnapshotOptions } from "../snapshot/snapshot-types.js";
|
|
10
11
|
import { syncDirectory } from "../state/json-file.js";
|
|
@@ -34,6 +35,10 @@ export async function preflightMergedTargets(
|
|
|
34
35
|
for (const relative of paths) {
|
|
35
36
|
if (changed.has(relative)) await assertFilesystemTarget(root, relative, options, removedTargets, beforeHashes);
|
|
36
37
|
}
|
|
38
|
+
await assertCanonicalMutationTargets(
|
|
39
|
+
[...changed].map((relative) => snapshotTarget(root, relative, options.sessionDir)),
|
|
40
|
+
options,
|
|
41
|
+
);
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
function replacementPaths(removed: Set<string>, afterHashes: Readonly<Record<string, string>>) {
|
|
@@ -244,6 +249,7 @@ export async function applyMergedSnapshot(
|
|
|
244
249
|
protectedTarget,
|
|
245
250
|
new Set(Object.keys(afterHashes)),
|
|
246
251
|
);
|
|
252
|
+
await assertCanonicalMutationTargets(targets, options);
|
|
247
253
|
// Existing case aliases share Pi's non-reentrant realpath queue. Reserve the
|
|
248
254
|
// missing destination separately only after deleting its reviewed preimage.
|
|
249
255
|
const targetsByQueue = new Map<string, string[]>();
|
package/src/sync/merged-sync.ts
CHANGED
|
@@ -102,7 +102,7 @@ export async function mergeSync(
|
|
|
102
102
|
options.agentRetryRefusal &&
|
|
103
103
|
(!config.agentMode || options.auto || options.background || !ctx.hasUI || !["tui", "rpc"].includes(ctx.mode))
|
|
104
104
|
)
|
|
105
|
-
throw new Error("Refused-candidate retry requires foreground
|
|
105
|
+
throw new Error("Refused-candidate retry requires foreground Autonomous sync in TUI or RPC.");
|
|
106
106
|
};
|
|
107
107
|
const config = await loadConfig(options.setup);
|
|
108
108
|
validateOwner(config);
|
|
@@ -131,7 +131,7 @@ export async function mergeSync(
|
|
|
131
131
|
throw new Error("Session storage root changed during transfer; evidence retained for review.");
|
|
132
132
|
}
|
|
133
133
|
if (config.agentMode && JSON.stringify(ctx.model) !== modelToken)
|
|
134
|
-
throw new Error("
|
|
134
|
+
throw new Error("Autonomous sync paused: selected model changed during transfer.");
|
|
135
135
|
if (options.auto && (!startupTransferEnabled(latest) || !ctx.isIdle()))
|
|
136
136
|
throw new Error("Automatic transfer is no longer authorized at this idle boundary.");
|
|
137
137
|
});
|
|
@@ -171,7 +171,7 @@ export async function mergeSync(
|
|
|
171
171
|
// Recovery above never publishes. This gate applies only to new autonomous transfers.
|
|
172
172
|
if ((options.auto || config.agentMode) && backend.capability === "read-check-write-verify") {
|
|
173
173
|
throw new Error(
|
|
174
|
-
"Automatic transfer requires conditional publication or a lease; this backend supports only read/check/write/verify. Disable
|
|
174
|
+
"Automatic transfer requires conditional publication or a lease; this backend supports only read/check/write/verify. Disable Autonomous sync and use manual sync.",
|
|
175
175
|
);
|
|
176
176
|
}
|
|
177
177
|
const textBudget = { remaining: TEXT_MERGE_WORK_LIMIT };
|
|
@@ -353,7 +353,7 @@ export async function mergeSync(
|
|
|
353
353
|
if (plan.kind !== "planned" || (plan.conflicts.length && !config.partialSync))
|
|
354
354
|
throw new ContinuousError(
|
|
355
355
|
"unsafe",
|
|
356
|
-
"
|
|
356
|
+
"Autonomous sync paused: unsupported or refused conflict groups remain; no transfer was performed.",
|
|
357
357
|
);
|
|
358
358
|
}
|
|
359
359
|
if (
|
|
@@ -433,7 +433,7 @@ export async function syncBoth(
|
|
|
433
433
|
options.background?.validate(config);
|
|
434
434
|
if (options.agentRetryRefusal) {
|
|
435
435
|
if (!config.agentMode || options.auto || options.background || !ctx.hasUI)
|
|
436
|
-
throw new Error("Enable
|
|
436
|
+
throw new Error("Enable Autonomous sync and use the foreground manager to retry a cached refusal.");
|
|
437
437
|
const state = await readStateForConfig(config);
|
|
438
438
|
validateMutation();
|
|
439
439
|
const pending = await readMergeJournal(config);
|
|
@@ -6,10 +6,10 @@ import { confirmMergeReview } from "./merge-review.js";
|
|
|
6
6
|
import { safeTerminalText } from "./terminal-text.js";
|
|
7
7
|
|
|
8
8
|
export const AGENT_MODE_DESCRIPTION =
|
|
9
|
-
"This setup
|
|
9
|
+
"This setup: sync eligible changes at session startup and on Sync now without transfer confirmation. Use the selected Pi model to resolve supported text and settings conflicts. Eligible conflict content may be sent to its provider and incur costs. Selected local and remote content can be replaced or deleted. Unresolved conflicts pause unless partial sync permits independent progress. Continuous sync is a separate opt-in. No automatic reload.";
|
|
10
10
|
|
|
11
11
|
export const CONTINUOUS_DESCRIPTION =
|
|
12
|
-
"This setup
|
|
12
|
+
"This setup: recurring sync while Pi is open. Requires Autonomous sync. Watches selected files, checks the remote every 60–66 seconds, and retries temporary failures. Local changes and retries can cause extra requests between polls. May disclose conflict text and incur model/storage costs. No automatic reload.";
|
|
13
13
|
|
|
14
14
|
export function saveContinuousMode(
|
|
15
15
|
ctx: ExtensionCommandContext,
|
|
@@ -33,8 +33,8 @@ export async function saveAgentMode(
|
|
|
33
33
|
const setup = settings.syncSetups[setupName];
|
|
34
34
|
if (!setup || settings.activeSyncSetup !== setupName) throw new Error("Current sync setup changed; reopen Settings.");
|
|
35
35
|
const continuous = preference === "continuous";
|
|
36
|
-
const label = continuous ? "Continuous sync" : "
|
|
37
|
-
if (enabled && continuous && !setup.sync.agentMode) throw new Error("Enable
|
|
36
|
+
const label = continuous ? "Continuous sync" : "Autonomous sync";
|
|
37
|
+
if (enabled && continuous && !setup.sync.agentMode) throw new Error("Enable Autonomous sync before Continuous sync.");
|
|
38
38
|
if (Boolean(setup.sync[preference]) === enabled) return true;
|
|
39
39
|
if (enabled) {
|
|
40
40
|
const summary = [
|
|
@@ -59,7 +59,7 @@ export async function saveAgentMode(
|
|
|
59
59
|
if (
|
|
60
60
|
!(await confirmMergeReview(
|
|
61
61
|
ctx,
|
|
62
|
-
`Enable
|
|
62
|
+
`Enable ${label}?`,
|
|
63
63
|
summary,
|
|
64
64
|
signal,
|
|
65
65
|
() => !signal?.aborted,
|
|
@@ -80,7 +80,7 @@ export async function saveAgentMode(
|
|
|
80
80
|
settings.storageConnections[setup.storage.connection],
|
|
81
81
|
)
|
|
82
82
|
)
|
|
83
|
-
throw new Error("Sync setup or connection changed during
|
|
83
|
+
throw new Error("Sync setup or connection changed during Autonomous sync review; reopen Settings.");
|
|
84
84
|
return {
|
|
85
85
|
...current,
|
|
86
86
|
syncSetups: { ...current.syncSetups, [setupName]: { ...setup, sync: { ...setup.sync, [preference]: enabled } } },
|
|
@@ -28,6 +28,7 @@ interface CancellableOperationOptions {
|
|
|
28
28
|
cancelledMessage?: string | null;
|
|
29
29
|
target?: string;
|
|
30
30
|
signal?: AbortSignal;
|
|
31
|
+
isCurrent?: () => boolean;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export async function runCancellableOperation(
|
|
@@ -37,21 +38,45 @@ export async function runCancellableOperation(
|
|
|
37
38
|
runRoute: RunRoute,
|
|
38
39
|
options: CancellableOperationOptions = {},
|
|
39
40
|
): Promise<CancellableOperationResult> {
|
|
41
|
+
const result = await runCancellableTask(
|
|
42
|
+
ctx,
|
|
43
|
+
message,
|
|
44
|
+
(signal, onCommit, onDialog) => runRoute(route, signal, onCommit, options.target, onDialog),
|
|
45
|
+
options,
|
|
46
|
+
);
|
|
47
|
+
return result.kind === "completed" ? (result.value ?? { kind: "failed" }) : result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type CancellableTaskResult<Value> =
|
|
51
|
+
| { kind: "completed"; value: Value }
|
|
52
|
+
| { kind: "closed" }
|
|
53
|
+
| { kind: "cancelled" };
|
|
54
|
+
|
|
55
|
+
/** Keep non-route work under the same cancellation and drain owner as transfers. */
|
|
56
|
+
export async function runCancellableTask<Value>(
|
|
57
|
+
ctx: ExtensionContext,
|
|
58
|
+
message: string,
|
|
59
|
+
task: (
|
|
60
|
+
signal: AbortSignal | undefined,
|
|
61
|
+
onCommit?: () => void,
|
|
62
|
+
onDialog?: (active: boolean) => void,
|
|
63
|
+
) => Promise<Value>,
|
|
64
|
+
options: Omit<CancellableOperationOptions, "target"> = {},
|
|
65
|
+
): Promise<CancellableTaskResult<Value>> {
|
|
40
66
|
const {
|
|
41
67
|
commitAware = false,
|
|
42
68
|
cancelAcrossDialogs = false,
|
|
43
69
|
cancelledMessage = "Check cancelled; no settings or files were changed.",
|
|
44
|
-
target,
|
|
45
70
|
signal,
|
|
71
|
+
isCurrent = () => true,
|
|
46
72
|
} = options;
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
}
|
|
73
|
+
if (signal?.aborted || !isCurrent()) return { kind: "closed" };
|
|
74
|
+
if (ctx.mode !== "tui") return { kind: "completed", value: await task(signal) };
|
|
50
75
|
let commitStarted = false;
|
|
51
|
-
let
|
|
76
|
+
let taskResult!: Value;
|
|
52
77
|
const interaction = await runCustomInteraction<{ cancelled?: boolean; error?: unknown }, ExtensionContext>(ctx, {
|
|
53
78
|
signal,
|
|
54
|
-
isCurrent: () => !signal?.aborted,
|
|
79
|
+
isCurrent: () => !signal?.aborted && isCurrent(),
|
|
55
80
|
create: ({ tui, theme, keybindings, signal: interactionSignal, complete }) => {
|
|
56
81
|
const loader = new BorderedLoader(tui, theme, message, { cancellable: false });
|
|
57
82
|
const cancelHint = formatInteractionHints(keybindings, [
|
|
@@ -80,13 +105,11 @@ export async function runCancellableOperation(
|
|
|
80
105
|
return cancel(data) ? { consume: true } : undefined;
|
|
81
106
|
})
|
|
82
107
|
: undefined;
|
|
83
|
-
let
|
|
108
|
+
let pendingTask: Promise<Value>;
|
|
84
109
|
try {
|
|
85
|
-
|
|
86
|
-
route,
|
|
110
|
+
pendingTask = task(
|
|
87
111
|
interactionSignal,
|
|
88
112
|
commitAware ? () => (commitStarted = true) : undefined,
|
|
89
|
-
target,
|
|
90
113
|
cancelAcrossDialogs
|
|
91
114
|
? (active) => {
|
|
92
115
|
activeDialogs += active ? 1 : -1;
|
|
@@ -98,9 +121,9 @@ export async function runCancellableOperation(
|
|
|
98
121
|
loader.dispose();
|
|
99
122
|
throw error;
|
|
100
123
|
}
|
|
101
|
-
const operation =
|
|
124
|
+
const operation = pendingTask.then(
|
|
102
125
|
(result) => {
|
|
103
|
-
|
|
126
|
+
taskResult = result;
|
|
104
127
|
complete({});
|
|
105
128
|
},
|
|
106
129
|
(error: unknown) => complete({ error }),
|
|
@@ -134,7 +157,7 @@ export async function runCancellableOperation(
|
|
|
134
157
|
return { kind: "cancelled" };
|
|
135
158
|
}
|
|
136
159
|
if (interaction.value.error) throw interaction.value.error;
|
|
137
|
-
return
|
|
160
|
+
return { kind: "completed", value: taskResult };
|
|
138
161
|
}
|
|
139
162
|
|
|
140
163
|
function hasControlCharacter(value: string) {
|
package/src/ui/manager-state.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
2
|
-
import { loadConfig } from "../settings/config.js";
|
|
2
|
+
import { loadConfig, syncCheckConfigFingerprint } from "../settings/config.js";
|
|
3
3
|
import { activeLocalConfigPath } from "../settings/config-file.js";
|
|
4
4
|
import { readLocalConfigObject } from "../settings/settings-store.js";
|
|
5
5
|
import type { AnySyncConfig } from "../settings/settings-types.js";
|
|
@@ -39,6 +39,7 @@ export const MAIN_MENU_ACTIONS = [
|
|
|
39
39
|
export interface ManagerDescription {
|
|
40
40
|
title: string;
|
|
41
41
|
actions: string[];
|
|
42
|
+
configReview?: { setupName: string; fingerprint: string };
|
|
42
43
|
operation?: OperationAvailability;
|
|
43
44
|
attention?: SyncAttentionState;
|
|
44
45
|
observation?: StartupObservation;
|
|
@@ -164,7 +165,7 @@ export async function describeManagerState(
|
|
|
164
165
|
`Included: ${selection.builtIns.length} built-in group${selection.builtIns.length === 1 ? "" : "s"} · ${selection.custom.length} extra path${selection.custom.length === 1 ? "" : "s"} · Sessions ${selection.sessions ? "on" : "off"}`,
|
|
165
166
|
`Automatic sync: ${automaticSyncSummary(config.automatic)}`,
|
|
166
167
|
`Startup transfer: ${startupTransferSummary(config)}`,
|
|
167
|
-
`
|
|
168
|
+
`Autonomous sync: ${config.agentMode ? "On (autonomous merged sync)" : "Off"}`,
|
|
168
169
|
...(config.continuous ? await (await import("../sync/continuous-record.js")).continuousStatusLines(config) : []),
|
|
169
170
|
`Last applied: ${lastAppliedSnapshot}`,
|
|
170
171
|
...(noSyncedContent
|
|
@@ -176,6 +177,7 @@ export async function describeManagerState(
|
|
|
176
177
|
return {
|
|
177
178
|
title: (changesBlocked ? ["Manage sync", ...operationStatusLines(operation)] : ordinaryTitle).join("\n"),
|
|
178
179
|
actions: operationActions(operation, noSyncedContent, canSwitch, mainActions),
|
|
180
|
+
configReview: { setupName: config.setupName, fingerprint: syncCheckConfigFingerprint(config) },
|
|
179
181
|
operation,
|
|
180
182
|
...(currentObservation ? { observation: currentObservation } : {}),
|
|
181
183
|
...(currentAttention
|
package/src/ui/manager-ui.ts
CHANGED
|
@@ -31,6 +31,15 @@ export async function showSyncManager(
|
|
|
31
31
|
await runRoute("help");
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
|
+
const validateOwner = captureMutationOwner(ctx, sessionSignal);
|
|
35
|
+
const isCurrent = () => {
|
|
36
|
+
try {
|
|
37
|
+
validateOwner();
|
|
38
|
+
return true;
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
34
43
|
type Screen = "main";
|
|
35
44
|
type Action =
|
|
36
45
|
| "review-attention"
|
|
@@ -77,10 +86,11 @@ export async function showSyncManager(
|
|
|
77
86
|
},
|
|
78
87
|
},
|
|
79
88
|
actions: {
|
|
80
|
-
"review-attention": async () => {
|
|
81
|
-
const attention =
|
|
89
|
+
"review-attention": async ({ state }) => {
|
|
90
|
+
const attention = state.manager.attention;
|
|
91
|
+
if (attention && options.getAttention?.() !== attention) return { kind: "stay" };
|
|
82
92
|
if (!attention) {
|
|
83
|
-
const observation =
|
|
93
|
+
const observation = state.manager.observation;
|
|
84
94
|
if (!observation) return { kind: "stay" };
|
|
85
95
|
// A head-only observation is not an authoritative decision. Load a fresh
|
|
86
96
|
// snapshot only after the user activates the existing review flow.
|
|
@@ -113,24 +123,21 @@ export async function showSyncManager(
|
|
|
113
123
|
);
|
|
114
124
|
return { kind: disposition };
|
|
115
125
|
},
|
|
116
|
-
sync: async () => {
|
|
126
|
+
sync: async ({ state }) => {
|
|
117
127
|
const pendingAttention = options.getAttention?.();
|
|
118
|
-
if (pendingAttention) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (pendingAttention.decision.setupName === activeConfig.setupName) {
|
|
122
|
-
ctx.ui.notify("Review synced content before starting Sync now.", "warning");
|
|
123
|
-
return { kind: "stay" };
|
|
124
|
-
}
|
|
128
|
+
if (pendingAttention?.decision.setupName === state.manager.configReview?.setupName) {
|
|
129
|
+
ctx.ui.notify("Review synced content before starting Sync now.", "warning");
|
|
130
|
+
return { kind: "stay" };
|
|
125
131
|
}
|
|
126
132
|
const result = await runCancellableOperation(ctx, "Checking current sync setup…", "sync", runRoute, {
|
|
127
133
|
commitAware: true,
|
|
128
134
|
signal: sessionSignal,
|
|
135
|
+
target: state.manager.configReview?.setupName,
|
|
129
136
|
});
|
|
130
137
|
const disposition = await dispatchManagerResult(ctx, result, "sync", runRoute, sessionSignal);
|
|
131
138
|
return disposition.kind === "close" ? { kind: "close" } : { kind: "stay" };
|
|
132
139
|
},
|
|
133
|
-
"retry-refusal": async ({ signal: actionSignal }) => {
|
|
140
|
+
"retry-refusal": async ({ state, signal: actionSignal }) => {
|
|
134
141
|
const signal = sessionSignal ? AbortSignal.any([sessionSignal, actionSignal]) : actionSignal;
|
|
135
142
|
const validate = captureMutationOwner(ctx, signal);
|
|
136
143
|
validate();
|
|
@@ -140,6 +147,7 @@ export async function showSyncManager(
|
|
|
140
147
|
commitAware: true,
|
|
141
148
|
cancelAcrossDialogs: true,
|
|
142
149
|
signal,
|
|
150
|
+
target: state.manager.configReview?.setupName,
|
|
143
151
|
cancelledMessage: "Model retry cancelled. Submitted requests and committed writes cannot be undone.",
|
|
144
152
|
});
|
|
145
153
|
if (signal.aborted) return { kind: "close" };
|
|
@@ -155,23 +163,25 @@ export async function showSyncManager(
|
|
|
155
163
|
const result = await showSetupSwitcher(ctx, runRoute, undefined, sessionSignal);
|
|
156
164
|
return result === "pull-attempted" || result === "closed" ? { kind: "close" } : { kind: "stay" };
|
|
157
165
|
},
|
|
158
|
-
settings: async () => {
|
|
159
|
-
const result = await showSyncSettings(ctx, runRoute, sessionSignal);
|
|
166
|
+
settings: async ({ state }) => {
|
|
167
|
+
const result = await showSyncSettings(ctx, runRoute, sessionSignal, state.manager.configReview?.setupName);
|
|
160
168
|
return { kind: result === "exit" ? "close" : "stay" };
|
|
161
169
|
},
|
|
162
|
-
pull: async () => {
|
|
170
|
+
pull: async ({ state }) => {
|
|
163
171
|
const result = await runCancellableOperation(ctx, "Checking remote changes…", "pull", runRoute, {
|
|
164
172
|
commitAware: true,
|
|
165
173
|
cancelledMessage: "Pull check cancelled; no local files were changed.",
|
|
174
|
+
target: state.manager.configReview?.setupName,
|
|
166
175
|
signal: sessionSignal,
|
|
167
176
|
});
|
|
168
177
|
const disposition = await dispatchManagerResult(ctx, result, "pull", runRoute, sessionSignal);
|
|
169
178
|
return disposition.kind === "close" ? { kind: "close" } : { kind: "stay" };
|
|
170
179
|
},
|
|
171
|
-
push: async () => {
|
|
180
|
+
push: async ({ state }) => {
|
|
172
181
|
const result = await runCancellableOperation(ctx, "Preparing push preview…", "push", runRoute, {
|
|
173
182
|
commitAware: true,
|
|
174
183
|
cancelledMessage: "Push preparation cancelled; no remote files were changed.",
|
|
184
|
+
target: state.manager.configReview?.setupName,
|
|
175
185
|
signal: sessionSignal,
|
|
176
186
|
});
|
|
177
187
|
const disposition = await dispatchManagerResult(ctx, result, "push", runRoute, sessionSignal);
|
|
@@ -185,31 +195,34 @@ export async function showSyncManager(
|
|
|
185
195
|
await showStorageConnections(ctx, sessionSignal);
|
|
186
196
|
return { kind: "stay" };
|
|
187
197
|
},
|
|
188
|
-
history: async () => {
|
|
198
|
+
history: async ({ state }) => {
|
|
189
199
|
const result = await runCancellableOperation(ctx, "Loading sync history…", "history", runRoute, {
|
|
190
200
|
signal: sessionSignal,
|
|
191
201
|
commitAware: true,
|
|
192
202
|
cancelAcrossDialogs: true,
|
|
203
|
+
target: state.manager.configReview?.setupName,
|
|
193
204
|
cancelledMessage: "History review cancelled; no files were changed.",
|
|
194
205
|
});
|
|
195
206
|
return { kind: result.kind === "closed" ? "close" : "stay" };
|
|
196
207
|
},
|
|
197
|
-
diff: async () => {
|
|
208
|
+
diff: async ({ state }) => {
|
|
198
209
|
const result = await runCancellableOperation(ctx, "Comparing local and remote…", "diff", runRoute, {
|
|
210
|
+
target: state.manager.configReview?.setupName,
|
|
199
211
|
signal: sessionSignal,
|
|
200
212
|
});
|
|
201
213
|
return { kind: result.kind === "closed" ? "close" : "stay" };
|
|
202
214
|
},
|
|
203
|
-
status: async () => {
|
|
215
|
+
status: async ({ state }) => {
|
|
204
216
|
const result = await runCancellableOperation(ctx, "Checking remote status…", "status", runRoute, {
|
|
217
|
+
target: state.manager.configReview?.setupName,
|
|
205
218
|
signal: sessionSignal,
|
|
206
219
|
});
|
|
207
220
|
return { kind: result.kind === "closed" ? "close" : "stay" };
|
|
208
221
|
},
|
|
209
|
-
doctor: async ({ signal: actionSignal }) => {
|
|
222
|
+
doctor: async ({ state, signal: actionSignal }) => {
|
|
210
223
|
const signal = sessionSignal ? AbortSignal.any([sessionSignal, actionSignal]) : actionSignal;
|
|
211
224
|
const validate = captureMutationOwner(ctx, signal);
|
|
212
|
-
const config = await loadConfig();
|
|
225
|
+
const config = await loadConfig(state.manager.configReview?.setupName);
|
|
213
226
|
validate();
|
|
214
227
|
const confirmed = await ctx.ui.confirm(
|
|
215
228
|
"Run diagnostics?",
|
|
@@ -241,11 +254,12 @@ export async function showSyncManager(
|
|
|
241
254
|
return { kind: "close" };
|
|
242
255
|
},
|
|
243
256
|
init: async () => {
|
|
244
|
-
await runRoute("init");
|
|
257
|
+
await runRoute("init", sessionSignal);
|
|
245
258
|
return { kind: "stay" };
|
|
246
259
|
},
|
|
247
|
-
conflicts: async () => {
|
|
260
|
+
conflicts: async ({ state }) => {
|
|
248
261
|
const result = await runCancellableOperation(ctx, "Reviewing private conflicts…", "conflicts", runRoute, {
|
|
262
|
+
target: state.manager.configReview?.setupName,
|
|
249
263
|
commitAware: true,
|
|
250
264
|
signal: sessionSignal,
|
|
251
265
|
});
|
|
@@ -254,6 +268,44 @@ export async function showSyncManager(
|
|
|
254
268
|
},
|
|
255
269
|
},
|
|
256
270
|
});
|
|
271
|
+
// Setup-specific actions must use the configuration shown by this screen, not
|
|
272
|
+
// an active setup selected by another process while the menu was open.
|
|
273
|
+
for (const action of [
|
|
274
|
+
"review-attention",
|
|
275
|
+
"sync",
|
|
276
|
+
"retry-refusal",
|
|
277
|
+
"settings",
|
|
278
|
+
"pull",
|
|
279
|
+
"push",
|
|
280
|
+
"history",
|
|
281
|
+
"diff",
|
|
282
|
+
"status",
|
|
283
|
+
"doctor",
|
|
284
|
+
"conflicts",
|
|
285
|
+
] as const) {
|
|
286
|
+
const activate = menu.actions[action];
|
|
287
|
+
menu.actions[action] = async (invocation) => {
|
|
288
|
+
validateOwner();
|
|
289
|
+
const review = invocation.state.manager.configReview;
|
|
290
|
+
let current = false;
|
|
291
|
+
try {
|
|
292
|
+
const config = await loadConfig();
|
|
293
|
+
validateOwner();
|
|
294
|
+
invocation.signal.throwIfAborted();
|
|
295
|
+
current = review?.fingerprint === syncCheckConfigFingerprint(config);
|
|
296
|
+
} catch {
|
|
297
|
+
if (!isCurrent() || invocation.signal.aborted) return { kind: "close" };
|
|
298
|
+
}
|
|
299
|
+
if (!current) {
|
|
300
|
+
ctx.ui.notify(
|
|
301
|
+
"Sync setup changed while the manager was open. Review the refreshed setup before continuing.",
|
|
302
|
+
"warning",
|
|
303
|
+
);
|
|
304
|
+
return { kind: "stay" };
|
|
305
|
+
}
|
|
306
|
+
return activate(invocation);
|
|
307
|
+
};
|
|
308
|
+
}
|
|
257
309
|
const menuContext: ExtensionCommandContext = {
|
|
258
310
|
...ctx,
|
|
259
311
|
ui: {
|
|
@@ -293,7 +345,7 @@ export async function showSyncManager(
|
|
|
293
345
|
return { manager };
|
|
294
346
|
},
|
|
295
347
|
signal: sessionSignal,
|
|
296
|
-
isCurrent
|
|
348
|
+
isCurrent,
|
|
297
349
|
});
|
|
298
350
|
}
|
|
299
351
|
|