@narumitw/pi-sync 0.54.3 → 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 (179) hide show
  1. package/README.md +21 -4
  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-GC3QOL6J.ts → chunk-AAEEHR7H.ts} +31 -87
  14. package/dist/chunks/chunk-AAEEHR7H.ts.map +7 -0
  15. package/dist/chunks/{chunk-R35N4CL3.ts → chunk-B4QZBDBT.ts} +2 -2
  16. package/dist/chunks/{chunk-KGE47GCZ.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-HMXUKU43.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-YTHQEW35.ts → chunk-GP5DU62N.ts} +8 -9
  27. package/dist/chunks/chunk-GP5DU62N.ts.map +7 -0
  28. package/dist/chunks/{chunk-AAZDBIBJ.ts → chunk-GSNPASKB.ts} +2 -2
  29. package/dist/chunks/{chunk-E6ONFG57.ts → chunk-IWZH5KYS.ts} +11 -8
  30. package/dist/chunks/chunk-IWZH5KYS.ts.map +7 -0
  31. package/dist/chunks/{chunk-CAGJOKB7.ts → chunk-JJTARUSO.ts} +15 -14
  32. package/dist/chunks/chunk-JJTARUSO.ts.map +7 -0
  33. package/dist/chunks/{chunk-ITFR6NNQ.ts → chunk-NLWS55B6.ts} +2 -1
  34. package/dist/chunks/{chunk-ITFR6NNQ.ts.map → chunk-NLWS55B6.ts.map} +2 -2
  35. package/dist/chunks/{chunk-2FETCGGT.ts → chunk-OEWQVWLC.ts} +4 -4
  36. package/dist/chunks/{chunk-35GM6XKM.ts → chunk-OTZRCKYP.ts} +128 -382
  37. package/dist/chunks/chunk-OTZRCKYP.ts.map +7 -0
  38. package/dist/chunks/{chunk-XU2VXGQV.ts → chunk-OWBAZA6J.ts} +14 -8
  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-KATKRR3D.ts → chunk-QO333NM4.ts} +32 -11
  45. package/dist/chunks/chunk-QO333NM4.ts.map +7 -0
  46. package/dist/chunks/{chunk-I4JYVFUS.ts → chunk-QSHMOL3I.ts} +21 -22
  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-VCL7TZYW.ts → chunk-UPNVIUBN.ts} +3 -3
  57. package/dist/chunks/chunk-VJN7KU4J.ts +350 -0
  58. package/dist/chunks/chunk-VJN7KU4J.ts.map +7 -0
  59. package/dist/chunks/chunk-YHT4YTYG.ts +31 -0
  60. package/dist/chunks/chunk-YHT4YTYG.ts.map +7 -0
  61. package/dist/chunks/{chunk-A3TFXY4O.ts → chunk-YK52VDXG.ts} +6 -5
  62. package/dist/chunks/chunk-YK52VDXG.ts.map +7 -0
  63. package/dist/chunks/config-KGIYTTXQ.ts +37 -0
  64. package/dist/chunks/{conflict-artifacts-4CZFB7WI.ts → conflict-artifacts-FXFIIBCB.ts} +8 -7
  65. package/dist/chunks/{conflict-review-ZOTOHL2E.ts → conflict-review-6WIEQLXW.ts} +31 -25
  66. package/dist/chunks/{conflict-review-ZOTOHL2E.ts.map → conflict-review-6WIEQLXW.ts.map} +1 -1
  67. package/dist/chunks/continuous-record-LFRVXQBD.ts +30 -0
  68. package/dist/chunks/continuous-sync-AWAAOXK2.ts +545 -0
  69. package/dist/chunks/continuous-sync-AWAAOXK2.ts.map +7 -0
  70. package/dist/chunks/{file-selection-UNHBVVON.ts → file-selection-3JJYOVNR.ts} +5 -5
  71. package/dist/chunks/{git-backend-FD7AVSK6.ts → git-backend-KYDZ673X.ts} +28 -32
  72. package/dist/chunks/git-backend-KYDZ673X.ts.map +7 -0
  73. package/dist/chunks/{manager-result-dispatcher-HVRTPEVK.ts → manager-result-dispatcher-AGBBVDDS.ts} +10 -9
  74. package/dist/chunks/{manager-ui-ZIN4BUSF.ts → manager-ui-KRAW3NE2.ts} +184 -45
  75. package/dist/chunks/manager-ui-KRAW3NE2.ts.map +7 -0
  76. package/dist/chunks/{merge-baseline-store-ICXVTCML.ts → merge-baseline-store-RFCAH7KL.ts} +5 -5
  77. package/dist/chunks/{remote-selection-ui-H6AEVA3Q.ts → remote-selection-ui-5725JDNC.ts} +9 -8
  78. package/dist/chunks/remote-selection-ui-5725JDNC.ts.map +7 -0
  79. package/dist/chunks/{s3-backend-Y4P3O5B5.ts → s3-backend-Y7WSCLJI.ts} +38 -38
  80. package/dist/chunks/s3-backend-Y7WSCLJI.ts.map +7 -0
  81. package/dist/chunks/{setup-switch-ZMW5C4M7.ts → setup-switch-E7PHKI6J.ts} +7 -6
  82. package/dist/chunks/setup-switch-E7PHKI6J.ts.map +7 -0
  83. package/dist/chunks/{setup-wizard-VW7F3JN4.ts → setup-wizard-65DROCIJ.ts} +14 -13
  84. package/dist/chunks/setup-wizard-65DROCIJ.ts.map +7 -0
  85. package/dist/chunks/{sync-inspection-3EMS2J5B.ts → sync-inspection-XFJOAMJD.ts} +8 -8
  86. package/dist/chunks/sync-inspection-XFJOAMJD.ts.map +7 -0
  87. package/dist/chunks/{sync-operations-2CWST333.ts → sync-operations-HDUDT5PD.ts} +62 -63
  88. package/dist/chunks/sync-operations-HDUDT5PD.ts.map +7 -0
  89. package/dist/chunks/{webdav-backend-TBILVEQK.ts → webdav-backend-6K26F6LT.ts} +39 -48
  90. package/dist/chunks/webdav-backend-6K26F6LT.ts.map +7 -0
  91. package/dist/index.ts +165 -85
  92. package/dist/index.ts.map +3 -3
  93. package/docs/agent-mode-verification.md +99 -0
  94. package/docs/continuous-sync-verification.md +87 -0
  95. package/docs/settings.md +84 -1
  96. package/package.json +3 -2
  97. package/src/backends/git/git-backend.ts +1 -4
  98. package/src/backends/git/git-runner.ts +1 -5
  99. package/src/backends/s3/s3-backend.ts +3 -25
  100. package/src/backends/s3/s3-client.ts +14 -2
  101. package/src/backends/snapshot-pointer.ts +27 -0
  102. package/src/backends/webdav/webdav-backend.ts +3 -29
  103. package/src/backends/webdav/webdav-client.ts +11 -7
  104. package/src/commands/command-execution.ts +5 -2
  105. package/src/commands/command-handler.ts +15 -3
  106. package/src/commands/command-types.ts +6 -0
  107. package/src/commands/command.ts +1 -0
  108. package/src/settings/config.ts +7 -0
  109. package/src/settings/settings-store.ts +0 -34
  110. package/src/settings/settings-types.ts +8 -0
  111. package/src/settings/settings-validation.ts +1 -1
  112. package/src/snapshot/snapshot-codec.ts +1 -5
  113. package/src/state/lock.ts +13 -10
  114. package/src/state/merge-baseline-store.ts +2 -2
  115. package/src/state/state-directory.ts +8 -1
  116. package/src/sync/agent-candidate.ts +251 -0
  117. package/src/sync/agent-plan.ts +87 -0
  118. package/src/sync/agent-policy.ts +13 -0
  119. package/src/sync/agent-request.ts +17 -0
  120. package/src/sync/agent-runner.ts +188 -0
  121. package/src/sync/agent-store.ts +151 -0
  122. package/src/sync/agent-sync.ts +91 -0
  123. package/src/sync/automatic-sync.ts +2 -1
  124. package/src/sync/continuous-errors.ts +85 -0
  125. package/src/sync/continuous-lease.ts +221 -0
  126. package/src/sync/continuous-record.ts +179 -0
  127. package/src/sync/continuous-storage.ts +55 -0
  128. package/src/sync/continuous-sync.ts +516 -0
  129. package/src/sync/continuous-watch.ts +59 -0
  130. package/src/sync/merged-sync.ts +70 -12
  131. package/src/sync/remote-snapshot.ts +2 -3
  132. package/src/sync/settings-merge.ts +19 -3
  133. package/src/sync/setup-switch.ts +1 -5
  134. package/src/sync/startup-check.ts +15 -11
  135. package/src/sync/sync-inspection.ts +8 -1
  136. package/src/sync/sync-mutations.ts +6 -17
  137. package/src/sync/sync-queries.ts +5 -0
  138. package/src/sync/text-merge.ts +3 -3
  139. package/src/sync-extension.ts +75 -19
  140. package/src/ui/agent-mode-setting.ts +91 -0
  141. package/src/ui/manager-state.ts +2 -0
  142. package/src/ui/remote-selection-ui.ts +5 -5
  143. package/src/ui/settings-ui.ts +83 -1
  144. package/src/ui/setup/s3-credentials-ui.ts +1 -5
  145. package/src/ui/setup/setup-name-ui.ts +2 -5
  146. package/src/ui/setup/setup-switcher.ts +2 -0
  147. package/src/ui/sync-setups-ui.ts +3 -5
  148. package/dist/chunks/chunk-26PMXY7E.ts +0 -362
  149. package/dist/chunks/chunk-26PMXY7E.ts.map +0 -7
  150. package/dist/chunks/chunk-35GM6XKM.ts.map +0 -7
  151. package/dist/chunks/chunk-A3TFXY4O.ts.map +0 -7
  152. package/dist/chunks/chunk-BVVP33U5.ts.map +0 -7
  153. package/dist/chunks/chunk-CAGJOKB7.ts.map +0 -7
  154. package/dist/chunks/chunk-E6ONFG57.ts.map +0 -7
  155. package/dist/chunks/chunk-GC3QOL6J.ts.map +0 -7
  156. package/dist/chunks/chunk-I4JYVFUS.ts.map +0 -7
  157. package/dist/chunks/chunk-KATKRR3D.ts.map +0 -7
  158. package/dist/chunks/chunk-XU2VXGQV.ts.map +0 -7
  159. package/dist/chunks/chunk-YTHQEW35.ts.map +0 -7
  160. package/dist/chunks/chunk-ZX2Q6THB.ts.map +0 -7
  161. package/dist/chunks/git-backend-FD7AVSK6.ts.map +0 -7
  162. package/dist/chunks/manager-ui-ZIN4BUSF.ts.map +0 -7
  163. package/dist/chunks/s3-backend-Y4P3O5B5.ts.map +0 -7
  164. package/dist/chunks/setup-wizard-VW7F3JN4.ts.map +0 -7
  165. package/dist/chunks/sync-operations-2CWST333.ts.map +0 -7
  166. package/dist/chunks/webdav-backend-TBILVEQK.ts.map +0 -7
  167. /package/dist/chunks/{conflict-artifacts-4CZFB7WI.ts.map → agent-candidate-3JN6DJKT.ts.map} +0 -0
  168. /package/dist/chunks/{chunk-R35N4CL3.ts.map → chunk-B4QZBDBT.ts.map} +0 -0
  169. /package/dist/chunks/{chunk-KGE47GCZ.ts.map → chunk-CRKTAPSL.ts.map} +0 -0
  170. /package/dist/chunks/{chunk-HMXUKU43.ts.map → chunk-EUMINX7L.ts.map} +0 -0
  171. /package/dist/chunks/{chunk-AAZDBIBJ.ts.map → chunk-GSNPASKB.ts.map} +0 -0
  172. /package/dist/chunks/{chunk-2FETCGGT.ts.map → chunk-OEWQVWLC.ts.map} +0 -0
  173. /package/dist/chunks/{chunk-VCL7TZYW.ts.map → chunk-UPNVIUBN.ts.map} +0 -0
  174. /package/dist/chunks/{manager-result-dispatcher-HVRTPEVK.ts.map → config-KGIYTTXQ.ts.map} +0 -0
  175. /package/dist/chunks/{merge-baseline-store-ICXVTCML.ts.map → conflict-artifacts-FXFIIBCB.ts.map} +0 -0
  176. /package/dist/chunks/{remote-selection-ui-H6AEVA3Q.ts.map → continuous-record-LFRVXQBD.ts.map} +0 -0
  177. /package/dist/chunks/{file-selection-UNHBVVON.ts.map → file-selection-3JJYOVNR.ts.map} +0 -0
  178. /package/dist/chunks/{setup-switch-ZMW5C4M7.ts.map → manager-result-dispatcher-AGBBVDDS.ts.map} +0 -0
  179. /package/dist/chunks/{sync-inspection-3EMS2J5B.ts.map → merge-baseline-store-RFCAH7KL.ts.map} +0 -0
@@ -0,0 +1,91 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
3
+ import { requireSettings, updateLocalConfig } from "../settings/settings-store.js";
4
+ import { captureMutationOwner } from "../sync/sync-local.js";
5
+ import { confirmMergeReview } from "./merge-review.js";
6
+ import { safeTerminalText } from "./terminal-text.js";
7
+
8
+ export const AGENT_MODE_DESCRIPTION =
9
+ "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.";
10
+
11
+ export const CONTINUOUS_DESCRIPTION =
12
+ "This setup, experimental: recurring sync while Pi is open. Requires Agent mode. Watches selected files, checks the remote every 60–66 seconds, and retries temporary failures. Local changes and retries can cause extra requests between polls. May disclose conflict text and incur model/storage costs. No automatic reload.";
13
+
14
+ export function saveContinuousMode(
15
+ ctx: ExtensionCommandContext,
16
+ setupName: string,
17
+ enabled: boolean,
18
+ signal?: AbortSignal,
19
+ ) {
20
+ return saveAgentMode(ctx, setupName, enabled, signal, "continuous");
21
+ }
22
+
23
+ export async function saveAgentMode(
24
+ ctx: ExtensionCommandContext,
25
+ setupName: string,
26
+ enabled: boolean,
27
+ signal?: AbortSignal,
28
+ preference: "agentMode" | "continuous" = "agentMode",
29
+ ) {
30
+ const validate = captureMutationOwner(ctx, signal);
31
+ const settings = await requireSettings();
32
+ validate();
33
+ const setup = settings.syncSetups[setupName];
34
+ if (!setup || settings.activeSyncSetup !== setupName) throw new Error("Current sync setup changed; reopen Settings.");
35
+ const continuous = preference === "continuous";
36
+ const label = continuous ? "Continuous sync" : "Agent mode";
37
+ if (enabled && continuous && !setup.sync.agentMode) throw new Error("Enable Agent mode before Continuous sync.");
38
+ if (Boolean(setup.sync[preference]) === enabled) return true;
39
+ if (enabled) {
40
+ const summary = [
41
+ `Setup: ${safeTerminalText(setupName)}`,
42
+ `Storage connection: ${safeTerminalText(setup.storage.connection)}`,
43
+ `Storage path: ${safeTerminalText(setup.storage.path)}`,
44
+ ...(setup.storage.bucket ? [`Bucket: ${safeTerminalText(String(setup.storage.bucket))}`] : []),
45
+ ...(setup.storage.branch ? [`Branch: ${safeTerminalText(String(setup.storage.branch))}`] : []),
46
+ `Included paths: ${setup.sync.include.map(safeTerminalText).join(", ")}`,
47
+ continuous ? CONTINUOUS_DESCRIPTION : AGENT_MODE_DESCRIPTION,
48
+ ...(continuous
49
+ ? [
50
+ "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.",
51
+ "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.",
52
+ ]
53
+ : setup.sync.continuous
54
+ ? ["The saved Continuous sync preference will resume monitoring after the /sync command or manager closes."]
55
+ : []),
56
+ "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.",
57
+ "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.",
58
+ ].join("\n\n");
59
+ if (
60
+ !(await confirmMergeReview(
61
+ ctx,
62
+ `Enable experimental ${label}?`,
63
+ summary,
64
+ signal,
65
+ () => !signal?.aborted,
66
+ `Enable ${label}`,
67
+ summary.split("\n"),
68
+ ))
69
+ )
70
+ return false;
71
+ validate();
72
+ }
73
+ await updateLocalConfig((current) => {
74
+ validate();
75
+ if (
76
+ current.activeSyncSetup !== setupName ||
77
+ !isDeepStrictEqual(current.syncSetups[setupName], setup) ||
78
+ !isDeepStrictEqual(
79
+ current.storageConnections[setup.storage.connection],
80
+ settings.storageConnections[setup.storage.connection],
81
+ )
82
+ )
83
+ throw new Error("Sync setup or connection changed during Agent mode review; reopen Settings.");
84
+ return {
85
+ ...current,
86
+ syncSetups: { ...current.syncSetups, [setupName]: { ...setup, sync: { ...setup.sync, [preference]: enabled } } },
87
+ };
88
+ }, signal);
89
+ // The atomic writer has committed. Later cancellation cannot undo this save.
90
+ return true;
91
+ }
@@ -163,6 +163,8 @@ export async function describeManagerState(
163
163
  `Included: ${selection.builtIns.length} built-in group${selection.builtIns.length === 1 ? "" : "s"} · ${selection.custom.length} extra path${selection.custom.length === 1 ? "" : "s"} · Sessions ${selection.sessions ? "on" : "off"}`,
164
164
  `Automatic sync: ${automaticSyncSummary(config.automatic)}`,
165
165
  `Startup transfer: ${startupTransferSummary(config)}`,
166
+ `Agent mode: ${config.agentMode ? "On (experimental; autonomous merged sync)" : "Off"}`,
167
+ ...(config.continuous ? await (await import("../sync/continuous-record.js")).continuousStatusLines(config) : []),
166
168
  `Last applied: ${lastAppliedSnapshot}`,
167
169
  ...(noSyncedContent
168
170
  ? ["", "No included content is selected. Choose included content in Settings before syncing."]
@@ -11,6 +11,7 @@ import {
11
11
  import { SyncSetupReviewChangedError, updateSyncSetup } from "../settings/settings-management.js";
12
12
  import type { AnySyncConfig, PartialConfig } from "../settings/settings-types.js";
13
13
  import { readSnapshotForHead } from "../sync/remote-snapshot.js";
14
+ import { throwIfAborted } from "../sync/signals.js";
14
15
  import { errorMessage } from "../sync/sync-errors.js";
15
16
  import {
16
17
  compareSyncInclude,
@@ -240,6 +241,8 @@ async function showSelectionDifference(
240
241
  include: [...state.decision.remoteInclude],
241
242
  automatic: review.config.automatic,
242
243
  automaticTransfer: review.config.automaticTransfer,
244
+ agentMode: review.config.agentMode,
245
+ continuous: review.config.continuous,
243
246
  onSwitch: review.config.onSwitch,
244
247
  showStatus: review.config.showStatus,
245
248
  };
@@ -592,6 +595,8 @@ function sameContinuationReview(left: PartialConfig, right: PartialConfig) {
592
595
  left.storagePath === right.storagePath &&
593
596
  left.bucket === right.bucket &&
594
597
  left.branch === right.branch &&
598
+ Boolean(left.agentMode) === Boolean(right.agentMode) &&
599
+ Boolean(left.continuous) === Boolean(right.continuous) &&
595
600
  sameSyncInclude(left.include, right.include)
596
601
  );
597
602
  }
@@ -639,8 +644,3 @@ function runWithOptionalStateAccess<T>(
639
644
  function combineSignals(sessionSignal: AbortSignal | undefined, actionSignal: AbortSignal) {
640
645
  return sessionSignal ? AbortSignal.any([sessionSignal, actionSignal]) : actionSignal;
641
646
  }
642
-
643
- function throwIfAborted(signal: AbortSignal) {
644
- if (!signal.aborted) return;
645
- throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError");
646
- }
@@ -12,6 +12,12 @@ import {
12
12
  setupSwitchActionLabel,
13
13
  } from "../sync/setup-switch.js";
14
14
  import { captureMutationOwner } from "../sync/sync-local.js";
15
+ import {
16
+ AGENT_MODE_DESCRIPTION,
17
+ CONTINUOUS_DESCRIPTION,
18
+ saveAgentMode,
19
+ saveContinuousMode,
20
+ } from "./agent-mode-setting.js";
15
21
  import type { RunRoute } from "./cancellable-operation.js";
16
22
  import { dispatchManagerResult } from "./manager-result-dispatcher.js";
17
23
  import { boundedSettingsUI } from "./settings-frame.js";
@@ -71,6 +77,8 @@ export async function showSyncSettings(
71
77
  type Action =
72
78
  | "automatic"
73
79
  | "automatic-transfer"
80
+ | "agent-mode"
81
+ | "continuous"
74
82
  | "merge-settings"
75
83
  | "local-fields"
76
84
  | "content-policy"
@@ -87,7 +95,10 @@ export async function showSyncSettings(
87
95
  type Screen = "categories" | Group;
88
96
  const groups: Record<Group, { label: string; ids: readonly string[] }> = {
89
97
  content: { label: "Sync content", ids: ["include", "remoteInclude", "localFields"] },
90
- behavior: { label: "Sync behavior", ids: ["automatic", "automaticTransfer", "onSwitch"] },
98
+ behavior: {
99
+ label: "Sync behavior",
100
+ ids: ["automatic", "automaticTransfer", "agentMode", "continuous", "onSwitch"],
101
+ },
91
102
  storage: { label: "Storage & setups", ids: ["storageLocation", "setups", "connections"] },
92
103
  advanced: { label: "Advanced", ids: ["mergeSettings", "contentPolicy", "skipSecretScan", "showStatus", "debug"] },
93
104
  all: { label: "All settings (search)", ids: [] },
@@ -174,6 +185,22 @@ export async function showSyncSettings(
174
185
  values: ["On", "Off"],
175
186
  action: "automatic-transfer",
176
187
  },
188
+ {
189
+ id: "agentMode",
190
+ label: "Agent mode (experimental)",
191
+ description: AGENT_MODE_DESCRIPTION,
192
+ currentValue: state.agentMode ? "On" : "Off",
193
+ values: ["On", "Off"],
194
+ action: "agent-mode",
195
+ },
196
+ {
197
+ id: "continuous",
198
+ label: "Continuous sync (experimental)",
199
+ description: `${CONTINUOUS_DESCRIPTION}${state.continuous && !state.agentMode ? " Saved On, but paused until Agent mode is enabled." : ""}`,
200
+ currentValue: state.continuous ? "On" : "Off",
201
+ values: ["On", "Off"],
202
+ action: "continuous",
203
+ },
177
204
  {
178
205
  id: "onSwitch",
179
206
  label: "On setup switch",
@@ -413,6 +440,61 @@ export async function showSyncSettings(
413
440
  return { kind: "rejected" };
414
441
  }
415
442
  },
443
+ "agent-mode": async ({ value, signal: actionSignal }) => {
444
+ const mutationSignal = signal ? AbortSignal.any([signal, actionSignal]) : actionSignal;
445
+ const validate = captureMutationOwner(ctx, mutationSignal);
446
+ try {
447
+ const saved = await saveAgentMode(ctx, setupName, value === "On", mutationSignal);
448
+ if (!saved) return { kind: "rejected" };
449
+ try {
450
+ validate();
451
+ ctx.ui.notify(
452
+ "Agent mode saved. Applies to subsequent sync and the next session start; no transfer started.",
453
+ "info",
454
+ );
455
+ } catch {
456
+ // The save committed. A closed or replaced UI must not report a failed save.
457
+ // The next Settings read shows the persisted value.
458
+ }
459
+ return { kind: "stay" };
460
+ } catch (error) {
461
+ try {
462
+ validate();
463
+ } catch {
464
+ return { kind: "rejected" };
465
+ }
466
+ notifySaveFailure(ctx, error);
467
+ return { kind: "rejected" };
468
+ }
469
+ },
470
+ continuous: async ({ value, signal: actionSignal }) => {
471
+ const mutationSignal = signal ? AbortSignal.any([signal, actionSignal]) : actionSignal;
472
+ const validate = captureMutationOwner(ctx, mutationSignal);
473
+ try {
474
+ const saved = await saveContinuousMode(ctx, setupName, value === "On", mutationSignal);
475
+ if (!saved) return { kind: "rejected" };
476
+ try {
477
+ validate();
478
+ ctx.ui.notify(
479
+ value === "On"
480
+ ? "Continuous sync enabled. Monitoring starts after the /sync command or manager closes; completed work is not undone."
481
+ : "Continuous sync disabled. Completed work is not undone.",
482
+ "info",
483
+ );
484
+ } catch {
485
+ /* committed save; do not use stale UI */
486
+ }
487
+ return { kind: "stay" };
488
+ } catch (error) {
489
+ try {
490
+ validate();
491
+ } catch {
492
+ return { kind: "rejected" };
493
+ }
494
+ notifySaveFailure(ctx, error);
495
+ return { kind: "rejected" };
496
+ }
497
+ },
416
498
  "automatic-transfer": async ({ value, signal: actionSignal }) => {
417
499
  const automaticTransfer = value === "On";
418
500
  const mutationSignal = signal ? AbortSignal.any([signal, actionSignal]) : actionSignal;
@@ -1,4 +1,5 @@
1
1
  import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
+ import { throwIfAborted } from "../../sync/signals.js";
2
3
  import { promptSecret } from "../secret-input.js";
3
4
  import { requiredValueInput } from "./text-input.js";
4
5
 
@@ -70,8 +71,3 @@ export async function chooseS3Credentials(
70
71
  ready: true,
71
72
  };
72
73
  }
73
-
74
- function throwIfAborted(signal?: AbortSignal) {
75
- if (!signal?.aborted) return;
76
- throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError");
77
- }
@@ -1,5 +1,6 @@
1
1
  import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
2
  import { validateConfigName } from "../../settings/settings-validation.js";
3
+ import { throwIfAborted } from "../../sync/signals.js";
3
4
  import { errorMessage } from "../../sync/sync-errors.js";
4
5
  import { safeTerminalText } from "../terminal-text.js";
5
6
 
@@ -10,11 +11,7 @@ export async function promptInitialSetupName(ctx: ExtensionCommandContext, signa
10
11
  const guidance = ctx.mode === "tui" ? ctx.ui.theme.fg("muted", hint) : hint;
11
12
  // This compact prompt owns its default hint; the general helper would repeat it.
12
13
  const value = await ctx.ui.input(`Sync setup name\n${guidance}`, undefined, { signal });
13
- if (signal?.aborted) {
14
- throw signal.reason instanceof Error
15
- ? signal.reason
16
- : new DOMException("The operation was aborted", "AbortError");
17
- }
14
+ throwIfAborted(signal);
18
15
  if (value === undefined) return undefined;
19
16
  const name = value.trim() || "default";
20
17
  try {
@@ -129,6 +129,8 @@ export async function showSetupSwitcher(
129
129
  `Included content: ${config.include.length} paths`,
130
130
  `Automatic sync: ${automaticSyncSummary(config.automatic)} · Sessions: ${config.include.includes("sessions") ? "On" : "Off"}`,
131
131
  `Automatic transfer at startup: ${config.automaticTransfer ? "On — may upload, replace, or delete selected files; no reload" : "Off"}`,
132
+ `Agent mode: ${config.agentMode ? "On (experimental; autonomous merged sync)" : "Off"}`,
133
+ `Continuous sync: ${config.continuous ? (config.agentMode ? "On (experimental; recurring requests and transfers)" : "Saved On; paused until Agent mode is enabled") : "Off"}`,
132
134
  "",
133
135
  switchEffect,
134
136
  ].join("\n"),
@@ -3,6 +3,7 @@ import { defineMenu, runMenu } from "@narumitw/pi-tui-kit";
3
3
  import { loadConfig } from "../settings/config.js";
4
4
  import { readLocalConfigObject } from "../settings/settings-store.js";
5
5
  import { ownRecord } from "../settings/settings-validation.js";
6
+ import { throwIfAborted } from "../sync/signals.js";
6
7
  import { syncErrorGuidance } from "../sync/sync-error-guidance.js";
7
8
  import { syncIncludeSelection } from "../sync/sync-policy.js";
8
9
  import { safeTerminalText } from "./terminal-text.js";
@@ -24,11 +25,6 @@ export async function countValidSyncSetups(setups: Record<string, unknown> | und
24
25
  return count;
25
26
  }
26
27
 
27
- function throwIfAborted(signal?: AbortSignal) {
28
- if (!signal?.aborted) return;
29
- throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError");
30
- }
31
-
32
28
  type SyncSetupActions = {
33
29
  add(signal?: AbortSignal): Promise<void>;
34
30
  edit(name: string, signal?: AbortSignal): Promise<void>;
@@ -197,6 +193,8 @@ async function loadSetupMenuState(selectedName: string | undefined, signal?: Abo
197
193
  `Sessions: ${selection.sessions ? "On — privacy-sensitive" : "Off"}`,
198
194
  `Automatic sync: ${config.automatic ? "On" : "Off"}`,
199
195
  `Automatic transfer at startup: ${config.automaticTransfer ? "On — may upload, replace, or delete selected files; no reload" : "Off"}`,
196
+ `Agent mode: ${config.agentMode ? "On (experimental; autonomous merged sync)" : "Off"}`,
197
+ `Continuous sync: ${config.continuous ? (config.agentMode ? "On (experimental; recurring requests and transfers)" : "Saved On; paused until Agent mode is enabled") : "Off"}`,
200
198
  isCurrent
201
199
  ? "Edit included content and automatic sync in /sync → Settings."
202
200
  : "To edit included content or automatic sync, make this setup current, then open /sync → Settings.",