@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.
Files changed (194) hide show
  1. package/README.md +40 -15
  2. package/dist/chunks/agent-candidate-3JN6DJKT.ts +26 -0
  3. package/dist/chunks/agent-plan-QCUJWBP4.ts +99 -0
  4. package/dist/chunks/agent-plan-QCUJWBP4.ts.map +7 -0
  5. package/dist/chunks/agent-runner-E2JU2VZ6.ts +182 -0
  6. package/dist/chunks/agent-runner-E2JU2VZ6.ts.map +7 -0
  7. package/dist/chunks/agent-store-A3F6IYWZ.ts +152 -0
  8. package/dist/chunks/agent-store-A3F6IYWZ.ts.map +7 -0
  9. package/dist/chunks/chunk-5Q5SSKNG.ts +128 -0
  10. package/dist/chunks/chunk-5Q5SSKNG.ts.map +7 -0
  11. package/dist/chunks/chunk-5XZOC7FD.ts +215 -0
  12. package/dist/chunks/chunk-5XZOC7FD.ts.map +7 -0
  13. package/dist/chunks/{chunk-IJ6VV6FN.ts → chunk-AAEEHR7H.ts} +31 -87
  14. package/dist/chunks/chunk-AAEEHR7H.ts.map +7 -0
  15. package/dist/chunks/{chunk-CHSIAC4N.ts → chunk-B4QZBDBT.ts} +2 -2
  16. package/dist/chunks/{chunk-CL7BZRRQ.ts → chunk-CRKTAPSL.ts} +8 -8
  17. package/dist/chunks/chunk-DT7M4VQS.ts +69 -0
  18. package/dist/chunks/chunk-DT7M4VQS.ts.map +7 -0
  19. package/dist/chunks/chunk-ED3TYIVW.ts +229 -0
  20. package/dist/chunks/chunk-ED3TYIVW.ts.map +7 -0
  21. package/dist/chunks/{chunk-VHXBCEVF.ts → chunk-EUMINX7L.ts} +6 -6
  22. package/dist/chunks/chunk-FQOSHGJ7.ts +215 -0
  23. package/dist/chunks/chunk-FQOSHGJ7.ts.map +7 -0
  24. package/dist/chunks/chunk-GNH73BJ5.ts +153 -0
  25. package/dist/chunks/chunk-GNH73BJ5.ts.map +7 -0
  26. package/dist/chunks/{chunk-UQ3ALBIB.ts → chunk-GP5DU62N.ts} +8 -9
  27. package/dist/chunks/chunk-GP5DU62N.ts.map +7 -0
  28. package/dist/chunks/{chunk-5RZPINUQ.ts → chunk-GSNPASKB.ts} +2 -2
  29. package/dist/chunks/{chunk-QE7NZZWT.ts → chunk-IWZH5KYS.ts} +23 -7
  30. package/dist/chunks/chunk-IWZH5KYS.ts.map +7 -0
  31. package/dist/chunks/{chunk-5ZWQ33XL.ts → chunk-JJTARUSO.ts} +15 -14
  32. package/dist/chunks/chunk-JJTARUSO.ts.map +7 -0
  33. package/dist/chunks/{chunk-2DI6PDZV.ts → chunk-NLWS55B6.ts} +15 -2
  34. package/dist/chunks/chunk-NLWS55B6.ts.map +7 -0
  35. package/dist/chunks/{chunk-SDZCTCYQ.ts → chunk-OEWQVWLC.ts} +4 -4
  36. package/dist/chunks/{chunk-MYPSMJHJ.ts → chunk-OTZRCKYP.ts} +380 -510
  37. package/dist/chunks/chunk-OTZRCKYP.ts.map +7 -0
  38. package/dist/chunks/{chunk-WFKUKVCP.ts → chunk-OWBAZA6J.ts} +24 -12
  39. package/dist/chunks/chunk-OWBAZA6J.ts.map +7 -0
  40. package/dist/chunks/chunk-PRUEIIK7.ts +24 -0
  41. package/dist/chunks/chunk-PRUEIIK7.ts.map +7 -0
  42. package/dist/chunks/chunk-QGK445K7.ts +21 -0
  43. package/dist/chunks/chunk-QGK445K7.ts.map +7 -0
  44. package/dist/chunks/{chunk-3B4Z2SKV.ts → chunk-QO333NM4.ts} +49 -21
  45. package/dist/chunks/chunk-QO333NM4.ts.map +7 -0
  46. package/dist/chunks/{chunk-4SBTAVFL.ts → chunk-QSHMOL3I.ts} +22 -24
  47. package/dist/chunks/chunk-QSHMOL3I.ts.map +7 -0
  48. package/dist/chunks/chunk-S2FMH5FT.ts +97 -0
  49. package/dist/chunks/chunk-S2FMH5FT.ts.map +7 -0
  50. package/dist/chunks/{chunk-BVVP33U5.ts → chunk-SWRVXVFD.ts} +12 -9
  51. package/dist/chunks/chunk-SWRVXVFD.ts.map +7 -0
  52. package/dist/chunks/{chunk-ZX2Q6THB.ts → chunk-UBV24CG4.ts} +4 -5
  53. package/dist/chunks/chunk-UBV24CG4.ts.map +7 -0
  54. package/dist/chunks/chunk-UOHR7LRO.ts +70 -0
  55. package/dist/chunks/chunk-UOHR7LRO.ts.map +7 -0
  56. package/dist/chunks/{chunk-4QVPTIZB.ts → chunk-UPNVIUBN.ts} +3 -3
  57. package/dist/chunks/chunk-UZPMDLTL.ts +54 -0
  58. package/dist/chunks/chunk-UZPMDLTL.ts.map +7 -0
  59. package/dist/chunks/chunk-VJN7KU4J.ts +350 -0
  60. package/dist/chunks/chunk-VJN7KU4J.ts.map +7 -0
  61. package/dist/chunks/chunk-YHT4YTYG.ts +31 -0
  62. package/dist/chunks/chunk-YHT4YTYG.ts.map +7 -0
  63. package/dist/chunks/{chunk-V6VZ5TLG.ts → chunk-YK52VDXG.ts} +6 -5
  64. package/dist/chunks/chunk-YK52VDXG.ts.map +7 -0
  65. package/dist/chunks/config-KGIYTTXQ.ts +37 -0
  66. package/dist/chunks/{conflict-artifacts-BB2FZMIE.ts → conflict-artifacts-FXFIIBCB.ts} +8 -7
  67. package/dist/chunks/{conflict-review-4PLVLMRO.ts → conflict-review-6WIEQLXW.ts} +31 -24
  68. package/dist/chunks/{conflict-review-4PLVLMRO.ts.map → conflict-review-6WIEQLXW.ts.map} +1 -1
  69. package/dist/chunks/continuous-record-LFRVXQBD.ts +30 -0
  70. package/dist/chunks/continuous-sync-AWAAOXK2.ts +545 -0
  71. package/dist/chunks/continuous-sync-AWAAOXK2.ts.map +7 -0
  72. package/dist/chunks/{file-selection-WZX7HAY2.ts → file-selection-3JJYOVNR.ts} +5 -5
  73. package/dist/chunks/{git-backend-FD7AVSK6.ts → git-backend-KYDZ673X.ts} +28 -32
  74. package/dist/chunks/git-backend-KYDZ673X.ts.map +7 -0
  75. package/dist/chunks/{manager-result-dispatcher-6MQKFCWB.ts → manager-result-dispatcher-AGBBVDDS.ts} +10 -9
  76. package/dist/chunks/{manager-ui-IURS4WXY.ts → manager-ui-KRAW3NE2.ts} +464 -164
  77. package/dist/chunks/manager-ui-KRAW3NE2.ts.map +7 -0
  78. package/dist/chunks/{merge-baseline-store-EVQAAML6.ts → merge-baseline-store-RFCAH7KL.ts} +7 -5
  79. package/dist/chunks/{remote-selection-ui-6LJJ4IMQ.ts → remote-selection-ui-5725JDNC.ts} +9 -8
  80. package/dist/chunks/remote-selection-ui-5725JDNC.ts.map +7 -0
  81. package/dist/chunks/{s3-backend-AQGV6LFT.ts → s3-backend-Y7WSCLJI.ts} +38 -38
  82. package/dist/chunks/s3-backend-Y7WSCLJI.ts.map +7 -0
  83. package/dist/chunks/{setup-switch-KTUWL6DE.ts → setup-switch-E7PHKI6J.ts} +7 -6
  84. package/dist/chunks/setup-switch-E7PHKI6J.ts.map +7 -0
  85. package/dist/chunks/{setup-wizard-GSDPDFC4.ts → setup-wizard-65DROCIJ.ts} +14 -13
  86. package/dist/chunks/setup-wizard-65DROCIJ.ts.map +7 -0
  87. package/dist/chunks/{sync-inspection-K45QVLHB.ts → sync-inspection-XFJOAMJD.ts} +9 -8
  88. package/dist/chunks/sync-inspection-XFJOAMJD.ts.map +7 -0
  89. package/dist/chunks/{sync-operations-47OUX76C.ts → sync-operations-HDUDT5PD.ts} +188 -73
  90. package/dist/chunks/sync-operations-HDUDT5PD.ts.map +7 -0
  91. package/dist/chunks/{webdav-backend-TBILVEQK.ts → webdav-backend-6K26F6LT.ts} +39 -48
  92. package/dist/chunks/webdav-backend-6K26F6LT.ts.map +7 -0
  93. package/dist/index.ts +246 -131
  94. package/dist/index.ts.map +3 -3
  95. package/docs/agent-mode-verification.md +99 -0
  96. package/docs/continuous-sync-verification.md +87 -0
  97. package/docs/settings.md +131 -11
  98. package/docs/tui-contract.md +61 -0
  99. package/package.json +3 -2
  100. package/src/backends/git/git-backend.ts +1 -4
  101. package/src/backends/git/git-runner.ts +1 -5
  102. package/src/backends/s3/s3-backend.ts +3 -25
  103. package/src/backends/s3/s3-client.ts +14 -2
  104. package/src/backends/snapshot-pointer.ts +27 -0
  105. package/src/backends/webdav/webdav-backend.ts +3 -29
  106. package/src/backends/webdav/webdav-client.ts +11 -7
  107. package/src/commands/command-execution.ts +7 -3
  108. package/src/commands/command-handler.ts +15 -3
  109. package/src/commands/command-types.ts +10 -0
  110. package/src/commands/command.ts +4 -1
  111. package/src/settings/config.ts +20 -10
  112. package/src/settings/settings-store.ts +1 -34
  113. package/src/settings/settings-types.ts +10 -0
  114. package/src/settings/settings-validation.ts +4 -1
  115. package/src/snapshot/snapshot-codec.ts +1 -5
  116. package/src/state/lock.ts +13 -10
  117. package/src/state/merge-baseline-store.ts +19 -1
  118. package/src/state/state-directory.ts +8 -1
  119. package/src/sync/agent-candidate.ts +251 -0
  120. package/src/sync/agent-plan.ts +87 -0
  121. package/src/sync/agent-policy.ts +13 -0
  122. package/src/sync/agent-request.ts +17 -0
  123. package/src/sync/agent-runner.ts +188 -0
  124. package/src/sync/agent-store.ts +151 -0
  125. package/src/sync/agent-sync.ts +91 -0
  126. package/src/sync/apply-review.ts +63 -0
  127. package/src/sync/automatic-sync.ts +46 -33
  128. package/src/sync/continuous-errors.ts +85 -0
  129. package/src/sync/continuous-lease.ts +221 -0
  130. package/src/sync/continuous-record.ts +179 -0
  131. package/src/sync/continuous-storage.ts +55 -0
  132. package/src/sync/continuous-sync.ts +516 -0
  133. package/src/sync/continuous-watch.ts +59 -0
  134. package/src/sync/merged-sync.ts +268 -112
  135. package/src/sync/remote-snapshot.ts +2 -3
  136. package/src/sync/settings-merge.ts +19 -3
  137. package/src/sync/setup-switch.ts +1 -5
  138. package/src/sync/startup-check.ts +51 -29
  139. package/src/sync/startup-timings.ts +90 -0
  140. package/src/sync/sync-inspection.ts +16 -5
  141. package/src/sync/sync-mutations.ts +55 -28
  142. package/src/sync/sync-queries.ts +19 -3
  143. package/src/sync/text-merge.ts +3 -3
  144. package/src/sync-extension.ts +75 -19
  145. package/src/ui/agent-mode-setting.ts +91 -0
  146. package/src/ui/manager-state.ts +12 -6
  147. package/src/ui/manager-ui.ts +57 -7
  148. package/src/ui/merge-review.ts +3 -3
  149. package/src/ui/merged-sync-summary.ts +2 -18
  150. package/src/ui/remote-selection-ui.ts +5 -5
  151. package/src/ui/settings-frame.ts +55 -0
  152. package/src/ui/settings-ui.ts +223 -43
  153. package/src/ui/setup/s3-credentials-ui.ts +1 -5
  154. package/src/ui/setup/setup-name-ui.ts +2 -5
  155. package/src/ui/setup/setup-prompts.ts +4 -3
  156. package/src/ui/setup/setup-switcher.ts +2 -0
  157. package/src/ui/sync-behavior.ts +15 -0
  158. package/src/ui/sync-format.ts +69 -38
  159. package/src/ui/sync-setups-ui.ts +3 -5
  160. package/src/ui/transfer-changes.ts +33 -0
  161. package/src/ui/transfer-review.ts +27 -0
  162. package/dist/chunks/chunk-2DI6PDZV.ts.map +0 -7
  163. package/dist/chunks/chunk-3B4Z2SKV.ts.map +0 -7
  164. package/dist/chunks/chunk-4SBTAVFL.ts.map +0 -7
  165. package/dist/chunks/chunk-5ZWQ33XL.ts.map +0 -7
  166. package/dist/chunks/chunk-BVVP33U5.ts.map +0 -7
  167. package/dist/chunks/chunk-IJ6VV6FN.ts.map +0 -7
  168. package/dist/chunks/chunk-MYPSMJHJ.ts.map +0 -7
  169. package/dist/chunks/chunk-ORQRHEEO.ts +0 -362
  170. package/dist/chunks/chunk-ORQRHEEO.ts.map +0 -7
  171. package/dist/chunks/chunk-QE7NZZWT.ts.map +0 -7
  172. package/dist/chunks/chunk-UQ3ALBIB.ts.map +0 -7
  173. package/dist/chunks/chunk-V6VZ5TLG.ts.map +0 -7
  174. package/dist/chunks/chunk-WFKUKVCP.ts.map +0 -7
  175. package/dist/chunks/chunk-ZX2Q6THB.ts.map +0 -7
  176. package/dist/chunks/git-backend-FD7AVSK6.ts.map +0 -7
  177. package/dist/chunks/manager-ui-IURS4WXY.ts.map +0 -7
  178. package/dist/chunks/s3-backend-AQGV6LFT.ts.map +0 -7
  179. package/dist/chunks/setup-wizard-GSDPDFC4.ts.map +0 -7
  180. package/dist/chunks/sync-operations-47OUX76C.ts.map +0 -7
  181. package/dist/chunks/webdav-backend-TBILVEQK.ts.map +0 -7
  182. /package/dist/chunks/{conflict-artifacts-BB2FZMIE.ts.map → agent-candidate-3JN6DJKT.ts.map} +0 -0
  183. /package/dist/chunks/{chunk-CHSIAC4N.ts.map → chunk-B4QZBDBT.ts.map} +0 -0
  184. /package/dist/chunks/{chunk-CL7BZRRQ.ts.map → chunk-CRKTAPSL.ts.map} +0 -0
  185. /package/dist/chunks/{chunk-VHXBCEVF.ts.map → chunk-EUMINX7L.ts.map} +0 -0
  186. /package/dist/chunks/{chunk-5RZPINUQ.ts.map → chunk-GSNPASKB.ts.map} +0 -0
  187. /package/dist/chunks/{chunk-SDZCTCYQ.ts.map → chunk-OEWQVWLC.ts.map} +0 -0
  188. /package/dist/chunks/{chunk-4QVPTIZB.ts.map → chunk-UPNVIUBN.ts.map} +0 -0
  189. /package/dist/chunks/{manager-result-dispatcher-6MQKFCWB.ts.map → config-KGIYTTXQ.ts.map} +0 -0
  190. /package/dist/chunks/{merge-baseline-store-EVQAAML6.ts.map → conflict-artifacts-FXFIIBCB.ts.map} +0 -0
  191. /package/dist/chunks/{remote-selection-ui-6LJJ4IMQ.ts.map → continuous-record-LFRVXQBD.ts.map} +0 -0
  192. /package/dist/chunks/{file-selection-WZX7HAY2.ts.map → file-selection-3JJYOVNR.ts.map} +0 -0
  193. /package/dist/chunks/{setup-switch-KTUWL6DE.ts.map → manager-result-dispatcher-AGBBVDDS.ts.map} +0 -0
  194. /package/dist/chunks/{sync-inspection-K45QVLHB.ts.map → merge-baseline-store-RFCAH7KL.ts.map} +0 -0
@@ -1,7 +1,9 @@
1
1
  // @generated by scripts/build-runtime.mjs; do not edit.
2
2
  // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
3
3
  import {
4
- AUTOMATIC_SYNC_DESCRIPTION,
4
+ confirmMergeReview
5
+ } from "./chunk-5XZOC7FD.ts";
6
+ import {
5
7
  applyS3CredentialUpdate,
6
8
  chooseS3CredentialUpdate,
7
9
  chooseS3Credentials,
@@ -20,56 +22,58 @@ import {
20
22
  showEditS3Target,
21
23
  showEditWebDavStorageProfile,
22
24
  showEditWebDavTarget
23
- } from "./chunk-4SBTAVFL.ts";
25
+ } from "./chunk-QSHMOL3I.ts";
24
26
  import {
25
27
  dispatchManagerResult
26
- } from "./chunk-VHXBCEVF.ts";
28
+ } from "./chunk-EUMINX7L.ts";
27
29
  import {
28
30
  runCancellableOperation
29
- } from "./chunk-5ZWQ33XL.ts";
30
- import "./chunk-V6VZ5TLG.ts";
31
- import "./chunk-SDZCTCYQ.ts";
31
+ } from "./chunk-JJTARUSO.ts";
32
+ import "./chunk-YK52VDXG.ts";
32
33
  import {
33
34
  SETUP_SWITCH_ACTION_OPTIONS,
34
35
  saveOnSwitch,
35
36
  setupSwitchActionFromLabel,
36
37
  setupSwitchActionLabel,
37
38
  useSyncSetup
38
- } from "./chunk-UQ3ALBIB.ts";
39
+ } from "./chunk-GP5DU62N.ts";
40
+ import "./chunk-OEWQVWLC.ts";
41
+ import {
42
+ addStorageConnection,
43
+ removeStorageConnection,
44
+ removeSyncSetup,
45
+ updateStorageConnection,
46
+ updateSyncSetup
47
+ } from "./chunk-B4QZBDBT.ts";
39
48
  import {
40
49
  observationMatchesConfig,
41
50
  observationSummary,
42
51
  syncAttentionMatchesConfig
43
- } from "./chunk-5RZPINUQ.ts";
52
+ } from "./chunk-GSNPASKB.ts";
44
53
  import {
45
- captureMutationOwner,
46
- inspectLock,
47
- isLockGuardHeld,
48
- isStaleLock
49
- } from "./chunk-IJ6VV6FN.ts";
50
- import "./chunk-PWMWOJP4.ts";
54
+ captureMutationOwner
55
+ } from "./chunk-UOHR7LRO.ts";
51
56
  import {
52
57
  automaticSyncSummary
53
58
  } from "./chunk-ECYCNZOE.ts";
54
59
  import {
55
60
  configureSyncStatus
56
61
  } from "./chunk-K3RIEL6J.ts";
57
- import "./chunk-2DI6PDZV.ts";
62
+ import {
63
+ AUTOMATIC_SYNC_DESCRIPTION,
64
+ STARTUP_TRANSFER_DESCRIPTION,
65
+ startupTransferSummary
66
+ } from "./chunk-NLWS55B6.ts";
58
67
  import {
59
68
  safeTerminalText
60
69
  } from "./chunk-AMJRUXFT.ts";
70
+ import "./chunk-UBV24CG4.ts";
61
71
  import {
62
- readStateForConfig,
63
- syncStateFingerprint
64
- } from "./chunk-4QVPTIZB.ts";
65
- import "./chunk-BVVP33U5.ts";
72
+ syncErrorGuidance
73
+ } from "./chunk-G4RUXRWZ.ts";
66
74
  import {
67
- addStorageConnection,
68
- removeStorageConnection,
69
- removeSyncSetup,
70
- updateStorageConnection,
71
- updateSyncSetup
72
- } from "./chunk-CHSIAC4N.ts";
75
+ errorMessage
76
+ } from "./chunk-7D5SF7P3.ts";
73
77
  import {
74
78
  activeLocalConfigPath,
75
79
  isCloudflareR2Endpoint,
@@ -80,27 +84,35 @@ import {
80
84
  normalizeS3Endpoint,
81
85
  ownRecord,
82
86
  readLocalConfigObject,
87
+ requireSettings,
88
+ syncCheckConfigFingerprint,
83
89
  syncConfigReviewIdentity,
84
90
  updateLocalConfig
85
- } from "./chunk-3B4Z2SKV.ts";
86
- import "./chunk-2JZFHNLM.ts";
87
- import "./chunk-T2SJD3N7.ts";
88
- import "./chunk-ZX2Q6THB.ts";
89
- import {
90
- syncErrorGuidance
91
- } from "./chunk-G4RUXRWZ.ts";
92
- import {
93
- errorMessage
94
- } from "./chunk-7D5SF7P3.ts";
91
+ } from "./chunk-QO333NM4.ts";
95
92
  import {
96
93
  normalizeLocalFields,
97
94
  sameLocalFields
98
95
  } from "./chunk-A6HU4ERM.ts";
96
+ import {
97
+ inspectLock,
98
+ isLockGuardHeld,
99
+ isStaleLock
100
+ } from "./chunk-AAEEHR7H.ts";
101
+ import {
102
+ throwIfAborted
103
+ } from "./chunk-PWMWOJP4.ts";
99
104
  import {
100
105
  compareSyncInclude,
101
106
  syncIncludeSelection
102
107
  } from "./chunk-QOFMXPM5.ts";
103
108
  import "./chunk-ZW5ZLGBI.ts";
109
+ import {
110
+ readStateForConfig,
111
+ syncStateFingerprint
112
+ } from "./chunk-UPNVIUBN.ts";
113
+ import "./chunk-SWRVXVFD.ts";
114
+ import "./chunk-2JZFHNLM.ts";
115
+ import "./chunk-T2SJD3N7.ts";
104
116
  import "./chunk-HUZ5CR4W.ts";
105
117
 
106
118
  // src/ui/manager-ui.ts
@@ -172,7 +184,7 @@ function blockedSyncMenuItem(label, manager) {
172
184
  };
173
185
  }
174
186
  async function showManagerAttention(ctx, attention, runRoute, signal, onSelectionResolved) {
175
- const { showRemoteSelectionReview } = await import("./remote-selection-ui-6LJJ4IMQ.ts");
187
+ const { showRemoteSelectionReview } = await import("./remote-selection-ui-5725JDNC.ts");
176
188
  if (signal?.aborted) return "close";
177
189
  const review = await showRemoteSelectionReview(ctx, attention.decision.setupName, signal, void 0, {
178
190
  decision: attention.decision,
@@ -254,10 +266,6 @@ async function countValidSyncSetups(setups, signal) {
254
266
  }
255
267
  return count;
256
268
  }
257
- function throwIfAborted(signal) {
258
- if (!signal?.aborted) return;
259
- throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError");
260
- }
261
269
  async function showSyncSetups(ctx, actions, signal) {
262
270
  let selectedName;
263
271
  let exit = false;
@@ -395,6 +403,8 @@ async function loadSetupMenuState(selectedName, signal) {
395
403
  `Sessions: ${selection.sessions ? "On \u2014 privacy-sensitive" : "Off"}`,
396
404
  `Automatic sync: ${config.automatic ? "On" : "Off"}`,
397
405
  `Automatic transfer at startup: ${config.automaticTransfer ? "On \u2014 may upload, replace, or delete selected files; no reload" : "Off"}`,
406
+ `Agent mode: ${config.agentMode ? "On (experimental; autonomous merged sync)" : "Off"}`,
407
+ `Continuous sync: ${config.continuous ? config.agentMode ? "On (experimental; recurring requests and transfers)" : "Saved On; paused until Agent mode is enabled" : "Off"}`,
398
408
  isCurrent ? "Edit included content and automatic sync in /sync \u2192 Settings." : "To edit included content or automatic sync, make this setup current, then open /sync \u2192 Settings."
399
409
  ];
400
410
  } catch (error) {
@@ -453,6 +463,8 @@ var MAIN_MENU_ACTIONS = [
453
463
  "Sync now",
454
464
  "Pull from remote\u2026",
455
465
  "Push to remote\u2026",
466
+ "View differences",
467
+ "Check remote status",
456
468
  "Settings",
457
469
  "History",
458
470
  "Diagnostics"
@@ -514,7 +526,7 @@ async function describeManagerState(signal, attention, inspectOperation = inspec
514
526
  const noSyncedContent = config.include.length === 0;
515
527
  const syncState = changesBlocked ? void 0 : await readStateForConfig(config).catch(() => void 0);
516
528
  const currentObservation = observation && observationMatchesConfig(observation, config) && syncState && syncStateFingerprint(syncState) === observation.inspection.stateIdentity ? observation : void 0;
517
- const lastAppliedSnapshot = changesBlocked ? "Unavailable while operations are locked" : syncState?.lastAppliedSnapshot ? safeTerminalText(syncState.lastAppliedSnapshot) : syncState ? "Never synced" : "Unavailable";
529
+ const lastAppliedSnapshot = changesBlocked ? "Unavailable while operations are locked" : syncState?.lastAppliedSnapshot ? `${truncateToWidth(safeTerminalText(syncState.lastAppliedSnapshot), 20, "\u2026")} (application time unavailable; full ID in History)` : syncState ? "Never synced" : "Unavailable";
518
530
  const canSwitch = await countValidSyncSetups(configuredTargets, signal) > 1;
519
531
  const mainActions = [...MAIN_MENU_ACTIONS];
520
532
  if (canSwitch) mainActions.push("Switch sync setup");
@@ -524,10 +536,7 @@ async function describeManagerState(signal, attention, inspectOperation = inspec
524
536
  "",
525
537
  `Current sync setup: ${safeTerminalText(config.setupName)}`,
526
538
  `Storage: ${backendStorageDescription(config)}`,
527
- `Included: ${selection.builtIns.length} built-in group${selection.builtIns.length === 1 ? "" : "s"} \xB7 ${selection.custom.length} extra path${selection.custom.length === 1 ? "" : "s"} \xB7 Sessions ${selection.sessions ? "on" : "off"}`,
528
- `Automatic sync: ${automaticSyncSummary(config.automatic)}`,
529
- `Automatic transfer at startup: ${config.automaticTransfer ? "On \u2014 upload/replace/delete at idle; no reload" : "Off"}`,
530
- `Last applied: ${lastAppliedSnapshot}`,
539
+ "CURRENT STATE",
531
540
  ...syncState?.unresolved?.length ? [
532
541
  `Unresolved: ${syncState.unresolved.length} dependency groups \xB7 choose Review unresolved conflicts`,
533
542
  `Last observed: ${safeTerminalText(syncState.lastObservedSnapshot ?? "unknown")}`
@@ -540,9 +549,15 @@ async function describeManagerState(signal, attention, inspectOperation = inspec
540
549
  `Startup check (${safeTerminalText(currentObservation.checkedAt)}): ${observationSummary(currentObservation)}`,
541
550
  "Check-time observation only; synchronization rechecks current content."
542
551
  ] : ["Remote status: Not checked"],
552
+ `Included: ${selection.builtIns.length} built-in group${selection.builtIns.length === 1 ? "" : "s"} \xB7 ${selection.custom.length} extra path${selection.custom.length === 1 ? "" : "s"} \xB7 Sessions ${selection.sessions ? "on" : "off"}`,
553
+ `Automatic sync: ${automaticSyncSummary(config.automatic)}`,
554
+ `Startup transfer: ${startupTransferSummary(config)}`,
555
+ `Agent mode: ${config.agentMode ? "On (experimental; autonomous merged sync)" : "Off"}`,
556
+ ...config.continuous ? await (await import("./continuous-record-LFRVXQBD.ts")).continuousStatusLines(config) : [],
557
+ `Last applied: ${lastAppliedSnapshot}`,
543
558
  ...noSyncedContent ? ["", "No included content is selected. Choose included content in Settings before syncing."] : [],
544
559
  "",
545
- "What do you want to do?"
560
+ "ACTIONS \xB7 transfer, inspect, or manage"
546
561
  ];
547
562
  return {
548
563
  title: (changesBlocked ? ["Manage sync", ...operationStatusLines(operation)] : ordinaryTitle).join("\n"),
@@ -630,8 +645,112 @@ function backendStorageDescription(config) {
630
645
  // src/ui/settings-ui.ts
631
646
  import { defineMenu as defineMenu4, runMenu as runMenu4 } from "@narumitw/pi-tui-kit";
632
647
 
633
- // src/ui/storage-connections-ui.ts
648
+ // src/ui/agent-mode-setting.ts
634
649
  import { isDeepStrictEqual } from "node:util";
650
+ var AGENT_MODE_DESCRIPTION = "This setup, experimental: one autonomous merged sync next startup and on Sync now. Sends eligible conflict text to the selected Pi model; may incur costs and replace/delete selected local and remote content without transfer prompts. No automatic reload. Unsupported conflicts pause.";
651
+ var CONTINUOUS_DESCRIPTION = "This setup, experimental: recurring sync while Pi is open. Requires Agent mode. Watches selected files, checks the remote every 60\u201366 seconds, and retries temporary failures. Local changes and retries can cause extra requests between polls. May disclose conflict text and incur model/storage costs. No automatic reload.";
652
+ function saveContinuousMode(ctx, setupName, enabled, signal) {
653
+ return saveAgentMode(ctx, setupName, enabled, signal, "continuous");
654
+ }
655
+ async function saveAgentMode(ctx, setupName, enabled, signal, preference = "agentMode") {
656
+ const validate = captureMutationOwner(ctx, signal);
657
+ const settings = await requireSettings();
658
+ validate();
659
+ const setup = settings.syncSetups[setupName];
660
+ if (!setup || settings.activeSyncSetup !== setupName) throw new Error("Current sync setup changed; reopen Settings.");
661
+ const continuous = preference === "continuous";
662
+ const label = continuous ? "Continuous sync" : "Agent mode";
663
+ if (enabled && continuous && !setup.sync.agentMode) throw new Error("Enable Agent mode before Continuous sync.");
664
+ if (Boolean(setup.sync[preference]) === enabled) return true;
665
+ if (enabled) {
666
+ const summary = [
667
+ `Setup: ${safeTerminalText(setupName)}`,
668
+ `Storage connection: ${safeTerminalText(setup.storage.connection)}`,
669
+ `Storage path: ${safeTerminalText(setup.storage.path)}`,
670
+ ...setup.storage.bucket ? [`Bucket: ${safeTerminalText(String(setup.storage.bucket))}`] : [],
671
+ ...setup.storage.branch ? [`Branch: ${safeTerminalText(String(setup.storage.branch))}`] : [],
672
+ `Included paths: ${setup.sync.include.map(safeTerminalText).join(", ")}`,
673
+ continuous ? CONTINUOUS_DESCRIPTION : AGENT_MODE_DESCRIPTION,
674
+ ...continuous ? [
675
+ "Upgrade all Pi processes sharing this agent directory before enabling Continuous sync. Monitoring starts after the /sync command or manager closes. An elected idle local session uses its selected Pi model; another session can use a different provider/model. Each computer is a separate replica.",
676
+ "Automatic requests are limited to two per candidate and two per replica binding in a rolling 15-minute window, across restarts. Network/storage checks and requests may incur costs. Retries start at five seconds and grow to five minutes. These are request bounds, not a currency cap."
677
+ ] : setup.sync.continuous ? ["The saved Continuous sync preference will resume monitoring after the /sync command or manager closes."] : [],
678
+ "Only bounded Markdown/plain-text conflicts and atomic global settings choices are eligible. Model errors can change intended behavior. Session histories, code, deletion conflicts and path collisions are not model-resolved.",
679
+ "The existing baseline, selection, secret-scan and publication checks remain. Pi Sync cannot exclude arbitrary concurrent local writers. Disabling does not undo completed transfers or provider charges. No transfer starts from Settings."
680
+ ].join("\n\n");
681
+ if (!await confirmMergeReview(
682
+ ctx,
683
+ `Enable experimental ${label}?`,
684
+ summary,
685
+ signal,
686
+ () => !signal?.aborted,
687
+ `Enable ${label}`,
688
+ summary.split("\n")
689
+ ))
690
+ return false;
691
+ validate();
692
+ }
693
+ await updateLocalConfig((current) => {
694
+ validate();
695
+ if (current.activeSyncSetup !== setupName || !isDeepStrictEqual(current.syncSetups[setupName], setup) || !isDeepStrictEqual(
696
+ current.storageConnections[setup.storage.connection],
697
+ settings.storageConnections[setup.storage.connection]
698
+ ))
699
+ throw new Error("Sync setup or connection changed during Agent mode review; reopen Settings.");
700
+ return {
701
+ ...current,
702
+ syncSetups: { ...current.syncSetups, [setupName]: { ...setup, sync: { ...setup.sync, [preference]: enabled } } }
703
+ };
704
+ }, signal);
705
+ return true;
706
+ }
707
+
708
+ // src/ui/settings-frame.ts
709
+ import { stripVTControlCharacters } from "node:util";
710
+ function boundedSettingsUI(ctx) {
711
+ return {
712
+ ...ctx.ui,
713
+ custom: (factory, options) => ctx.ui.custom((tui, ...args) => {
714
+ const terminal = new Proxy(tui.terminal, {
715
+ get: (target, key) => key === "rows" ? Math.max(8, target.rows) : Reflect.get(target, key)
716
+ });
717
+ const screenTui = new Proxy(tui, {
718
+ get(target, key) {
719
+ if (key === "terminal") return terminal;
720
+ const value = Reflect.get(target, key);
721
+ return typeof value === "function" ? value.bind(target) : value;
722
+ }
723
+ });
724
+ const guard = (component2) => {
725
+ const render = component2.render.bind(component2);
726
+ const handleMouse = component2.handleMouse?.bind(component2);
727
+ let sourceRow = 0;
728
+ const compact = () => tui.terminal.rows < 8;
729
+ component2.render = (width) => {
730
+ if (width <= 0) return [];
731
+ const lines = render(width);
732
+ if (!compact()) return lines;
733
+ const active = lines.findIndex((line) => stripVTControlCharacters(line).startsWith("\u2192"));
734
+ const search = lines.findIndex((line) => stripVTControlCharacters(line).startsWith(">"));
735
+ sourceRow = active < 0 ? Math.max(0, search) : active;
736
+ return lines.slice(sourceRow, sourceRow + 1);
737
+ };
738
+ if (handleMouse)
739
+ component2.handleMouse = (event) => {
740
+ if (event.width <= 0 || compact() && event.y !== 0) return void 0;
741
+ component2.render(event.width);
742
+ return handleMouse(compact() ? { ...event, y: event.y + sourceRow } : event);
743
+ };
744
+ return component2;
745
+ };
746
+ const component = factory(screenTui, ...args);
747
+ return component instanceof Promise ? component.then(guard) : guard(component);
748
+ }, options)
749
+ };
750
+ }
751
+
752
+ // src/ui/storage-connections-ui.ts
753
+ import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
635
754
  import { defineMenu as defineMenu2, runMenu as runMenu2 } from "@narumitw/pi-tui-kit";
636
755
  async function showStorageConnections(ctx, signal) {
637
756
  let selectedName;
@@ -826,7 +945,7 @@ async function editStorageConnection(ctx, name, profile, usedBy, signal) {
826
945
  (saveSignal) => updateStorageConnection(
827
946
  name,
828
947
  (current) => {
829
- if (current.type !== "s3" || !isDeepStrictEqual(current, profile)) {
948
+ if (current.type !== "s3" || !isDeepStrictEqual2(current, profile)) {
830
949
  throw new Error("Storage connection changed while it was open; reopen it.");
831
950
  }
832
951
  return {
@@ -1038,6 +1157,8 @@ async function showSetupSwitcher(ctx, runRoute, selectedName, signal) {
1038
1157
  `Included content: ${config.include.length} paths`,
1039
1158
  `Automatic sync: ${automaticSyncSummary(config.automatic)} \xB7 Sessions: ${config.include.includes("sessions") ? "On" : "Off"}`,
1040
1159
  `Automatic transfer at startup: ${config.automaticTransfer ? "On \u2014 may upload, replace, or delete selected files; no reload" : "Off"}`,
1160
+ `Agent mode: ${config.agentMode ? "On (experimental; autonomous merged sync)" : "Off"}`,
1161
+ `Continuous sync: ${config.continuous ? config.agentMode ? "On (experimental; recurring requests and transfers)" : "Saved On; paused until Agent mode is enabled" : "Off"}`,
1041
1162
  "",
1042
1163
  switchEffect
1043
1164
  ].join("\n"),
@@ -1227,118 +1348,172 @@ async function showSyncSettings(ctx, runRoute, signal) {
1227
1348
  const initial = await loadConfig();
1228
1349
  if (signal?.aborted) return "back";
1229
1350
  const setupName = initial.setupName;
1351
+ const groups = {
1352
+ content: { label: "Sync content", ids: ["include", "remoteInclude", "localFields"] },
1353
+ behavior: {
1354
+ label: "Sync behavior",
1355
+ ids: ["automatic", "automaticTransfer", "agentMode", "continuous", "onSwitch"]
1356
+ },
1357
+ storage: { label: "Storage & setups", ids: ["storageLocation", "setups", "connections"] },
1358
+ advanced: { label: "Advanced", ids: ["mergeSettings", "contentPolicy", "skipSecretScan", "showStatus", "debug"] },
1359
+ all: { label: "All settings (search)", ids: [] }
1360
+ };
1361
+ let saveStatus = "Simple choices save immediately. Back does not undo saved changes.";
1362
+ const settingsScreen = (state, group) => ({
1363
+ kind: "settings",
1364
+ title: `Pi Sync Settings \xB7 ${groups[group].label}`,
1365
+ lines: [
1366
+ `Setup: ${safeTerminalText(state.setupName)} \xB7 Connection: ${safeTerminalText(state.connectionName)}`,
1367
+ saveStatus
1368
+ ],
1369
+ items: [
1370
+ {
1371
+ id: "include",
1372
+ label: "Included paths",
1373
+ description: "This setup: choose paths in a draft, then review and Save or Discard. No transfer is started.",
1374
+ currentValue: `${state.include.length} paths \xB7 Edit`,
1375
+ action: "include"
1376
+ },
1377
+ {
1378
+ id: "remoteInclude",
1379
+ label: "Compare included paths",
1380
+ description: "This setup: compare included-path policies, not file contents. Review before saving either list; no files transfer unless you explicitly Continue.",
1381
+ currentValue: "Review",
1382
+ action: "remote-include"
1383
+ },
1384
+ {
1385
+ id: "localFields",
1386
+ label: "Machine-local fields",
1387
+ description: "This setup: settings.json root fields excluded from future portable snapshots, not project overrides. Policy changes require reviewed directional migration; old history remains.",
1388
+ currentValue: `${state.localFields?.length ?? 0} fields \xB7 Edit`,
1389
+ action: "local-fields"
1390
+ },
1391
+ {
1392
+ id: "storageLocation",
1393
+ label: "Storage location",
1394
+ description: "This setup: review and save bucket, branch, or path changes. Remote history is not moved.",
1395
+ currentValue: state.backend.type === "s3" ? `${safeTerminalText(state.backend.destination.bucket)} \xB7 ${safeTerminalText(state.backend.destination.prefix)}` : state.backend.type === "git" ? `${safeTerminalText(state.backend.destination.branch)} \xB7 ${safeTerminalText(state.backend.destination.directory)}` : safeTerminalText(state.backend.destination.path),
1396
+ action: "storage-location"
1397
+ },
1398
+ {
1399
+ id: "setups",
1400
+ label: "Manage sync setups",
1401
+ description: "This device: add, switch, edit, or remove sync setups. Each edit identifies its owner before saving.",
1402
+ currentValue: "Open",
1403
+ action: "setups"
1404
+ },
1405
+ {
1406
+ id: "connections",
1407
+ label: "Manage storage connections",
1408
+ description: "Shared connections on this device: review affected setups before saving server or credential changes.",
1409
+ currentValue: "Open",
1410
+ action: "connections"
1411
+ },
1412
+ {
1413
+ id: "automatic",
1414
+ label: "Automatic sync",
1415
+ description: AUTOMATIC_SYNC_DESCRIPTION,
1416
+ currentValue: state.automatic ? "On" : "Off",
1417
+ values: ["On", "Off"],
1418
+ action: "automatic"
1419
+ },
1420
+ {
1421
+ id: "automaticTransfer",
1422
+ label: "Startup transfer",
1423
+ description: `${STARTUP_TRANSFER_DESCRIPTION} Saved preference and requirements: ${startupTransferSummary(state)}.`,
1424
+ currentValue: state.automaticTransfer ? "On" : "Off",
1425
+ values: ["On", "Off"],
1426
+ action: "automatic-transfer"
1427
+ },
1428
+ {
1429
+ id: "agentMode",
1430
+ label: "Agent mode (experimental)",
1431
+ description: AGENT_MODE_DESCRIPTION,
1432
+ currentValue: state.agentMode ? "On" : "Off",
1433
+ values: ["On", "Off"],
1434
+ action: "agent-mode"
1435
+ },
1436
+ {
1437
+ id: "continuous",
1438
+ label: "Continuous sync (experimental)",
1439
+ description: `${CONTINUOUS_DESCRIPTION}${state.continuous && !state.agentMode ? " Saved On, but paused until Agent mode is enabled." : ""}`,
1440
+ currentValue: state.continuous ? "On" : "Off",
1441
+ values: ["On", "Off"],
1442
+ action: "continuous"
1443
+ },
1444
+ {
1445
+ id: "onSwitch",
1446
+ label: "On setup switch",
1447
+ description: "All setups on this device: ask before a pull review, open it automatically, or switch without pulling. Applies to subsequent switches.",
1448
+ currentValue: setupSwitchActionLabel(state.onSwitch),
1449
+ values: SETUP_SWITCH_ACTION_OPTIONS.map(({ label }) => label),
1450
+ action: "on-switch"
1451
+ },
1452
+ {
1453
+ id: "mergeSettings",
1454
+ label: "Merge settings fields",
1455
+ description: "This setup, experimental opt-in: merge independent global settings.json root fields with a verified private ancestor. Arrays and nested objects remain atomic. Applies to subsequent sync; no reload.",
1456
+ currentValue: state.mergeSettings ? "On" : "Off",
1457
+ values: ["On", "Off"],
1458
+ action: "merge-settings"
1459
+ },
1460
+ {
1461
+ id: "contentPolicy",
1462
+ label: "Content merge / partial progress",
1463
+ description: "This setup, experimental opt-in: review the version-5 upgrade before saving. Merge bounded text and prefix-only sessions; partial progress keeps withheld versions and baselines. Applies to subsequent sync; disabling does not clear recovery evidence.",
1464
+ currentValue: state.mergeContent ? state.partialSync ? "Merge + partial" : "Merge only" : state.partialSync ? "Partial only" : "Off",
1465
+ values: ["Off", "Merge only", "Merge + partial", "Partial only"],
1466
+ action: "content-policy"
1467
+ },
1468
+ {
1469
+ id: "skipSecretScan",
1470
+ label: "Skip secret scan",
1471
+ description: "All setups on this device: skip possible-secret checks on subsequent pushes, including automatic pushes. Other safety checks remain. Diagnostics still scans.",
1472
+ currentValue: state.skipSecretScan ? "On" : "Off",
1473
+ values: ["On", "Off"],
1474
+ action: "skip-secret-scan"
1475
+ },
1476
+ {
1477
+ id: "showStatus",
1478
+ label: "Show sync status",
1479
+ description: "All setups on this device: saved and applied immediately to Pi status. Review widgets and notifications remain available.",
1480
+ currentValue: state.showStatus ? "On" : "Off",
1481
+ values: ["On", "Off"],
1482
+ action: "show-status"
1483
+ },
1484
+ {
1485
+ id: "debug",
1486
+ label: "Debug startup timings",
1487
+ description: "All setups on this device: show startup durations in TUI/RPC notifications. Applies next session start; no credentials or file contents.",
1488
+ currentValue: state.debug ? "On" : "Off",
1489
+ values: ["On", "Off"],
1490
+ action: "debug"
1491
+ }
1492
+ ].filter((item) => group === "all" || groups[group].ids.includes(item.id))
1493
+ });
1230
1494
  const menu = defineMenu4({
1231
- start: "settings",
1495
+ start: "categories",
1232
1496
  screens: {
1233
- settings: ({ state }) => ({
1234
- kind: "settings",
1497
+ categories: ({ state }) => ({
1498
+ kind: "actions",
1235
1499
  title: "Pi Sync Settings",
1236
1500
  lines: [
1237
- `Sync setup: ${safeTerminalText(state.setupName)} \xB7 Storage connection: ${safeTerminalText(state.connectionName)}`
1501
+ `Setup: ${safeTerminalText(state.setupName)} \xB7 Connection: ${safeTerminalText(state.connectionName)}`,
1502
+ "Choose a task, or search all settings. Scope is shown in each setting's description.",
1503
+ saveStatus
1238
1504
  ],
1239
- // Flat, searchable task ordering: Content, Destination, Automation, Merge, Other.
1240
- // Kit has no heading rows; avoid focusable fake headings or a new component.
1241
- items: [
1242
- {
1243
- id: "include",
1244
- label: "Included content",
1245
- description: `${state.include.length} selected path${state.include.length === 1 ? "" : "s"}. Choose which paths this setup syncs.`,
1246
- currentValue: "Open editor",
1247
- action: "include"
1248
- },
1249
- {
1250
- id: "remoteInclude",
1251
- label: "Compare synced content",
1252
- description: "Review this device and remote content lists before choosing either one.",
1253
- currentValue: "Review",
1254
- action: "remote-include"
1255
- },
1256
- {
1257
- id: "localFields",
1258
- label: "Machine-local settings fields",
1259
- description: "Root field names omitted from future portable snapshots. Policy changes require directional migration; old history remains.",
1260
- currentValue: `${state.localFields?.length ?? 0} fields \xB7 Edit`,
1261
- action: "local-fields"
1262
- },
1263
- {
1264
- id: "storageLocation",
1265
- label: "Storage location",
1266
- description: "Destination: edit this setup's bucket, branch, or path; remote history is not moved.",
1267
- currentValue: state.backend.type === "s3" ? `${safeTerminalText(state.backend.destination.bucket)} \xB7 ${safeTerminalText(state.backend.destination.prefix)}` : state.backend.type === "git" ? `${safeTerminalText(state.backend.destination.branch)} \xB7 ${safeTerminalText(state.backend.destination.directory)}` : safeTerminalText(state.backend.destination.path),
1268
- action: "storage-location"
1269
- },
1270
- {
1271
- id: "setups",
1272
- label: "Manage sync setups",
1273
- description: "Destination: add, switch, edit, or remove sync setups.",
1274
- currentValue: "Open",
1275
- action: "setups"
1276
- },
1277
- {
1278
- id: "connections",
1279
- label: "Manage storage connections",
1280
- description: "Destination: manage shared server addresses and credentials; edits may affect multiple setups.",
1281
- currentValue: "Open",
1282
- action: "connections"
1283
- },
1284
- {
1285
- id: "automatic",
1286
- label: "Automatic sync",
1287
- description: AUTOMATIC_SYNC_DESCRIPTION,
1288
- currentValue: state.automatic ? "On" : "Off",
1289
- values: ["On", "Off"],
1290
- action: "automatic"
1291
- },
1292
- {
1293
- id: "automaticTransfer",
1294
- label: "Automatic transfer at startup",
1295
- description: "May upload, replace, or delete selected files once at idle startup in TUI/RPC. Requires a baseline and conditional/lease publication; never reloads resources. Turning off cancels pending work.",
1296
- currentValue: state.automaticTransfer ? "On" : "Off",
1297
- values: ["On", "Off"],
1298
- action: "automatic-transfer"
1299
- },
1300
- {
1301
- id: "onSwitch",
1302
- label: "After switching setup (all setups)",
1303
- description: "All setups: ask before a pull review, open it automatically, or switch without pulling.",
1304
- currentValue: setupSwitchActionLabel(state.onSwitch),
1305
- values: SETUP_SWITCH_ACTION_OPTIONS.map(({ label }) => label),
1306
- action: "on-switch"
1307
- },
1308
- {
1309
- id: "mergeSettings",
1310
- label: "Settings field merge (experimental)",
1311
- description: "Combine independent global settings.json fields using a verified private ancestor; arrays and nested objects remain atomic. No reload.",
1312
- currentValue: state.mergeSettings ? "On" : "Off",
1313
- values: ["On", "Off"],
1314
- action: "merge-settings"
1315
- },
1316
- {
1317
- id: "contentPolicy",
1318
- label: "Content / partial sync (experimental)",
1319
- description: "Version-5 opt-in: bounded text and prefix-only sessions; partial progress keeps full withheld versions and old baseline hashes.",
1320
- currentValue: state.mergeContent ? state.partialSync ? "Content & partial" : "Content only" : state.partialSync ? "Partial only" : "Off",
1321
- values: ["Off", "Content only", "Content & partial", "Partial only"],
1322
- action: "content-policy"
1323
- },
1324
- {
1325
- id: "skipSecretScan",
1326
- label: "Skip secret scan (all setups)",
1327
- description: "All setups: allow pushes without checking managed local files for possible secrets.",
1328
- currentValue: state.skipSecretScan ? "On" : "Off",
1329
- values: ["On", "Off"],
1330
- action: "skip-secret-scan"
1331
- },
1332
- {
1333
- id: "showStatus",
1334
- label: "Show status (all setups)",
1335
- description: "All setups: show sync progress and attention in Pi status.",
1336
- currentValue: state.showStatus ? "On" : "Off",
1337
- values: ["On", "Off"],
1338
- action: "show-status"
1339
- }
1340
- ]
1341
- })
1505
+ items: Object.entries(groups).map(([id, group]) => ({
1506
+ id,
1507
+ label: group.label,
1508
+ to: id
1509
+ })),
1510
+ hint: "back"
1511
+ }),
1512
+ content: ({ state }) => settingsScreen(state, "content"),
1513
+ behavior: ({ state }) => settingsScreen(state, "behavior"),
1514
+ storage: ({ state }) => settingsScreen(state, "storage"),
1515
+ advanced: ({ state }) => settingsScreen(state, "advanced"),
1516
+ all: ({ state }) => settingsScreen(state, "all")
1342
1517
  },
1343
1518
  actions: {
1344
1519
  "storage-location": async ({ signal: actionSignal }) => {
@@ -1362,8 +1537,8 @@ async function showSyncSettings(ctx, runRoute, signal) {
1362
1537
  try {
1363
1538
  const previous = await loadConfig(setupName);
1364
1539
  validate();
1365
- const mergeContent = value === "Content only" || value === "Content & partial";
1366
- const partialSync = value === "Partial only" || value === "Content & partial";
1540
+ const mergeContent = value === "Merge only" || value === "Merge + partial";
1541
+ const partialSync = value === "Partial only" || value === "Merge + partial";
1367
1542
  if (!await ctx.ui.confirm(
1368
1543
  "Save experimental content policy?",
1369
1544
  "This explicitly upgrades settings to version 5; partial snapshots use version 3 and older clients must refuse them. Text/session merge is conservative. Partial sync preserves withheld versions and old baselines. Disabling does not clear unresolved groups or recovery evidence. Review a directional migration first if portable policy changes.",
@@ -1371,6 +1546,7 @@ async function showSyncSettings(ctx, runRoute, signal) {
1371
1546
  ))
1372
1547
  return { kind: "rejected" };
1373
1548
  validate();
1549
+ ctx.ui.notify("Saving reviewed Pi Sync policy\u2026", "info");
1374
1550
  await updateLocalConfig((current) => {
1375
1551
  validate();
1376
1552
  const setup = current.syncSetups[setupName];
@@ -1386,6 +1562,8 @@ async function showSyncSettings(ctx, runRoute, signal) {
1386
1562
  };
1387
1563
  }, mutationSignal);
1388
1564
  validate();
1565
+ saveStatus = "Saved reviewed policy for this setup. Applies to subsequent sync operations; recovery evidence is retained.";
1566
+ ctx.ui.notify(saveStatus, "info");
1389
1567
  return { kind: "stay" };
1390
1568
  } catch (error) {
1391
1569
  if (!mutationSignal.aborted) notifySaveFailure(ctx, error);
@@ -1436,6 +1614,7 @@ async function showSyncSettings(ctx, runRoute, signal) {
1436
1614
  );
1437
1615
  validate();
1438
1616
  if (!confirmed) return { kind: "rejected" };
1617
+ ctx.ui.notify("Saving reviewed Pi Sync policy\u2026", "info");
1439
1618
  await updateLocalConfig((current) => {
1440
1619
  validate();
1441
1620
  const setup = current.syncSetups[setupName];
@@ -1451,6 +1630,8 @@ async function showSyncSettings(ctx, runRoute, signal) {
1451
1630
  };
1452
1631
  }, mutationSignal);
1453
1632
  validate();
1633
+ saveStatus = "Saved reviewed policy for this setup. Applies to subsequent sync operations; recovery evidence is retained.";
1634
+ ctx.ui.notify(saveStatus, "info");
1454
1635
  return { kind: "stay" };
1455
1636
  } catch (error) {
1456
1637
  if (!mutationSignal.aborted) notifySaveFailure(ctx, error);
@@ -1478,6 +1659,56 @@ async function showSyncSettings(ctx, runRoute, signal) {
1478
1659
  return { kind: "rejected" };
1479
1660
  }
1480
1661
  },
1662
+ "agent-mode": async ({ value, signal: actionSignal }) => {
1663
+ const mutationSignal = signal ? AbortSignal.any([signal, actionSignal]) : actionSignal;
1664
+ const validate = captureMutationOwner(ctx, mutationSignal);
1665
+ try {
1666
+ const saved = await saveAgentMode(ctx, setupName, value === "On", mutationSignal);
1667
+ if (!saved) return { kind: "rejected" };
1668
+ try {
1669
+ validate();
1670
+ ctx.ui.notify(
1671
+ "Agent mode saved. Applies to subsequent sync and the next session start; no transfer started.",
1672
+ "info"
1673
+ );
1674
+ } catch {
1675
+ }
1676
+ return { kind: "stay" };
1677
+ } catch (error) {
1678
+ try {
1679
+ validate();
1680
+ } catch {
1681
+ return { kind: "rejected" };
1682
+ }
1683
+ notifySaveFailure(ctx, error);
1684
+ return { kind: "rejected" };
1685
+ }
1686
+ },
1687
+ continuous: async ({ value, signal: actionSignal }) => {
1688
+ const mutationSignal = signal ? AbortSignal.any([signal, actionSignal]) : actionSignal;
1689
+ const validate = captureMutationOwner(ctx, mutationSignal);
1690
+ try {
1691
+ const saved = await saveContinuousMode(ctx, setupName, value === "On", mutationSignal);
1692
+ if (!saved) return { kind: "rejected" };
1693
+ try {
1694
+ validate();
1695
+ ctx.ui.notify(
1696
+ value === "On" ? "Continuous sync enabled. Monitoring starts after the /sync command or manager closes; completed work is not undone." : "Continuous sync disabled. Completed work is not undone.",
1697
+ "info"
1698
+ );
1699
+ } catch {
1700
+ }
1701
+ return { kind: "stay" };
1702
+ } catch (error) {
1703
+ try {
1704
+ validate();
1705
+ } catch {
1706
+ return { kind: "rejected" };
1707
+ }
1708
+ notifySaveFailure(ctx, error);
1709
+ return { kind: "rejected" };
1710
+ }
1711
+ },
1481
1712
  "automatic-transfer": async ({ value, signal: actionSignal }) => {
1482
1713
  const automaticTransfer = value === "On";
1483
1714
  const mutationSignal = signal ? AbortSignal.any([signal, actionSignal]) : actionSignal;
@@ -1529,6 +1760,18 @@ async function showSyncSettings(ctx, runRoute, signal) {
1529
1760
  return { kind: "rejected" };
1530
1761
  }
1531
1762
  },
1763
+ debug: async ({ value, signal: actionSignal }) => {
1764
+ const debug = value === "On";
1765
+ const mutationSignal = signal ? AbortSignal.any([signal, actionSignal]) : actionSignal;
1766
+ try {
1767
+ await updateLocalConfig((settings) => ({ ...settings, debug }), mutationSignal);
1768
+ if (mutationSignal.aborted) return { kind: "rejected" };
1769
+ return { kind: "stay" };
1770
+ } catch (error) {
1771
+ if (!mutationSignal.aborted) notifySaveFailure(ctx, error);
1772
+ return { kind: "rejected" };
1773
+ }
1774
+ },
1532
1775
  "on-switch": async ({ value, signal: actionSignal }) => {
1533
1776
  const action = value ? setupSwitchActionFromLabel(value) : void 0;
1534
1777
  if (!action) return { kind: "rejected" };
@@ -1553,7 +1796,7 @@ async function showSyncSettings(ctx, runRoute, signal) {
1553
1796
  },
1554
1797
  "remote-include": async ({ signal: actionSignal }) => {
1555
1798
  const reviewSignal = signal ? AbortSignal.any([signal, actionSignal]) : actionSignal;
1556
- const { showRemoteSelectionReview } = await import("./remote-selection-ui-6LJJ4IMQ.ts");
1799
+ const { showRemoteSelectionReview } = await import("./remote-selection-ui-5725JDNC.ts");
1557
1800
  if (reviewSignal.aborted) return { kind: "rejected" };
1558
1801
  const review = await showRemoteSelectionReview(ctx, setupName, reviewSignal, void 0, {
1559
1802
  origin: "settings",
@@ -1568,6 +1811,28 @@ async function showSyncSettings(ctx, runRoute, signal) {
1568
1811
  }
1569
1812
  }
1570
1813
  });
1814
+ for (const id of [
1815
+ "automatic",
1816
+ "automatic-transfer",
1817
+ "merge-settings",
1818
+ "skip-secret-scan",
1819
+ "show-status",
1820
+ "debug",
1821
+ "on-switch"
1822
+ ]) {
1823
+ const save = menu.actions[id];
1824
+ menu.actions[id] = async (action) => {
1825
+ const validate = captureMutationOwner(ctx, action.signal);
1826
+ validate();
1827
+ ctx.ui.notify("Saving Pi Sync setting\u2026", "info");
1828
+ const result = await save(action);
1829
+ if (action.signal.aborted || signal?.aborted) return { kind: "rejected" };
1830
+ validate();
1831
+ saveStatus = result?.kind === "rejected" ? "Save failed or rejected. Previous value retained; retry is available." : id === "automatic-transfer" || id === "automatic" || id === "debug" ? "Saved. Enabling startup work applies next session start; completed transfers are not undone." : "Saved. Applies to subsequent operations; status visibility applies immediately.";
1832
+ ctx.ui.notify(saveStatus, result?.kind === "rejected" ? "warning" : "info");
1833
+ return result;
1834
+ };
1835
+ }
1571
1836
  async function resumeSettings(editorSignal) {
1572
1837
  if (editorSignal.aborted) return { kind: "close" };
1573
1838
  try {
@@ -1578,7 +1843,7 @@ async function showSyncSettings(ctx, runRoute, signal) {
1578
1843
  return { kind: "close" };
1579
1844
  }
1580
1845
  }
1581
- await runMenu4(ctx, menu, {
1846
+ await runMenu4({ ...ctx, ui: boundedSettingsUI(ctx) }, menu, {
1582
1847
  getState: () => loadConfig(setupName),
1583
1848
  signal,
1584
1849
  isCurrent: () => !signal?.aborted
@@ -1626,7 +1891,7 @@ async function showSyncManager(ctx, runRoute, sessionSignal, options = {}) {
1626
1891
  if (!attention) {
1627
1892
  const observation = options.getObservation?.();
1628
1893
  if (!observation) return { kind: "stay" };
1629
- const { showRemoteSelectionReview } = await import("./remote-selection-ui-6LJJ4IMQ.ts");
1894
+ const { showRemoteSelectionReview } = await import("./remote-selection-ui-5725JDNC.ts");
1630
1895
  if (sessionSignal?.aborted) return { kind: "close" };
1631
1896
  const config = await loadConfig();
1632
1897
  if (sessionSignal?.aborted) return { kind: "close" };
@@ -1717,12 +1982,45 @@ async function showSyncManager(ctx, runRoute, sessionSignal, options = {}) {
1717
1982
  });
1718
1983
  return { kind: result.kind === "closed" ? "close" : "stay" };
1719
1984
  },
1720
- doctor: async () => {
1721
- const result = await runCancellableOperation(ctx, "Checking setup access\u2026", "doctor", runRoute, {
1985
+ diff: async () => {
1986
+ const result = await runCancellableOperation(ctx, "Comparing local and remote\u2026", "diff", runRoute, {
1987
+ signal: sessionSignal
1988
+ });
1989
+ return { kind: result.kind === "closed" ? "close" : "stay" };
1990
+ },
1991
+ status: async () => {
1992
+ const result = await runCancellableOperation(ctx, "Checking remote status\u2026", "status", runRoute, {
1722
1993
  signal: sessionSignal
1723
1994
  });
1724
1995
  return { kind: result.kind === "closed" ? "close" : "stay" };
1725
1996
  },
1997
+ doctor: async ({ signal: actionSignal }) => {
1998
+ const signal = sessionSignal ? AbortSignal.any([sessionSignal, actionSignal]) : actionSignal;
1999
+ const validate = captureMutationOwner(ctx, signal);
2000
+ const config = await loadConfig();
2001
+ validate();
2002
+ const confirmed = await ctx.ui.confirm(
2003
+ "Run diagnostics?",
2004
+ `Setup: ${safeTerminalText(config.setupName)}
2005
+ Storage: ${backendStorageDescription(config)}
2006
+ S3/R2 reads the latest pointer without writing. Git reads remote snapshots and checks its cache. WebDAV probes conditional writes and cleanup, and can repair a missing history entry. Possible-secret checks still run. This is not the read-only Check remote status action.`,
2007
+ { signal }
2008
+ );
2009
+ if (signal.aborted) return { kind: "close" };
2010
+ validate();
2011
+ if (!confirmed) return { kind: "stay" };
2012
+ const latest = await loadConfig();
2013
+ validate();
2014
+ if (syncCheckConfigFingerprint(config) !== syncCheckConfigFingerprint(latest)) {
2015
+ ctx.ui.notify("Setup changed during diagnostics review; reopen Diagnostics.", "warning");
2016
+ return { kind: "stay" };
2017
+ }
2018
+ const result = await runCancellableOperation(ctx, "Checking setup access\u2026", "doctor", runRoute, {
2019
+ signal,
2020
+ target: config.setupName
2021
+ });
2022
+ return { kind: result.kind === "closed" ? "close" : "stay" };
2023
+ },
1726
2024
  recover: async ({ state, signal: actionSignal }) => {
1727
2025
  const result = await recoverSyncAccess(ctx, state.manager, runRoute, sessionSignal, actionSignal);
1728
2026
  return { kind: result === "close" ? "close" : "stay" };
@@ -1769,6 +2067,8 @@ function syncMainMenuItem(label) {
1769
2067
  "Sync now": "sync",
1770
2068
  "Pull from remote\u2026": "pull",
1771
2069
  "Push to remote\u2026": "push",
2070
+ "View differences": "diff",
2071
+ "Check remote status": "status",
1772
2072
  "Switch sync setup": "switch",
1773
2073
  Settings: "settings",
1774
2074
  History: "history",
@@ -1788,10 +2088,10 @@ function syncMainMenuItem(label) {
1788
2088
  id: action,
1789
2089
  label,
1790
2090
  action,
1791
- ...action === "doctor" ? { description: "Check setup access; WebDAV also probes writes and repairs history." } : {}
2091
+ description: action === "doctor" ? "Manage: review backend checks before running." : action === "settings" || action === "history" || action === "switch" ? "Manage" : action === "sync" ? "Transfer: choose a safe direction or review conflicts." : action === "pull" ? "Transfer: remote \u2192 local; review writes and removals." : action === "push" ? "Transfer: local \u2192 remote active snapshot; review publication." : action === "status" || action === "diff" ? "Inspect: read current local and remote state without transferring files." : void 0
1792
2092
  };
1793
2093
  }
1794
2094
  export {
1795
2095
  showSyncManager
1796
2096
  };
1797
- //# sourceMappingURL=manager-ui-IURS4WXY.ts.map
2097
+ //# sourceMappingURL=manager-ui-KRAW3NE2.ts.map