@narumitw/pi-sync 0.54.3 → 0.57.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 (181) hide show
  1. package/README.md +38 -11
  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 +32 -247
  96. package/package.json +11 -5
  97. package/skills/configure-pi-sync/SKILL.md +54 -0
  98. package/skills/configure-pi-sync/references/configuration.md +650 -0
  99. package/src/backends/git/git-backend.ts +1 -4
  100. package/src/backends/git/git-runner.ts +1 -5
  101. package/src/backends/s3/s3-backend.ts +3 -25
  102. package/src/backends/s3/s3-client.ts +14 -2
  103. package/src/backends/snapshot-pointer.ts +27 -0
  104. package/src/backends/webdav/webdav-backend.ts +3 -29
  105. package/src/backends/webdav/webdav-client.ts +11 -7
  106. package/src/commands/command-execution.ts +5 -2
  107. package/src/commands/command-handler.ts +15 -3
  108. package/src/commands/command-types.ts +6 -0
  109. package/src/commands/command.ts +1 -0
  110. package/src/settings/config.ts +7 -0
  111. package/src/settings/settings-store.ts +0 -34
  112. package/src/settings/settings-types.ts +8 -0
  113. package/src/settings/settings-validation.ts +1 -1
  114. package/src/snapshot/snapshot-codec.ts +1 -5
  115. package/src/state/lock.ts +13 -10
  116. package/src/state/merge-baseline-store.ts +2 -2
  117. package/src/state/state-directory.ts +8 -1
  118. package/src/sync/agent-candidate.ts +251 -0
  119. package/src/sync/agent-plan.ts +87 -0
  120. package/src/sync/agent-policy.ts +13 -0
  121. package/src/sync/agent-request.ts +17 -0
  122. package/src/sync/agent-runner.ts +188 -0
  123. package/src/sync/agent-store.ts +151 -0
  124. package/src/sync/agent-sync.ts +91 -0
  125. package/src/sync/automatic-sync.ts +2 -1
  126. package/src/sync/continuous-errors.ts +85 -0
  127. package/src/sync/continuous-lease.ts +221 -0
  128. package/src/sync/continuous-record.ts +179 -0
  129. package/src/sync/continuous-storage.ts +55 -0
  130. package/src/sync/continuous-sync.ts +516 -0
  131. package/src/sync/continuous-watch.ts +59 -0
  132. package/src/sync/merged-sync.ts +70 -12
  133. package/src/sync/remote-snapshot.ts +2 -3
  134. package/src/sync/settings-merge.ts +19 -3
  135. package/src/sync/setup-switch.ts +1 -5
  136. package/src/sync/startup-check.ts +15 -11
  137. package/src/sync/sync-inspection.ts +8 -1
  138. package/src/sync/sync-mutations.ts +6 -17
  139. package/src/sync/sync-queries.ts +5 -0
  140. package/src/sync/text-merge.ts +3 -3
  141. package/src/sync-extension.ts +75 -19
  142. package/src/ui/agent-mode-setting.ts +91 -0
  143. package/src/ui/manager-state.ts +2 -0
  144. package/src/ui/remote-selection-ui.ts +5 -5
  145. package/src/ui/settings-ui.ts +83 -1
  146. package/src/ui/setup/s3-credentials-ui.ts +1 -5
  147. package/src/ui/setup/setup-name-ui.ts +2 -5
  148. package/src/ui/setup/setup-switcher.ts +2 -0
  149. package/src/ui/sync-setups-ui.ts +3 -5
  150. package/dist/chunks/chunk-26PMXY7E.ts +0 -362
  151. package/dist/chunks/chunk-26PMXY7E.ts.map +0 -7
  152. package/dist/chunks/chunk-35GM6XKM.ts.map +0 -7
  153. package/dist/chunks/chunk-A3TFXY4O.ts.map +0 -7
  154. package/dist/chunks/chunk-BVVP33U5.ts.map +0 -7
  155. package/dist/chunks/chunk-CAGJOKB7.ts.map +0 -7
  156. package/dist/chunks/chunk-E6ONFG57.ts.map +0 -7
  157. package/dist/chunks/chunk-GC3QOL6J.ts.map +0 -7
  158. package/dist/chunks/chunk-I4JYVFUS.ts.map +0 -7
  159. package/dist/chunks/chunk-KATKRR3D.ts.map +0 -7
  160. package/dist/chunks/chunk-XU2VXGQV.ts.map +0 -7
  161. package/dist/chunks/chunk-YTHQEW35.ts.map +0 -7
  162. package/dist/chunks/chunk-ZX2Q6THB.ts.map +0 -7
  163. package/dist/chunks/git-backend-FD7AVSK6.ts.map +0 -7
  164. package/dist/chunks/manager-ui-ZIN4BUSF.ts.map +0 -7
  165. package/dist/chunks/s3-backend-Y4P3O5B5.ts.map +0 -7
  166. package/dist/chunks/setup-wizard-VW7F3JN4.ts.map +0 -7
  167. package/dist/chunks/sync-operations-2CWST333.ts.map +0 -7
  168. package/dist/chunks/webdav-backend-TBILVEQK.ts.map +0 -7
  169. /package/dist/chunks/{conflict-artifacts-4CZFB7WI.ts.map → agent-candidate-3JN6DJKT.ts.map} +0 -0
  170. /package/dist/chunks/{chunk-R35N4CL3.ts.map → chunk-B4QZBDBT.ts.map} +0 -0
  171. /package/dist/chunks/{chunk-KGE47GCZ.ts.map → chunk-CRKTAPSL.ts.map} +0 -0
  172. /package/dist/chunks/{chunk-HMXUKU43.ts.map → chunk-EUMINX7L.ts.map} +0 -0
  173. /package/dist/chunks/{chunk-AAZDBIBJ.ts.map → chunk-GSNPASKB.ts.map} +0 -0
  174. /package/dist/chunks/{chunk-2FETCGGT.ts.map → chunk-OEWQVWLC.ts.map} +0 -0
  175. /package/dist/chunks/{chunk-VCL7TZYW.ts.map → chunk-UPNVIUBN.ts.map} +0 -0
  176. /package/dist/chunks/{manager-result-dispatcher-HVRTPEVK.ts.map → config-KGIYTTXQ.ts.map} +0 -0
  177. /package/dist/chunks/{merge-baseline-store-ICXVTCML.ts.map → conflict-artifacts-FXFIIBCB.ts.map} +0 -0
  178. /package/dist/chunks/{remote-selection-ui-H6AEVA3Q.ts.map → continuous-record-LFRVXQBD.ts.map} +0 -0
  179. /package/dist/chunks/{file-selection-UNHBVVON.ts.map → file-selection-3JJYOVNR.ts.map} +0 -0
  180. /package/dist/chunks/{setup-switch-ZMW5C4M7.ts.map → manager-result-dispatcher-AGBBVDDS.ts.map} +0 -0
  181. /package/dist/chunks/{sync-inspection-3EMS2J5B.ts.map → merge-baseline-store-RFCAH7KL.ts.map} +0 -0
@@ -0,0 +1,215 @@
1
+ // @generated by scripts/build-runtime.mjs; do not edit.
2
+ // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
3
+ import {
4
+ COUPLED_FIELDS,
5
+ mergeSettingsJson,
6
+ snapshotFile
7
+ } from "./chunk-5Q5SSKNG.ts";
8
+ import {
9
+ scanSnapshot
10
+ } from "./chunk-QOFMXPM5.ts";
11
+ import {
12
+ edits,
13
+ isMergeTextPath,
14
+ lines,
15
+ text
16
+ } from "./chunk-IWZH5KYS.ts";
17
+ import {
18
+ parseJsonObjectDocument,
19
+ parseSettingsDocument
20
+ } from "./chunk-ZW5ZLGBI.ts";
21
+ import {
22
+ AGENT_INPUT_BYTES,
23
+ AGENT_MAX_UNITS,
24
+ AGENT_OUTPUT_BYTES
25
+ } from "./chunk-PRUEIIK7.ts";
26
+
27
+ // src/sync/agent-candidate.ts
28
+ import { createHash } from "node:crypto";
29
+ var digest = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
30
+ function overlaps(a, b) {
31
+ return a.start === a.end || b.start === b.end ? a.start <= b.end && b.start <= a.end : a.start < b.end && b.start < a.end;
32
+ }
33
+ function textParts(filePath, baseText, localText, remoteText) {
34
+ const base = lines(baseText);
35
+ const changes = [
36
+ ...edits(base, lines(localText)).map((hunk) => ({ ...hunk, side: "local" })),
37
+ ...edits(base, lines(remoteText)).map((hunk) => ({ ...hunk, side: "remote" }))
38
+ ].sort((a, b) => a.start - b.start || a.end - b.end);
39
+ const clusters = [];
40
+ for (const change of changes) {
41
+ const last = clusters.at(-1);
42
+ if (last?.some((item) => overlaps(item, change))) last.push(change);
43
+ else clusters.push([change]);
44
+ }
45
+ const result = [];
46
+ let cursor = 0;
47
+ for (const cluster of clusters) {
48
+ const start = Math.min(...cluster.map((item) => item.start));
49
+ const end = Math.max(...cluster.map((item) => item.end));
50
+ result.push(base.slice(cursor, start).join(""));
51
+ const image = (side) => {
52
+ let position = start;
53
+ const output = [];
54
+ for (const hunk of cluster.filter((item) => item.side === side)) {
55
+ output.push(...base.slice(position, hunk.start), ...hunk.lines);
56
+ position = hunk.end;
57
+ }
58
+ return [...output, ...base.slice(position, end)].join("");
59
+ };
60
+ const local = image("local");
61
+ const remote = image("remote");
62
+ const original = base.slice(start, end).join("");
63
+ if (local === remote || local === original || remote === original) result.push(local === original ? remote : local);
64
+ else result.push({ id: "", path: filePath, kind: "text", base: original, local, remote });
65
+ cursor = end;
66
+ }
67
+ result.push(base.slice(cursor).join(""));
68
+ return result;
69
+ }
70
+ function buildAgentEvidence(plan, local, remote, ancestors) {
71
+ const evidence = { units: [], files: [], unsupported: [] };
72
+ if (plan.kind !== "planned") return evidence;
73
+ if (plan.conflicts.length > AGENT_MAX_UNITS)
74
+ throw new Error("Agent mode paused: too many conflict paths for bounded analysis.");
75
+ for (const conflict of plan.conflicts) {
76
+ try {
77
+ if (conflict.reason !== "both-changed") throw new Error("unsupported conflict");
78
+ const versions = [ancestors, local.files, remote.files].map((files) => {
79
+ const file2 = files.find((item) => item.path === conflict.path);
80
+ if (!file2) throw new Error("missing ancestor/version");
81
+ const bytes = Buffer.from(file2.contentBase64, "base64");
82
+ if (bytes.length > 8192) throw new Error("file bound");
83
+ return text(bytes);
84
+ });
85
+ const [base, left, right] = versions;
86
+ const file = { path: conflict.path, base, local: left, remote: right };
87
+ let units;
88
+ if (conflict.path === "settings.json") {
89
+ const documents = versions.map((value) => parseSettingsDocument(Buffer.from(value)));
90
+ const merge = mergeSettingsJson(...versions.map((value) => Buffer.from(value)));
91
+ if (merge.kind !== "review" || merge.reason !== "field-conflict") throw new Error("unsupported settings");
92
+ const fields = new Set(merge.fields);
93
+ const groups = COUPLED_FIELDS.map((group) => [...group]);
94
+ const coupled = new Set(groups.flat());
95
+ groups.push(...[...fields].filter((key) => !coupled.has(key)).map((key) => [key]));
96
+ file.fields = [];
97
+ units = groups.filter((group) => group.some((key) => fields.has(key))).map((group) => {
98
+ const values = documents.map(
99
+ (document) => JSON.stringify(
100
+ Object.fromEntries(
101
+ group.flatMap((key) => {
102
+ const node = document.root.members.get(key)?.node;
103
+ return node ? [[key, node.value]] : [];
104
+ })
105
+ )
106
+ )
107
+ );
108
+ const unit = {
109
+ id: "",
110
+ path: file.path,
111
+ kind: "settings",
112
+ base: values[0] ?? "{}",
113
+ local: values[1] ?? "{}",
114
+ remote: values[2] ?? "{}"
115
+ };
116
+ file.fields?.push({ id: String(evidence.units.length + file.fields.length), key: group[0] ?? "" });
117
+ return unit;
118
+ });
119
+ } else {
120
+ if (!isMergeTextPath(conflict.path)) throw new Error("unsupported path");
121
+ file.parts = textParts(file.path, base, left, right);
122
+ units = file.parts.filter((part) => typeof part !== "string");
123
+ }
124
+ for (const unit of units) {
125
+ unit.id = String(evidence.units.length);
126
+ evidence.units.push(unit);
127
+ }
128
+ evidence.files.push(file);
129
+ } catch {
130
+ evidence.unsupported.push(conflict.path);
131
+ }
132
+ }
133
+ if (evidence.units.length > AGENT_MAX_UNITS || Buffer.byteLength(JSON.stringify(evidence.units)) > AGENT_INPUT_BYTES)
134
+ throw new Error("Agent mode paused: conflict evidence exceeds its input bound.");
135
+ if (scanSnapshot({
136
+ ...local,
137
+ files: evidence.units.flatMap(
138
+ (unit) => [unit.path, unit.base, unit.local, unit.remote].map((value) => snapshotFile(unit.path, Buffer.from(value)))
139
+ )
140
+ }).length)
141
+ throw new Error("Agent mode paused: possible secrets in model evidence; nothing was disclosed.");
142
+ return evidence;
143
+ }
144
+ function parseAgentCandidate(raw, evidence) {
145
+ if (Buffer.byteLength(raw) > AGENT_OUTPUT_BYTES) throw new Error("Agent output exceeds its bound.");
146
+ const parsed = parseJsonObjectDocument(Buffer.from(raw)).root.value;
147
+ if (Object.keys(parsed).sort().join(",") !== "results,version" || parsed.version !== 1 || !Array.isArray(parsed.results) || parsed.results.length !== evidence.units.length)
148
+ throw new Error("Invalid agent candidate contract.");
149
+ const seen = /* @__PURE__ */ new Set();
150
+ for (const result of parsed.results) {
151
+ if (!result || typeof result !== "object" || Array.isArray(result)) throw new Error("Invalid agent result.");
152
+ const row = result;
153
+ const unit = evidence.units.find((unit2) => unit2.id === row.id);
154
+ if (!unit || seen.has(unit.id)) throw new Error("Unowned or duplicate agent unit.");
155
+ seen.add(unit.id);
156
+ const keys = Object.keys(row).sort().join(",");
157
+ if (keys === "id,refuse" && row.refuse === true) continue;
158
+ if (unit.kind === "settings" && keys === "id,source" && (row.source === "local" || row.source === "remote"))
159
+ continue;
160
+ if (unit.kind === "text" && keys === "content,id" && typeof row.content === "string" && row.content.length > 0) {
161
+ text(Buffer.from(row.content));
162
+ if (/^(?:<{7}|={7}|>{7}|\|{7})/mu.test(row.content)) throw new Error("Unresolved conflict markers.");
163
+ continue;
164
+ }
165
+ throw new Error("Unsupported agent action.");
166
+ }
167
+ return parsed;
168
+ }
169
+ function applyAgentCandidate(plan, evidence, candidate) {
170
+ parseAgentCandidate(JSON.stringify(candidate), evidence);
171
+ if (plan.kind !== "planned") throw new Error("Unverified merge plan.");
172
+ const results = new Map(candidate.results.map((row) => [row.id, row]));
173
+ const replacements = /* @__PURE__ */ new Map();
174
+ for (const file of evidence.files) {
175
+ const units = evidence.units.filter((unit) => unit.path === file.path);
176
+ if (units.some((unit) => results.get(unit.id) && "refuse" in (results.get(unit.id) ?? {}))) continue;
177
+ let bytes;
178
+ if (file.parts) {
179
+ bytes = Buffer.from(
180
+ file.parts.map((part) => typeof part === "string" ? part : results.get(part.id).content).join("")
181
+ );
182
+ } else {
183
+ const selections = new Map(
184
+ (file.fields ?? []).map(({ id, key }) => [key, results.get(id).source])
185
+ );
186
+ const merged = mergeSettingsJson(
187
+ Buffer.from(file.base),
188
+ Buffer.from(file.local),
189
+ Buffer.from(file.remote),
190
+ selections
191
+ );
192
+ if (merged.kind !== "merged") throw new Error("Agent settings units remain unresolved.");
193
+ parseSettingsDocument(merged.content);
194
+ for (const { key } of file.fields ?? []) {
195
+ if (!selections.has(key)) throw new Error("Missing atomic settings selection.");
196
+ }
197
+ bytes = merged.content;
198
+ }
199
+ if (bytes.length > AGENT_OUTPUT_BYTES) throw new Error("Agent file exceeds output bound.");
200
+ replacements.set(file.path, snapshotFile(file.path, bytes));
201
+ }
202
+ const decisions = plan.decisions.map((decision) => {
203
+ const file = replacements.get(decision.path);
204
+ return file && decision.kind === "conflict" ? { kind: "accepted", path: file.path, source: "merged", file } : decision;
205
+ });
206
+ return { kind: "planned", decisions, conflicts: decisions.filter((item) => item.kind === "conflict") };
207
+ }
208
+
209
+ export {
210
+ digest,
211
+ buildAgentEvidence,
212
+ parseAgentCandidate,
213
+ applyAgentCandidate
214
+ };
215
+ //# sourceMappingURL=chunk-FQOSHGJ7.ts.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/sync/agent-candidate.ts"],
4
+ "sourcesContent": ["import { createHash } from \"node:crypto\";\nimport { scanSnapshot } from \"../snapshot/snapshot.js\";\nimport type { Snapshot, SnapshotFile } from \"../snapshot/snapshot-types.js\";\nimport { AGENT_INPUT_BYTES, AGENT_MAX_UNITS, AGENT_OUTPUT_BYTES } from \"./agent-policy.js\";\nimport { snapshotFile } from \"./content-conflicts.js\";\nimport type { FileMergePlan } from \"./file-merge-planner.js\";\nimport { parseJsonObjectDocument, parseSettingsDocument } from \"./json-document.js\";\nimport { COUPLED_FIELDS, mergeSettingsJson } from \"./settings-merge.js\";\nimport { edits, type Hunk, isMergeTextPath, lines, text } from \"./text-merge.js\";\n\nexport interface AgentUnit {\n id: string;\n path: string;\n kind: \"text\" | \"settings\";\n base: string;\n local: string;\n remote: string;\n}\ninterface CandidateFile {\n path: string;\n base: string;\n local: string;\n remote: string;\n parts?: (string | AgentUnit)[];\n fields?: { id: string; key: string }[];\n}\nexport interface AgentEvidence {\n units: AgentUnit[];\n files: CandidateFile[];\n unsupported: string[];\n}\nexport interface AgentCandidate {\n version: 1;\n results: (\n | { id: string; content: string }\n | { id: string; source: \"local\" | \"remote\" }\n | { id: string; refuse: true }\n )[];\n}\nexport const digest = (value: unknown) => createHash(\"sha256\").update(JSON.stringify(value)).digest(\"hex\");\n\nfunction overlaps(a: Hunk, b: Hunk) {\n return a.start === a.end || b.start === b.end\n ? a.start <= b.end && b.start <= a.end\n : a.start < b.end && b.start < a.end;\n}\n\n/** Only overlapping edit regions are model-writable; all other bytes are assembled by code. */\nfunction textParts(filePath: string, baseText: string, localText: string, remoteText: string): (string | AgentUnit)[] {\n const base = lines(baseText);\n const changes = [\n ...edits(base, lines(localText)).map((hunk) => ({ ...hunk, side: \"local\" as const })),\n ...edits(base, lines(remoteText)).map((hunk) => ({ ...hunk, side: \"remote\" as const })),\n ].sort((a, b) => a.start - b.start || a.end - b.end);\n const clusters: (typeof changes)[] = [];\n for (const change of changes) {\n const last = clusters.at(-1);\n if (last?.some((item) => overlaps(item, change))) last.push(change);\n else clusters.push([change]);\n }\n const result: (string | AgentUnit)[] = [];\n let cursor = 0;\n for (const cluster of clusters) {\n const start = Math.min(...cluster.map((item) => item.start));\n const end = Math.max(...cluster.map((item) => item.end));\n result.push(base.slice(cursor, start).join(\"\"));\n const image = (side: \"local\" | \"remote\") => {\n let position = start;\n const output: string[] = [];\n for (const hunk of cluster.filter((item) => item.side === side)) {\n output.push(...base.slice(position, hunk.start), ...hunk.lines);\n position = hunk.end;\n }\n return [...output, ...base.slice(position, end)].join(\"\");\n };\n const local = image(\"local\");\n const remote = image(\"remote\");\n const original = base.slice(start, end).join(\"\");\n if (local === remote || local === original || remote === original) result.push(local === original ? remote : local);\n else result.push({ id: \"\", path: filePath, kind: \"text\", base: original, local, remote });\n cursor = end;\n }\n result.push(base.slice(cursor).join(\"\"));\n return result;\n}\n\nexport function buildAgentEvidence(\n plan: FileMergePlan,\n local: Snapshot,\n remote: Snapshot,\n ancestors: SnapshotFile[],\n): AgentEvidence {\n const evidence: AgentEvidence = { units: [], files: [], unsupported: [] };\n if (plan.kind !== \"planned\") return evidence;\n if (plan.conflicts.length > AGENT_MAX_UNITS)\n throw new Error(\"Agent mode paused: too many conflict paths for bounded analysis.\");\n for (const conflict of plan.conflicts) {\n try {\n if (conflict.reason !== \"both-changed\") throw new Error(\"unsupported conflict\");\n const versions = [ancestors, local.files, remote.files].map((files) => {\n const file = files.find((item) => item.path === conflict.path);\n if (!file) throw new Error(\"missing ancestor/version\");\n const bytes = Buffer.from(file.contentBase64, \"base64\");\n if (bytes.length > 8192) throw new Error(\"file bound\");\n return text(bytes);\n }) as [string, string, string];\n const [base, left, right] = versions;\n const file: CandidateFile = { path: conflict.path, base, local: left, remote: right };\n let units: AgentUnit[];\n if (conflict.path === \"settings.json\") {\n const documents = versions.map((value) => parseSettingsDocument(Buffer.from(value)));\n const merge = mergeSettingsJson(...(versions.map((value) => Buffer.from(value)) as [Buffer, Buffer, Buffer]));\n if (merge.kind !== \"review\" || merge.reason !== \"field-conflict\") throw new Error(\"unsupported settings\");\n const fields = new Set(merge.fields);\n const groups = COUPLED_FIELDS.map((group) => [...group] as string[]);\n const coupled = new Set(groups.flat());\n groups.push(...[...fields].filter((key) => !coupled.has(key)).map((key) => [key]));\n file.fields = [];\n units = groups\n .filter((group) => group.some((key) => fields.has(key)))\n .map((group) => {\n const values = documents.map((document) =>\n JSON.stringify(\n Object.fromEntries(\n group.flatMap((key) => {\n const node = document.root.members.get(key)?.node;\n return node ? [[key, node.value]] : [];\n }),\n ),\n ),\n );\n const unit: AgentUnit = {\n id: \"\",\n path: file.path,\n kind: \"settings\",\n base: values[0] ?? \"{}\",\n local: values[1] ?? \"{}\",\n remote: values[2] ?? \"{}\",\n };\n file.fields?.push({ id: String(evidence.units.length + file.fields.length), key: group[0] ?? \"\" });\n return unit;\n });\n } else {\n if (!isMergeTextPath(conflict.path)) throw new Error(\"unsupported path\");\n file.parts = textParts(file.path, base, left, right);\n units = file.parts.filter((part): part is AgentUnit => typeof part !== \"string\");\n }\n for (const unit of units) {\n unit.id = String(evidence.units.length);\n evidence.units.push(unit);\n }\n evidence.files.push(file);\n } catch {\n evidence.unsupported.push(conflict.path);\n }\n }\n if (evidence.units.length > AGENT_MAX_UNITS || Buffer.byteLength(JSON.stringify(evidence.units)) > AGENT_INPUT_BYTES)\n throw new Error(\"Agent mode paused: conflict evidence exceeds its input bound.\");\n // Scan only the actual disclosure image; skipSecretScan never authorizes model disclosure.\n if (\n scanSnapshot({\n ...local,\n files: evidence.units.flatMap((unit) =>\n [unit.path, unit.base, unit.local, unit.remote].map((value) => snapshotFile(unit.path, Buffer.from(value))),\n ),\n }).length\n )\n throw new Error(\"Agent mode paused: possible secrets in model evidence; nothing was disclosed.\");\n return evidence;\n}\n\nexport function parseAgentCandidate(raw: string, evidence: AgentEvidence): AgentCandidate {\n if (Buffer.byteLength(raw) > AGENT_OUTPUT_BYTES) throw new Error(\"Agent output exceeds its bound.\");\n const parsed = parseJsonObjectDocument(Buffer.from(raw)).root.value as Record<string, unknown>;\n if (\n Object.keys(parsed).sort().join(\",\") !== \"results,version\" ||\n parsed.version !== 1 ||\n !Array.isArray(parsed.results) ||\n parsed.results.length !== evidence.units.length\n )\n throw new Error(\"Invalid agent candidate contract.\");\n const seen = new Set<string>();\n for (const result of parsed.results) {\n if (!result || typeof result !== \"object\" || Array.isArray(result)) throw new Error(\"Invalid agent result.\");\n const row = result as Record<string, unknown>;\n const unit = evidence.units.find((unit) => unit.id === row.id);\n if (!unit || seen.has(unit.id)) throw new Error(\"Unowned or duplicate agent unit.\");\n seen.add(unit.id);\n const keys = Object.keys(row).sort().join(\",\");\n if (keys === \"id,refuse\" && row.refuse === true) continue;\n if (unit.kind === \"settings\" && keys === \"id,source\" && (row.source === \"local\" || row.source === \"remote\"))\n continue;\n if (unit.kind === \"text\" && keys === \"content,id\" && typeof row.content === \"string\" && row.content.length > 0) {\n text(Buffer.from(row.content));\n if (/^(?:<{7}|={7}|>{7}|\\|{7})/mu.test(row.content)) throw new Error(\"Unresolved conflict markers.\");\n continue;\n }\n throw new Error(\"Unsupported agent action.\");\n }\n return parsed as unknown as AgentCandidate;\n}\n\nexport function applyAgentCandidate(\n plan: FileMergePlan,\n evidence: AgentEvidence,\n candidate: AgentCandidate,\n): FileMergePlan {\n // Persisted candidates cross a trust boundary too.\n parseAgentCandidate(JSON.stringify(candidate), evidence);\n if (plan.kind !== \"planned\") throw new Error(\"Unverified merge plan.\");\n const results = new Map(candidate.results.map((row) => [row.id, row]));\n const replacements = new Map<string, SnapshotFile>();\n for (const file of evidence.files) {\n const units = evidence.units.filter((unit) => unit.path === file.path);\n if (units.some((unit) => results.get(unit.id) && \"refuse\" in (results.get(unit.id) ?? {}))) continue;\n let bytes: Buffer;\n if (file.parts) {\n bytes = Buffer.from(\n file.parts\n .map((part) => (typeof part === \"string\" ? part : (results.get(part.id) as { content: string }).content))\n .join(\"\"),\n );\n } else {\n const selections = new Map(\n (file.fields ?? []).map(({ id, key }) => [key, (results.get(id) as { source: \"local\" | \"remote\" }).source]),\n );\n const merged = mergeSettingsJson(\n Buffer.from(file.base),\n Buffer.from(file.local),\n Buffer.from(file.remote),\n selections,\n );\n if (merged.kind !== \"merged\") throw new Error(\"Agent settings units remain unresolved.\");\n // The existing renderer/parser retains atomic values, unknown members and exact numeric policy.\n parseSettingsDocument(merged.content);\n for (const { key } of file.fields ?? []) {\n if (!selections.has(key)) throw new Error(\"Missing atomic settings selection.\");\n }\n bytes = merged.content;\n }\n if (bytes.length > AGENT_OUTPUT_BYTES) throw new Error(\"Agent file exceeds output bound.\");\n replacements.set(file.path, snapshotFile(file.path, bytes));\n }\n const decisions = plan.decisions.map((decision) => {\n const file = replacements.get(decision.path);\n return file && decision.kind === \"conflict\"\n ? { kind: \"accepted\" as const, path: file.path, source: \"merged\" as const, file }\n : decision;\n });\n return { kind: \"planned\", decisions, conflicts: decisions.filter((item) => item.kind === \"conflict\") };\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,kBAAkB;AAuCpB,IAAM,SAAS,CAAC,UAAmB,WAAW,QAAQ,EAAE,OAAO,KAAK,UAAU,KAAK,CAAC,EAAE,OAAO,KAAK;AAEzG,SAAS,SAAS,GAAS,GAAS;AAClC,SAAO,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,MACtC,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,MACjC,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE;AACrC;AAGA,SAAS,UAAU,UAAkB,UAAkB,WAAmB,YAA4C;AACpH,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,UAAU;AAAA,IACd,GAAG,MAAM,MAAM,MAAM,SAAS,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,GAAG,MAAM,MAAM,QAAiB,EAAE;AAAA,IACpF,GAAG,MAAM,MAAM,MAAM,UAAU,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,GAAG,MAAM,MAAM,SAAkB,EAAE;AAAA,EACxF,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG;AACnD,QAAM,WAA+B,CAAC;AACtC,aAAW,UAAU,SAAS;AAC5B,UAAM,OAAO,SAAS,GAAG,EAAE;AAC3B,QAAI,MAAM,KAAK,CAAC,SAAS,SAAS,MAAM,MAAM,CAAC,EAAG,MAAK,KAAK,MAAM;AAAA,QAC7D,UAAS,KAAK,CAAC,MAAM,CAAC;AAAA,EAC7B;AACA,QAAM,SAAiC,CAAC;AACxC,MAAI,SAAS;AACb,aAAW,WAAW,UAAU;AAC9B,UAAM,QAAQ,KAAK,IAAI,GAAG,QAAQ,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC;AAC3D,UAAM,MAAM,KAAK,IAAI,GAAG,QAAQ,IAAI,CAAC,SAAS,KAAK,GAAG,CAAC;AACvD,WAAO,KAAK,KAAK,MAAM,QAAQ,KAAK,EAAE,KAAK,EAAE,CAAC;AAC9C,UAAM,QAAQ,CAAC,SAA6B;AAC1C,UAAI,WAAW;AACf,YAAM,SAAmB,CAAC;AAC1B,iBAAW,QAAQ,QAAQ,OAAO,CAAC,SAAS,KAAK,SAAS,IAAI,GAAG;AAC/D,eAAO,KAAK,GAAG,KAAK,MAAM,UAAU,KAAK,KAAK,GAAG,GAAG,KAAK,KAAK;AAC9D,mBAAW,KAAK;AAAA,MAClB;AACA,aAAO,CAAC,GAAG,QAAQ,GAAG,KAAK,MAAM,UAAU,GAAG,CAAC,EAAE,KAAK,EAAE;AAAA,IAC1D;AACA,UAAM,QAAQ,MAAM,OAAO;AAC3B,UAAM,SAAS,MAAM,QAAQ;AAC7B,UAAM,WAAW,KAAK,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE;AAC/C,QAAI,UAAU,UAAU,UAAU,YAAY,WAAW,SAAU,QAAO,KAAK,UAAU,WAAW,SAAS,KAAK;AAAA,QAC7G,QAAO,KAAK,EAAE,IAAI,IAAI,MAAM,UAAU,MAAM,QAAQ,MAAM,UAAU,OAAO,OAAO,CAAC;AACxF,aAAS;AAAA,EACX;AACA,SAAO,KAAK,KAAK,MAAM,MAAM,EAAE,KAAK,EAAE,CAAC;AACvC,SAAO;AACT;AAEO,SAAS,mBACd,MACA,OACA,QACA,WACe;AACf,QAAM,WAA0B,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,GAAG,aAAa,CAAC,EAAE;AACxE,MAAI,KAAK,SAAS,UAAW,QAAO;AACpC,MAAI,KAAK,UAAU,SAAS;AAC1B,UAAM,IAAI,MAAM,kEAAkE;AACpF,aAAW,YAAY,KAAK,WAAW;AACrC,QAAI;AACF,UAAI,SAAS,WAAW,eAAgB,OAAM,IAAI,MAAM,sBAAsB;AAC9E,YAAM,WAAW,CAAC,WAAW,MAAM,OAAO,OAAO,KAAK,EAAE,IAAI,CAAC,UAAU;AACrE,cAAMA,QAAO,MAAM,KAAK,CAAC,SAAS,KAAK,SAAS,SAAS,IAAI;AAC7D,YAAI,CAACA,MAAM,OAAM,IAAI,MAAM,0BAA0B;AACrD,cAAM,QAAQ,OAAO,KAAKA,MAAK,eAAe,QAAQ;AACtD,YAAI,MAAM,SAAS,KAAM,OAAM,IAAI,MAAM,YAAY;AACrD,eAAO,KAAK,KAAK;AAAA,MACnB,CAAC;AACD,YAAM,CAAC,MAAM,MAAM,KAAK,IAAI;AAC5B,YAAM,OAAsB,EAAE,MAAM,SAAS,MAAM,MAAM,OAAO,MAAM,QAAQ,MAAM;AACpF,UAAI;AACJ,UAAI,SAAS,SAAS,iBAAiB;AACrC,cAAM,YAAY,SAAS,IAAI,CAAC,UAAU,sBAAsB,OAAO,KAAK,KAAK,CAAC,CAAC;AACnF,cAAM,QAAQ,kBAAkB,GAAI,SAAS,IAAI,CAAC,UAAU,OAAO,KAAK,KAAK,CAAC,CAA8B;AAC5G,YAAI,MAAM,SAAS,YAAY,MAAM,WAAW,iBAAkB,OAAM,IAAI,MAAM,sBAAsB;AACxG,cAAM,SAAS,IAAI,IAAI,MAAM,MAAM;AACnC,cAAM,SAAS,eAAe,IAAI,CAAC,UAAU,CAAC,GAAG,KAAK,CAAa;AACnE,cAAM,UAAU,IAAI,IAAI,OAAO,KAAK,CAAC;AACrC,eAAO,KAAK,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,QAAQ,IAAI,GAAG,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AACjF,aAAK,SAAS,CAAC;AACf,gBAAQ,OACL,OAAO,CAAC,UAAU,MAAM,KAAK,CAAC,QAAQ,OAAO,IAAI,GAAG,CAAC,CAAC,EACtD,IAAI,CAAC,UAAU;AACd,gBAAM,SAAS,UAAU;AAAA,YAAI,CAAC,aAC5B,KAAK;AAAA,cACH,OAAO;AAAA,gBACL,MAAM,QAAQ,CAAC,QAAQ;AACrB,wBAAM,OAAO,SAAS,KAAK,QAAQ,IAAI,GAAG,GAAG;AAC7C,yBAAO,OAAO,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,IAAI,CAAC;AAAA,gBACvC,CAAC;AAAA,cACH;AAAA,YACF;AAAA,UACF;AACA,gBAAM,OAAkB;AAAA,YACtB,IAAI;AAAA,YACJ,MAAM,KAAK;AAAA,YACX,MAAM;AAAA,YACN,MAAM,OAAO,CAAC,KAAK;AAAA,YACnB,OAAO,OAAO,CAAC,KAAK;AAAA,YACpB,QAAQ,OAAO,CAAC,KAAK;AAAA,UACvB;AACA,eAAK,QAAQ,KAAK,EAAE,IAAI,OAAO,SAAS,MAAM,SAAS,KAAK,OAAO,MAAM,GAAG,KAAK,MAAM,CAAC,KAAK,GAAG,CAAC;AACjG,iBAAO;AAAA,QACT,CAAC;AAAA,MACL,OAAO;AACL,YAAI,CAAC,gBAAgB,SAAS,IAAI,EAAG,OAAM,IAAI,MAAM,kBAAkB;AACvE,aAAK,QAAQ,UAAU,KAAK,MAAM,MAAM,MAAM,KAAK;AACnD,gBAAQ,KAAK,MAAM,OAAO,CAAC,SAA4B,OAAO,SAAS,QAAQ;AAAA,MACjF;AACA,iBAAW,QAAQ,OAAO;AACxB,aAAK,KAAK,OAAO,SAAS,MAAM,MAAM;AACtC,iBAAS,MAAM,KAAK,IAAI;AAAA,MAC1B;AACA,eAAS,MAAM,KAAK,IAAI;AAAA,IAC1B,QAAQ;AACN,eAAS,YAAY,KAAK,SAAS,IAAI;AAAA,IACzC;AAAA,EACF;AACA,MAAI,SAAS,MAAM,SAAS,mBAAmB,OAAO,WAAW,KAAK,UAAU,SAAS,KAAK,CAAC,IAAI;AACjG,UAAM,IAAI,MAAM,+DAA+D;AAEjF,MACE,aAAa;AAAA,IACX,GAAG;AAAA,IACH,OAAO,SAAS,MAAM;AAAA,MAAQ,CAAC,SAC7B,CAAC,KAAK,MAAM,KAAK,MAAM,KAAK,OAAO,KAAK,MAAM,EAAE,IAAI,CAAC,UAAU,aAAa,KAAK,MAAM,OAAO,KAAK,KAAK,CAAC,CAAC;AAAA,IAC5G;AAAA,EACF,CAAC,EAAE;AAEH,UAAM,IAAI,MAAM,+EAA+E;AACjG,SAAO;AACT;AAEO,SAAS,oBAAoB,KAAa,UAAyC;AACxF,MAAI,OAAO,WAAW,GAAG,IAAI,mBAAoB,OAAM,IAAI,MAAM,iCAAiC;AAClG,QAAM,SAAS,wBAAwB,OAAO,KAAK,GAAG,CAAC,EAAE,KAAK;AAC9D,MACE,OAAO,KAAK,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,qBACzC,OAAO,YAAY,KACnB,CAAC,MAAM,QAAQ,OAAO,OAAO,KAC7B,OAAO,QAAQ,WAAW,SAAS,MAAM;AAEzC,UAAM,IAAI,MAAM,mCAAmC;AACrD,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,UAAU,OAAO,SAAS;AACnC,QAAI,CAAC,UAAU,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,EAAG,OAAM,IAAI,MAAM,uBAAuB;AAC3G,UAAM,MAAM;AACZ,UAAM,OAAO,SAAS,MAAM,KAAK,CAACC,UAASA,MAAK,OAAO,IAAI,EAAE;AAC7D,QAAI,CAAC,QAAQ,KAAK,IAAI,KAAK,EAAE,EAAG,OAAM,IAAI,MAAM,kCAAkC;AAClF,SAAK,IAAI,KAAK,EAAE;AAChB,UAAM,OAAO,OAAO,KAAK,GAAG,EAAE,KAAK,EAAE,KAAK,GAAG;AAC7C,QAAI,SAAS,eAAe,IAAI,WAAW,KAAM;AACjD,QAAI,KAAK,SAAS,cAAc,SAAS,gBAAgB,IAAI,WAAW,WAAW,IAAI,WAAW;AAChG;AACF,QAAI,KAAK,SAAS,UAAU,SAAS,gBAAgB,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,SAAS,GAAG;AAC9G,WAAK,OAAO,KAAK,IAAI,OAAO,CAAC;AAC7B,UAAI,8BAA8B,KAAK,IAAI,OAAO,EAAG,OAAM,IAAI,MAAM,8BAA8B;AACnG;AAAA,IACF;AACA,UAAM,IAAI,MAAM,2BAA2B;AAAA,EAC7C;AACA,SAAO;AACT;AAEO,SAAS,oBACd,MACA,UACA,WACe;AAEf,sBAAoB,KAAK,UAAU,SAAS,GAAG,QAAQ;AACvD,MAAI,KAAK,SAAS,UAAW,OAAM,IAAI,MAAM,wBAAwB;AACrE,QAAM,UAAU,IAAI,IAAI,UAAU,QAAQ,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC;AACrE,QAAM,eAAe,oBAAI,IAA0B;AACnD,aAAW,QAAQ,SAAS,OAAO;AACjC,UAAM,QAAQ,SAAS,MAAM,OAAO,CAAC,SAAS,KAAK,SAAS,KAAK,IAAI;AACrE,QAAI,MAAM,KAAK,CAAC,SAAS,QAAQ,IAAI,KAAK,EAAE,KAAK,aAAa,QAAQ,IAAI,KAAK,EAAE,KAAK,CAAC,EAAE,EAAG;AAC5F,QAAI;AACJ,QAAI,KAAK,OAAO;AACd,cAAQ,OAAO;AAAA,QACb,KAAK,MACF,IAAI,CAAC,SAAU,OAAO,SAAS,WAAW,OAAQ,QAAQ,IAAI,KAAK,EAAE,EAA0B,OAAQ,EACvG,KAAK,EAAE;AAAA,MACZ;AAAA,IACF,OAAO;AACL,YAAM,aAAa,IAAI;AAAA,SACpB,KAAK,UAAU,CAAC,GAAG,IAAI,CAAC,EAAE,IAAI,IAAI,MAAM,CAAC,KAAM,QAAQ,IAAI,EAAE,EAAqC,MAAM,CAAC;AAAA,MAC5G;AACA,YAAM,SAAS;AAAA,QACb,OAAO,KAAK,KAAK,IAAI;AAAA,QACrB,OAAO,KAAK,KAAK,KAAK;AAAA,QACtB,OAAO,KAAK,KAAK,MAAM;AAAA,QACvB;AAAA,MACF;AACA,UAAI,OAAO,SAAS,SAAU,OAAM,IAAI,MAAM,yCAAyC;AAEvF,4BAAsB,OAAO,OAAO;AACpC,iBAAW,EAAE,IAAI,KAAK,KAAK,UAAU,CAAC,GAAG;AACvC,YAAI,CAAC,WAAW,IAAI,GAAG,EAAG,OAAM,IAAI,MAAM,oCAAoC;AAAA,MAChF;AACA,cAAQ,OAAO;AAAA,IACjB;AACA,QAAI,MAAM,SAAS,mBAAoB,OAAM,IAAI,MAAM,kCAAkC;AACzF,iBAAa,IAAI,KAAK,MAAM,aAAa,KAAK,MAAM,KAAK,CAAC;AAAA,EAC5D;AACA,QAAM,YAAY,KAAK,UAAU,IAAI,CAAC,aAAa;AACjD,UAAM,OAAO,aAAa,IAAI,SAAS,IAAI;AAC3C,WAAO,QAAQ,SAAS,SAAS,aAC7B,EAAE,MAAM,YAAqB,MAAM,KAAK,MAAM,QAAQ,UAAmB,KAAK,IAC9E;AAAA,EACN,CAAC;AACD,SAAO,EAAE,MAAM,WAAW,WAAW,WAAW,UAAU,OAAO,CAAC,SAAS,KAAK,SAAS,UAAU,EAAE;AACvG;",
6
+ "names": ["file", "unit"]
7
+ }
@@ -0,0 +1,153 @@
1
+ // @generated by scripts/build-runtime.mjs; do not edit.
2
+ // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
3
+ import {
4
+ fileHashMap,
5
+ sameHashes
6
+ } from "./chunk-BDB4IDM3.ts";
7
+ import {
8
+ portableSnapshot,
9
+ sameLocalFields,
10
+ validatePortableSnapshot,
11
+ validateSnapshotFieldPolicy
12
+ } from "./chunk-A6HU4ERM.ts";
13
+ import {
14
+ planFileMerge
15
+ } from "./chunk-76NB4R7U.ts";
16
+ import {
17
+ statePathForConfig,
18
+ syncStateFingerprint
19
+ } from "./chunk-UPNVIUBN.ts";
20
+ import {
21
+ readJsonIfExists,
22
+ syncDirectory,
23
+ writeJson
24
+ } from "./chunk-HUZ5CR4W.ts";
25
+
26
+ // src/sync/merge-journal.ts
27
+ import { randomUUID } from "node:crypto";
28
+ import fs from "node:fs/promises";
29
+ import path from "node:path";
30
+ function mergeJournalIdentity(config, backendIdentity) {
31
+ return JSON.stringify([config.setupName, backendIdentity, [...config.include].sort(), config.localFields ?? null]);
32
+ }
33
+ function mergeJournalPath(config) {
34
+ return `${statePathForConfig(config)}.merge-journal.json`;
35
+ }
36
+ async function readMergeJournal(config) {
37
+ let journal;
38
+ try {
39
+ const stat = await fs.lstat(mergeJournalPath(config));
40
+ if (!stat.isFile() || stat.size > 384 * 1024 * 1024) throw new Error("Unsafe or oversized journal.");
41
+ journal = await readJsonIfExists(mergeJournalPath(config));
42
+ } catch (error) {
43
+ if (error.code === "ENOENT") return;
44
+ throw new Error("Cannot read the private merge journal; preserve it and review recovery.");
45
+ }
46
+ if (!journal) return;
47
+ if (journal.version !== 1 || typeof journal.identity !== "string" || typeof journal.backup !== "string" || typeof journal.stateIdentity !== "string" || journal.applyOnly !== void 0 && (journal.applyOnly !== true || !journal.committedHead || journal.committedHead.revision !== journal.expectedHead?.revision || journal.committedHead.snapshotId !== journal.expectedHead?.snapshotId || journal.upload?.id !== journal.expectedHead?.snapshotId) || journal.sessionRoot !== void 0 && (typeof journal.sessionRoot !== "string" || !path.isAbsolute(journal.sessionRoot) || path.resolve(journal.sessionRoot) !== journal.sessionRoot) || journal.warnings !== void 0 && (!Array.isArray(journal.warnings) || journal.warnings.length > 64 || journal.warnings.some((item) => typeof item !== "string")) || !journal.expectedHead || typeof journal.expectedHead.revision !== "string" || !journal.before || !journal.after || !journal.upload || !Array.isArray(journal.before.files) || !Array.isArray(journal.after.files) || !Array.isArray(journal.upload.files))
48
+ throw new Error("Unsupported or damaged merge journal; preserve it and review recovery before syncing.");
49
+ try {
50
+ const pinnedPaths = new Set(journal.progress?.groups.flatMap((group) => group.paths) ?? []);
51
+ for (const snapshot of [
52
+ journal.before,
53
+ journal.after,
54
+ journal.upload,
55
+ ...journal.accepted ? [journal.accepted] : []
56
+ ]) {
57
+ validateSnapshotFieldPolicy(snapshot);
58
+ if (snapshot.version !== 1 && snapshot.version !== 2 && snapshot.version !== 3 || typeof snapshot.id !== "string" || typeof snapshot.profile !== "string" || snapshot.files.length > 16384 || snapshot.files.reduce(
59
+ (bytes, file) => bytes + (typeof file?.contentBase64 === "string" ? file.contentBase64.length * 0.75 : Infinity),
60
+ 0
61
+ ) > 64 * 1024 * 1024)
62
+ throw new Error("Invalid journal input.");
63
+ const plan = planFileMerge({ baseline: {}, local: snapshot.files, remote: [], selectionCompatible: true });
64
+ if (plan.kind !== "planned" || plan.conflicts.some((conflict) => conflict.reason !== "path-collision" || !pinnedPaths.has(conflict.path)))
65
+ throw new Error("Invalid journal collision group.");
66
+ }
67
+ validatePortableSnapshot(journal.upload);
68
+ if ((journal.upload.version === 2 || journal.upload.version === 3) && !journal.accepted)
69
+ throw new Error("Portable merge journal requires an explicit accepted projection.");
70
+ if (journal.accepted) validatePortableSnapshot(journal.accepted);
71
+ } catch {
72
+ throw new Error("Invalid merge journal paths, metadata, or bytes; preserve evidence for review.");
73
+ }
74
+ try {
75
+ if (journal.accepted && (!sameLocalFields(journal.accepted.localFields, journal.upload.localFields) || !sameHashes(
76
+ fileHashMap(portableSnapshot(journal.after, journal.upload.localFields)),
77
+ fileHashMap(portableSnapshot(journal.accepted, journal.upload.localFields))
78
+ )))
79
+ throw new Error("Projection mismatch.");
80
+ } catch {
81
+ throw new Error("Invalid accepted merge projection; preserve journal evidence.");
82
+ }
83
+ if (journal.progress) {
84
+ const known = /* @__PURE__ */ new Set([
85
+ ...Object.keys(journal.progress.previous?.lastFileHashes ?? {}),
86
+ ...[...journal.before.files, ...journal.after.files, ...journal.upload.files].map((file) => file.path)
87
+ ]);
88
+ const progress = journal.progress;
89
+ if (!config.partialSync || !progress.previous || syncStateFingerprint(progress.previous) !== journal.stateIdentity || !Array.isArray(progress.groups) || progress.groups.length > 16384 || progress.groups.some(
90
+ (group) => !Array.isArray(group.paths) || !group.paths.length || group.paths.length > 16384 || group.paths.some((filePath) => !known.has(filePath)) || !/^[a-f0-9-]{36}$/u.test(group.artifact)
91
+ ))
92
+ throw new Error("Invalid partial acceptance metadata; preserve journal evidence.");
93
+ }
94
+ if (journal.progress) {
95
+ const before = fileHashMap(journal.before);
96
+ const after = fileHashMap(journal.after);
97
+ const { readConflictArtifact } = await import("./conflict-artifacts-FXFIIBCB.ts");
98
+ const backendIdentity = JSON.parse(journal.identity)[1];
99
+ if (typeof backendIdentity !== "string") throw new Error("Invalid partial backend identity.");
100
+ const byArtifact = /* @__PURE__ */ new Map();
101
+ for (const group of journal.progress.groups) {
102
+ const groups = byArtifact.get(group.artifact) ?? [];
103
+ groups.push(group);
104
+ byArtifact.set(group.artifact, groups);
105
+ }
106
+ for (const [token, groups] of byArtifact) {
107
+ const artifact = await readConflictArtifact(config, backendIdentity, token);
108
+ const retainedGroups = new Set(artifact.groups.map((group) => JSON.stringify(group.paths)));
109
+ const remote = fileHashMap(artifact.remote);
110
+ const upload = fileHashMap(journal.upload);
111
+ for (const group of groups) {
112
+ if (!retainedGroups.has(JSON.stringify(group.paths)))
113
+ throw new Error("Partial group does not match retained artifact evidence.");
114
+ for (const filePath of group.paths)
115
+ if (before[filePath] !== after[filePath] || remote[filePath] !== upload[filePath] || journal.progress.previous.lastFileHashes[filePath] !== artifact.state.lastFileHashes[filePath])
116
+ throw new Error("Withheld version changed in journal; preserve evidence.");
117
+ }
118
+ }
119
+ }
120
+ return journal;
121
+ }
122
+ async function writeMergeJournal(config, journal) {
123
+ await writeJson(mergeJournalPath(config), journal, { maxBytes: 384 * 1024 * 1024 });
124
+ }
125
+ async function clearMergeJournal(config) {
126
+ await fs.rm(mergeJournalPath(config), { force: true });
127
+ await syncDirectory(path.dirname(mergeJournalPath(config)));
128
+ }
129
+ async function retireMergeJournal(config) {
130
+ if (!await readMergeJournal(config)) return;
131
+ const file = mergeJournalPath(config);
132
+ await fs.rename(file, `${file}.${randomUUID()}.resolved`);
133
+ await syncDirectory(path.dirname(file));
134
+ }
135
+ async function requireNoMergeJournal(config, options = {}) {
136
+ const journal = await readMergeJournal(config);
137
+ if (options.force || !journal) return;
138
+ if (journal) {
139
+ throw new Error(
140
+ "A merged transfer needs recovery. Run /sync sync to reconcile it. If newer local edits or remote revisions prevent recovery, review /sync diff and explicitly choose push --force or pull --force; the selected directional result will archive the old journal, not restore old bytes. Keep its backup; do not downgrade while recovery is pending."
141
+ );
142
+ }
143
+ }
144
+
145
+ export {
146
+ mergeJournalIdentity,
147
+ readMergeJournal,
148
+ writeMergeJournal,
149
+ clearMergeJournal,
150
+ retireMergeJournal,
151
+ requireNoMergeJournal
152
+ };
153
+ //# sourceMappingURL=chunk-GNH73BJ5.ts.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/sync/merge-journal.ts"],
4
+ "sourcesContent": ["import { randomUUID } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { RemoteHead } from \"../backends/sync-backend.js\";\nimport type { AnySyncConfig } from \"../settings/settings-types.js\";\nimport type { Snapshot } from \"../snapshot/snapshot-types.js\";\nimport { readJsonIfExists, syncDirectory, writeJson } from \"../state/json-file.js\";\nimport { statePathForConfig, syncStateFingerprint } from \"../state/sync-state-store.js\";\nimport { planFileMerge } from \"./file-merge-planner.js\";\nimport {\n portableSnapshot,\n sameLocalFields,\n validatePortableSnapshot,\n validateSnapshotFieldPolicy,\n} from \"./local-fields.js\";\nimport type { PartialProgress } from \"./partial-progress.js\";\nimport { fileHashMap, sameHashes } from \"./sync-state.js\";\n\nexport interface MergeJournal {\n version: 1;\n identity: string;\n before: Snapshot;\n after: Snapshot;\n accepted?: Snapshot;\n progress?: PartialProgress;\n upload: Snapshot;\n expectedHead: RemoteHead;\n committedHead?: RemoteHead;\n /** No remote publication was attempted; a stale candidate can retire if local preimages remain intact. */\n applyOnly?: boolean;\n backup: string;\n stateIdentity: string;\n /** Effective collection/apply root; absent legacy evidence cannot authorize session recovery. */\n sessionRoot?: string;\n warnings?: string[];\n}\n\nexport function mergeJournalIdentity(config: AnySyncConfig, backendIdentity: string) {\n return JSON.stringify([config.setupName, backendIdentity, [...config.include].sort(), config.localFields ?? null]);\n}\n\nexport function mergeJournalPath(config: AnySyncConfig) {\n return `${statePathForConfig(config)}.merge-journal.json`;\n}\n\nexport async function readMergeJournal(config: AnySyncConfig): Promise<MergeJournal | undefined> {\n let journal: MergeJournal | undefined;\n try {\n const stat = await fs.lstat(mergeJournalPath(config));\n if (!stat.isFile() || stat.size > 384 * 1024 * 1024) throw new Error(\"Unsafe or oversized journal.\");\n journal = await readJsonIfExists<MergeJournal>(mergeJournalPath(config));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return;\n // A JSON parser error may quote sensitive journal contents; do not retain it in a public error.\n throw new Error(\"Cannot read the private merge journal; preserve it and review recovery.\");\n }\n if (!journal) return;\n if (\n journal.version !== 1 ||\n typeof journal.identity !== \"string\" ||\n typeof journal.backup !== \"string\" ||\n typeof journal.stateIdentity !== \"string\" ||\n (journal.applyOnly !== undefined &&\n (journal.applyOnly !== true ||\n !journal.committedHead ||\n journal.committedHead.revision !== journal.expectedHead?.revision ||\n journal.committedHead.snapshotId !== journal.expectedHead?.snapshotId ||\n journal.upload?.id !== journal.expectedHead?.snapshotId)) ||\n (journal.sessionRoot !== undefined &&\n (typeof journal.sessionRoot !== \"string\" ||\n !path.isAbsolute(journal.sessionRoot) ||\n path.resolve(journal.sessionRoot) !== journal.sessionRoot)) ||\n (journal.warnings !== undefined &&\n (!Array.isArray(journal.warnings) ||\n journal.warnings.length > 64 ||\n journal.warnings.some((item) => typeof item !== \"string\"))) ||\n !journal.expectedHead ||\n typeof journal.expectedHead.revision !== \"string\" ||\n !journal.before ||\n !journal.after ||\n !journal.upload ||\n !Array.isArray(journal.before.files) ||\n !Array.isArray(journal.after.files) ||\n !Array.isArray(journal.upload.files)\n )\n throw new Error(\"Unsupported or damaged merge journal; preserve it and review recovery before syncing.\");\n // Verify every path and byte, including the unmanaged remote files retained for publication.\n try {\n const pinnedPaths = new Set(journal.progress?.groups.flatMap((group) => group.paths) ?? []);\n for (const snapshot of [\n journal.before,\n journal.after,\n journal.upload,\n ...(journal.accepted ? [journal.accepted] : []),\n ]) {\n validateSnapshotFieldPolicy(snapshot);\n if (\n (snapshot.version !== 1 && snapshot.version !== 2 && snapshot.version !== 3) ||\n typeof snapshot.id !== \"string\" ||\n typeof snapshot.profile !== \"string\" ||\n snapshot.files.length > 16_384 ||\n snapshot.files.reduce(\n (bytes, file) =>\n bytes + (typeof file?.contentBase64 === \"string\" ? file.contentBase64.length * 0.75 : Infinity),\n 0,\n ) >\n 64 * 1024 * 1024\n )\n throw new Error(\"Invalid journal input.\");\n const plan = planFileMerge({ baseline: {}, local: snapshot.files, remote: [], selectionCompatible: true });\n if (\n plan.kind !== \"planned\" ||\n plan.conflicts.some((conflict) => conflict.reason !== \"path-collision\" || !pinnedPaths.has(conflict.path))\n )\n throw new Error(\"Invalid journal collision group.\");\n }\n validatePortableSnapshot(journal.upload);\n if ((journal.upload.version === 2 || journal.upload.version === 3) && !journal.accepted)\n throw new Error(\"Portable merge journal requires an explicit accepted projection.\");\n if (journal.accepted) validatePortableSnapshot(journal.accepted);\n } catch {\n throw new Error(\"Invalid merge journal paths, metadata, or bytes; preserve evidence for review.\");\n }\n // Integrity is intrinsic to the recorded transaction, not the user's current recovery policy.\n // completeJournal separately checks that the recorded policy matches the current setup.\n try {\n if (\n journal.accepted &&\n (!sameLocalFields(journal.accepted.localFields, journal.upload.localFields) ||\n !sameHashes(\n fileHashMap(portableSnapshot(journal.after, journal.upload.localFields)),\n fileHashMap(portableSnapshot(journal.accepted, journal.upload.localFields)),\n ))\n )\n throw new Error(\"Projection mismatch.\");\n } catch {\n throw new Error(\"Invalid accepted merge projection; preserve journal evidence.\");\n }\n if (journal.progress) {\n const known = new Set([\n ...Object.keys(journal.progress.previous?.lastFileHashes ?? {}),\n ...[...journal.before.files, ...journal.after.files, ...journal.upload.files].map((file) => file.path),\n ]);\n const progress = journal.progress;\n if (\n !config.partialSync ||\n !progress.previous ||\n syncStateFingerprint(progress.previous) !== journal.stateIdentity ||\n !Array.isArray(progress.groups) ||\n progress.groups.length > 16_384 ||\n progress.groups.some(\n (group) =>\n !Array.isArray(group.paths) ||\n !group.paths.length ||\n group.paths.length > 16_384 ||\n group.paths.some((filePath) => !known.has(filePath)) ||\n !/^[a-f0-9-]{36}$/u.test(group.artifact),\n )\n )\n throw new Error(\"Invalid partial acceptance metadata; preserve journal evidence.\");\n }\n if (journal.progress) {\n const before = fileHashMap(journal.before);\n const after = fileHashMap(journal.after);\n const { readConflictArtifact } = await import(\"./conflict-artifacts.js\");\n const backendIdentity = (JSON.parse(journal.identity) as unknown[])[1];\n if (typeof backendIdentity !== \"string\") throw new Error(\"Invalid partial backend identity.\");\n const byArtifact = new Map<string, typeof journal.progress.groups>();\n for (const group of journal.progress.groups) {\n const groups = byArtifact.get(group.artifact) ?? [];\n groups.push(group);\n byArtifact.set(group.artifact, groups);\n }\n for (const [token, groups] of byArtifact) {\n const artifact = await readConflictArtifact(config, backendIdentity, token);\n const retainedGroups = new Set(artifact.groups.map((group) => JSON.stringify(group.paths)));\n const remote = fileHashMap(artifact.remote);\n const upload = fileHashMap(journal.upload);\n for (const group of groups) {\n if (!retainedGroups.has(JSON.stringify(group.paths)))\n throw new Error(\"Partial group does not match retained artifact evidence.\");\n for (const filePath of group.paths)\n if (\n before[filePath] !== after[filePath] ||\n remote[filePath] !== upload[filePath] ||\n journal.progress.previous.lastFileHashes[filePath] !== artifact.state.lastFileHashes[filePath]\n )\n throw new Error(\"Withheld version changed in journal; preserve evidence.\");\n }\n }\n }\n return journal;\n}\n\nexport async function writeMergeJournal(config: AnySyncConfig, journal: MergeJournal) {\n await writeJson(mergeJournalPath(config), journal, { maxBytes: 384 * 1024 * 1024 });\n}\n\nexport async function clearMergeJournal(config: AnySyncConfig) {\n await fs.rm(mergeJournalPath(config), { force: true });\n await syncDirectory(path.dirname(mergeJournalPath(config)));\n}\n\nexport async function retireMergeJournal(config: AnySyncConfig) {\n if (!(await readMergeJournal(config))) return;\n const file = mergeJournalPath(config);\n await fs.rename(file, `${file}.${randomUUID()}.resolved`);\n await syncDirectory(path.dirname(file));\n}\n\nexport async function requireNoMergeJournal(config: AnySyncConfig, options: { force?: boolean } = {}) {\n const journal = await readMergeJournal(config);\n if (options.force || !journal) return;\n if (journal) {\n throw new Error(\n \"A merged transfer needs recovery. Run /sync sync to reconcile it. If newer local edits or remote revisions prevent recovery, review /sync diff and explicitly choose push --force or pull --force; the selected directional result will archive the old journal, not restore old bytes. Keep its backup; do not downgrade while recovery is pending.\",\n );\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -2,10 +2,13 @@
2
2
  // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
3
3
  import {
4
4
  setSyncSetupCompletions
5
- } from "./chunk-ITFR6NNQ.ts";
5
+ } from "./chunk-NLWS55B6.ts";
6
6
  import {
7
7
  safeTerminalText
8
8
  } from "./chunk-AMJRUXFT.ts";
9
+ import {
10
+ SetupPullRequiresUiError
11
+ } from "./chunk-7D5SF7P3.ts";
9
12
  import {
10
13
  configuredSyncSetupNames,
11
14
  loadConfig,
@@ -13,10 +16,10 @@ import {
13
16
  syncConfigReviewIdentity,
14
17
  syncSetupReviewIdentity,
15
18
  updateLocalConfig
16
- } from "./chunk-KATKRR3D.ts";
19
+ } from "./chunk-QO333NM4.ts";
17
20
  import {
18
- SetupPullRequiresUiError
19
- } from "./chunk-7D5SF7P3.ts";
21
+ throwIfAborted
22
+ } from "./chunk-PWMWOJP4.ts";
20
23
 
21
24
  // src/sync/setup-switch.ts
22
25
  var SETUP_SWITCH_ACTION_OPTIONS = [
@@ -113,10 +116,6 @@ async function useSyncSetup(ctx, name, pullCurrentSetup, expectedAction, signal,
113
116
  }
114
117
  return { pullApplied: pullOutcome === "applied" };
115
118
  }
116
- function throwIfAborted(signal) {
117
- if (!signal?.aborted) return;
118
- throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError");
119
- }
120
119
 
121
120
  export {
122
121
  SETUP_SWITCH_ACTION_OPTIONS,
@@ -125,4 +124,4 @@ export {
125
124
  saveOnSwitch,
126
125
  useSyncSetup
127
126
  };
128
- //# sourceMappingURL=chunk-YTHQEW35.ts.map
127
+ //# sourceMappingURL=chunk-GP5DU62N.ts.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/sync/setup-switch.ts"],
4
+ "sourcesContent": ["import type { ExtensionCommandContext } from \"@earendil-works/pi-coding-agent\";\nimport { setSyncSetupCompletions } from \"../commands/command.js\";\nimport { loadConfig, syncConfigReviewIdentity, syncSetupReviewIdentity } from \"../settings/config.js\";\nimport { configuredSyncSetupNames, updateLocalConfig } from \"../settings/settings-store.js\";\nimport type { OnSwitchAction } from \"../settings/settings-types.js\";\nimport { normalizeOnSwitch } from \"../settings/settings-validation.js\";\nimport { safeTerminalText } from \"../ui/terminal-text.js\";\nimport { throwIfAborted } from \"./signals.js\";\nimport { SetupPullRequiresUiError } from \"./sync-errors.js\";\n\nexport { SetupPullRequiresUiError } from \"./sync-errors.js\";\n\nexport const SETUP_SWITCH_ACTION_OPTIONS: ReadonlyArray<{\n label: string;\n value: OnSwitchAction;\n}> = [\n { label: \"Ask before pull\", value: \"ask-before-pull\" },\n { label: \"Start pull\", value: \"pull-after-switch\" },\n { label: \"Switch only\", value: \"switch-only\" },\n];\n\nexport function setupSwitchActionLabel(action: OnSwitchAction) {\n return SETUP_SWITCH_ACTION_OPTIONS.find((option) => option.value === action)?.label ?? action;\n}\n\nexport function setupSwitchActionFromLabel(label: string): OnSwitchAction | undefined {\n return SETUP_SWITCH_ACTION_OPTIONS.find((option) => option.label === label)?.value;\n}\n\nexport async function saveOnSwitch(action: OnSwitchAction, signal?: AbortSignal) {\n await updateLocalConfig((settings) => ({ ...settings, onSwitch: action }), signal);\n}\n\nexport type SetupPullOutcome = \"applied\" | \"cancelled\";\n\nexport interface SetupSwitchResult {\n pullApplied: boolean;\n}\n\nexport async function useSyncSetup(\n ctx: ExtensionCommandContext,\n name: string,\n pullCurrentSetup?: (setup: string) => Promise<SetupPullOutcome | undefined>,\n expectedAction?: OnSwitchAction,\n signal?: AbortSignal,\n expectedSetupIdentity?: string,\n): Promise<SetupSwitchResult> {\n const normalized = name.trim();\n if (!normalized) throw new Error(\"Usage: /sync use <setup>\");\n const loadedConfig = await loadConfig(normalized);\n const reviewedSetupIdentity = expectedSetupIdentity ?? syncConfigReviewIdentity(loadedConfig);\n throwIfAborted(signal);\n const switchResult: { action: OnSwitchAction; switched: boolean } = {\n action: \"ask-before-pull\",\n switched: false,\n };\n await updateLocalConfig((current) => {\n throwIfAborted(signal);\n const setup = current.syncSetups[normalized];\n if (!setup) {\n throw new Error(`Sync setup \u201C${safeTerminalText(normalized)}\u201D no longer exists.`);\n }\n const connectionName = setup.storage.connection;\n const connection = current.storageConnections[connectionName];\n if (\n !connection ||\n syncSetupReviewIdentity(normalized, setup, connectionName, connection) !== reviewedSetupIdentity\n ) {\n throw new Error(\n `Sync setup \u201C${safeTerminalText(normalized)}\u201D changed while the switch preview was open; reopen it and review the current destination.`,\n );\n }\n switchResult.action = normalizeOnSwitch(current.onSwitch);\n if (expectedAction !== undefined && switchResult.action !== expectedAction) {\n throw new Error(\"Setup-switch behavior changed while the preview was open; reopen it and retry.\");\n }\n if (switchResult.action === \"pull-after-switch\" && !ctx.hasUI) {\n throw new SetupPullRequiresUiError(\n `Automatic setup pulls require interactive confirmation; sync setup \u201C${safeTerminalText(normalized)}\u201D was not switched. Use TUI or RPC mode.`,\n );\n }\n if (current.activeSyncSetup === normalized) return current;\n switchResult.switched = true;\n return { ...current, activeSyncSetup: normalized };\n }, signal);\n throwIfAborted(signal);\n if (!switchResult.switched) {\n ctx.ui.notify(`Sync setup \u201C${safeTerminalText(normalized)}\u201D is already current.`, \"info\");\n return { pullApplied: false };\n }\n setSyncSetupCompletions(await configuredSyncSetupNames());\n throwIfAborted(signal);\n\n if (switchResult.action === \"switch-only\") {\n ctx.ui.notify(`Switched to \u201C${safeTerminalText(normalized)}\u201D. No files were pulled.`, \"info\");\n return { pullApplied: false };\n }\n if (switchResult.action === \"ask-before-pull\") {\n if (ctx.mode !== \"tui\") {\n ctx.ui.notify(\n `Switched to \u201C${safeTerminalText(normalized)}\u201D. No files were pulled because confirmation requires TUI mode; run /sync pull to apply this setup.`,\n \"info\",\n );\n return { pullApplied: false };\n }\n const confirmed = await ctx.ui.confirm(\n `Review a pull for sync setup \u201C${safeTerminalText(normalized)}\u201D now?`,\n \"pi-sync will check the remote snapshot and show the exact local writes and deletions before applying anything.\",\n { signal },\n );\n throwIfAborted(signal);\n if (!confirmed) {\n ctx.ui.notify(`Switched to \u201C${safeTerminalText(normalized)}\u201D; files were not pulled.`, \"info\");\n return { pullApplied: false };\n }\n }\n\n ctx.ui.notify(`Switched to \u201C${safeTerminalText(normalized)}\u201D. Checking remote files for a reviewed pull\u2026`, \"info\");\n if (!pullCurrentSetup) {\n throw new Error(`Switched to \u201C${safeTerminalText(normalized)}\u201D, but pull is unavailable.`);\n }\n const pullOutcome = await pullCurrentSetup(normalized);\n throwIfAborted(signal);\n if (pullOutcome === \"cancelled\") {\n ctx.ui.notify(\n `Pull cancelled; sync setup \u201C${safeTerminalText(normalized)}\u201D remains current and synced files were not changed.`,\n \"info\",\n );\n }\n return { pullApplied: pullOutcome === \"applied\" };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -9,7 +9,7 @@ import {
9
9
  import {
10
10
  syncCheckConfigFingerprint,
11
11
  syncConfigReviewFingerprint
12
- } from "./chunk-KATKRR3D.ts";
12
+ } from "./chunk-QO333NM4.ts";
13
13
  import {
14
14
  compareSyncInclude,
15
15
  sameSyncInclude
@@ -164,4 +164,4 @@ export {
164
164
  observationMatchesConfig,
165
165
  observationSummary
166
166
  };
167
- //# sourceMappingURL=chunk-AAZDBIBJ.ts.map
167
+ //# sourceMappingURL=chunk-GSNPASKB.ts.map
@@ -4,17 +4,17 @@ import {
4
4
  collidingPaths,
5
5
  planFileMerge
6
6
  } from "./chunk-76NB4R7U.ts";
7
+ import {
8
+ parseJsonObjectDocument,
9
+ parseSettingsDocument
10
+ } from "./chunk-ZW5ZLGBI.ts";
7
11
  import {
8
12
  statePathForConfig,
9
13
  syncStateFingerprint
10
- } from "./chunk-VCL7TZYW.ts";
14
+ } from "./chunk-UPNVIUBN.ts";
11
15
  import {
12
16
  backendIdentityCoordinates
13
17
  } from "./chunk-2JZFHNLM.ts";
14
- import {
15
- parseJsonObjectDocument,
16
- parseSettingsDocument
17
- } from "./chunk-ZW5ZLGBI.ts";
18
18
  import {
19
19
  syncDirectory,
20
20
  writeJson
@@ -277,7 +277,7 @@ function baselinePath(config, state) {
277
277
  async function stageMergeBaseline(config, snapshot, accepted, previous) {
278
278
  const collisions = collidingPaths(snapshot.files.map((file) => file.path));
279
279
  const files = snapshot.files.filter((file) => {
280
- return !collisions.has(file.path) && accepted.lastFileHashes[file.path] === file.sha256 && (file.path === "settings.json" || config.mergeContent) && eligible(file);
280
+ return !collisions.has(file.path) && accepted.lastFileHashes[file.path] === file.sha256 && (file.path === "settings.json" || config.mergeContent || config.agentMode && isMergeTextPath(file.path)) && eligible(file);
281
281
  });
282
282
  if (previous) {
283
283
  const stagedPaths = new Set(files.map((file) => file.path));
@@ -339,7 +339,7 @@ async function hasCurrentMergeBaseline(config, snapshot, state) {
339
339
  if (!ancestors) return false;
340
340
  const hashes = new Map(ancestors.map((file) => [file.path, file.sha256]));
341
341
  return snapshot.files.every(
342
- (file) => !(file.path === "settings.json" || config.mergeContent) || !eligible(file) || hashes.get(file.path) === file.sha256
342
+ (file) => !(file.path === "settings.json" || config.mergeContent || config.agentMode && isMergeTextPath(file.path)) || !eligible(file) || hashes.get(file.path) === file.sha256
343
343
  );
344
344
  } catch {
345
345
  return false;
@@ -382,6 +382,9 @@ async function pruneMergeBaselines(config, state, validate) {
382
382
  export {
383
383
  TEXT_MERGE_WORK_LIMIT,
384
384
  isMergeTextPath,
385
+ text,
386
+ lines,
387
+ edits,
385
388
  mergeText,
386
389
  mergeSession,
387
390
  stageMergeBaseline,
@@ -390,4 +393,4 @@ export {
390
393
  readMergeAncestor,
391
394
  pruneMergeBaselines
392
395
  };
393
- //# sourceMappingURL=chunk-E6ONFG57.ts.map
396
+ //# sourceMappingURL=chunk-IWZH5KYS.ts.map