@narumitw/pi-sync 0.54.2 → 0.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -15
- package/dist/chunks/agent-candidate-3JN6DJKT.ts +26 -0
- package/dist/chunks/agent-plan-QCUJWBP4.ts +99 -0
- package/dist/chunks/agent-plan-QCUJWBP4.ts.map +7 -0
- package/dist/chunks/agent-runner-E2JU2VZ6.ts +182 -0
- package/dist/chunks/agent-runner-E2JU2VZ6.ts.map +7 -0
- package/dist/chunks/agent-store-A3F6IYWZ.ts +152 -0
- package/dist/chunks/agent-store-A3F6IYWZ.ts.map +7 -0
- package/dist/chunks/chunk-5Q5SSKNG.ts +128 -0
- package/dist/chunks/chunk-5Q5SSKNG.ts.map +7 -0
- package/dist/chunks/chunk-5XZOC7FD.ts +215 -0
- package/dist/chunks/chunk-5XZOC7FD.ts.map +7 -0
- package/dist/chunks/{chunk-IJ6VV6FN.ts → chunk-AAEEHR7H.ts} +31 -87
- package/dist/chunks/chunk-AAEEHR7H.ts.map +7 -0
- package/dist/chunks/{chunk-CHSIAC4N.ts → chunk-B4QZBDBT.ts} +2 -2
- package/dist/chunks/{chunk-CL7BZRRQ.ts → chunk-CRKTAPSL.ts} +8 -8
- package/dist/chunks/chunk-DT7M4VQS.ts +69 -0
- package/dist/chunks/chunk-DT7M4VQS.ts.map +7 -0
- package/dist/chunks/chunk-ED3TYIVW.ts +229 -0
- package/dist/chunks/chunk-ED3TYIVW.ts.map +7 -0
- package/dist/chunks/{chunk-VHXBCEVF.ts → chunk-EUMINX7L.ts} +6 -6
- package/dist/chunks/chunk-FQOSHGJ7.ts +215 -0
- package/dist/chunks/chunk-FQOSHGJ7.ts.map +7 -0
- package/dist/chunks/chunk-GNH73BJ5.ts +153 -0
- package/dist/chunks/chunk-GNH73BJ5.ts.map +7 -0
- package/dist/chunks/{chunk-UQ3ALBIB.ts → chunk-GP5DU62N.ts} +8 -9
- package/dist/chunks/chunk-GP5DU62N.ts.map +7 -0
- package/dist/chunks/{chunk-5RZPINUQ.ts → chunk-GSNPASKB.ts} +2 -2
- package/dist/chunks/{chunk-QE7NZZWT.ts → chunk-IWZH5KYS.ts} +23 -7
- package/dist/chunks/chunk-IWZH5KYS.ts.map +7 -0
- package/dist/chunks/{chunk-5ZWQ33XL.ts → chunk-JJTARUSO.ts} +15 -14
- package/dist/chunks/chunk-JJTARUSO.ts.map +7 -0
- package/dist/chunks/{chunk-2DI6PDZV.ts → chunk-NLWS55B6.ts} +15 -2
- package/dist/chunks/chunk-NLWS55B6.ts.map +7 -0
- package/dist/chunks/{chunk-SDZCTCYQ.ts → chunk-OEWQVWLC.ts} +4 -4
- package/dist/chunks/{chunk-MYPSMJHJ.ts → chunk-OTZRCKYP.ts} +380 -510
- package/dist/chunks/chunk-OTZRCKYP.ts.map +7 -0
- package/dist/chunks/{chunk-WFKUKVCP.ts → chunk-OWBAZA6J.ts} +24 -12
- package/dist/chunks/chunk-OWBAZA6J.ts.map +7 -0
- package/dist/chunks/chunk-PRUEIIK7.ts +24 -0
- package/dist/chunks/chunk-PRUEIIK7.ts.map +7 -0
- package/dist/chunks/chunk-QGK445K7.ts +21 -0
- package/dist/chunks/chunk-QGK445K7.ts.map +7 -0
- package/dist/chunks/{chunk-3B4Z2SKV.ts → chunk-QO333NM4.ts} +49 -21
- package/dist/chunks/chunk-QO333NM4.ts.map +7 -0
- package/dist/chunks/{chunk-4SBTAVFL.ts → chunk-QSHMOL3I.ts} +22 -24
- package/dist/chunks/chunk-QSHMOL3I.ts.map +7 -0
- package/dist/chunks/chunk-S2FMH5FT.ts +97 -0
- package/dist/chunks/chunk-S2FMH5FT.ts.map +7 -0
- package/dist/chunks/{chunk-BVVP33U5.ts → chunk-SWRVXVFD.ts} +12 -9
- package/dist/chunks/chunk-SWRVXVFD.ts.map +7 -0
- package/dist/chunks/{chunk-ZX2Q6THB.ts → chunk-UBV24CG4.ts} +4 -5
- package/dist/chunks/chunk-UBV24CG4.ts.map +7 -0
- package/dist/chunks/chunk-UOHR7LRO.ts +70 -0
- package/dist/chunks/chunk-UOHR7LRO.ts.map +7 -0
- package/dist/chunks/{chunk-4QVPTIZB.ts → chunk-UPNVIUBN.ts} +3 -3
- package/dist/chunks/chunk-UZPMDLTL.ts +54 -0
- package/dist/chunks/chunk-UZPMDLTL.ts.map +7 -0
- package/dist/chunks/chunk-VJN7KU4J.ts +350 -0
- package/dist/chunks/chunk-VJN7KU4J.ts.map +7 -0
- package/dist/chunks/chunk-YHT4YTYG.ts +31 -0
- package/dist/chunks/chunk-YHT4YTYG.ts.map +7 -0
- package/dist/chunks/{chunk-V6VZ5TLG.ts → chunk-YK52VDXG.ts} +6 -5
- package/dist/chunks/chunk-YK52VDXG.ts.map +7 -0
- package/dist/chunks/config-KGIYTTXQ.ts +37 -0
- package/dist/chunks/{conflict-artifacts-BB2FZMIE.ts → conflict-artifacts-FXFIIBCB.ts} +8 -7
- package/dist/chunks/{conflict-review-4PLVLMRO.ts → conflict-review-6WIEQLXW.ts} +31 -24
- package/dist/chunks/{conflict-review-4PLVLMRO.ts.map → conflict-review-6WIEQLXW.ts.map} +1 -1
- package/dist/chunks/continuous-record-LFRVXQBD.ts +30 -0
- package/dist/chunks/continuous-sync-AWAAOXK2.ts +545 -0
- package/dist/chunks/continuous-sync-AWAAOXK2.ts.map +7 -0
- package/dist/chunks/{file-selection-WZX7HAY2.ts → file-selection-3JJYOVNR.ts} +5 -5
- package/dist/chunks/{git-backend-FD7AVSK6.ts → git-backend-KYDZ673X.ts} +28 -32
- package/dist/chunks/git-backend-KYDZ673X.ts.map +7 -0
- package/dist/chunks/{manager-result-dispatcher-6MQKFCWB.ts → manager-result-dispatcher-AGBBVDDS.ts} +10 -9
- package/dist/chunks/{manager-ui-IURS4WXY.ts → manager-ui-KRAW3NE2.ts} +464 -164
- package/dist/chunks/manager-ui-KRAW3NE2.ts.map +7 -0
- package/dist/chunks/{merge-baseline-store-EVQAAML6.ts → merge-baseline-store-RFCAH7KL.ts} +7 -5
- package/dist/chunks/{remote-selection-ui-6LJJ4IMQ.ts → remote-selection-ui-5725JDNC.ts} +9 -8
- package/dist/chunks/remote-selection-ui-5725JDNC.ts.map +7 -0
- package/dist/chunks/{s3-backend-AQGV6LFT.ts → s3-backend-Y7WSCLJI.ts} +38 -38
- package/dist/chunks/s3-backend-Y7WSCLJI.ts.map +7 -0
- package/dist/chunks/{setup-switch-KTUWL6DE.ts → setup-switch-E7PHKI6J.ts} +7 -6
- package/dist/chunks/setup-switch-E7PHKI6J.ts.map +7 -0
- package/dist/chunks/{setup-wizard-GSDPDFC4.ts → setup-wizard-65DROCIJ.ts} +14 -13
- package/dist/chunks/setup-wizard-65DROCIJ.ts.map +7 -0
- package/dist/chunks/{sync-inspection-K45QVLHB.ts → sync-inspection-XFJOAMJD.ts} +9 -8
- package/dist/chunks/sync-inspection-XFJOAMJD.ts.map +7 -0
- package/dist/chunks/{sync-operations-47OUX76C.ts → sync-operations-HDUDT5PD.ts} +188 -73
- package/dist/chunks/sync-operations-HDUDT5PD.ts.map +7 -0
- package/dist/chunks/{webdav-backend-TBILVEQK.ts → webdav-backend-6K26F6LT.ts} +39 -48
- package/dist/chunks/webdav-backend-6K26F6LT.ts.map +7 -0
- package/dist/index.ts +246 -131
- package/dist/index.ts.map +3 -3
- package/docs/agent-mode-verification.md +99 -0
- package/docs/continuous-sync-verification.md +87 -0
- package/docs/settings.md +131 -11
- package/docs/tui-contract.md +61 -0
- package/package.json +3 -2
- package/src/backends/git/git-backend.ts +1 -4
- package/src/backends/git/git-runner.ts +1 -5
- package/src/backends/s3/s3-backend.ts +3 -25
- package/src/backends/s3/s3-client.ts +14 -2
- package/src/backends/snapshot-pointer.ts +27 -0
- package/src/backends/webdav/webdav-backend.ts +3 -29
- package/src/backends/webdav/webdav-client.ts +11 -7
- package/src/commands/command-execution.ts +7 -3
- package/src/commands/command-handler.ts +15 -3
- package/src/commands/command-types.ts +10 -0
- package/src/commands/command.ts +4 -1
- package/src/settings/config.ts +20 -10
- package/src/settings/settings-store.ts +1 -34
- package/src/settings/settings-types.ts +10 -0
- package/src/settings/settings-validation.ts +4 -1
- package/src/snapshot/snapshot-codec.ts +1 -5
- package/src/state/lock.ts +13 -10
- package/src/state/merge-baseline-store.ts +19 -1
- package/src/state/state-directory.ts +8 -1
- package/src/sync/agent-candidate.ts +251 -0
- package/src/sync/agent-plan.ts +87 -0
- package/src/sync/agent-policy.ts +13 -0
- package/src/sync/agent-request.ts +17 -0
- package/src/sync/agent-runner.ts +188 -0
- package/src/sync/agent-store.ts +151 -0
- package/src/sync/agent-sync.ts +91 -0
- package/src/sync/apply-review.ts +63 -0
- package/src/sync/automatic-sync.ts +46 -33
- package/src/sync/continuous-errors.ts +85 -0
- package/src/sync/continuous-lease.ts +221 -0
- package/src/sync/continuous-record.ts +179 -0
- package/src/sync/continuous-storage.ts +55 -0
- package/src/sync/continuous-sync.ts +516 -0
- package/src/sync/continuous-watch.ts +59 -0
- package/src/sync/merged-sync.ts +268 -112
- package/src/sync/remote-snapshot.ts +2 -3
- package/src/sync/settings-merge.ts +19 -3
- package/src/sync/setup-switch.ts +1 -5
- package/src/sync/startup-check.ts +51 -29
- package/src/sync/startup-timings.ts +90 -0
- package/src/sync/sync-inspection.ts +16 -5
- package/src/sync/sync-mutations.ts +55 -28
- package/src/sync/sync-queries.ts +19 -3
- package/src/sync/text-merge.ts +3 -3
- package/src/sync-extension.ts +75 -19
- package/src/ui/agent-mode-setting.ts +91 -0
- package/src/ui/manager-state.ts +12 -6
- package/src/ui/manager-ui.ts +57 -7
- package/src/ui/merge-review.ts +3 -3
- package/src/ui/merged-sync-summary.ts +2 -18
- package/src/ui/remote-selection-ui.ts +5 -5
- package/src/ui/settings-frame.ts +55 -0
- package/src/ui/settings-ui.ts +223 -43
- package/src/ui/setup/s3-credentials-ui.ts +1 -5
- package/src/ui/setup/setup-name-ui.ts +2 -5
- package/src/ui/setup/setup-prompts.ts +4 -3
- package/src/ui/setup/setup-switcher.ts +2 -0
- package/src/ui/sync-behavior.ts +15 -0
- package/src/ui/sync-format.ts +69 -38
- package/src/ui/sync-setups-ui.ts +3 -5
- package/src/ui/transfer-changes.ts +33 -0
- package/src/ui/transfer-review.ts +27 -0
- package/dist/chunks/chunk-2DI6PDZV.ts.map +0 -7
- package/dist/chunks/chunk-3B4Z2SKV.ts.map +0 -7
- package/dist/chunks/chunk-4SBTAVFL.ts.map +0 -7
- package/dist/chunks/chunk-5ZWQ33XL.ts.map +0 -7
- package/dist/chunks/chunk-BVVP33U5.ts.map +0 -7
- package/dist/chunks/chunk-IJ6VV6FN.ts.map +0 -7
- package/dist/chunks/chunk-MYPSMJHJ.ts.map +0 -7
- package/dist/chunks/chunk-ORQRHEEO.ts +0 -362
- package/dist/chunks/chunk-ORQRHEEO.ts.map +0 -7
- package/dist/chunks/chunk-QE7NZZWT.ts.map +0 -7
- package/dist/chunks/chunk-UQ3ALBIB.ts.map +0 -7
- package/dist/chunks/chunk-V6VZ5TLG.ts.map +0 -7
- package/dist/chunks/chunk-WFKUKVCP.ts.map +0 -7
- package/dist/chunks/chunk-ZX2Q6THB.ts.map +0 -7
- package/dist/chunks/git-backend-FD7AVSK6.ts.map +0 -7
- package/dist/chunks/manager-ui-IURS4WXY.ts.map +0 -7
- package/dist/chunks/s3-backend-AQGV6LFT.ts.map +0 -7
- package/dist/chunks/setup-wizard-GSDPDFC4.ts.map +0 -7
- package/dist/chunks/sync-operations-47OUX76C.ts.map +0 -7
- package/dist/chunks/webdav-backend-TBILVEQK.ts.map +0 -7
- /package/dist/chunks/{conflict-artifacts-BB2FZMIE.ts.map → agent-candidate-3JN6DJKT.ts.map} +0 -0
- /package/dist/chunks/{chunk-CHSIAC4N.ts.map → chunk-B4QZBDBT.ts.map} +0 -0
- /package/dist/chunks/{chunk-CL7BZRRQ.ts.map → chunk-CRKTAPSL.ts.map} +0 -0
- /package/dist/chunks/{chunk-VHXBCEVF.ts.map → chunk-EUMINX7L.ts.map} +0 -0
- /package/dist/chunks/{chunk-5RZPINUQ.ts.map → chunk-GSNPASKB.ts.map} +0 -0
- /package/dist/chunks/{chunk-SDZCTCYQ.ts.map → chunk-OEWQVWLC.ts.map} +0 -0
- /package/dist/chunks/{chunk-4QVPTIZB.ts.map → chunk-UPNVIUBN.ts.map} +0 -0
- /package/dist/chunks/{manager-result-dispatcher-6MQKFCWB.ts.map → config-KGIYTTXQ.ts.map} +0 -0
- /package/dist/chunks/{merge-baseline-store-EVQAAML6.ts.map → conflict-artifacts-FXFIIBCB.ts.map} +0 -0
- /package/dist/chunks/{remote-selection-ui-6LJJ4IMQ.ts.map → continuous-record-LFRVXQBD.ts.map} +0 -0
- /package/dist/chunks/{file-selection-WZX7HAY2.ts.map → file-selection-3JJYOVNR.ts.map} +0 -0
- /package/dist/chunks/{setup-switch-KTUWL6DE.ts.map → manager-result-dispatcher-AGBBVDDS.ts.map} +0 -0
- /package/dist/chunks/{sync-inspection-K45QVLHB.ts.map → merge-baseline-store-RFCAH7KL.ts.map} +0 -0
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
import {
|
|
4
4
|
runCancellableOperation,
|
|
5
5
|
showRemoteSelectionReview
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-JJTARUSO.ts";
|
|
7
7
|
import {
|
|
8
8
|
safeTerminalText
|
|
9
9
|
} from "./chunk-AMJRUXFT.ts";
|
|
10
|
-
import {
|
|
11
|
-
loadConfig,
|
|
12
|
-
syncConfigReviewIdentity
|
|
13
|
-
} from "./chunk-3B4Z2SKV.ts";
|
|
14
10
|
import {
|
|
15
11
|
errorMessage
|
|
16
12
|
} from "./chunk-7D5SF7P3.ts";
|
|
13
|
+
import {
|
|
14
|
+
loadConfig,
|
|
15
|
+
syncConfigReviewIdentity
|
|
16
|
+
} from "./chunk-QO333NM4.ts";
|
|
17
17
|
|
|
18
18
|
// src/ui/sync-resolution-ui.ts
|
|
19
19
|
import { defineMenu, runMenu } from "@narumitw/pi-tui-kit";
|
|
@@ -206,4 +206,4 @@ async function dispatchManagerResult(ctx, initialResult, origin, runRoute, signa
|
|
|
206
206
|
export {
|
|
207
207
|
dispatchManagerResult
|
|
208
208
|
};
|
|
209
|
-
//# sourceMappingURL=chunk-
|
|
209
|
+
//# sourceMappingURL=chunk-EUMINX7L.ts.map
|
|
@@ -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": "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",
|
|
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": ";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,kBAAkB;AAC3B,OAAO,QAAQ;AACf,OAAO,UAAU;AAmCV,SAAS,qBAAqB,QAAuB,iBAAyB;AACnF,SAAO,KAAK,UAAU,CAAC,OAAO,WAAW,iBAAiB,CAAC,GAAG,OAAO,OAAO,EAAE,KAAK,GAAG,OAAO,eAAe,IAAI,CAAC;AACnH;AAEO,SAAS,iBAAiB,QAAuB;AACtD,SAAO,GAAG,mBAAmB,MAAM,CAAC;AACtC;AAEA,eAAsB,iBAAiB,QAA0D;AAC/F,MAAI;AACJ,MAAI;AACF,UAAM,OAAO,MAAM,GAAG,MAAM,iBAAiB,MAAM,CAAC;AACpD,QAAI,CAAC,KAAK,OAAO,KAAK,KAAK,OAAO,MAAM,OAAO,KAAM,OAAM,IAAI,MAAM,8BAA8B;AACnG,cAAU,MAAM,iBAA+B,iBAAiB,MAAM,CAAC;AAAA,EACzE,SAAS,OAAO;AACd,QAAK,MAAgC,SAAS,SAAU;AAExD,UAAM,IAAI,MAAM,yEAAyE;AAAA,EAC3F;AACA,MAAI,CAAC,QAAS;AACd,MACE,QAAQ,YAAY,KACpB,OAAO,QAAQ,aAAa,YAC5B,OAAO,QAAQ,WAAW,YAC1B,OAAO,QAAQ,kBAAkB,YAChC,QAAQ,cAAc,WACpB,QAAQ,cAAc,QACrB,CAAC,QAAQ,iBACT,QAAQ,cAAc,aAAa,QAAQ,cAAc,YACzD,QAAQ,cAAc,eAAe,QAAQ,cAAc,cAC3D,QAAQ,QAAQ,OAAO,QAAQ,cAAc,eAChD,QAAQ,gBAAgB,WACtB,OAAO,QAAQ,gBAAgB,YAC9B,CAAC,KAAK,WAAW,QAAQ,WAAW,KACpC,KAAK,QAAQ,QAAQ,WAAW,MAAM,QAAQ,gBACjD,QAAQ,aAAa,WACnB,CAAC,MAAM,QAAQ,QAAQ,QAAQ,KAC9B,QAAQ,SAAS,SAAS,MAC1B,QAAQ,SAAS,KAAK,CAAC,SAAS,OAAO,SAAS,QAAQ,MAC5D,CAAC,QAAQ,gBACT,OAAO,QAAQ,aAAa,aAAa,YACzC,CAAC,QAAQ,UACT,CAAC,QAAQ,SACT,CAAC,QAAQ,UACT,CAAC,MAAM,QAAQ,QAAQ,OAAO,KAAK,KACnC,CAAC,MAAM,QAAQ,QAAQ,MAAM,KAAK,KAClC,CAAC,MAAM,QAAQ,QAAQ,OAAO,KAAK;AAEnC,UAAM,IAAI,MAAM,uFAAuF;AAEzG,MAAI;AACF,UAAM,cAAc,IAAI,IAAI,QAAQ,UAAU,OAAO,QAAQ,CAAC,UAAU,MAAM,KAAK,KAAK,CAAC,CAAC;AAC1F,eAAW,YAAY;AAAA,MACrB,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,GAAI,QAAQ,WAAW,CAAC,QAAQ,QAAQ,IAAI,CAAC;AAAA,IAC/C,GAAG;AACD,kCAA4B,QAAQ;AACpC,UACG,SAAS,YAAY,KAAK,SAAS,YAAY,KAAK,SAAS,YAAY,KAC1E,OAAO,SAAS,OAAO,YACvB,OAAO,SAAS,YAAY,YAC5B,SAAS,MAAM,SAAS,SACxB,SAAS,MAAM;AAAA,QACb,CAAC,OAAO,SACN,SAAS,OAAO,MAAM,kBAAkB,WAAW,KAAK,cAAc,SAAS,OAAO;AAAA,QACxF;AAAA,MACF,IACE,KAAK,OAAO;AAEd,cAAM,IAAI,MAAM,wBAAwB;AAC1C,YAAM,OAAO,cAAc,EAAE,UAAU,CAAC,GAAG,OAAO,SAAS,OAAO,QAAQ,CAAC,GAAG,qBAAqB,KAAK,CAAC;AACzG,UACE,KAAK,SAAS,aACd,KAAK,UAAU,KAAK,CAAC,aAAa,SAAS,WAAW,oBAAoB,CAAC,YAAY,IAAI,SAAS,IAAI,CAAC;AAEzG,cAAM,IAAI,MAAM,kCAAkC;AAAA,IACtD;AACA,6BAAyB,QAAQ,MAAM;AACvC,SAAK,QAAQ,OAAO,YAAY,KAAK,QAAQ,OAAO,YAAY,MAAM,CAAC,QAAQ;AAC7E,YAAM,IAAI,MAAM,kEAAkE;AACpF,QAAI,QAAQ,SAAU,0BAAyB,QAAQ,QAAQ;AAAA,EACjE,QAAQ;AACN,UAAM,IAAI,MAAM,gFAAgF;AAAA,EAClG;AAGA,MAAI;AACF,QACE,QAAQ,aACP,CAAC,gBAAgB,QAAQ,SAAS,aAAa,QAAQ,OAAO,WAAW,KACxE,CAAC;AAAA,MACC,YAAY,iBAAiB,QAAQ,OAAO,QAAQ,OAAO,WAAW,CAAC;AAAA,MACvE,YAAY,iBAAiB,QAAQ,UAAU,QAAQ,OAAO,WAAW,CAAC;AAAA,IAC5E;AAEF,YAAM,IAAI,MAAM,sBAAsB;AAAA,EAC1C,QAAQ;AACN,UAAM,IAAI,MAAM,+DAA+D;AAAA,EACjF;AACA,MAAI,QAAQ,UAAU;AACpB,UAAM,QAAQ,oBAAI,IAAI;AAAA,MACpB,GAAG,OAAO,KAAK,QAAQ,SAAS,UAAU,kBAAkB,CAAC,CAAC;AAAA,MAC9D,GAAG,CAAC,GAAG,QAAQ,OAAO,OAAO,GAAG,QAAQ,MAAM,OAAO,GAAG,QAAQ,OAAO,KAAK,EAAE,IAAI,CAAC,SAAS,KAAK,IAAI;AAAA,IACvG,CAAC;AACD,UAAM,WAAW,QAAQ;AACzB,QACE,CAAC,OAAO,eACR,CAAC,SAAS,YACV,qBAAqB,SAAS,QAAQ,MAAM,QAAQ,iBACpD,CAAC,MAAM,QAAQ,SAAS,MAAM,KAC9B,SAAS,OAAO,SAAS,SACzB,SAAS,OAAO;AAAA,MACd,CAAC,UACC,CAAC,MAAM,QAAQ,MAAM,KAAK,KAC1B,CAAC,MAAM,MAAM,UACb,MAAM,MAAM,SAAS,SACrB,MAAM,MAAM,KAAK,CAAC,aAAa,CAAC,MAAM,IAAI,QAAQ,CAAC,KACnD,CAAC,mBAAmB,KAAK,MAAM,QAAQ;AAAA,IAC3C;AAEA,YAAM,IAAI,MAAM,iEAAiE;AAAA,EACrF;AACA,MAAI,QAAQ,UAAU;AACpB,UAAM,SAAS,YAAY,QAAQ,MAAM;AACzC,UAAM,QAAQ,YAAY,QAAQ,KAAK;AACvC,UAAM,EAAE,qBAAqB,IAAI,MAAM,OAAO,kCAAyB;AACvE,UAAM,kBAAmB,KAAK,MAAM,QAAQ,QAAQ,EAAgB,CAAC;AACrE,QAAI,OAAO,oBAAoB,SAAU,OAAM,IAAI,MAAM,mCAAmC;AAC5F,UAAM,aAAa,oBAAI,IAA4C;AACnE,eAAW,SAAS,QAAQ,SAAS,QAAQ;AAC3C,YAAM,SAAS,WAAW,IAAI,MAAM,QAAQ,KAAK,CAAC;AAClD,aAAO,KAAK,KAAK;AACjB,iBAAW,IAAI,MAAM,UAAU,MAAM;AAAA,IACvC;AACA,eAAW,CAAC,OAAO,MAAM,KAAK,YAAY;AACxC,YAAM,WAAW,MAAM,qBAAqB,QAAQ,iBAAiB,KAAK;AAC1E,YAAM,iBAAiB,IAAI,IAAI,SAAS,OAAO,IAAI,CAAC,UAAU,KAAK,UAAU,MAAM,KAAK,CAAC,CAAC;AAC1F,YAAM,SAAS,YAAY,SAAS,MAAM;AAC1C,YAAM,SAAS,YAAY,QAAQ,MAAM;AACzC,iBAAW,SAAS,QAAQ;AAC1B,YAAI,CAAC,eAAe,IAAI,KAAK,UAAU,MAAM,KAAK,CAAC;AACjD,gBAAM,IAAI,MAAM,0DAA0D;AAC5E,mBAAW,YAAY,MAAM;AAC3B,cACE,OAAO,QAAQ,MAAM,MAAM,QAAQ,KACnC,OAAO,QAAQ,MAAM,OAAO,QAAQ,KACpC,QAAQ,SAAS,SAAS,eAAe,QAAQ,MAAM,SAAS,MAAM,eAAe,QAAQ;AAE7F,kBAAM,IAAI,MAAM,yDAAyD;AAAA,MAC/E;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,kBAAkB,QAAuB,SAAuB;AACpF,QAAM,UAAU,iBAAiB,MAAM,GAAG,SAAS,EAAE,UAAU,MAAM,OAAO,KAAK,CAAC;AACpF;AAEA,eAAsB,kBAAkB,QAAuB;AAC7D,QAAM,GAAG,GAAG,iBAAiB,MAAM,GAAG,EAAE,OAAO,KAAK,CAAC;AACrD,QAAM,cAAc,KAAK,QAAQ,iBAAiB,MAAM,CAAC,CAAC;AAC5D;AAEA,eAAsB,mBAAmB,QAAuB;AAC9D,MAAI,CAAE,MAAM,iBAAiB,MAAM,EAAI;AACvC,QAAM,OAAO,iBAAiB,MAAM;AACpC,QAAM,GAAG,OAAO,MAAM,GAAG,IAAI,IAAI,WAAW,CAAC,WAAW;AACxD,QAAM,cAAc,KAAK,QAAQ,IAAI,CAAC;AACxC;AAEA,eAAsB,sBAAsB,QAAuB,UAA+B,CAAC,GAAG;AACpG,QAAM,UAAU,MAAM,iBAAiB,MAAM;AAC7C,MAAI,QAAQ,SAAS,CAAC,QAAS;AAC/B,MAAI,SAAS;AACX,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;",
|
|
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-
|
|
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-
|
|
19
|
+
} from "./chunk-QO333NM4.ts";
|
|
17
20
|
import {
|
|
18
|
-
|
|
19
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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));
|
|
@@ -333,6 +333,18 @@ async function readMergeAncestors(config, state) {
|
|
|
333
333
|
}
|
|
334
334
|
return record.files;
|
|
335
335
|
}
|
|
336
|
+
async function hasCurrentMergeBaseline(config, snapshot, state) {
|
|
337
|
+
try {
|
|
338
|
+
const ancestors = await readMergeAncestors(config, state);
|
|
339
|
+
if (!ancestors) return false;
|
|
340
|
+
const hashes = new Map(ancestors.map((file) => [file.path, file.sha256]));
|
|
341
|
+
return snapshot.files.every(
|
|
342
|
+
(file) => !(file.path === "settings.json" || config.mergeContent || config.agentMode && isMergeTextPath(file.path)) || !eligible(file) || hashes.get(file.path) === file.sha256
|
|
343
|
+
);
|
|
344
|
+
} catch {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
336
348
|
async function readMergeAncestor(config, state, filePath) {
|
|
337
349
|
const file = (await readMergeAncestors(config, state))?.find((file2) => file2.path === filePath);
|
|
338
350
|
return file ? Buffer.from(file.contentBase64, "base64") : void 0;
|
|
@@ -370,11 +382,15 @@ async function pruneMergeBaselines(config, state, validate) {
|
|
|
370
382
|
export {
|
|
371
383
|
TEXT_MERGE_WORK_LIMIT,
|
|
372
384
|
isMergeTextPath,
|
|
385
|
+
text,
|
|
386
|
+
lines,
|
|
387
|
+
edits,
|
|
373
388
|
mergeText,
|
|
374
389
|
mergeSession,
|
|
375
390
|
stageMergeBaseline,
|
|
376
391
|
readMergeAncestors,
|
|
392
|
+
hasCurrentMergeBaseline,
|
|
377
393
|
readMergeAncestor,
|
|
378
394
|
pruneMergeBaselines
|
|
379
395
|
};
|
|
380
|
-
//# sourceMappingURL=chunk-
|
|
396
|
+
//# sourceMappingURL=chunk-IWZH5KYS.ts.map
|