@narumitw/pi-sync 0.57.10 → 0.57.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +5 -1
- package/dist/chunks/{agent-candidate-EYB35AGL.ts → agent-candidate-OCDPRI5G.ts} +11 -11
- package/dist/chunks/{agent-plan-KO43GTOR.ts → agent-plan-P5B5RGZ7.ts} +14 -14
- package/dist/chunks/{agent-refusal-retry-C2HQROTP.ts → agent-refusal-retry-476YGNU5.ts} +10 -10
- package/dist/chunks/{agent-store-UT52DS7B.ts → agent-store-RJSMNOYP.ts} +7 -7
- package/dist/chunks/{agent-sync-VM3TXWA3.ts → agent-sync-EUK2CPSS.ts} +6 -6
- package/dist/chunks/{chunk-UCD7TQDT.ts → chunk-3IYAPMST.ts} +4 -4
- 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-IMMMZUFC.ts → chunk-67INJPKR.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-2VSN3MFX.ts → chunk-7OLLPPLU.ts} +29 -23
- package/dist/chunks/chunk-7OLLPPLU.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-PWA3YN5Z.ts → chunk-CPT2BN6N.ts} +2 -2
- package/dist/chunks/{chunk-73MDIVYJ.ts → chunk-DN4IA7MR.ts} +6 -6
- package/dist/chunks/{chunk-4RLATUP4.ts → chunk-DRTC4PKM.ts} +6 -6
- package/dist/chunks/{chunk-PHANBTVM.ts → chunk-ESKOPQVB.ts} +4 -4
- package/dist/chunks/{chunk-ODF2M4RF.ts → chunk-F7E3VFXU.ts} +5 -5
- 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-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-QWV3XKNJ.ts → chunk-TIMWN2RH.ts} +5 -5
- package/dist/chunks/{chunk-Z5O2TLNF.ts → chunk-TZC7GDJE.ts} +7 -7
- 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-M5IV3KL2.ts → chunk-ZDDBZH72.ts} +3 -3
- package/dist/chunks/{command-handler-FO6F2APH.ts → command-handler-F2E7A5T5.ts} +22 -17
- package/dist/chunks/command-handler-F2E7A5T5.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-2QMO2K6Q.ts} +23 -23
- package/dist/chunks/{continuous-record-4DQ2EAZS.ts → continuous-record-AH7JGYKM.ts} +6 -6
- package/dist/chunks/{continuous-sync-ZT3UEQLK.ts → continuous-sync-VRMXCA5X.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-LM7DSJ2Y.ts} +103 -52
- package/dist/chunks/manager-ui-LM7DSJ2Y.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-PIPXYPCW.ts} +5 -5
- package/dist/chunks/{setup-wizard-OHZXJVQZ.ts → setup-wizard-RR5U3SUB.ts} +6 -6
- 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-CV3TULLD.ts} +25 -25
- 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 +19 -19
- package/package.json +1 -1
- package/skills/configure-pi-sync/references/configuration.md +8 -1
- package/src/backends/s3/s3-client.ts +8 -1
- package/src/commands/command-execution.ts +8 -3
- 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/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/merge-apply.ts +6 -0
- package/src/ui/cancellable-operation.ts +36 -13
- package/src/ui/manager-state.ts +3 -1
- package/src/ui/manager-ui.ts +76 -24
- package/src/ui/remote-selection-ui.ts +131 -82
- package/src/ui/settings-ui.ts +2 -1
- 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-OCDPRI5G.ts.map} +0 -0
- /package/dist/chunks/{agent-plan-KO43GTOR.ts.map → agent-plan-P5B5RGZ7.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-store-UT52DS7B.ts.map → agent-store-RJSMNOYP.ts.map} +0 -0
- /package/dist/chunks/{agent-sync-VM3TXWA3.ts.map → agent-sync-EUK2CPSS.ts.map} +0 -0
- /package/dist/chunks/{chunk-UCD7TQDT.ts.map → chunk-3IYAPMST.ts.map} +0 -0
- /package/dist/chunks/{chunk-DOJRPBQU.ts.map → chunk-5TSCBBPP.ts.map} +0 -0
- /package/dist/chunks/{chunk-IMMMZUFC.ts.map → chunk-67INJPKR.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-PWA3YN5Z.ts.map → chunk-CPT2BN6N.ts.map} +0 -0
- /package/dist/chunks/{chunk-73MDIVYJ.ts.map → chunk-DN4IA7MR.ts.map} +0 -0
- /package/dist/chunks/{chunk-4RLATUP4.ts.map → chunk-DRTC4PKM.ts.map} +0 -0
- /package/dist/chunks/{chunk-PHANBTVM.ts.map → chunk-ESKOPQVB.ts.map} +0 -0
- /package/dist/chunks/{chunk-ODF2M4RF.ts.map → chunk-F7E3VFXU.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-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-2QMO2K6Q.ts.map} +0 -0
- /package/dist/chunks/{continuous-record-4DQ2EAZS.ts.map → continuous-record-AH7JGYKM.ts.map} +0 -0
- /package/dist/chunks/{continuous-sync-ZT3UEQLK.ts.map → continuous-sync-VRMXCA5X.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-PIPXYPCW.ts.map} +0 -0
- /package/dist/chunks/{setup-wizard-OHZXJVQZ.ts.map → setup-wizard-RR5U3SUB.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-operations-X4AOXMBT.ts.map → sync-operations-CV3TULLD.ts.map} +0 -0
- /package/dist/chunks/{sync-state-SWLSMK4G.ts.map → sync-state-7L4NT7W2.ts.map} +0 -0
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
|
|
|
@@ -2,17 +2,13 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import { defineMenu, runMenu } from "@narumitw/pi-tui-kit";
|
|
3
3
|
import { createSyncBackend, type SyncBackendFactory } from "../backends/backend-factory.js";
|
|
4
4
|
import type { RemoteHead, SyncBackend } from "../backends/sync-backend.js";
|
|
5
|
-
import {
|
|
6
|
-
loadConfig,
|
|
7
|
-
loadPartialConfig,
|
|
8
|
-
type SyncSetupStorageReview,
|
|
9
|
-
syncConfigReviewFingerprint,
|
|
10
|
-
} from "../settings/config.js";
|
|
5
|
+
import { loadConfig, syncCheckConfigFingerprint, syncConfigReviewFingerprint } from "../settings/config.js";
|
|
11
6
|
import { SyncSetupReviewChangedError, updateSyncSetup } from "../settings/settings-management.js";
|
|
12
|
-
import type { AnySyncConfig
|
|
7
|
+
import type { AnySyncConfig } from "../settings/settings-types.js";
|
|
13
8
|
import { readSnapshotForHead } from "../sync/remote-snapshot.js";
|
|
14
9
|
import { throwIfAborted } from "../sync/signals.js";
|
|
15
10
|
import { errorMessage } from "../sync/sync-errors.js";
|
|
11
|
+
import { captureMutationOwner } from "../sync/sync-local.js";
|
|
16
12
|
import {
|
|
17
13
|
compareSyncInclude,
|
|
18
14
|
inspectRemoteSelection,
|
|
@@ -20,7 +16,12 @@ import {
|
|
|
20
16
|
type RemoteSelectionState,
|
|
21
17
|
sameSyncInclude,
|
|
22
18
|
} from "../sync/sync-policy.js";
|
|
23
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
type RunRoute,
|
|
21
|
+
type RunRouteResult,
|
|
22
|
+
runCancellableOperation,
|
|
23
|
+
runCancellableTask,
|
|
24
|
+
} from "./cancellable-operation.js";
|
|
24
25
|
import { setSyncStatus } from "./sync-status.js";
|
|
25
26
|
import { safeTerminalText } from "./terminal-text.js";
|
|
26
27
|
|
|
@@ -50,11 +51,23 @@ export async function showRemoteSelectionReview(
|
|
|
50
51
|
factory: SyncBackendFactory = createSyncBackend,
|
|
51
52
|
options: RemoteSelectionReviewOptions = {},
|
|
52
53
|
): Promise<RemoteSelectionReviewResult> {
|
|
54
|
+
const validateOwner = captureMutationOwner(ctx, signal);
|
|
55
|
+
const isCurrent = () => isReviewOwnerCurrent(validateOwner);
|
|
53
56
|
try {
|
|
57
|
+
validateOwner();
|
|
54
58
|
let decision = options.decision;
|
|
55
59
|
if (!decision) {
|
|
56
|
-
const
|
|
57
|
-
|
|
60
|
+
const inspection = await runCancellableTask(
|
|
61
|
+
ctx,
|
|
62
|
+
"Checking remote content list…",
|
|
63
|
+
(workSignal) => inspectConfiguredRemoteSelection(ctx, setupName, workSignal, factory, validateOwner),
|
|
64
|
+
{ signal, isCurrent, cancelledMessage: null },
|
|
65
|
+
);
|
|
66
|
+
if (!isCurrent()) return { kind: "stale" };
|
|
67
|
+
if (inspection.kind !== "completed") return { kind: inspection.kind === "cancelled" ? "back" : "closed" };
|
|
68
|
+
validateOwner();
|
|
69
|
+
const inspected = inspection.value;
|
|
70
|
+
if (!inspected) return { kind: "stale" };
|
|
58
71
|
// A successful fresh read can supersede an old explicit mismatch without
|
|
59
72
|
// changing local settings. This is not user cancellation or proof of file equality.
|
|
60
73
|
if (inspected.kind === "empty" || inspected.state.kind !== "different") {
|
|
@@ -95,12 +108,23 @@ export async function showRemoteSelectionReview(
|
|
|
95
108
|
signal,
|
|
96
109
|
factory,
|
|
97
110
|
options,
|
|
111
|
+
validateOwner,
|
|
98
112
|
);
|
|
99
113
|
if (result.kind !== "refresh") return result;
|
|
100
|
-
const
|
|
101
|
-
|
|
114
|
+
const refresh = await runCancellableTask(
|
|
115
|
+
ctx,
|
|
116
|
+
"Refreshing remote content list…",
|
|
117
|
+
(workSignal) =>
|
|
118
|
+
runWithOptionalStateAccess(options, () =>
|
|
119
|
+
inspectConfiguredRemoteSelection(ctx, currentDecision.setupName, workSignal, factory, validateOwner),
|
|
120
|
+
),
|
|
121
|
+
{ signal, isCurrent, cancelledMessage: null },
|
|
102
122
|
);
|
|
103
|
-
if (!
|
|
123
|
+
if (!isCurrent()) return { kind: "stale" };
|
|
124
|
+
if (refresh.kind !== "completed") return { kind: refresh.kind === "cancelled" ? "back" : "closed" };
|
|
125
|
+
validateOwner();
|
|
126
|
+
const refreshed = refresh.value;
|
|
127
|
+
if (!refreshed) return { kind: "stale" };
|
|
104
128
|
if (refreshed.kind === "empty") {
|
|
105
129
|
options.onSelectionResolved?.();
|
|
106
130
|
ctx.ui.notify("Remote storage no longer has a snapshot or synced-content list.", "warning");
|
|
@@ -119,11 +143,11 @@ export async function showRemoteSelectionReview(
|
|
|
119
143
|
currentDecision = decisionFromState(refreshed.config, refreshed.state);
|
|
120
144
|
}
|
|
121
145
|
} catch (error) {
|
|
122
|
-
if (
|
|
146
|
+
if (!isCurrent()) return { kind: "stale" };
|
|
123
147
|
ctx.ui.notify(`Could not review synced content: ${errorMessage(error)}`, "error");
|
|
124
148
|
return { kind: "back" };
|
|
125
149
|
} finally {
|
|
126
|
-
setSyncStatus(ctx, undefined);
|
|
150
|
+
if (isCurrent()) setSyncStatus(ctx, undefined);
|
|
127
151
|
}
|
|
128
152
|
}
|
|
129
153
|
|
|
@@ -137,6 +161,7 @@ async function showSelectionDifference(
|
|
|
137
161
|
sessionSignal: AbortSignal | undefined,
|
|
138
162
|
factory: SyncBackendFactory,
|
|
139
163
|
options: Pick<RemoteSelectionReviewOptions, "cancelLabel" | "onSelectionResolved" | "withStateAccess">,
|
|
164
|
+
validateOwner: () => void,
|
|
140
165
|
): Promise<DifferenceFlowResult> {
|
|
141
166
|
type Screen = "choice" | "review" | "saved";
|
|
142
167
|
type Action = "adopt" | "keep" | "cancel" | "continue" | "done";
|
|
@@ -144,8 +169,9 @@ async function showSelectionDifference(
|
|
|
144
169
|
decision: RemoteSelectionDecision;
|
|
145
170
|
saved: boolean;
|
|
146
171
|
}
|
|
172
|
+
const isCurrent = () => isReviewOwnerCurrent(validateOwner);
|
|
147
173
|
let flowState: FlowState = { decision: initialDecision, saved: false };
|
|
148
|
-
let
|
|
174
|
+
let continuationFingerprint: string | undefined;
|
|
149
175
|
let nextResult: RemoteSelectionReviewResult | undefined;
|
|
150
176
|
let refreshRequested = false;
|
|
151
177
|
const route = origin === "settings" ? "sync" : origin;
|
|
@@ -215,41 +241,58 @@ async function showSelectionDifference(
|
|
|
215
241
|
adopt: async ({ state, signal: actionSignal }) => {
|
|
216
242
|
const signal = combineSignals(sessionSignal, actionSignal);
|
|
217
243
|
try {
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
244
|
+
const adoption = await runCancellableTask(
|
|
245
|
+
ctx,
|
|
246
|
+
"Checking and saving remote content list…",
|
|
247
|
+
async (workSignal, _onCommit, onDialog) => {
|
|
248
|
+
const ownedSignal = workSignal ?? signal;
|
|
249
|
+
const currentConfig = await loadConfig(state.decision.setupName);
|
|
250
|
+
ownedSignal.throwIfAborted();
|
|
251
|
+
validateOwner();
|
|
252
|
+
assertLocalSelectionCurrent(currentConfig, state.decision);
|
|
253
|
+
if (!currentConfig.include.includes("sessions") && state.decision.remoteInclude.includes("sessions")) {
|
|
254
|
+
onDialog?.(true);
|
|
255
|
+
let acknowledged: boolean;
|
|
256
|
+
try {
|
|
257
|
+
acknowledged = await ctx.ui.confirm(
|
|
258
|
+
"Use a content list that includes session conversations?",
|
|
259
|
+
"Session JSONL may contain prompts, tool output, file paths, images, and secrets. This saves the list only; it does not pull files.",
|
|
260
|
+
{ signal: ownedSignal },
|
|
261
|
+
);
|
|
262
|
+
} finally {
|
|
263
|
+
onDialog?.(false);
|
|
264
|
+
}
|
|
265
|
+
ownedSignal.throwIfAborted();
|
|
266
|
+
validateOwner();
|
|
267
|
+
if (!acknowledged) return undefined;
|
|
268
|
+
}
|
|
269
|
+
return runWithOptionalStateAccess(options, async () => {
|
|
270
|
+
const loaded = await loadAdoptionReview(state.decision, ownedSignal, factory, validateOwner);
|
|
271
|
+
ownedSignal.throwIfAborted();
|
|
272
|
+
validateOwner();
|
|
273
|
+
await revalidateAndAdopt(loaded, state.decision, ownedSignal, validateOwner);
|
|
274
|
+
return loaded;
|
|
275
|
+
});
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
signal,
|
|
279
|
+
isCurrent,
|
|
280
|
+
cancelAcrossDialogs: true,
|
|
281
|
+
cancelledMessage: "Remote content-list adoption cancelled.",
|
|
282
|
+
},
|
|
283
|
+
);
|
|
284
|
+
if (signal.aborted || adoption.kind === "closed") return { kind: "close" as const };
|
|
285
|
+
if (adoption.kind === "cancelled" || !adoption.value) return { kind: "stay" as const };
|
|
286
|
+
validateOwner();
|
|
237
287
|
options.onSelectionResolved?.();
|
|
238
|
-
|
|
239
|
-
...
|
|
240
|
-
setupName: state.decision.setupName,
|
|
288
|
+
continuationFingerprint = syncCheckConfigFingerprint({
|
|
289
|
+
...adoption.value.config,
|
|
241
290
|
include: [...state.decision.remoteInclude],
|
|
242
|
-
|
|
243
|
-
automaticTransfer: review.config.automaticTransfer,
|
|
244
|
-
agentMode: review.config.agentMode,
|
|
245
|
-
continuous: review.config.continuous,
|
|
246
|
-
onSwitch: review.config.onSwitch,
|
|
247
|
-
showStatus: review.config.showStatus,
|
|
248
|
-
};
|
|
291
|
+
});
|
|
249
292
|
flowState = { decision: state.decision, saved: true };
|
|
250
293
|
return { kind: "to" as const, screen: "saved" as const };
|
|
251
294
|
} catch (error) {
|
|
252
|
-
if (signal.aborted) return { kind: "close" as const };
|
|
295
|
+
if (signal.aborted || !isCurrent()) return { kind: "close" as const };
|
|
253
296
|
if (isStaleReviewError(error)) {
|
|
254
297
|
ctx.ui.notify(`${errorMessage(error)} Refreshing the comparison.`, "warning");
|
|
255
298
|
refreshRequested = true;
|
|
@@ -268,6 +311,7 @@ async function showSelectionDifference(
|
|
|
268
311
|
try {
|
|
269
312
|
const latest = await loadConfig(state.decision.setupName);
|
|
270
313
|
if (signal.aborted) return { kind: "close" as const };
|
|
314
|
+
validateOwner();
|
|
271
315
|
assertLocalSelectionCurrent(latest, state.decision);
|
|
272
316
|
const result = await runCancellableOperation(
|
|
273
317
|
ctx,
|
|
@@ -283,7 +327,7 @@ async function showSelectionDifference(
|
|
|
283
327
|
);
|
|
284
328
|
return handleNestedRouteResult(result, "push");
|
|
285
329
|
} catch (error) {
|
|
286
|
-
if (signal.aborted) return { kind: "close" as const };
|
|
330
|
+
if (signal.aborted || !isCurrent()) return { kind: "close" as const };
|
|
287
331
|
if (isStaleReviewError(error)) {
|
|
288
332
|
ctx.ui.notify(`${errorMessage(error)} Refreshing the comparison.`, "warning");
|
|
289
333
|
refreshRequested = true;
|
|
@@ -294,12 +338,13 @@ async function showSelectionDifference(
|
|
|
294
338
|
}
|
|
295
339
|
},
|
|
296
340
|
continue: async ({ state, signal: actionSignal }) => {
|
|
297
|
-
if (!runRoute || !
|
|
341
|
+
if (!runRoute || !continuationFingerprint) return { kind: "stay" as const };
|
|
298
342
|
const signal = combineSignals(sessionSignal, actionSignal);
|
|
299
343
|
try {
|
|
300
|
-
const latest = await
|
|
344
|
+
const latest = await loadConfig(state.decision.setupName);
|
|
301
345
|
if (signal.aborted) return { kind: "close" as const };
|
|
302
|
-
|
|
346
|
+
validateOwner();
|
|
347
|
+
if (syncCheckConfigFingerprint(latest) !== continuationFingerprint) {
|
|
303
348
|
throw new StaleRemoteSelectionReviewError(
|
|
304
349
|
`Sync setup “${safeTerminalText(state.decision.setupName)}” changed after the remote content list was saved.`,
|
|
305
350
|
);
|
|
@@ -312,7 +357,7 @@ async function showSelectionDifference(
|
|
|
312
357
|
});
|
|
313
358
|
return handleNestedRouteResult(result, route);
|
|
314
359
|
} catch (error) {
|
|
315
|
-
if (signal.aborted) return { kind: "close" as const };
|
|
360
|
+
if (signal.aborted || !isCurrent()) return { kind: "close" as const };
|
|
316
361
|
ctx.ui.notify(`Could not continue: ${errorMessage(error)}`, "error");
|
|
317
362
|
return { kind: "stay" as const };
|
|
318
363
|
}
|
|
@@ -350,10 +395,10 @@ async function showSelectionDifference(
|
|
|
350
395
|
const menuResult = await runMenu(ctx, menu, {
|
|
351
396
|
getState: () => flowState,
|
|
352
397
|
signal: sessionSignal,
|
|
353
|
-
isCurrent
|
|
398
|
+
isCurrent,
|
|
354
399
|
onError: (_menuCtx, error) => ctx.ui.notify(errorMessage(error), "error"),
|
|
355
400
|
});
|
|
356
|
-
if (
|
|
401
|
+
if (!isCurrent() || menuResult.kind === "stale") return { kind: "stale" };
|
|
357
402
|
if (refreshRequested) return { kind: "refresh" };
|
|
358
403
|
if (nextResult) return nextResult;
|
|
359
404
|
if (menuResult.kind === "closed") {
|
|
@@ -364,7 +409,6 @@ async function showSelectionDifference(
|
|
|
364
409
|
|
|
365
410
|
interface AdoptionReview {
|
|
366
411
|
config: AnySyncConfig;
|
|
367
|
-
storageReview: SyncSetupStorageReview;
|
|
368
412
|
backend: SyncBackend;
|
|
369
413
|
reviewedHead: RemoteHead;
|
|
370
414
|
}
|
|
@@ -373,53 +417,58 @@ async function loadAdoptionReview(
|
|
|
373
417
|
decision: RemoteSelectionDecision,
|
|
374
418
|
signal: AbortSignal,
|
|
375
419
|
factory: SyncBackendFactory,
|
|
420
|
+
validateOwner: () => void,
|
|
376
421
|
): Promise<AdoptionReview> {
|
|
377
422
|
const config = await loadConfig(decision.setupName);
|
|
378
423
|
throwIfAborted(signal);
|
|
424
|
+
validateOwner();
|
|
379
425
|
assertLocalSelectionCurrent(config, decision);
|
|
380
|
-
const partial = await loadPartialConfig(decision.setupName);
|
|
381
|
-
throwIfAborted(signal);
|
|
382
|
-
if (!sameSyncInclude(partial.include, decision.localInclude)) {
|
|
383
|
-
throw new StaleRemoteSelectionReviewError(
|
|
384
|
-
`Sync setup “${safeTerminalText(decision.setupName)}” changed while the comparison was open.`,
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
426
|
const backend = await factory(config);
|
|
388
427
|
throwIfAborted(signal);
|
|
428
|
+
validateOwner();
|
|
389
429
|
const reviewedHead = await backend.readHead(signal);
|
|
390
430
|
throwIfAborted(signal);
|
|
431
|
+
validateOwner();
|
|
391
432
|
if (!reviewedHead) {
|
|
392
433
|
throw new StaleRemoteSelectionReviewError("Remote storage changed while the comparison was open.");
|
|
393
434
|
}
|
|
394
435
|
const snapshot = await readSnapshotForHead(backend, reviewedHead, signal);
|
|
395
436
|
throwIfAborted(signal);
|
|
437
|
+
validateOwner();
|
|
396
438
|
const state = inspectRemoteSelection(config.include, snapshot);
|
|
397
439
|
if (state.kind !== "different" || !sameSyncInclude(state.include, decision.remoteInclude)) {
|
|
398
440
|
throw new StaleRemoteSelectionReviewError("Remote synced content changed while the comparison was open.");
|
|
399
441
|
}
|
|
400
|
-
return { config,
|
|
442
|
+
return { config, backend, reviewedHead };
|
|
401
443
|
}
|
|
402
444
|
|
|
403
|
-
async function revalidateAndAdopt(
|
|
445
|
+
async function revalidateAndAdopt(
|
|
446
|
+
review: AdoptionReview,
|
|
447
|
+
decision: RemoteSelectionDecision,
|
|
448
|
+
signal: AbortSignal,
|
|
449
|
+
validateOwner: () => void,
|
|
450
|
+
) {
|
|
404
451
|
const currentHead = await review.backend.readHead(signal);
|
|
405
452
|
throwIfAborted(signal);
|
|
453
|
+
validateOwner();
|
|
406
454
|
if (!currentHead || !review.backend.sameRevision(review.reviewedHead.revision, currentHead.revision)) {
|
|
407
455
|
throw new StaleRemoteSelectionReviewError("Remote storage changed while the comparison was open.");
|
|
408
456
|
}
|
|
409
457
|
const currentSnapshot = await readSnapshotForHead(review.backend, currentHead, signal);
|
|
410
458
|
throwIfAborted(signal);
|
|
459
|
+
validateOwner();
|
|
411
460
|
const currentState = inspectRemoteSelection(review.config.include, currentSnapshot);
|
|
412
461
|
if (currentState.kind !== "different" || !sameSyncInclude(currentState.include, decision.remoteInclude)) {
|
|
413
462
|
throw new StaleRemoteSelectionReviewError("Remote synced content changed while the comparison was open.");
|
|
414
463
|
}
|
|
415
464
|
await updateSyncSetup(
|
|
416
465
|
decision.setupName,
|
|
417
|
-
(setup) =>
|
|
418
|
-
|
|
419
|
-
sync: { ...setup.sync, include: [...decision.remoteInclude] }
|
|
420
|
-
}
|
|
466
|
+
(setup) => {
|
|
467
|
+
validateOwner();
|
|
468
|
+
return { ...setup, sync: { ...setup.sync, include: [...decision.remoteInclude] } };
|
|
469
|
+
},
|
|
421
470
|
{
|
|
422
|
-
|
|
471
|
+
expectedConfigFingerprint: syncCheckConfigFingerprint(review.config),
|
|
423
472
|
expectedInclude: decision.localInclude,
|
|
424
473
|
signal,
|
|
425
474
|
},
|
|
@@ -431,6 +480,7 @@ async function inspectConfiguredRemoteSelection(
|
|
|
431
480
|
setupName: string | undefined,
|
|
432
481
|
signal: AbortSignal | undefined,
|
|
433
482
|
factory: SyncBackendFactory,
|
|
483
|
+
validateOwner: () => void,
|
|
434
484
|
): Promise<
|
|
435
485
|
| { kind: "empty"; config: AnySyncConfig }
|
|
436
486
|
| { kind: "selection"; config: AnySyncConfig; state: RemoteSelectionState }
|
|
@@ -438,14 +488,18 @@ async function inspectConfiguredRemoteSelection(
|
|
|
438
488
|
> {
|
|
439
489
|
const config = await loadConfig(setupName);
|
|
440
490
|
if (signal?.aborted) return undefined;
|
|
491
|
+
validateOwner();
|
|
441
492
|
setSyncStatus(ctx, `checking synced content for ${safeTerminalText(config.setupName)}`);
|
|
442
493
|
const backend = await factory(config);
|
|
443
494
|
if (signal?.aborted) return undefined;
|
|
495
|
+
validateOwner();
|
|
444
496
|
const head = await backend.readHead(signal);
|
|
445
497
|
if (signal?.aborted) return undefined;
|
|
498
|
+
validateOwner();
|
|
446
499
|
if (!head) return { kind: "empty", config };
|
|
447
500
|
const snapshot = await readSnapshotForHead(backend, head, signal);
|
|
448
501
|
if (signal?.aborted) return undefined;
|
|
502
|
+
validateOwner();
|
|
449
503
|
return {
|
|
450
504
|
kind: "selection",
|
|
451
505
|
config,
|
|
@@ -587,20 +641,6 @@ function assertLocalSelectionCurrent(config: AnySyncConfig, decision: RemoteSele
|
|
|
587
641
|
);
|
|
588
642
|
}
|
|
589
643
|
|
|
590
|
-
function sameContinuationReview(left: PartialConfig, right: PartialConfig) {
|
|
591
|
-
return (
|
|
592
|
-
left.setupName === right.setupName &&
|
|
593
|
-
left.connectionName === right.connectionName &&
|
|
594
|
-
left.storageKind === right.storageKind &&
|
|
595
|
-
left.storagePath === right.storagePath &&
|
|
596
|
-
left.bucket === right.bucket &&
|
|
597
|
-
left.branch === right.branch &&
|
|
598
|
-
Boolean(left.agentMode) === Boolean(right.agentMode) &&
|
|
599
|
-
Boolean(left.continuous) === Boolean(right.continuous) &&
|
|
600
|
-
sameSyncInclude(left.include, right.include)
|
|
601
|
-
);
|
|
602
|
-
}
|
|
603
|
-
|
|
604
644
|
function continueLabel(origin: RemoteSelectionOrigin) {
|
|
605
645
|
if (origin === "pull") return "Continue Pull now…";
|
|
606
646
|
if (origin === "push") return "Continue Push now…";
|
|
@@ -641,6 +681,15 @@ function runWithOptionalStateAccess<T>(
|
|
|
641
681
|
return options.withStateAccess ? options.withStateAccess(task) : task();
|
|
642
682
|
}
|
|
643
683
|
|
|
684
|
+
function isReviewOwnerCurrent(validateOwner: () => void) {
|
|
685
|
+
try {
|
|
686
|
+
validateOwner();
|
|
687
|
+
return true;
|
|
688
|
+
} catch {
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
644
693
|
function combineSignals(sessionSignal: AbortSignal | undefined, actionSignal: AbortSignal) {
|
|
645
694
|
return sessionSignal ? AbortSignal.any([sessionSignal, actionSignal]) : actionSignal;
|
|
646
695
|
}
|
package/src/ui/settings-ui.ts
CHANGED
|
@@ -33,6 +33,7 @@ export async function showSyncSettings(
|
|
|
33
33
|
ctx: ExtensionCommandContext,
|
|
34
34
|
runRoute: SyncSettingsRoute,
|
|
35
35
|
signal?: AbortSignal,
|
|
36
|
+
reviewedSetup?: string,
|
|
36
37
|
): Promise<"back" | "exit"> {
|
|
37
38
|
let disposition: "back" | "exit" = "back";
|
|
38
39
|
const exitSettings = () => {
|
|
@@ -71,7 +72,7 @@ export async function showSyncSettings(
|
|
|
71
72
|
}
|
|
72
73
|
return disposition;
|
|
73
74
|
}
|
|
74
|
-
const initial = await loadConfig();
|
|
75
|
+
const initial = await loadConfig(reviewedSetup);
|
|
75
76
|
if (signal?.aborted) return "back";
|
|
76
77
|
const setupName = initial.setupName;
|
|
77
78
|
type Action =
|