@narumitw/pi-sync 0.50.3 → 0.51.1

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.
Files changed (218) hide show
  1. package/README.md +4 -1
  2. package/dist/chunks/{chunk-6LP3MCR2.ts → chunk-2JZFHNLM.ts} +3 -5
  3. package/dist/chunks/chunk-2JZFHNLM.ts.map +7 -0
  4. package/dist/chunks/{chunk-LCRI3ABW.ts → chunk-2RZSQWBH.ts} +8 -19
  5. package/dist/chunks/chunk-2RZSQWBH.ts.map +7 -0
  6. package/dist/chunks/{chunk-IJ6FJFA5.ts → chunk-4AIHXRKE.ts} +11 -17
  7. package/dist/chunks/chunk-4AIHXRKE.ts.map +7 -0
  8. package/dist/chunks/{chunk-C7XY5NF5.ts → chunk-4MPIH46N.ts} +29 -51
  9. package/dist/chunks/chunk-4MPIH46N.ts.map +7 -0
  10. package/dist/chunks/{chunk-5B3FXBLE.ts → chunk-6546TS54.ts} +16 -20
  11. package/dist/chunks/chunk-6546TS54.ts.map +7 -0
  12. package/dist/chunks/{chunk-Q2TUM5X6.ts → chunk-6NT3YFGY.ts} +2 -2
  13. package/dist/chunks/chunk-6NT3YFGY.ts.map +7 -0
  14. package/dist/chunks/{chunk-2B2ELUF5.ts → chunk-7D5SF7P3.ts} +1 -1
  15. package/dist/chunks/chunk-7D5SF7P3.ts.map +7 -0
  16. package/dist/chunks/{chunk-YDWUZVPN.ts → chunk-7KHKWL3Q.ts} +28 -47
  17. package/dist/chunks/chunk-7KHKWL3Q.ts.map +7 -0
  18. package/dist/chunks/{chunk-ZI5MXMEX.ts → chunk-BVVP33U5.ts} +2 -4
  19. package/dist/chunks/chunk-BVVP33U5.ts.map +7 -0
  20. package/dist/chunks/{chunk-SZWBGPID.ts → chunk-DM4X4FIA.ts} +8 -13
  21. package/dist/chunks/chunk-DM4X4FIA.ts.map +7 -0
  22. package/dist/chunks/{chunk-SL64RLXR.ts → chunk-DZUSTGQV.ts} +8 -16
  23. package/dist/chunks/chunk-DZUSTGQV.ts.map +7 -0
  24. package/dist/chunks/{chunk-YHEMENKV.ts → chunk-E42GXD6D.ts} +13 -19
  25. package/dist/chunks/chunk-E42GXD6D.ts.map +7 -0
  26. package/dist/chunks/{chunk-Z4DQF6L3.ts → chunk-ECYCNZOE.ts} +1 -1
  27. package/dist/chunks/chunk-ECYCNZOE.ts.map +7 -0
  28. package/dist/chunks/{chunk-OEX4ABY6.ts → chunk-EEJPYTWE.ts} +11 -21
  29. package/dist/chunks/chunk-EEJPYTWE.ts.map +7 -0
  30. package/dist/chunks/{chunk-CPEHGXJV.ts → chunk-G4RUXRWZ.ts} +3 -5
  31. package/dist/chunks/chunk-G4RUXRWZ.ts.map +7 -0
  32. package/dist/chunks/{chunk-T2B5IU74.ts → chunk-HGJH56KR.ts} +4 -4
  33. package/dist/chunks/chunk-HGJH56KR.ts.map +7 -0
  34. package/dist/chunks/{chunk-JAAKLL6M.ts → chunk-IVXEDPAD.ts} +10 -34
  35. package/dist/chunks/chunk-IVXEDPAD.ts.map +7 -0
  36. package/dist/chunks/chunk-K3RIEL6J.ts +20 -0
  37. package/dist/chunks/chunk-K3RIEL6J.ts.map +7 -0
  38. package/dist/chunks/{chunk-LTBWGSBV.ts → chunk-MSSW6F3R.ts} +1 -1
  39. package/dist/chunks/chunk-MSSW6F3R.ts.map +7 -0
  40. package/dist/chunks/{chunk-T3HH3UEJ.ts → chunk-OVDTF5CH.ts} +4 -10
  41. package/dist/chunks/chunk-OVDTF5CH.ts.map +7 -0
  42. package/dist/chunks/{chunk-NJFUIVYL.ts → chunk-PWMWOJP4.ts} +1 -1
  43. package/dist/chunks/chunk-PWMWOJP4.ts.map +7 -0
  44. package/dist/chunks/{chunk-HUDQATCL.ts → chunk-Q32CQWPM.ts} +6 -8
  45. package/dist/chunks/chunk-Q32CQWPM.ts.map +7 -0
  46. package/dist/chunks/{chunk-ETTRJDNP.ts → chunk-QLSN7AGX.ts} +6 -13
  47. package/dist/chunks/chunk-QLSN7AGX.ts.map +7 -0
  48. package/dist/chunks/{chunk-THMF4BVL.ts → chunk-SWUXA3MH.ts} +1 -1
  49. package/dist/chunks/chunk-SWUXA3MH.ts.map +7 -0
  50. package/dist/chunks/{chunk-TEN2ZBJV.ts → chunk-T2SJD3N7.ts} +2 -4
  51. package/dist/chunks/chunk-T2SJD3N7.ts.map +7 -0
  52. package/dist/chunks/{chunk-AUSDLD6F.ts → chunk-T56RNRZJ.ts} +10 -26
  53. package/dist/chunks/chunk-T56RNRZJ.ts.map +7 -0
  54. package/dist/chunks/{chunk-DHVFS4QK.ts → chunk-UA7RXOIT.ts} +37 -90
  55. package/dist/chunks/chunk-UA7RXOIT.ts.map +7 -0
  56. package/dist/chunks/{chunk-D5X27LCA.ts → chunk-UETBKTDI.ts} +1 -1
  57. package/dist/chunks/chunk-UETBKTDI.ts.map +7 -0
  58. package/dist/chunks/{chunk-CHH26D4F.ts → chunk-YFADF5ZQ.ts} +7 -6
  59. package/dist/chunks/chunk-YFADF5ZQ.ts.map +7 -0
  60. package/dist/chunks/{chunk-CCSN3F47.ts → chunk-YPXGU5OR.ts} +4 -4
  61. package/dist/chunks/chunk-YPXGU5OR.ts.map +7 -0
  62. package/dist/chunks/{file-selection-FTTKAOYX.ts → file-selection-RAVHRYFG.ts} +14 -26
  63. package/dist/chunks/file-selection-RAVHRYFG.ts.map +7 -0
  64. package/dist/chunks/{git-backend-GMVS4PD4.ts → git-backend-IAANDL3K.ts} +41 -104
  65. package/dist/chunks/git-backend-IAANDL3K.ts.map +7 -0
  66. package/dist/chunks/manager-result-dispatcher-OTUMQM26.ts +23 -0
  67. package/dist/chunks/{manager-ui-IPAOAWZA.ts → manager-ui-763NEKJY.ts} +124 -251
  68. package/dist/chunks/manager-ui-763NEKJY.ts.map +7 -0
  69. package/dist/chunks/remote-selection-ui-TZTJF32Y.ts +22 -0
  70. package/dist/chunks/{s3-backend-66O4FGBH.ts → s3-backend-XGXUOPUN.ts} +23 -54
  71. package/dist/chunks/s3-backend-XGXUOPUN.ts.map +7 -0
  72. package/dist/chunks/{setup-switch-IS2PEZ3J.ts → setup-switch-5G2VNJHL.ts} +9 -9
  73. package/dist/chunks/{setup-wizard-JUJZYNNM.ts → setup-wizard-YIEWGX32.ts} +12 -12
  74. package/dist/chunks/setup-wizard-YIEWGX32.ts.map +7 -0
  75. package/dist/chunks/{snapshot-HKEWCVK6.ts → snapshot-JL5DFSXI.ts} +5 -5
  76. package/dist/chunks/sync-inspection-P3ENJLNF.ts +20 -0
  77. package/dist/chunks/{sync-operations-LBX3GKCM.ts → sync-operations-AR76EB6C.ts} +85 -201
  78. package/dist/chunks/sync-operations-AR76EB6C.ts.map +7 -0
  79. package/dist/chunks/{sync-state-Q2FTXTW6.ts → sync-state-VHJUZ4YG.ts} +6 -6
  80. package/dist/chunks/{webdav-backend-ECIKD7SO.ts → webdav-backend-3EGUONAT.ts} +20 -51
  81. package/dist/chunks/webdav-backend-3EGUONAT.ts.map +7 -0
  82. package/dist/index.ts +49 -74
  83. package/dist/index.ts.map +3 -3
  84. package/docs/settings.md +9 -0
  85. package/package.json +61 -62
  86. package/src/backends/backend-factory.ts +15 -15
  87. package/src/backends/backend-identity.ts +31 -31
  88. package/src/backends/backend-types.ts +42 -42
  89. package/src/backends/git/git-backend.ts +714 -788
  90. package/src/backends/git/git-config.ts +104 -111
  91. package/src/backends/git/git-runner.ts +230 -247
  92. package/src/backends/git/git-storage.ts +228 -245
  93. package/src/backends/s3/s3-backend.ts +408 -441
  94. package/src/backends/s3/s3-client.ts +288 -333
  95. package/src/backends/sync-backend.ts +65 -65
  96. package/src/backends/webdav/webdav-backend.ts +542 -589
  97. package/src/backends/webdav/webdav-client.ts +292 -316
  98. package/src/backends/webdav/webdav-config.ts +57 -68
  99. package/src/commands/command-execution.ts +270 -289
  100. package/src/commands/command-handler.ts +160 -163
  101. package/src/commands/command-types.ts +13 -13
  102. package/src/commands/command.ts +190 -196
  103. package/src/commands/setup-completions.ts +1 -1
  104. package/src/paths.ts +47 -47
  105. package/src/settings/config-errors.ts +4 -5
  106. package/src/settings/config-file.ts +473 -506
  107. package/src/settings/config.ts +171 -174
  108. package/src/settings/settings-management.ts +191 -216
  109. package/src/settings/settings-store.ts +63 -77
  110. package/src/settings/settings-types.ts +78 -75
  111. package/src/settings/settings-validation.ts +328 -364
  112. package/src/snapshot/session-paths.ts +41 -55
  113. package/src/snapshot/snapshot-apply.ts +197 -229
  114. package/src/snapshot/snapshot-codec.ts +29 -31
  115. package/src/snapshot/snapshot-paths.ts +4 -6
  116. package/src/snapshot/snapshot-transaction.ts +161 -170
  117. package/src/snapshot/snapshot-types.ts +24 -24
  118. package/src/snapshot/snapshot.ts +310 -343
  119. package/src/state/json-file.ts +11 -11
  120. package/src/state/lock.ts +226 -242
  121. package/src/state/lockfile-fs.ts +20 -20
  122. package/src/state/operation-availability.ts +36 -41
  123. package/src/state/state-directory.ts +171 -173
  124. package/src/state/state-types.ts +15 -15
  125. package/src/state/sync-state-store.ts +23 -23
  126. package/src/sync/automatic-sync.ts +75 -80
  127. package/src/sync/remote-snapshot.ts +52 -60
  128. package/src/sync/setup-switch.ts +99 -114
  129. package/src/sync/signals.ts +3 -5
  130. package/src/sync/startup-check.ts +97 -111
  131. package/src/sync/sync-decision.ts +76 -80
  132. package/src/sync/sync-error-guidance.ts +41 -48
  133. package/src/sync/sync-errors.ts +10 -10
  134. package/src/sync/sync-inspection.ts +29 -33
  135. package/src/sync/sync-loaders.ts +28 -36
  136. package/src/sync/sync-mutations.ts +510 -622
  137. package/src/sync/sync-operations.ts +7 -7
  138. package/src/sync/sync-policy.ts +296 -326
  139. package/src/sync/sync-queries.ts +175 -194
  140. package/src/sync/sync-state.ts +74 -100
  141. package/src/sync-extension.ts +89 -101
  142. package/src/sync.ts +26 -26
  143. package/src/types.ts +37 -37
  144. package/src/ui/automatic-sync-summary.ts +1 -3
  145. package/src/ui/cancellable-operation.ts +89 -101
  146. package/src/ui/file-selection.ts +250 -274
  147. package/src/ui/manager-attention.ts +51 -63
  148. package/src/ui/manager-recovery.ts +48 -57
  149. package/src/ui/manager-result-dispatcher.ts +62 -76
  150. package/src/ui/manager-state.ts +200 -232
  151. package/src/ui/manager-ui.ts +264 -362
  152. package/src/ui/remote-selection-ui.ts +547 -588
  153. package/src/ui/secret-input.ts +197 -205
  154. package/src/ui/settings-ui.ts +175 -172
  155. package/src/ui/setup/git-ui.ts +232 -261
  156. package/src/ui/setup/s3-credentials-ui.ts +58 -65
  157. package/src/ui/setup/s3-ui.ts +171 -195
  158. package/src/ui/setup/setup-actions.ts +91 -101
  159. package/src/ui/setup/setup-location-ui.ts +121 -137
  160. package/src/ui/setup/setup-name-ui.ts +27 -27
  161. package/src/ui/setup/setup-prompts.ts +65 -65
  162. package/src/ui/setup/setup-review.ts +58 -59
  163. package/src/ui/setup/setup-switcher.ts +156 -160
  164. package/src/ui/setup/setup-wizard.ts +28 -28
  165. package/src/ui/setup/text-input.ts +54 -58
  166. package/src/ui/setup/webdav-ui.ts +246 -293
  167. package/src/ui/storage-connections-ui.ts +317 -369
  168. package/src/ui/sync-attention.ts +148 -164
  169. package/src/ui/sync-format.ts +110 -121
  170. package/src/ui/sync-resolution-ui.ts +147 -153
  171. package/src/ui/sync-setups-ui.ts +218 -238
  172. package/src/ui/sync-status.ts +17 -0
  173. package/src/ui/terminal-text.ts +2 -2
  174. package/dist/chunks/chunk-2B2ELUF5.ts.map +0 -7
  175. package/dist/chunks/chunk-5B3FXBLE.ts.map +0 -7
  176. package/dist/chunks/chunk-6LP3MCR2.ts.map +0 -7
  177. package/dist/chunks/chunk-AUSDLD6F.ts.map +0 -7
  178. package/dist/chunks/chunk-C7XY5NF5.ts.map +0 -7
  179. package/dist/chunks/chunk-CCSN3F47.ts.map +0 -7
  180. package/dist/chunks/chunk-CHH26D4F.ts.map +0 -7
  181. package/dist/chunks/chunk-CPEHGXJV.ts.map +0 -7
  182. package/dist/chunks/chunk-D5X27LCA.ts.map +0 -7
  183. package/dist/chunks/chunk-DHVFS4QK.ts.map +0 -7
  184. package/dist/chunks/chunk-ETTRJDNP.ts.map +0 -7
  185. package/dist/chunks/chunk-HUDQATCL.ts.map +0 -7
  186. package/dist/chunks/chunk-IJ6FJFA5.ts.map +0 -7
  187. package/dist/chunks/chunk-JAAKLL6M.ts.map +0 -7
  188. package/dist/chunks/chunk-LCRI3ABW.ts.map +0 -7
  189. package/dist/chunks/chunk-LTBWGSBV.ts.map +0 -7
  190. package/dist/chunks/chunk-NJFUIVYL.ts.map +0 -7
  191. package/dist/chunks/chunk-OEX4ABY6.ts.map +0 -7
  192. package/dist/chunks/chunk-Q2TUM5X6.ts.map +0 -7
  193. package/dist/chunks/chunk-SL64RLXR.ts.map +0 -7
  194. package/dist/chunks/chunk-SZWBGPID.ts.map +0 -7
  195. package/dist/chunks/chunk-T2B5IU74.ts.map +0 -7
  196. package/dist/chunks/chunk-T3HH3UEJ.ts.map +0 -7
  197. package/dist/chunks/chunk-TEN2ZBJV.ts.map +0 -7
  198. package/dist/chunks/chunk-THMF4BVL.ts.map +0 -7
  199. package/dist/chunks/chunk-YDWUZVPN.ts.map +0 -7
  200. package/dist/chunks/chunk-YHEMENKV.ts.map +0 -7
  201. package/dist/chunks/chunk-Z4DQF6L3.ts.map +0 -7
  202. package/dist/chunks/chunk-ZI5MXMEX.ts.map +0 -7
  203. package/dist/chunks/file-selection-FTTKAOYX.ts.map +0 -7
  204. package/dist/chunks/git-backend-GMVS4PD4.ts.map +0 -7
  205. package/dist/chunks/manager-result-dispatcher-ASKGOSW5.ts +0 -22
  206. package/dist/chunks/manager-ui-IPAOAWZA.ts.map +0 -7
  207. package/dist/chunks/remote-selection-ui-QDNR7WEI.ts +0 -21
  208. package/dist/chunks/s3-backend-66O4FGBH.ts.map +0 -7
  209. package/dist/chunks/setup-wizard-JUJZYNNM.ts.map +0 -7
  210. package/dist/chunks/sync-inspection-CST2KL65.ts +0 -20
  211. package/dist/chunks/sync-operations-LBX3GKCM.ts.map +0 -7
  212. package/dist/chunks/webdav-backend-ECIKD7SO.ts.map +0 -7
  213. /package/dist/chunks/{manager-result-dispatcher-ASKGOSW5.ts.map → manager-result-dispatcher-OTUMQM26.ts.map} +0 -0
  214. /package/dist/chunks/{remote-selection-ui-QDNR7WEI.ts.map → remote-selection-ui-TZTJF32Y.ts.map} +0 -0
  215. /package/dist/chunks/{setup-switch-IS2PEZ3J.ts.map → setup-switch-5G2VNJHL.ts.map} +0 -0
  216. /package/dist/chunks/{snapshot-HKEWCVK6.ts.map → snapshot-JL5DFSXI.ts.map} +0 -0
  217. /package/dist/chunks/{sync-inspection-CST2KL65.ts.map → sync-inspection-P3ENJLNF.ts.map} +0 -0
  218. /package/dist/chunks/{sync-state-Q2FTXTW6.ts.map → sync-state-VHJUZ4YG.ts.map} +0 -0
@@ -7,79 +7,67 @@ import type { ManagerDescription } from "./manager-state.js";
7
7
  import type { SyncAttentionState } from "./sync-attention.js";
8
8
 
9
9
  export interface SyncManagerAttentionOptions {
10
- getObservation?: () => StartupObservation | undefined;
11
- onObservationInvalidated?: () => void;
12
- getAttention?: () => SyncAttentionState | undefined;
13
- onSelectionResolved?: (expected: SyncAttentionState) => void;
10
+ getObservation?: () => StartupObservation | undefined;
11
+ onObservationInvalidated?: () => void;
12
+ getAttention?: () => SyncAttentionState | undefined;
13
+ onSelectionResolved?: (expected: SyncAttentionState) => void;
14
14
  }
15
15
 
16
16
  export function attentionMainMenuItems(
17
- manager: ManagerDescription,
17
+ manager: ManagerDescription,
18
18
  ): ActionMenuItem<"main" | "more" | "recovery", "review-attention">[] {
19
- if (!manager.attention && manager.observation?.inspection.selectionState?.kind !== "different")
20
- return [];
21
- const disabled = manager.attentionReviewDisabled === true;
22
- return [
23
- {
24
- id: "review-attention",
25
- label: "Review synced content (recommended)",
26
- action: "review-attention",
27
- ...(disabled
28
- ? {
29
- disabled: true,
30
- disabledReason: "Finish or recover the active operation first.",
31
- }
32
- : {}),
33
- },
34
- ];
19
+ if (!manager.attention && manager.observation?.inspection.selectionState?.kind !== "different") return [];
20
+ const disabled = manager.attentionReviewDisabled === true;
21
+ return [
22
+ {
23
+ id: "review-attention",
24
+ label: "Review synced content (recommended)",
25
+ action: "review-attention",
26
+ ...(disabled
27
+ ? {
28
+ disabled: true,
29
+ disabledReason: "Finish or recover the active operation first.",
30
+ }
31
+ : {}),
32
+ },
33
+ ];
35
34
  }
36
35
 
37
36
  export function blockedSyncMenuItem(
38
- label: string,
39
- manager: ManagerDescription,
37
+ label: string,
38
+ manager: ManagerDescription,
40
39
  ): ActionMenuItem<"main" | "more" | "recovery", "sync"> | undefined {
41
- if (label !== "Sync now (recommended)" || !manager.attentionBlocksSync) return undefined;
42
- return {
43
- id: "sync",
44
- label,
45
- description: "Review first.",
46
- action: "sync",
47
- disabled: true,
48
- disabledReason: "Review synced content first.",
49
- };
40
+ if (label !== "Sync now (recommended)" || !manager.attentionBlocksSync) return undefined;
41
+ return {
42
+ id: "sync",
43
+ label,
44
+ description: "Review first.",
45
+ action: "sync",
46
+ disabled: true,
47
+ disabledReason: "Review synced content first.",
48
+ };
50
49
  }
51
50
 
52
51
  export async function showManagerAttention(
53
- ctx: ExtensionCommandContext,
54
- attention: SyncAttentionState,
55
- runRoute: RunRoute,
56
- signal: AbortSignal | undefined,
57
- onSelectionResolved: (() => void) | undefined,
52
+ ctx: ExtensionCommandContext,
53
+ attention: SyncAttentionState,
54
+ runRoute: RunRoute,
55
+ signal: AbortSignal | undefined,
56
+ onSelectionResolved: (() => void) | undefined,
58
57
  ): Promise<"close" | "stay"> {
59
- const { showRemoteSelectionReview } = await import("./remote-selection-ui.js");
60
- if (signal?.aborted) return "close";
61
- const review = await showRemoteSelectionReview(
62
- ctx,
63
- attention.decision.setupName,
64
- signal,
65
- undefined,
66
- {
67
- decision: attention.decision,
68
- origin: attention.origin,
69
- runRoute,
70
- onSelectionResolved,
71
- },
72
- );
73
- if (review.kind === "route-result") {
74
- const disposition = await dispatchManagerResult(
75
- ctx,
76
- review.result,
77
- review.route,
78
- runRoute,
79
- signal,
80
- { onSelectionResolved },
81
- );
82
- return disposition.kind;
83
- }
84
- return review.kind === "closed" || review.kind === "stale" ? "close" : "stay";
58
+ const { showRemoteSelectionReview } = await import("./remote-selection-ui.js");
59
+ if (signal?.aborted) return "close";
60
+ const review = await showRemoteSelectionReview(ctx, attention.decision.setupName, signal, undefined, {
61
+ decision: attention.decision,
62
+ origin: attention.origin,
63
+ runRoute,
64
+ onSelectionResolved,
65
+ });
66
+ if (review.kind === "route-result") {
67
+ const disposition = await dispatchManagerResult(ctx, review.result, review.route, runRoute, signal, {
68
+ onSelectionResolved,
69
+ });
70
+ return disposition.kind;
71
+ }
72
+ return review.kind === "closed" || review.kind === "stale" ? "close" : "stay";
85
73
  }
@@ -1,9 +1,6 @@
1
1
  import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
2
  import { runConfirmation } from "@narumitw/pi-tui-kit";
3
- import {
4
- inspectOperationAvailability,
5
- operationCanRecover,
6
- } from "../state/operation-availability.js";
3
+ import { inspectOperationAvailability, operationCanRecover } from "../state/operation-availability.js";
7
4
  import { errorMessage } from "../sync/sync-errors.js";
8
5
  import type { RunRoute } from "./cancellable-operation.js";
9
6
  import type { ManagerDescription } from "./manager-state.js";
@@ -12,58 +9,52 @@ import { safeTerminalText } from "./terminal-text.js";
12
9
  export type RecoveryDisposition = "restored" | "stay" | "close";
13
10
 
14
11
  export async function recoverSyncAccess(
15
- ctx: ExtensionCommandContext,
16
- manager: ManagerDescription,
17
- runRoute: RunRoute,
18
- sessionSignal: AbortSignal | undefined,
19
- actionSignal: AbortSignal,
12
+ ctx: ExtensionCommandContext,
13
+ manager: ManagerDescription,
14
+ runRoute: RunRoute,
15
+ sessionSignal: AbortSignal | undefined,
16
+ actionSignal: AbortSignal,
20
17
  ): Promise<RecoveryDisposition> {
21
- const operation = manager.operation;
22
- if (!operation || !operationCanRecover(operation)) {
23
- ctx.ui.notify(
24
- "Operation status changed. Refresh the manager before retrying recovery.",
25
- "warning",
26
- );
27
- return "stay";
28
- }
29
- const signal = sessionSignal ? AbortSignal.any([sessionSignal, actionSignal]) : actionSignal;
30
- if (signal.aborted) return "close";
31
- const unreadable = operation.kind === "recoverable-unreadable";
32
- const details = unreadable
33
- ? "Pi-sync cannot verify who owns the unreadable lock. Close other Pi sessions that may be syncing before continuing."
34
- : `The recorded ${safeTerminalText(operation.lock.command)} operation (pid ${operation.lock.pid}) appears to have stopped. Close other Pi sessions that may still be syncing before continuing.`;
35
- const confirmation = await runConfirmation(ctx, {
36
- title: "Restore sync access?",
37
- message: [
38
- details,
39
- "",
40
- "This removes only the local operation lock.",
41
- "It does not change settings, local files, sync state, or remote data.",
42
- ].join("\n"),
43
- confirmLabel: "Remove local lock and continue",
44
- cancelLabel: "Cancel",
45
- signal,
46
- isCurrent: () => !signal.aborted,
47
- onError: (_currentCtx, error) => {
48
- ctx.ui.notify(
49
- `Recovery confirmation failed: ${safeTerminalText(errorMessage(error))}`,
50
- "error",
51
- );
52
- },
53
- });
54
- if (confirmation.kind === "stale") return "close";
55
- if (confirmation.kind === "closed") {
56
- if (confirmation.reason === "close") return "close";
57
- if (!signal.aborted) {
58
- ctx.ui.notify("Recovery cancelled; the local operation lock was not changed.", "info");
59
- }
60
- return "stay";
61
- }
62
- if (confirmation.kind !== "confirmed") return "stay";
63
- if (signal.aborted) return "close";
64
- await runRoute(unreadable ? "unlock --stale" : "unlock", signal);
65
- if (signal.aborted) return "close";
66
- const latest = await inspectOperationAvailability();
67
- if (signal.aborted) return "close";
68
- return latest.kind === "free" ? "restored" : "stay";
18
+ const operation = manager.operation;
19
+ if (!operation || !operationCanRecover(operation)) {
20
+ ctx.ui.notify("Operation status changed. Refresh the manager before retrying recovery.", "warning");
21
+ return "stay";
22
+ }
23
+ const signal = sessionSignal ? AbortSignal.any([sessionSignal, actionSignal]) : actionSignal;
24
+ if (signal.aborted) return "close";
25
+ const unreadable = operation.kind === "recoverable-unreadable";
26
+ const details = unreadable
27
+ ? "Pi-sync cannot verify who owns the unreadable lock. Close other Pi sessions that may be syncing before continuing."
28
+ : `The recorded ${safeTerminalText(operation.lock.command)} operation (pid ${operation.lock.pid}) appears to have stopped. Close other Pi sessions that may still be syncing before continuing.`;
29
+ const confirmation = await runConfirmation(ctx, {
30
+ title: "Restore sync access?",
31
+ message: [
32
+ details,
33
+ "",
34
+ "This removes only the local operation lock.",
35
+ "It does not change settings, local files, sync state, or remote data.",
36
+ ].join("\n"),
37
+ confirmLabel: "Remove local lock and continue",
38
+ cancelLabel: "Cancel",
39
+ signal,
40
+ isCurrent: () => !signal.aborted,
41
+ onError: (_currentCtx, error) => {
42
+ ctx.ui.notify(`Recovery confirmation failed: ${safeTerminalText(errorMessage(error))}`, "error");
43
+ },
44
+ });
45
+ if (confirmation.kind === "stale") return "close";
46
+ if (confirmation.kind === "closed") {
47
+ if (confirmation.reason === "close") return "close";
48
+ if (!signal.aborted) {
49
+ ctx.ui.notify("Recovery cancelled; the local operation lock was not changed.", "info");
50
+ }
51
+ return "stay";
52
+ }
53
+ if (confirmation.kind !== "confirmed") return "stay";
54
+ if (signal.aborted) return "close";
55
+ await runRoute(unreadable ? "unlock --stale" : "unlock", signal);
56
+ if (signal.aborted) return "close";
57
+ const latest = await inspectOperationAvailability();
58
+ if (signal.aborted) return "close";
59
+ return latest.kind === "free" ? "restored" : "stay";
69
60
  }
@@ -1,93 +1,79 @@
1
1
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import type {
3
- CancellableOperationResult,
4
- RunRoute,
5
- RunRouteResult,
6
- } from "./cancellable-operation.js";
2
+ import type { CancellableOperationResult, RunRoute, RunRouteResult } from "./cancellable-operation.js";
7
3
  import { type RemoteSelectionOrigin, showRemoteSelectionReview } from "./remote-selection-ui.js";
8
4
  import { showSyncResolution } from "./sync-resolution-ui.js";
9
5
 
10
6
  const MAX_DECISION_TRANSITIONS = 32;
11
7
 
12
8
  export interface ManagerResultDisposition {
13
- kind: "close" | "stay";
14
- appliedRoute?: "sync" | "pull" | "push";
9
+ kind: "close" | "stay";
10
+ appliedRoute?: "sync" | "pull" | "push";
15
11
  }
16
12
 
17
13
  export interface ManagerResultOptions {
18
- cancelLabel?: string;
19
- onSelectionResolved?: () => void;
20
- withStateAccess?: <T>(task: () => Promise<T>) => Promise<T>;
14
+ cancelLabel?: string;
15
+ onSelectionResolved?: () => void;
16
+ withStateAccess?: <T>(task: () => Promise<T>) => Promise<T>;
21
17
  }
22
18
 
23
19
  export async function dispatchManagerResult(
24
- ctx: ExtensionContext,
25
- initialResult: CancellableOperationResult,
26
- origin: Exclude<RemoteSelectionOrigin, "settings">,
27
- runRoute: RunRoute,
28
- signal?: AbortSignal,
29
- options: ManagerResultOptions = {},
20
+ ctx: ExtensionContext,
21
+ initialResult: CancellableOperationResult,
22
+ origin: Exclude<RemoteSelectionOrigin, "settings">,
23
+ runRoute: RunRoute,
24
+ signal?: AbortSignal,
25
+ options: ManagerResultOptions = {},
30
26
  ): Promise<ManagerResultDisposition> {
31
- let result: CancellableOperationResult | RunRouteResult = initialResult;
32
- let currentRoute: "sync" | "pull" | "push" = origin;
33
- let resolving = false;
27
+ let result: CancellableOperationResult | RunRouteResult = initialResult;
28
+ let currentRoute: "sync" | "pull" | "push" = origin;
29
+ let resolving = false;
34
30
 
35
- for (let transition = 0; transition < MAX_DECISION_TRANSITIONS; transition += 1) {
36
- if (signal?.aborted) return { kind: "close" };
37
- if (result.kind === "remote-selection-required") {
38
- resolving = true;
39
- const resolution = await showRemoteSelectionReview(
40
- ctx,
41
- result.decision.setupName,
42
- signal,
43
- undefined,
44
- {
45
- decision: result.decision,
46
- origin: currentRoute,
47
- runRoute,
48
- cancelLabel: options.cancelLabel,
49
- onSelectionResolved: options.onSelectionResolved,
50
- withStateAccess: options.withStateAccess,
51
- },
52
- );
53
- if (resolution.kind === "route-result") {
54
- result = resolution.result;
55
- currentRoute = resolution.route;
56
- continue;
57
- }
58
- return resolution.kind === "closed" || resolution.kind === "stale"
59
- ? { kind: "close" }
60
- : { kind: "stay" };
61
- }
62
- if (result.kind === "decision-required") {
63
- resolving = true;
64
- const resolution = await showSyncResolution(ctx, result.decision, runRoute, signal);
65
- if (resolution.kind === "route-result") {
66
- result = resolution.result;
67
- currentRoute = resolution.route;
68
- continue;
69
- }
70
- if (resolution.kind === "resolved") {
71
- return { kind: "close", appliedRoute: resolution.direction };
72
- }
73
- return resolution.kind === "closed" || resolution.kind === "stale"
74
- ? { kind: "close" }
75
- : { kind: "stay" };
76
- }
77
- if (result.kind === "closed") return { kind: "close" };
78
- if (result.kind === "completed") {
79
- const appliedRoute = result.outcome === "applied" ? currentRoute : undefined;
80
- if (resolving || (origin === "pull" && result.outcome === "applied")) {
81
- return { kind: "close", ...(appliedRoute ? { appliedRoute } : {}) };
82
- }
83
- return { kind: "stay", ...(appliedRoute ? { appliedRoute } : {}) };
84
- }
85
- return { kind: "stay" };
86
- }
31
+ for (let transition = 0; transition < MAX_DECISION_TRANSITIONS; transition += 1) {
32
+ if (signal?.aborted) return { kind: "close" };
33
+ if (result.kind === "remote-selection-required") {
34
+ resolving = true;
35
+ const resolution = await showRemoteSelectionReview(ctx, result.decision.setupName, signal, undefined, {
36
+ decision: result.decision,
37
+ origin: currentRoute,
38
+ runRoute,
39
+ cancelLabel: options.cancelLabel,
40
+ onSelectionResolved: options.onSelectionResolved,
41
+ withStateAccess: options.withStateAccess,
42
+ });
43
+ if (resolution.kind === "route-result") {
44
+ result = resolution.result;
45
+ currentRoute = resolution.route;
46
+ continue;
47
+ }
48
+ return resolution.kind === "closed" || resolution.kind === "stale" ? { kind: "close" } : { kind: "stay" };
49
+ }
50
+ if (result.kind === "decision-required") {
51
+ resolving = true;
52
+ const resolution = await showSyncResolution(ctx, result.decision, runRoute, signal);
53
+ if (resolution.kind === "route-result") {
54
+ result = resolution.result;
55
+ currentRoute = resolution.route;
56
+ continue;
57
+ }
58
+ if (resolution.kind === "resolved") {
59
+ return { kind: "close", appliedRoute: resolution.direction };
60
+ }
61
+ return resolution.kind === "closed" || resolution.kind === "stale" ? { kind: "close" } : { kind: "stay" };
62
+ }
63
+ if (result.kind === "closed") return { kind: "close" };
64
+ if (result.kind === "completed") {
65
+ const appliedRoute = result.outcome === "applied" ? currentRoute : undefined;
66
+ if (resolving || (origin === "pull" && result.outcome === "applied")) {
67
+ return { kind: "close", ...(appliedRoute ? { appliedRoute } : {}) };
68
+ }
69
+ return { kind: "stay", ...(appliedRoute ? { appliedRoute } : {}) };
70
+ }
71
+ return { kind: "stay" };
72
+ }
87
73
 
88
- ctx.ui.notify(
89
- "Sync resolution stopped after too many state changes. Start /sync again to review fresh state.",
90
- "warning",
91
- );
92
- return { kind: "stay" };
74
+ ctx.ui.notify(
75
+ "Sync resolution stopped after too many state changes. Start /sync again to review fresh state.",
76
+ "warning",
77
+ );
78
+ return { kind: "stay" };
93
79
  }