@narumitw/pi-sync 0.54.2 → 0.56.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/README.md +40 -15
- package/dist/chunks/agent-candidate-3JN6DJKT.ts +26 -0
- package/dist/chunks/agent-plan-QCUJWBP4.ts +99 -0
- package/dist/chunks/agent-plan-QCUJWBP4.ts.map +7 -0
- package/dist/chunks/agent-runner-E2JU2VZ6.ts +182 -0
- package/dist/chunks/agent-runner-E2JU2VZ6.ts.map +7 -0
- package/dist/chunks/agent-store-A3F6IYWZ.ts +152 -0
- package/dist/chunks/agent-store-A3F6IYWZ.ts.map +7 -0
- package/dist/chunks/chunk-5Q5SSKNG.ts +128 -0
- package/dist/chunks/chunk-5Q5SSKNG.ts.map +7 -0
- package/dist/chunks/chunk-5XZOC7FD.ts +215 -0
- package/dist/chunks/chunk-5XZOC7FD.ts.map +7 -0
- package/dist/chunks/{chunk-IJ6VV6FN.ts → chunk-AAEEHR7H.ts} +31 -87
- package/dist/chunks/chunk-AAEEHR7H.ts.map +7 -0
- package/dist/chunks/{chunk-CHSIAC4N.ts → chunk-B4QZBDBT.ts} +2 -2
- package/dist/chunks/{chunk-CL7BZRRQ.ts → chunk-CRKTAPSL.ts} +8 -8
- package/dist/chunks/chunk-DT7M4VQS.ts +69 -0
- package/dist/chunks/chunk-DT7M4VQS.ts.map +7 -0
- package/dist/chunks/chunk-ED3TYIVW.ts +229 -0
- package/dist/chunks/chunk-ED3TYIVW.ts.map +7 -0
- package/dist/chunks/{chunk-VHXBCEVF.ts → chunk-EUMINX7L.ts} +6 -6
- package/dist/chunks/chunk-FQOSHGJ7.ts +215 -0
- package/dist/chunks/chunk-FQOSHGJ7.ts.map +7 -0
- package/dist/chunks/chunk-GNH73BJ5.ts +153 -0
- package/dist/chunks/chunk-GNH73BJ5.ts.map +7 -0
- package/dist/chunks/{chunk-UQ3ALBIB.ts → chunk-GP5DU62N.ts} +8 -9
- package/dist/chunks/chunk-GP5DU62N.ts.map +7 -0
- package/dist/chunks/{chunk-5RZPINUQ.ts → chunk-GSNPASKB.ts} +2 -2
- package/dist/chunks/{chunk-QE7NZZWT.ts → chunk-IWZH5KYS.ts} +23 -7
- package/dist/chunks/chunk-IWZH5KYS.ts.map +7 -0
- package/dist/chunks/{chunk-5ZWQ33XL.ts → chunk-JJTARUSO.ts} +15 -14
- package/dist/chunks/chunk-JJTARUSO.ts.map +7 -0
- package/dist/chunks/{chunk-2DI6PDZV.ts → chunk-NLWS55B6.ts} +15 -2
- package/dist/chunks/chunk-NLWS55B6.ts.map +7 -0
- package/dist/chunks/{chunk-SDZCTCYQ.ts → chunk-OEWQVWLC.ts} +4 -4
- package/dist/chunks/{chunk-MYPSMJHJ.ts → chunk-OTZRCKYP.ts} +380 -510
- package/dist/chunks/chunk-OTZRCKYP.ts.map +7 -0
- package/dist/chunks/{chunk-WFKUKVCP.ts → chunk-OWBAZA6J.ts} +24 -12
- package/dist/chunks/chunk-OWBAZA6J.ts.map +7 -0
- package/dist/chunks/chunk-PRUEIIK7.ts +24 -0
- package/dist/chunks/chunk-PRUEIIK7.ts.map +7 -0
- package/dist/chunks/chunk-QGK445K7.ts +21 -0
- package/dist/chunks/chunk-QGK445K7.ts.map +7 -0
- package/dist/chunks/{chunk-3B4Z2SKV.ts → chunk-QO333NM4.ts} +49 -21
- package/dist/chunks/chunk-QO333NM4.ts.map +7 -0
- package/dist/chunks/{chunk-4SBTAVFL.ts → chunk-QSHMOL3I.ts} +22 -24
- package/dist/chunks/chunk-QSHMOL3I.ts.map +7 -0
- package/dist/chunks/chunk-S2FMH5FT.ts +97 -0
- package/dist/chunks/chunk-S2FMH5FT.ts.map +7 -0
- package/dist/chunks/{chunk-BVVP33U5.ts → chunk-SWRVXVFD.ts} +12 -9
- package/dist/chunks/chunk-SWRVXVFD.ts.map +7 -0
- package/dist/chunks/{chunk-ZX2Q6THB.ts → chunk-UBV24CG4.ts} +4 -5
- package/dist/chunks/chunk-UBV24CG4.ts.map +7 -0
- package/dist/chunks/chunk-UOHR7LRO.ts +70 -0
- package/dist/chunks/chunk-UOHR7LRO.ts.map +7 -0
- package/dist/chunks/{chunk-4QVPTIZB.ts → chunk-UPNVIUBN.ts} +3 -3
- package/dist/chunks/chunk-UZPMDLTL.ts +54 -0
- package/dist/chunks/chunk-UZPMDLTL.ts.map +7 -0
- package/dist/chunks/chunk-VJN7KU4J.ts +350 -0
- package/dist/chunks/chunk-VJN7KU4J.ts.map +7 -0
- package/dist/chunks/chunk-YHT4YTYG.ts +31 -0
- package/dist/chunks/chunk-YHT4YTYG.ts.map +7 -0
- package/dist/chunks/{chunk-V6VZ5TLG.ts → chunk-YK52VDXG.ts} +6 -5
- package/dist/chunks/chunk-YK52VDXG.ts.map +7 -0
- package/dist/chunks/config-KGIYTTXQ.ts +37 -0
- package/dist/chunks/{conflict-artifacts-BB2FZMIE.ts → conflict-artifacts-FXFIIBCB.ts} +8 -7
- package/dist/chunks/{conflict-review-4PLVLMRO.ts → conflict-review-6WIEQLXW.ts} +31 -24
- package/dist/chunks/{conflict-review-4PLVLMRO.ts.map → conflict-review-6WIEQLXW.ts.map} +1 -1
- package/dist/chunks/continuous-record-LFRVXQBD.ts +30 -0
- package/dist/chunks/continuous-sync-AWAAOXK2.ts +545 -0
- package/dist/chunks/continuous-sync-AWAAOXK2.ts.map +7 -0
- package/dist/chunks/{file-selection-WZX7HAY2.ts → file-selection-3JJYOVNR.ts} +5 -5
- package/dist/chunks/{git-backend-FD7AVSK6.ts → git-backend-KYDZ673X.ts} +28 -32
- package/dist/chunks/git-backend-KYDZ673X.ts.map +7 -0
- package/dist/chunks/{manager-result-dispatcher-6MQKFCWB.ts → manager-result-dispatcher-AGBBVDDS.ts} +10 -9
- package/dist/chunks/{manager-ui-IURS4WXY.ts → manager-ui-KRAW3NE2.ts} +464 -164
- package/dist/chunks/manager-ui-KRAW3NE2.ts.map +7 -0
- package/dist/chunks/{merge-baseline-store-EVQAAML6.ts → merge-baseline-store-RFCAH7KL.ts} +7 -5
- package/dist/chunks/{remote-selection-ui-6LJJ4IMQ.ts → remote-selection-ui-5725JDNC.ts} +9 -8
- package/dist/chunks/remote-selection-ui-5725JDNC.ts.map +7 -0
- package/dist/chunks/{s3-backend-AQGV6LFT.ts → s3-backend-Y7WSCLJI.ts} +38 -38
- package/dist/chunks/s3-backend-Y7WSCLJI.ts.map +7 -0
- package/dist/chunks/{setup-switch-KTUWL6DE.ts → setup-switch-E7PHKI6J.ts} +7 -6
- package/dist/chunks/setup-switch-E7PHKI6J.ts.map +7 -0
- package/dist/chunks/{setup-wizard-GSDPDFC4.ts → setup-wizard-65DROCIJ.ts} +14 -13
- package/dist/chunks/setup-wizard-65DROCIJ.ts.map +7 -0
- package/dist/chunks/{sync-inspection-K45QVLHB.ts → sync-inspection-XFJOAMJD.ts} +9 -8
- package/dist/chunks/sync-inspection-XFJOAMJD.ts.map +7 -0
- package/dist/chunks/{sync-operations-47OUX76C.ts → sync-operations-HDUDT5PD.ts} +188 -73
- package/dist/chunks/sync-operations-HDUDT5PD.ts.map +7 -0
- package/dist/chunks/{webdav-backend-TBILVEQK.ts → webdav-backend-6K26F6LT.ts} +39 -48
- package/dist/chunks/webdav-backend-6K26F6LT.ts.map +7 -0
- package/dist/index.ts +246 -131
- package/dist/index.ts.map +3 -3
- package/docs/agent-mode-verification.md +99 -0
- package/docs/continuous-sync-verification.md +87 -0
- package/docs/settings.md +131 -11
- package/docs/tui-contract.md +61 -0
- package/package.json +3 -2
- package/src/backends/git/git-backend.ts +1 -4
- package/src/backends/git/git-runner.ts +1 -5
- package/src/backends/s3/s3-backend.ts +3 -25
- package/src/backends/s3/s3-client.ts +14 -2
- package/src/backends/snapshot-pointer.ts +27 -0
- package/src/backends/webdav/webdav-backend.ts +3 -29
- package/src/backends/webdav/webdav-client.ts +11 -7
- package/src/commands/command-execution.ts +7 -3
- package/src/commands/command-handler.ts +15 -3
- package/src/commands/command-types.ts +10 -0
- package/src/commands/command.ts +4 -1
- package/src/settings/config.ts +20 -10
- package/src/settings/settings-store.ts +1 -34
- package/src/settings/settings-types.ts +10 -0
- package/src/settings/settings-validation.ts +4 -1
- package/src/snapshot/snapshot-codec.ts +1 -5
- package/src/state/lock.ts +13 -10
- package/src/state/merge-baseline-store.ts +19 -1
- package/src/state/state-directory.ts +8 -1
- package/src/sync/agent-candidate.ts +251 -0
- package/src/sync/agent-plan.ts +87 -0
- package/src/sync/agent-policy.ts +13 -0
- package/src/sync/agent-request.ts +17 -0
- package/src/sync/agent-runner.ts +188 -0
- package/src/sync/agent-store.ts +151 -0
- package/src/sync/agent-sync.ts +91 -0
- package/src/sync/apply-review.ts +63 -0
- package/src/sync/automatic-sync.ts +46 -33
- package/src/sync/continuous-errors.ts +85 -0
- package/src/sync/continuous-lease.ts +221 -0
- package/src/sync/continuous-record.ts +179 -0
- package/src/sync/continuous-storage.ts +55 -0
- package/src/sync/continuous-sync.ts +516 -0
- package/src/sync/continuous-watch.ts +59 -0
- package/src/sync/merged-sync.ts +268 -112
- package/src/sync/remote-snapshot.ts +2 -3
- package/src/sync/settings-merge.ts +19 -3
- package/src/sync/setup-switch.ts +1 -5
- package/src/sync/startup-check.ts +51 -29
- package/src/sync/startup-timings.ts +90 -0
- package/src/sync/sync-inspection.ts +16 -5
- package/src/sync/sync-mutations.ts +55 -28
- package/src/sync/sync-queries.ts +19 -3
- package/src/sync/text-merge.ts +3 -3
- package/src/sync-extension.ts +75 -19
- package/src/ui/agent-mode-setting.ts +91 -0
- package/src/ui/manager-state.ts +12 -6
- package/src/ui/manager-ui.ts +57 -7
- package/src/ui/merge-review.ts +3 -3
- package/src/ui/merged-sync-summary.ts +2 -18
- package/src/ui/remote-selection-ui.ts +5 -5
- package/src/ui/settings-frame.ts +55 -0
- package/src/ui/settings-ui.ts +223 -43
- package/src/ui/setup/s3-credentials-ui.ts +1 -5
- package/src/ui/setup/setup-name-ui.ts +2 -5
- package/src/ui/setup/setup-prompts.ts +4 -3
- package/src/ui/setup/setup-switcher.ts +2 -0
- package/src/ui/sync-behavior.ts +15 -0
- package/src/ui/sync-format.ts +69 -38
- package/src/ui/sync-setups-ui.ts +3 -5
- package/src/ui/transfer-changes.ts +33 -0
- package/src/ui/transfer-review.ts +27 -0
- package/dist/chunks/chunk-2DI6PDZV.ts.map +0 -7
- package/dist/chunks/chunk-3B4Z2SKV.ts.map +0 -7
- package/dist/chunks/chunk-4SBTAVFL.ts.map +0 -7
- package/dist/chunks/chunk-5ZWQ33XL.ts.map +0 -7
- package/dist/chunks/chunk-BVVP33U5.ts.map +0 -7
- package/dist/chunks/chunk-IJ6VV6FN.ts.map +0 -7
- package/dist/chunks/chunk-MYPSMJHJ.ts.map +0 -7
- package/dist/chunks/chunk-ORQRHEEO.ts +0 -362
- package/dist/chunks/chunk-ORQRHEEO.ts.map +0 -7
- package/dist/chunks/chunk-QE7NZZWT.ts.map +0 -7
- package/dist/chunks/chunk-UQ3ALBIB.ts.map +0 -7
- package/dist/chunks/chunk-V6VZ5TLG.ts.map +0 -7
- package/dist/chunks/chunk-WFKUKVCP.ts.map +0 -7
- package/dist/chunks/chunk-ZX2Q6THB.ts.map +0 -7
- package/dist/chunks/git-backend-FD7AVSK6.ts.map +0 -7
- package/dist/chunks/manager-ui-IURS4WXY.ts.map +0 -7
- package/dist/chunks/s3-backend-AQGV6LFT.ts.map +0 -7
- package/dist/chunks/setup-wizard-GSDPDFC4.ts.map +0 -7
- package/dist/chunks/sync-operations-47OUX76C.ts.map +0 -7
- package/dist/chunks/webdav-backend-TBILVEQK.ts.map +0 -7
- /package/dist/chunks/{conflict-artifacts-BB2FZMIE.ts.map → agent-candidate-3JN6DJKT.ts.map} +0 -0
- /package/dist/chunks/{chunk-CHSIAC4N.ts.map → chunk-B4QZBDBT.ts.map} +0 -0
- /package/dist/chunks/{chunk-CL7BZRRQ.ts.map → chunk-CRKTAPSL.ts.map} +0 -0
- /package/dist/chunks/{chunk-VHXBCEVF.ts.map → chunk-EUMINX7L.ts.map} +0 -0
- /package/dist/chunks/{chunk-5RZPINUQ.ts.map → chunk-GSNPASKB.ts.map} +0 -0
- /package/dist/chunks/{chunk-SDZCTCYQ.ts.map → chunk-OEWQVWLC.ts.map} +0 -0
- /package/dist/chunks/{chunk-4QVPTIZB.ts.map → chunk-UPNVIUBN.ts.map} +0 -0
- /package/dist/chunks/{manager-result-dispatcher-6MQKFCWB.ts.map → config-KGIYTTXQ.ts.map} +0 -0
- /package/dist/chunks/{merge-baseline-store-EVQAAML6.ts.map → conflict-artifacts-FXFIIBCB.ts.map} +0 -0
- /package/dist/chunks/{remote-selection-ui-6LJJ4IMQ.ts.map → continuous-record-LFRVXQBD.ts.map} +0 -0
- /package/dist/chunks/{file-selection-WZX7HAY2.ts.map → file-selection-3JJYOVNR.ts.map} +0 -0
- /package/dist/chunks/{setup-switch-KTUWL6DE.ts.map → manager-result-dispatcher-AGBBVDDS.ts.map} +0 -0
- /package/dist/chunks/{sync-inspection-K45QVLHB.ts.map → merge-baseline-store-RFCAH7KL.ts.map} +0 -0
package/src/sync/merged-sync.ts
CHANGED
|
@@ -22,7 +22,12 @@ import {
|
|
|
22
22
|
scanSnapshot,
|
|
23
23
|
} from "../snapshot/snapshot.js";
|
|
24
24
|
import type { Snapshot } from "../snapshot/snapshot-types.js";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
hasCurrentMergeBaseline,
|
|
27
|
+
pruneMergeBaselines,
|
|
28
|
+
readMergeAncestors,
|
|
29
|
+
stageMergeBaseline,
|
|
30
|
+
} from "../state/merge-baseline-store.js";
|
|
26
31
|
import {
|
|
27
32
|
readStateForConfig,
|
|
28
33
|
statePathForConfig,
|
|
@@ -32,6 +37,7 @@ import {
|
|
|
32
37
|
import { confirmMergeReview } from "../ui/merge-review.js";
|
|
33
38
|
import { mergedSyncSummary } from "../ui/merged-sync-summary.js";
|
|
34
39
|
import { safeTerminalText } from "../ui/terminal-text.js";
|
|
40
|
+
import { startupTransferEnabled } from "./agent-policy.js";
|
|
35
41
|
import { releaseConflictArtifacts } from "./conflict-artifact-ownership.js";
|
|
36
42
|
import {
|
|
37
43
|
type CreatedConflictArtifact,
|
|
@@ -60,6 +66,7 @@ import {
|
|
|
60
66
|
requireCompatibleRemoteSelection,
|
|
61
67
|
} from "./remote-snapshot.js";
|
|
62
68
|
import { resolveSettingsConflicts } from "./settings-conflicts.js";
|
|
69
|
+
import { createStartupTimings, type StartupTimings } from "./startup-timings.js";
|
|
63
70
|
import { createSyncDecision } from "./sync-decision.js";
|
|
64
71
|
import { backupLocal, captureMutationOwner, protectedSessionPaths } from "./sync-local.js";
|
|
65
72
|
import { inspectRemoteSelection } from "./sync-policy.js";
|
|
@@ -75,7 +82,12 @@ export async function mergeSync(
|
|
|
75
82
|
factory: SyncBackendFactory = createSyncBackend,
|
|
76
83
|
resolution?: ConflictResolution,
|
|
77
84
|
) {
|
|
78
|
-
const
|
|
85
|
+
const timings = options.startupTimings ?? createStartupTimings();
|
|
86
|
+
const sessionOwner = captureMutationOwner(ctx, options.signal);
|
|
87
|
+
const validateOwner = (config?: AnySyncConfig) => {
|
|
88
|
+
sessionOwner();
|
|
89
|
+
options.background?.validate(config);
|
|
90
|
+
};
|
|
79
91
|
const isCurrent = () => {
|
|
80
92
|
try {
|
|
81
93
|
validateOwner();
|
|
@@ -85,7 +97,9 @@ export async function mergeSync(
|
|
|
85
97
|
}
|
|
86
98
|
};
|
|
87
99
|
const config = await loadConfig(options.setup);
|
|
100
|
+
validateOwner(config);
|
|
88
101
|
const configToken = syncCheckConfigFingerprint(config);
|
|
102
|
+
const modelToken = JSON.stringify(ctx.model);
|
|
89
103
|
const sessionRoot = config.include.includes("sessions") ? await effectiveSessionRoot(ctx) : undefined;
|
|
90
104
|
validateOwner();
|
|
91
105
|
const snapshotOptions = {
|
|
@@ -93,33 +107,43 @@ export async function mergeSync(
|
|
|
93
107
|
sessionDir: sessionRoot,
|
|
94
108
|
signal: options.signal,
|
|
95
109
|
};
|
|
96
|
-
const validate =
|
|
97
|
-
|
|
98
|
-
const latest = await loadConfig(options.setup);
|
|
99
|
-
validateOwner();
|
|
100
|
-
if (configToken !== syncCheckConfigFingerprint(latest))
|
|
101
|
-
throw new Error("Sync setup or settings changed during transfer; reopen sync.");
|
|
102
|
-
if (sessionRoot !== undefined) {
|
|
103
|
-
const currentRoot = await effectiveSessionRoot(ctx);
|
|
110
|
+
const validate = () =>
|
|
111
|
+
timings.measure("transfer authorization", async () => {
|
|
104
112
|
validateOwner();
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
const latest = await loadConfig(options.setup);
|
|
114
|
+
validateOwner(latest);
|
|
115
|
+
if (configToken !== syncCheckConfigFingerprint(latest))
|
|
116
|
+
throw new Error("Sync setup or settings changed during transfer; reopen sync.");
|
|
117
|
+
if (sessionRoot !== undefined) {
|
|
118
|
+
const currentRoot = await effectiveSessionRoot(ctx);
|
|
119
|
+
validateOwner();
|
|
120
|
+
if (currentRoot !== sessionRoot)
|
|
121
|
+
throw new Error("Session storage root changed during transfer; evidence retained for review.");
|
|
122
|
+
}
|
|
123
|
+
if (config.agentMode && JSON.stringify(ctx.model) !== modelToken)
|
|
124
|
+
throw new Error("Agent mode paused: selected model changed during transfer.");
|
|
125
|
+
if (options.auto && (!startupTransferEnabled(latest) || !ctx.isIdle()))
|
|
126
|
+
throw new Error("Automatic transfer is no longer authorized at this idle boundary.");
|
|
127
|
+
});
|
|
111
128
|
await validate();
|
|
112
|
-
const backend = await factory(config);
|
|
129
|
+
const backend = await timings.measure("transfer backend initialization", () => factory(config));
|
|
113
130
|
await validate();
|
|
114
|
-
|
|
115
|
-
throw new Error(
|
|
116
|
-
"Automatic transfer requires conditional publication or a lease; this backend supports only read/check/write/verify. Use manual sync.",
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
const pending = await readMergeJournal(config);
|
|
131
|
+
const pending = await timings.measure("transfer journal read", () => readMergeJournal(config));
|
|
120
132
|
await validate();
|
|
121
133
|
if (pending) {
|
|
122
|
-
const recovered = await
|
|
134
|
+
const recovered = await timings.measure("transfer journal recovery", () =>
|
|
135
|
+
completeJournal(
|
|
136
|
+
ctx,
|
|
137
|
+
config,
|
|
138
|
+
backend,
|
|
139
|
+
pending,
|
|
140
|
+
validate,
|
|
141
|
+
timings,
|
|
142
|
+
options.signal,
|
|
143
|
+
options.auto,
|
|
144
|
+
options.background?.validate,
|
|
145
|
+
),
|
|
146
|
+
);
|
|
123
147
|
if (!recovered) {
|
|
124
148
|
if ((options.auto || !options.silent) && !options.signal?.aborted)
|
|
125
149
|
ctx.ui.notify(
|
|
@@ -132,24 +156,69 @@ export async function mergeSync(
|
|
|
132
156
|
ctx.ui.notify("Recovered the committed merged transfer. Run sync again to inspect newer changes.", "info");
|
|
133
157
|
return "applied" as const;
|
|
134
158
|
}
|
|
159
|
+
// Recovery above never publishes. This gate applies only to new autonomous transfers.
|
|
160
|
+
if ((options.auto || config.agentMode) && backend.capability === "read-check-write-verify") {
|
|
161
|
+
throw new Error(
|
|
162
|
+
"Automatic transfer requires conditional publication or a lease; this backend supports only read/check/write/verify. Disable Agent mode and use manual sync.",
|
|
163
|
+
);
|
|
164
|
+
}
|
|
135
165
|
const textBudget = { remaining: TEXT_MERGE_WORK_LIMIT };
|
|
136
166
|
const createdArtifacts: CreatedConflictArtifact[] = [];
|
|
137
167
|
const ownedStatePath = statePathForConfig(config);
|
|
138
168
|
try {
|
|
139
169
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
140
170
|
await validate();
|
|
141
|
-
const state = await readStateForConfig(config);
|
|
171
|
+
const state = await timings.measure("transfer state read", () => readStateForConfig(config));
|
|
142
172
|
if (state.unresolved?.length && !config.partialSync)
|
|
143
173
|
throw new Error(
|
|
144
174
|
"Partial progress remains unresolved; re-enable partial sync or review an explicit force direction.",
|
|
145
175
|
);
|
|
146
|
-
const localRaw = await
|
|
176
|
+
const localRaw = await timings.measure("transfer local snapshot", () =>
|
|
177
|
+
createSnapshot(config.snapshotIdentity, snapshotOptions),
|
|
178
|
+
);
|
|
147
179
|
const local = portableSnapshot(localRaw, config.localFields);
|
|
148
180
|
if (!sameLocalFields(state.localFields, config.localFields))
|
|
149
181
|
throw new Error("Local-field rules changed; review and confirm a directional migration first.");
|
|
150
|
-
const head = await backend.readHead(options.signal);
|
|
182
|
+
const head = await timings.measure("transfer remote head", () => backend.readHead(options.signal));
|
|
151
183
|
await validate();
|
|
152
|
-
|
|
184
|
+
// This is a read-only shortcut, never authority to apply or publish. Pending
|
|
185
|
+
// journals were handled above. Legacy/partial baselines retain the full path.
|
|
186
|
+
if (
|
|
187
|
+
options.auto &&
|
|
188
|
+
!resolution &&
|
|
189
|
+
!config.partialSync &&
|
|
190
|
+
head &&
|
|
191
|
+
state.lastAppliedSnapshot &&
|
|
192
|
+
state.lastRemoteRevision &&
|
|
193
|
+
!state.unresolved?.length &&
|
|
194
|
+
head.snapshotId === state.lastAppliedSnapshot &&
|
|
195
|
+
backend.sameRevision(head.revision, state.lastRemoteRevision) &&
|
|
196
|
+
inspectRemoteSelection(config.include, { selection: head.selection, files: [] }).kind === "same" &&
|
|
197
|
+
matchesAcceptedState(state, config, head, local)
|
|
198
|
+
) {
|
|
199
|
+
const unchanged = await timings.measure("transfer unchanged verification", async () => {
|
|
200
|
+
if (!(await hasCurrentMergeBaseline(config, local, state))) return false;
|
|
201
|
+
await validate();
|
|
202
|
+
const currentLocal = await timings.measure("transfer local snapshot", () =>
|
|
203
|
+
createSnapshot(config.snapshotIdentity, snapshotOptions),
|
|
204
|
+
);
|
|
205
|
+
await validate();
|
|
206
|
+
const currentState = await timings.measure("transfer state read", () => readStateForConfig(config));
|
|
207
|
+
await validate();
|
|
208
|
+
return (
|
|
209
|
+
sameHashes(fileHashMap(currentLocal), fileHashMap(localRaw)) &&
|
|
210
|
+
syncStateFingerprint(currentState) === syncStateFingerprint(state)
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
await validate();
|
|
214
|
+
if (unchanged) {
|
|
215
|
+
timings.markUnchanged();
|
|
216
|
+
return "applied" as const;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const rawRemote = head
|
|
220
|
+
? await timings.measure("transfer snapshot download", () => readSnapshotForHead(backend, head, options.signal))
|
|
221
|
+
: undefined;
|
|
153
222
|
await validate();
|
|
154
223
|
if (rawRemote) {
|
|
155
224
|
const files = [...local.files, ...rawRemote.files];
|
|
@@ -167,12 +236,15 @@ export async function mergeSync(
|
|
|
167
236
|
: undefined;
|
|
168
237
|
if (rawRemote) {
|
|
169
238
|
requireCompatibleRemoteSelection(config, rawRemote);
|
|
170
|
-
const validation =
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
239
|
+
const validation = await timings.measure("transfer merge planning", () =>
|
|
240
|
+
planFileMerge({
|
|
241
|
+
baseline: state.lastFileHashes,
|
|
242
|
+
local: local.files,
|
|
243
|
+
remote: rawRemote.files,
|
|
244
|
+
selectionCompatible: true,
|
|
245
|
+
}),
|
|
246
|
+
);
|
|
247
|
+
await validate();
|
|
176
248
|
const managedPaths = new Set([
|
|
177
249
|
...Object.keys(state.lastFileHashes),
|
|
178
250
|
...[...local.files, ...(remote?.files ?? [])].map((file) => file.path),
|
|
@@ -206,30 +278,32 @@ export async function mergeSync(
|
|
|
206
278
|
throw review("The established remote is missing; choose an explicit recovery direction.", "remote-empty");
|
|
207
279
|
const selectionState = inspectRemoteSelection(config.include, rawRemote);
|
|
208
280
|
const selectionCompatible = selectionState.kind === "same";
|
|
209
|
-
let plan =
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
281
|
+
let plan = await timings.measure("transfer merge planning", () =>
|
|
282
|
+
planFileMerge({
|
|
283
|
+
baseline: state.lastFileHashes,
|
|
284
|
+
local: local.files,
|
|
285
|
+
remote: remote.files,
|
|
286
|
+
selectionCompatible,
|
|
287
|
+
protectedPaths: protectedSessionPaths(ctx, sessionRoot),
|
|
288
|
+
}),
|
|
289
|
+
);
|
|
290
|
+
await validate();
|
|
216
291
|
let resolved = { plan, fields: [] as string[] };
|
|
217
292
|
if (config.mergeSettings) {
|
|
218
293
|
try {
|
|
219
|
-
resolved = await
|
|
294
|
+
resolved = await timings.measure("transfer settings merge", () =>
|
|
295
|
+
resolveSettingsConflicts(config, state, local, remote, plan),
|
|
296
|
+
);
|
|
220
297
|
} catch (error) {
|
|
221
298
|
if (!config.partialSync) throw error;
|
|
222
299
|
}
|
|
223
300
|
}
|
|
224
301
|
await validate();
|
|
225
|
-
plan = await
|
|
226
|
-
config,
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
resolved.plan,
|
|
231
|
-
protectedSessionPaths(ctx, sessionRoot),
|
|
232
|
-
{ textBudget, signal: options.signal },
|
|
302
|
+
plan = await timings.measure("transfer content merge", () =>
|
|
303
|
+
resolveContentConflicts(config, state, local, remote, resolved.plan, protectedSessionPaths(ctx, sessionRoot), {
|
|
304
|
+
textBudget,
|
|
305
|
+
signal: options.signal,
|
|
306
|
+
}),
|
|
233
307
|
);
|
|
234
308
|
await validate();
|
|
235
309
|
if (resolution) {
|
|
@@ -246,7 +320,33 @@ export async function mergeSync(
|
|
|
246
320
|
);
|
|
247
321
|
await validate();
|
|
248
322
|
}
|
|
249
|
-
if (
|
|
323
|
+
if (config.agentMode && !resolution) {
|
|
324
|
+
const { resolveAgentPlan } = await import("./agent-plan.js");
|
|
325
|
+
await validate();
|
|
326
|
+
plan = await resolveAgentPlan(
|
|
327
|
+
ctx,
|
|
328
|
+
options,
|
|
329
|
+
config,
|
|
330
|
+
backend,
|
|
331
|
+
state,
|
|
332
|
+
local,
|
|
333
|
+
localRaw,
|
|
334
|
+
remote,
|
|
335
|
+
head,
|
|
336
|
+
plan,
|
|
337
|
+
snapshotOptions,
|
|
338
|
+
validate,
|
|
339
|
+
);
|
|
340
|
+
await validate();
|
|
341
|
+
if (plan.kind !== "planned" || (plan.conflicts.length && !config.partialSync))
|
|
342
|
+
throw new Error(
|
|
343
|
+
"Agent mode paused: unsupported or refused conflict groups remain; no transfer was performed.",
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
if (
|
|
347
|
+
plan.kind !== "planned" ||
|
|
348
|
+
(plan.conflicts.length && (!config.partialSync || (options.auto && !config.agentMode)))
|
|
349
|
+
) {
|
|
250
350
|
const decision = review(
|
|
251
351
|
selectionCompatible
|
|
252
352
|
? `Conflicting or protected paths require review; no merged transfer was performed. Use /sync diff and an explicit direction.${resolved.fields.length ? ` Settings fields: ${resolved.fields.map(safeTerminalText).join(", ")}` : ""}`
|
|
@@ -353,40 +453,49 @@ export async function mergeSync(
|
|
|
353
453
|
Boolean(config.partialSync && rawRemote.version !== 3);
|
|
354
454
|
const apply = !sameHashes(fileHashMap(localRaw), fileHashMap(after));
|
|
355
455
|
if (!publish && !apply) {
|
|
356
|
-
const beforeEvidence = await backend.readHead(options.signal);
|
|
456
|
+
const beforeEvidence = await timings.measure("transfer remote head", () => backend.readHead(options.signal));
|
|
357
457
|
await validate();
|
|
358
458
|
if (!beforeEvidence || !backend.sameRevision(beforeEvidence.revision, head.revision))
|
|
359
459
|
throw new Error("Remote changed before baseline acceptance; retry from a fresh observation.");
|
|
360
460
|
progress = await persistProgress();
|
|
361
461
|
await validate();
|
|
362
|
-
const currentLocal = await
|
|
462
|
+
const currentLocal = await timings.measure("transfer local snapshot", () =>
|
|
463
|
+
createSnapshot(config.snapshotIdentity, snapshotOptions),
|
|
464
|
+
);
|
|
363
465
|
await validate();
|
|
364
466
|
if (
|
|
365
467
|
!sameHashes(fileHashMap(currentLocal), fileHashMap(localRaw)) ||
|
|
366
|
-
syncStateFingerprint(await readStateForConfig(config)) !==
|
|
468
|
+
syncStateFingerprint(await timings.measure("transfer state read", () => readStateForConfig(config))) !==
|
|
469
|
+
syncStateFingerprint(state)
|
|
367
470
|
)
|
|
368
471
|
throw new Error(
|
|
369
472
|
"Local content or baseline changed before baseline acceptance; retry from a fresh observation.",
|
|
370
473
|
);
|
|
371
474
|
await validate();
|
|
372
|
-
const currentHead = await backend.readHead(options.signal);
|
|
475
|
+
const currentHead = await timings.measure("transfer remote head", () => backend.readHead(options.signal));
|
|
373
476
|
await validate();
|
|
374
477
|
if (!currentHead || !backend.sameRevision(currentHead.revision, head.revision))
|
|
375
478
|
throw new Error("Remote changed before baseline acceptance; retry from a fresh observation.");
|
|
376
|
-
await
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
479
|
+
await timings.measure("transfer baseline write", () =>
|
|
480
|
+
writeAcceptedState(config, head, accepted, validate, progress),
|
|
481
|
+
);
|
|
482
|
+
await timings.measure("transfer cleanup", () =>
|
|
483
|
+
pruneMergeBaselines(
|
|
484
|
+
config,
|
|
485
|
+
acceptedState(config, head, accepted, progress),
|
|
486
|
+
captureMutationOwner(ctx, options.signal),
|
|
487
|
+
),
|
|
381
488
|
);
|
|
382
489
|
await validate();
|
|
383
|
-
await
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
490
|
+
await timings.measure("transfer cleanup", () =>
|
|
491
|
+
pruneCompletedConflicts(
|
|
492
|
+
config,
|
|
493
|
+
backend.identity,
|
|
494
|
+
acceptedState(config, head, accepted, progress),
|
|
495
|
+
accepted,
|
|
496
|
+
remote,
|
|
497
|
+
captureMutationOwner(ctx, options.signal),
|
|
498
|
+
),
|
|
390
499
|
);
|
|
391
500
|
await validate();
|
|
392
501
|
if (!options.silent)
|
|
@@ -398,11 +507,13 @@ export async function mergeSync(
|
|
|
398
507
|
);
|
|
399
508
|
return "applied" as const;
|
|
400
509
|
}
|
|
401
|
-
await
|
|
510
|
+
await timings.measure("transfer preflight", () =>
|
|
511
|
+
preflightMergedTargets(localRaw, after, snapshotOptions, ctx.sessionManager.getSessionFile?.()),
|
|
512
|
+
);
|
|
402
513
|
await validate();
|
|
403
514
|
if (!config.skipSecretScan && scanSnapshot(upload).length)
|
|
404
515
|
throw new Error("Refusing to merge possible secrets. Review managed content before syncing.");
|
|
405
|
-
if (!options.yes) {
|
|
516
|
+
if (!options.yes && !config.agentMode) {
|
|
406
517
|
const summary = mergedSyncSummary({
|
|
407
518
|
setupName: config.setupName,
|
|
408
519
|
destination: backend.destination,
|
|
@@ -430,19 +541,26 @@ export async function mergeSync(
|
|
|
430
541
|
return "cancelled" as const;
|
|
431
542
|
}
|
|
432
543
|
await validate();
|
|
433
|
-
const refreshed = await
|
|
544
|
+
const refreshed = await timings.measure("transfer local snapshot", () =>
|
|
545
|
+
createSnapshot(config.snapshotIdentity, snapshotOptions),
|
|
546
|
+
);
|
|
434
547
|
if (!sameHashes(fileHashMap(refreshed), fileHashMap(localRaw)))
|
|
435
548
|
throw new Error("Local content changed during review; no merged transfer was performed.");
|
|
436
|
-
if (
|
|
549
|
+
if (
|
|
550
|
+
syncStateFingerprint(await timings.measure("transfer state read", () => readStateForConfig(config))) !==
|
|
551
|
+
syncStateFingerprint(state)
|
|
552
|
+
)
|
|
437
553
|
throw new Error("Sync baseline changed during review; retry from a fresh observation.");
|
|
438
554
|
await validate();
|
|
439
|
-
const refreshedHead = await backend.readHead(options.signal);
|
|
555
|
+
const refreshedHead = await timings.measure("transfer remote head", () => backend.readHead(options.signal));
|
|
440
556
|
await validate();
|
|
441
557
|
if (!refreshedHead || !backend.sameRevision(refreshedHead.revision, head.revision))
|
|
442
558
|
throw new Error("Remote changed during review; no merged transfer was performed.");
|
|
443
559
|
progress = await persistProgress();
|
|
444
560
|
await validate();
|
|
445
|
-
const backup = await
|
|
561
|
+
const backup = await timings.measure("transfer backup", () =>
|
|
562
|
+
backupLocal(config.snapshotIdentity, snapshotOptions, options.signal),
|
|
563
|
+
);
|
|
446
564
|
await validate();
|
|
447
565
|
const journal: MergeJournal = {
|
|
448
566
|
version: 1,
|
|
@@ -458,11 +576,15 @@ export async function mergeSync(
|
|
|
458
576
|
...(sessionRoot !== undefined ? { sessionRoot } : {}),
|
|
459
577
|
...(!publish ? { committedHead: head, applyOnly: true } : {}),
|
|
460
578
|
};
|
|
461
|
-
await writeMergeJournal(config, journal);
|
|
579
|
+
await timings.measure("transfer journal write", () => writeMergeJournal(config, journal));
|
|
462
580
|
// No backend call has begun: a stale candidate can be retired without ambiguous publication.
|
|
463
|
-
const atCommitState = syncStateFingerprint(
|
|
581
|
+
const atCommitState = syncStateFingerprint(
|
|
582
|
+
await timings.measure("transfer state read", () => readStateForConfig(config)),
|
|
583
|
+
);
|
|
464
584
|
await validate();
|
|
465
|
-
const atCommit = await
|
|
585
|
+
const atCommit = await timings.measure("transfer local snapshot", () =>
|
|
586
|
+
createSnapshot(config.snapshotIdentity, snapshotOptions),
|
|
587
|
+
);
|
|
466
588
|
await validate();
|
|
467
589
|
if (!sameHashes(fileHashMap(atCommit), fileHashMap(localRaw)) || atCommitState !== journal.stateIdentity) {
|
|
468
590
|
await validate();
|
|
@@ -473,7 +595,9 @@ export async function mergeSync(
|
|
|
473
595
|
}
|
|
474
596
|
// Backup/journal awaits can also change physical identity without changing any bytes.
|
|
475
597
|
try {
|
|
476
|
-
await
|
|
598
|
+
await timings.measure("transfer preflight", () =>
|
|
599
|
+
preflightMergedTargets(atCommit, after, snapshotOptions, ctx.sessionManager.getSessionFile?.()),
|
|
600
|
+
);
|
|
477
601
|
await validate();
|
|
478
602
|
} catch (error) {
|
|
479
603
|
await validate();
|
|
@@ -482,13 +606,15 @@ export async function mergeSync(
|
|
|
482
606
|
}
|
|
483
607
|
try {
|
|
484
608
|
if (publish) {
|
|
485
|
-
const result = await
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
609
|
+
const result = await timings.measure("transfer remote publication", () =>
|
|
610
|
+
backend.publishSnapshot(upload, expectedRemoteHead(head), {
|
|
611
|
+
signal: options.signal,
|
|
612
|
+
onCommit: options.onCommit,
|
|
613
|
+
}),
|
|
614
|
+
);
|
|
489
615
|
journal.committedHead = result.head;
|
|
490
616
|
journal.warnings = result.warnings;
|
|
491
|
-
await writeMergeJournal(config, journal);
|
|
617
|
+
await timings.measure("transfer journal write", () => writeMergeJournal(config, journal));
|
|
492
618
|
} else options.onCommit?.();
|
|
493
619
|
} catch (error) {
|
|
494
620
|
if (
|
|
@@ -504,7 +630,19 @@ export async function mergeSync(
|
|
|
504
630
|
// Every uncertain/after-commit outcome retains evidence and is never blindly retried.
|
|
505
631
|
throw mergeTransferError(error, config.backend);
|
|
506
632
|
}
|
|
507
|
-
const completed = await
|
|
633
|
+
const completed = await timings.measure("transfer journal recovery", () =>
|
|
634
|
+
completeJournal(
|
|
635
|
+
ctx,
|
|
636
|
+
config,
|
|
637
|
+
backend,
|
|
638
|
+
journal,
|
|
639
|
+
validate,
|
|
640
|
+
timings,
|
|
641
|
+
options.signal,
|
|
642
|
+
options.auto,
|
|
643
|
+
options.background?.validate,
|
|
644
|
+
),
|
|
645
|
+
);
|
|
508
646
|
if (!completed) {
|
|
509
647
|
if (!options.signal?.aborted && (options.auto || !options.silent))
|
|
510
648
|
ctx.ui.notify(
|
|
@@ -529,7 +667,7 @@ export async function mergeSync(
|
|
|
529
667
|
return "applied" as const;
|
|
530
668
|
}
|
|
531
669
|
} finally {
|
|
532
|
-
await releaseConflictArtifacts(config, ownedStatePath, createdArtifacts);
|
|
670
|
+
await timings.measure("transfer cleanup", () => releaseConflictArtifacts(config, ownedStatePath, createdArtifacts));
|
|
533
671
|
}
|
|
534
672
|
}
|
|
535
673
|
|
|
@@ -539,8 +677,10 @@ async function completeJournal(
|
|
|
539
677
|
backend: SyncBackend,
|
|
540
678
|
journal: MergeJournal,
|
|
541
679
|
validate: () => Promise<void>,
|
|
680
|
+
timings: StartupTimings,
|
|
542
681
|
signal?: AbortSignal,
|
|
543
682
|
auto = false,
|
|
683
|
+
validateBackground?: () => void,
|
|
544
684
|
) {
|
|
545
685
|
if (
|
|
546
686
|
journal.identity !== mergeJournalIdentity(config, backend.identity) ||
|
|
@@ -558,7 +698,7 @@ async function completeJournal(
|
|
|
558
698
|
);
|
|
559
699
|
}
|
|
560
700
|
const snapshotOptions = { ...snapshotOptionsForContext(ctx, config), sessionDir: sessionRoot, signal };
|
|
561
|
-
const head = await backend.readHead(signal);
|
|
701
|
+
const head = await timings.measure("transfer remote head", () => backend.readHead(signal));
|
|
562
702
|
if (
|
|
563
703
|
!journal.committedHead &&
|
|
564
704
|
head &&
|
|
@@ -566,10 +706,13 @@ async function completeJournal(
|
|
|
566
706
|
backend.matchesUncommittedRecoveryHead?.(head, journal.expectedHead))
|
|
567
707
|
) {
|
|
568
708
|
await validate();
|
|
569
|
-
const local = await
|
|
709
|
+
const local = await timings.measure("transfer local snapshot", () =>
|
|
710
|
+
createSnapshot(config.snapshotIdentity, snapshotOptions),
|
|
711
|
+
);
|
|
570
712
|
if (
|
|
571
713
|
!sameHashes(fileHashMap(local), fileHashMap(journal.before)) ||
|
|
572
|
-
syncStateFingerprint(await readStateForConfig(config)) !==
|
|
714
|
+
syncStateFingerprint(await timings.measure("transfer state read", () => readStateForConfig(config))) !==
|
|
715
|
+
journal.stateIdentity
|
|
573
716
|
) {
|
|
574
717
|
throw new Error(
|
|
575
718
|
"Uncertain publication is not active, but local content or baseline changed; keep the journal for reviewed recovery.",
|
|
@@ -582,7 +725,7 @@ async function completeJournal(
|
|
|
582
725
|
// Baseline publication is the durable completion boundary. A later remote revision
|
|
583
726
|
// cannot undo that acceptance; retire only the journal for the recorded commit.
|
|
584
727
|
if (journal.committedHead && (!head || !backend.sameRevision(head.revision, journal.committedHead.revision))) {
|
|
585
|
-
const state = await readStateForConfig(config);
|
|
728
|
+
const state = await timings.measure("transfer state read", () => readStateForConfig(config));
|
|
586
729
|
await validate();
|
|
587
730
|
if (
|
|
588
731
|
matchesAcceptedState(state, config, journal.committedHead, journal.accepted ?? journal.after, journal.progress)
|
|
@@ -595,8 +738,10 @@ async function completeJournal(
|
|
|
595
738
|
}
|
|
596
739
|
}
|
|
597
740
|
if (journal.applyOnly && (!head || !backend.sameRevision(head.revision, journal.expectedHead.revision))) {
|
|
598
|
-
const local = await
|
|
599
|
-
|
|
741
|
+
const local = await timings.measure("transfer local snapshot", () =>
|
|
742
|
+
createSnapshot(config.snapshotIdentity, snapshotOptions),
|
|
743
|
+
);
|
|
744
|
+
const state = await timings.measure("transfer state read", () => readStateForConfig(config));
|
|
600
745
|
await validate();
|
|
601
746
|
if (
|
|
602
747
|
sameHashes(fileHashMap(local), fileHashMap(journal.before)) &&
|
|
@@ -616,14 +761,16 @@ async function completeJournal(
|
|
|
616
761
|
"Publication outcome cannot be reconciled with the current remote head. Journal and backup retained; do not retry publication or restore old bytes blindly.",
|
|
617
762
|
);
|
|
618
763
|
}
|
|
619
|
-
const observed = await
|
|
764
|
+
const observed = await timings.measure("transfer snapshot download", () =>
|
|
765
|
+
readSnapshotForHead(backend, head, signal),
|
|
766
|
+
);
|
|
620
767
|
requireCompatibleRemoteSelection(config, observed);
|
|
621
768
|
if (
|
|
622
769
|
!sameHashes(fileHashMap(observed), fileHashMap(journal.upload)) ||
|
|
623
770
|
JSON.stringify(observed.selection) !== JSON.stringify(journal.upload.selection)
|
|
624
771
|
)
|
|
625
772
|
throw new Error("Remote snapshot does not match the recorded merge publication.");
|
|
626
|
-
const state = await readStateForConfig(config);
|
|
773
|
+
const state = await timings.measure("transfer state read", () => readStateForConfig(config));
|
|
627
774
|
if (matchesAcceptedState(state, config, head, journal.accepted ?? journal.after, journal.progress)) {
|
|
628
775
|
await validate();
|
|
629
776
|
await clearMergeJournal(config);
|
|
@@ -644,38 +791,47 @@ async function completeJournal(
|
|
|
644
791
|
const validateOwner = captureMutationOwner(ctx, signal);
|
|
645
792
|
const validateMutation = () => {
|
|
646
793
|
validateOwner();
|
|
794
|
+
validateBackground?.();
|
|
647
795
|
if (auto && !ctx.isIdle()) throw new Error("Automatic file apply is no longer idle; evidence retained.");
|
|
648
796
|
};
|
|
649
|
-
await
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
797
|
+
await timings.measure("transfer local apply", () =>
|
|
798
|
+
applyMergedSnapshot(
|
|
799
|
+
journal.before,
|
|
800
|
+
journal.after,
|
|
801
|
+
protectedSessionPaths(ctx, sessionRoot),
|
|
802
|
+
{ ...snapshotOptions, validateMutation },
|
|
803
|
+
validate,
|
|
804
|
+
ctx.sessionManager.getSessionFile?.(),
|
|
805
|
+
),
|
|
656
806
|
);
|
|
657
807
|
await validate();
|
|
658
|
-
const headAtAcceptance = await backend.readHead(signal);
|
|
808
|
+
const headAtAcceptance = await timings.measure("transfer remote head", () => backend.readHead(signal));
|
|
659
809
|
await validate();
|
|
660
810
|
if (!headAtAcceptance || !backend.sameRevision(headAtAcceptance.revision, head.revision))
|
|
661
811
|
throw new Error("Remote changed during merged apply; journal and backup retained for review.");
|
|
662
|
-
await
|
|
812
|
+
await timings.measure("transfer baseline write", () =>
|
|
813
|
+
writeAcceptedState(config, head, journal.accepted ?? journal.after, validate, journal.progress),
|
|
814
|
+
);
|
|
663
815
|
await validate();
|
|
664
816
|
await clearMergeJournal(config);
|
|
665
817
|
await validate();
|
|
666
|
-
await
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
818
|
+
await timings.measure("transfer cleanup", () =>
|
|
819
|
+
pruneMergeBaselines(
|
|
820
|
+
config,
|
|
821
|
+
acceptedState(config, head, journal.accepted ?? journal.after, journal.progress),
|
|
822
|
+
captureMutationOwner(ctx, signal),
|
|
823
|
+
),
|
|
670
824
|
);
|
|
671
825
|
await validate();
|
|
672
|
-
await
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
826
|
+
await timings.measure("transfer cleanup", () =>
|
|
827
|
+
pruneCompletedConflicts(
|
|
828
|
+
config,
|
|
829
|
+
backend.identity,
|
|
830
|
+
acceptedState(config, head, journal.accepted ?? journal.after, journal.progress),
|
|
831
|
+
journal.accepted ?? journal.after,
|
|
832
|
+
journal.upload,
|
|
833
|
+
captureMutationOwner(ctx, signal),
|
|
834
|
+
),
|
|
679
835
|
);
|
|
680
836
|
await validate();
|
|
681
837
|
return true;
|
|
@@ -5,6 +5,7 @@ import { filterSnapshotForConfigPolicy } from "../snapshot/snapshot.js";
|
|
|
5
5
|
import type { Snapshot } from "../snapshot/snapshot-types.js";
|
|
6
6
|
import { safeTerminalText } from "../ui/terminal-text.js";
|
|
7
7
|
import { sameLocalFields, validatePortableSnapshot } from "./local-fields.js";
|
|
8
|
+
import { throwIfAborted } from "./signals.js";
|
|
8
9
|
import {
|
|
9
10
|
inspectRemoteSelection,
|
|
10
11
|
type RemoteSelectionState,
|
|
@@ -15,9 +16,7 @@ import {
|
|
|
15
16
|
|
|
16
17
|
export async function readSnapshotForHead(backend: SyncBackend, head: RemoteHead, signal?: AbortSignal) {
|
|
17
18
|
const snapshot = await backend.readSnapshot(head.snapshotRef, signal);
|
|
18
|
-
|
|
19
|
-
throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError");
|
|
20
|
-
}
|
|
19
|
+
throwIfAborted(signal);
|
|
21
20
|
validatePortableSnapshot(snapshot);
|
|
22
21
|
if (snapshot.id !== head.snapshotId) {
|
|
23
22
|
throw new Error(`Remote head ${head.snapshotId} resolved to unexpected snapshot ${snapshot.id}.`);
|