@narumitw/pi-sync 0.56.0 → 0.57.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -9
- package/dist/chunks/{agent-candidate-3JN6DJKT.ts → agent-candidate-GNEYR225.ts} +3 -3
- package/dist/chunks/{agent-plan-QCUJWBP4.ts → agent-plan-K3CRYVXV.ts} +3 -3
- package/dist/chunks/{chunk-FQOSHGJ7.ts → chunk-5QAE3G3K.ts} +11 -6
- package/dist/chunks/chunk-5QAE3G3K.ts.map +7 -0
- package/dist/chunks/{chunk-S2FMH5FT.ts → chunk-QOC47PBC.ts} +2 -2
- package/dist/chunks/{chunk-5Q5SSKNG.ts → chunk-S7QS4AWK.ts} +89 -2
- package/dist/chunks/chunk-S7QS4AWK.ts.map +7 -0
- package/dist/chunks/{chunk-OTZRCKYP.ts → chunk-T3NC77TU.ts} +29 -16
- package/dist/chunks/chunk-T3NC77TU.ts.map +7 -0
- package/dist/chunks/{conflict-review-6WIEQLXW.ts → conflict-review-ZCGWFKEK.ts} +3 -3
- package/dist/chunks/{continuous-sync-AWAAOXK2.ts → continuous-sync-LAIMSW7D.ts} +2 -2
- package/dist/chunks/{sync-operations-HDUDT5PD.ts → sync-operations-W5HFFMTP.ts} +3 -3
- package/dist/index.ts +4 -4
- package/docs/settings.md +24 -322
- package/package.json +12 -5
- package/skills/configure-pi-sync/SKILL.md +54 -0
- package/skills/configure-pi-sync/references/configuration.md +650 -0
- package/src/sync/agent-candidate.ts +8 -3
- package/src/sync/merged-sync.ts +1 -1
- package/src/sync/package-merge.ts +87 -0
- package/src/sync/settings-conflicts.ts +25 -13
- package/src/sync/settings-merge.ts +48 -0
- package/dist/chunks/chunk-5Q5SSKNG.ts.map +0 -7
- package/dist/chunks/chunk-FQOSHGJ7.ts.map +0 -7
- package/dist/chunks/chunk-OTZRCKYP.ts.map +0 -7
- /package/dist/chunks/{agent-candidate-3JN6DJKT.ts.map → agent-candidate-GNEYR225.ts.map} +0 -0
- /package/dist/chunks/{agent-plan-QCUJWBP4.ts.map → agent-plan-K3CRYVXV.ts.map} +0 -0
- /package/dist/chunks/{chunk-S2FMH5FT.ts.map → chunk-QOC47PBC.ts.map} +0 -0
- /package/dist/chunks/{conflict-review-6WIEQLXW.ts.map → conflict-review-ZCGWFKEK.ts.map} +0 -0
- /package/dist/chunks/{continuous-sync-AWAAOXK2.ts.map → continuous-sync-LAIMSW7D.ts.map} +0 -0
- /package/dist/chunks/{sync-operations-HDUDT5PD.ts.map → sync-operations-W5HFFMTP.ts.map} +0 -0
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ Reusable storage connections hold credentials, while named sync setups define ex
|
|
|
15
15
|
- Writes settings atomically, preserves unknown fields, rejects stale edits, and fails closed on unsafe configuration.
|
|
16
16
|
- Offers default-off experimental Agent mode for autonomous, validated text/settings conflict resolution, with a separate Continuous sync opt-in for monitoring and recovery.
|
|
17
17
|
- Loads a generated split runtime with lazy UI and backend chunks.
|
|
18
|
+
- Bundles a manual-only skill for configuration questions and safe setup guidance.
|
|
18
19
|
|
|
19
20
|
## 📦 Install
|
|
20
21
|
|
|
@@ -137,7 +138,7 @@ Choose **Review differences (recommended)** in an ordinary file-direction confli
|
|
|
137
138
|
Then choose one reviewed direction:
|
|
138
139
|
|
|
139
140
|
- **Keep local content and replace remote…** uses the existing forced-push path.
|
|
140
|
-
It applies the [secret-scan setting](./
|
|
141
|
+
It applies the [secret-scan setting](./skills/configure-pi-sync/references/configuration.md#secret-scanning), shows the exact remote publication effect, re-reads a changed remote head, and asks again if the reviewed plan changed.
|
|
141
142
|
- **Use remote content and replace local…** uses the existing forced-pull path.
|
|
142
143
|
It shows exact local writes and deletions, protects the live session, and creates a local backup before applying.
|
|
143
144
|
- First sync uses **Use local as initial source…** and **Use remote as initial source…** labels.
|
|
@@ -167,7 +168,7 @@ Missing settings stay unconfigured without creating files or locks.
|
|
|
167
168
|
|
|
168
169
|
Set the top-level `"debug": true` in `pi-sync.json`, or enable **Settings → Advanced → Debug startup timings**, then restart Pi or use `/reload`.
|
|
169
170
|
The default is `false`. Timing reports appear in TUI/RPC notifications, not the model context or a log file.
|
|
170
|
-
See [Debug startup timings](./
|
|
171
|
+
See [Debug startup timings](./skills/configure-pi-sync/references/configuration.md#debug-startup-timings) for measured stages and limits.
|
|
171
172
|
|
|
172
173
|
### Background startup checks
|
|
173
174
|
|
|
@@ -185,7 +186,7 @@ Shutdown behavior is unchanged: when **Automatic sync** is On and sessions are i
|
|
|
185
186
|
|
|
186
187
|
**Warning:** eligible conflict text is sent to the selected Pi model and can incur costs. Accepted results can replace/delete selected local and remote content without another transfer prompt. The resolver has no tools or parent conversation, and validates candidate ownership and current versions before using the existing publication/backup/journal flow. Validation is not a guarantee of semantic correctness or lossless editing against other local processes.
|
|
187
188
|
|
|
188
|
-
The first implementation resolves bounded overlapping `AGENTS.md` and prompt/skill Markdown/plain-text edits, and selects complete atomic global settings units. Verified ancestors are required. Code, session histories, deletion conflicts and path collisions are not model-resolved; unsupported/refused work pauses, unless existing partial-sync policy permits independent progress. No resources reload automatically. Disabling stops subsequent authorization, not completed writes or provider charges. Read [Agent mode settings, limits, privacy and recovery](./
|
|
189
|
+
The first implementation resolves bounded overlapping `AGENTS.md` and prompt/skill Markdown/plain-text edits, and selects complete atomic global settings units. For `settings.json` packages, it keeps the higher explicit npm SemVer version from local and remote without a registry lookup; incompatible filters and non-comparable sources retain existing conflict handling. Verified ancestors are required. Code, session histories, deletion conflicts and path collisions are not model-resolved; unsupported/refused work pauses, unless existing partial-sync policy permits independent progress. No resources reload automatically. Disabling stops subsequent authorization, not completed writes or provider charges. Read [Agent mode settings, limits, privacy and recovery](./skills/configure-pi-sync/references/configuration.md#experimental-agent-mode) before enabling.
|
|
189
190
|
|
|
190
191
|
### Experimental Continuous sync
|
|
191
192
|
|
|
@@ -193,7 +194,7 @@ The first implementation resolves bounded overlapping `AGENTS.md` and prompt/ski
|
|
|
193
194
|
|
|
194
195
|
Selected-file hints are coalesced after one quiet second. A 60–66 second remote/local check covers missed events. Temporary failures use bounded backoff. One local session owns monitoring for each shared agent directory; each computer remains a separate replica. Busy sessions and foreground commands release ownership after draining work. Nothing runs after Pi exits, and resources never reload automatically.
|
|
195
196
|
|
|
196
|
-
**Warning:** local changes and retries can cause extra network requests between polls. Automatic model requests are limited to two per candidate and two per replica binding in a rolling 15-minute window, across restarts. These are request limits, not a currency cap. Safety blocks still require changed evidence or repair; uncertain provider/Git errors are not guessed to be retryable. See [Continuous sync consent, budgets and recovery](./
|
|
197
|
+
**Warning:** local changes and retries can cause extra network requests between polls. Automatic model requests are limited to two per candidate and two per replica binding in a rolling 15-minute window, across restarts. These are request limits, not a currency cap. Safety blocks still require changed evidence or repair; uncertain provider/Git errors are not guessed to be retryable. See [Continuous sync consent, budgets and recovery](./skills/configure-pi-sync/references/configuration.md#experimental-continuous-sync).
|
|
197
198
|
|
|
198
199
|
### Opt-in automatic transfer
|
|
199
200
|
|
|
@@ -201,13 +202,13 @@ Selected-file hints are coalesced after one quiet second. A 60–66 second remot
|
|
|
201
202
|
|
|
202
203
|
A baseline and matching authoritative content list are required. First-source choice, changed selection, missing established remote, secrets, and real conflicts remain review barriers. Automatic transfer requires conditional or lease-protected publication: Git, verified WebDAV, and R2/S3 with verified conditional writes are supported. The existing **Automatic sync** setting and shutdown policy are unchanged when this new opt-in is absent.
|
|
203
204
|
|
|
204
|
-
Unchanged automatic transfers use a read-only fast path after a fresh head read and verified baseline/cache checks, avoiding snapshot downloads and baseline rewrites. Recovery, partial-sync, legacy, and uncertain cases keep the full path. See [unchanged transfer conditions](./
|
|
205
|
+
Unchanged automatic transfers use a read-only fast path after a fresh head read and verified baseline/cache checks, avoiding snapshot downloads and baseline rewrites. Recovery, partial-sync, legacy, and uncertain cases keep the full path. See [unchanged transfer conditions](./skills/configure-pi-sync/references/configuration.md#unchanged-transfer-fast-path).
|
|
205
206
|
|
|
206
207
|
Successful transfers show one summary. They do not reload resources, activate extension code, or rewrite model context. After local resources change, reload or restart when ready; newly pulled conversations still require explicit resume. Headless print/JSON skip this new startup feature. Reviewed directional settings/session-root transitions record both roots and verified settings evidence so interrupted apply can recover before or after settings installation. Older pending journals without transition evidence retain their original root-ownership checks and may require reviewed recovery; preserve their backups rather than downgrading. See [merge safety and recovery](./docs/merge-implementation-audit.md) for journal boundaries, external-writer limits, and downgrade guidance.
|
|
207
208
|
|
|
208
209
|
**Settings → Advanced → Merge settings fields** (experimental) controls per-setup `sync.mergeSettings`, default **Off**. It combines independent global `settings.json` fields, not arbitrary JSON or keybindings. Missing/corrupt ancestors, unsupported syntax, and incompatible fields retain whole-transfer review. Sensitive accepted content is cached privately under the denied state root; disabling does not delete evidence.
|
|
209
210
|
|
|
210
|
-
**Settings → Sync content → Machine-local fields** edits explicit `sync.localFields` root field names for global `settings.json` (default none). Excluded values and absence stay local; future portable snapshots omit them. Enabling rules opts into settings version 4 / snapshot version 2, which older clients must refuse. All receiving machines must configure compatible rules. Additions/removals require a separately confirmed force-direction migration, even with `--yes`; old remote history is **not erased**. See [settings merge and local fields](./
|
|
211
|
+
**Settings → Sync content → Machine-local fields** edits explicit `sync.localFields` root field names for global `settings.json` (default none). Excluded values and absence stay local; future portable snapshots omit them. Enabling rules opts into settings version 4 / snapshot version 2, which older clients must refuse. All receiving machines must configure compatible rules. Additions/removals require a separately confirmed force-direction migration, even with `--yes`; old remote history is **not erased**. See [settings merge and local fields](./skills/configure-pi-sync/references/configuration.md#settings-field-merge-and-machine-local-fields) for formats, compatibility and recovery.
|
|
211
212
|
|
|
212
213
|
**Settings → Advanced → Content merge / partial progress** (experimental) controls independent `sync.mergeContent` and `sync.partialSync` opt-ins, both default **Off**. Saving opts into settings version 5; partial publications use snapshot version 3, rejected by older readers. Supported Markdown/plain-text resources merge bounded independent line edits without Git or conflict markers. Diff grids and hunk comparisons share an eight-million-work-unit budget across the operation and its retries; exhausted paths remain conflicts for review. Same-session reconciliation only accepts validated complete byte-prefix extensions; divergent histories and the loaded session require review. A syntactically clean text merge is **not a semantic correctness guarantee**.
|
|
213
214
|
|
|
@@ -216,7 +217,7 @@ Partial sync keeps each side's unresolved versions, preserves their previous acc
|
|
|
216
217
|
**Settings → Advanced → Show sync status** defaults to **On**. Turning it Off immediately clears and suppresses pi-sync status text for background checks, transfers, and review attention; widgets and notifications remain available.
|
|
217
218
|
|
|
218
219
|
**Settings → Advanced → Skip secret scan** defaults to **Off**; enable it only after reviewing the destination and selected content because it disables push scanning for every setup.
|
|
219
|
-
See [Secret scanning](./
|
|
220
|
+
See [Secret scanning](./skills/configure-pi-sync/references/configuration.md#secret-scanning) for the setting and diagnostic behavior.
|
|
220
221
|
|
|
221
222
|
A minimal Git setup uses an existing private remote and keeps automatic sync off:
|
|
222
223
|
|
|
@@ -257,7 +258,15 @@ Version 1, version 2, and non-empty unversioned settings require manual recovery
|
|
|
257
258
|
Adding `sessions` can upload prompts, tool output, paths, images, and secrets; interactive flows require a privacy acknowledgement.
|
|
258
259
|
Startup checks report included-content differences without changing anything. Transfers retain their existing included-content policy checks instead of silently expanding local scope.
|
|
259
260
|
|
|
260
|
-
Read the [settings reference](./
|
|
261
|
+
Read the [settings reference](./skills/configure-pi-sync/references/configuration.md) for complete S3/R2, Git, and WebDAV examples, backend fields, included-content rules, legacy paths, and recovery steps.
|
|
262
|
+
|
|
263
|
+
## 🧠 Skills
|
|
264
|
+
|
|
265
|
+
Run `/skill:configure-pi-sync` before asking Pi about storage choices, included content, automatic controls, or settings recovery.
|
|
266
|
+
The [configuration skill](./skills/configure-pi-sync/SKILL.md) is manual-only and provides guidance only: it does not inspect private settings, collect credentials, edit files, execute diagnostics, or sync content.
|
|
267
|
+
Enter credentials in the existing masked TUI and perform settings changes and reviewed transfers through `/sync` yourself.
|
|
268
|
+
The skill's instructions are not a permission sandbox for the agent's other tools.
|
|
269
|
+
Read the [settings reference](./skills/configure-pi-sync/references/configuration.md) directly when no agent assistance is needed.
|
|
261
270
|
|
|
262
271
|
## 💬 Commands
|
|
263
272
|
|
|
@@ -362,7 +371,8 @@ packages/pi-sync/
|
|
|
362
371
|
│ └── ui/ # Manager, settings, reviews, and setup flows
|
|
363
372
|
├── dist/ # Generated Jiti runtime
|
|
364
373
|
├── scripts/build-runtime.mjs # Runtime builder
|
|
365
|
-
├── docs/ #
|
|
374
|
+
├── docs/ # Operational audits and settings link compatibility
|
|
375
|
+
├── skills/configure-pi-sync/ # Manual-only guidance and authoritative settings reference
|
|
366
376
|
└── test/ # Behavior and lifecycle coverage
|
|
367
377
|
```
|
|
368
378
|
|
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
buildAgentEvidence,
|
|
6
6
|
digest,
|
|
7
7
|
parseAgentCandidate
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-5QAE3G3K.ts";
|
|
9
|
+
import "./chunk-S7QS4AWK.ts";
|
|
10
10
|
import "./chunk-QOFMXPM5.ts";
|
|
11
11
|
import "./chunk-IWZH5KYS.ts";
|
|
12
12
|
import "./chunk-76NB4R7U.ts";
|
|
@@ -23,4 +23,4 @@ export {
|
|
|
23
23
|
digest,
|
|
24
24
|
parseAgentCandidate
|
|
25
25
|
};
|
|
26
|
-
//# sourceMappingURL=agent-candidate-
|
|
26
|
+
//# sourceMappingURL=agent-candidate-GNEYR225.ts.map
|
|
@@ -19,8 +19,8 @@ import {
|
|
|
19
19
|
applyAgentCandidate,
|
|
20
20
|
buildAgentEvidence,
|
|
21
21
|
digest
|
|
22
|
-
} from "./chunk-
|
|
23
|
-
import "./chunk-
|
|
22
|
+
} from "./chunk-5QAE3G3K.ts";
|
|
23
|
+
import "./chunk-S7QS4AWK.ts";
|
|
24
24
|
import {
|
|
25
25
|
createSnapshot
|
|
26
26
|
} from "./chunk-QOFMXPM5.ts";
|
|
@@ -96,4 +96,4 @@ async function resolveAgentPlan(ctx, options, config, backend, state, local, loc
|
|
|
96
96
|
export {
|
|
97
97
|
resolveAgentPlan
|
|
98
98
|
};
|
|
99
|
-
//# sourceMappingURL=agent-plan-
|
|
99
|
+
//# sourceMappingURL=agent-plan-K3CRYVXV.ts.map
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
COUPLED_FIELDS,
|
|
5
5
|
mergeSettingsJson,
|
|
6
6
|
snapshotFile
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-S7QS4AWK.ts";
|
|
8
8
|
import {
|
|
9
9
|
scanSnapshot
|
|
10
10
|
} from "./chunk-QOFMXPM5.ts";
|
|
@@ -87,9 +87,13 @@ function buildAgentEvidence(plan, local, remote, ancestors) {
|
|
|
87
87
|
let units;
|
|
88
88
|
if (conflict.path === "settings.json") {
|
|
89
89
|
const documents = versions.map((value) => parseSettingsDocument(Buffer.from(value)));
|
|
90
|
-
const merge = mergeSettingsJson(
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
const merge = mergeSettingsJson(
|
|
91
|
+
...versions.map((value) => Buffer.from(value)),
|
|
92
|
+
void 0,
|
|
93
|
+
true
|
|
94
|
+
);
|
|
95
|
+
if (merge.kind === "review" && merge.reason !== "field-conflict") throw new Error("unsupported settings");
|
|
96
|
+
const fields = new Set(merge.kind === "review" ? merge.fields : []);
|
|
93
97
|
const groups = COUPLED_FIELDS.map((group) => [...group]);
|
|
94
98
|
const coupled = new Set(groups.flat());
|
|
95
99
|
groups.push(...[...fields].filter((key) => !coupled.has(key)).map((key) => [key]));
|
|
@@ -187,7 +191,8 @@ function applyAgentCandidate(plan, evidence, candidate) {
|
|
|
187
191
|
Buffer.from(file.base),
|
|
188
192
|
Buffer.from(file.local),
|
|
189
193
|
Buffer.from(file.remote),
|
|
190
|
-
selections
|
|
194
|
+
selections,
|
|
195
|
+
true
|
|
191
196
|
);
|
|
192
197
|
if (merged.kind !== "merged") throw new Error("Agent settings units remain unresolved.");
|
|
193
198
|
parseSettingsDocument(merged.content);
|
|
@@ -212,4 +217,4 @@ export {
|
|
|
212
217
|
parseAgentCandidate,
|
|
213
218
|
applyAgentCandidate
|
|
214
219
|
};
|
|
215
|
-
//# sourceMappingURL=chunk-
|
|
220
|
+
//# sourceMappingURL=chunk-5QAE3G3K.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(\n ...(versions.map((value) => Buffer.from(value)) as [Buffer, Buffer, Buffer]),\n undefined,\n true,\n );\n if (merge.kind === \"review\" && merge.reason !== \"field-conflict\") throw new Error(\"unsupported settings\");\n const fields = new Set(merge.kind === \"review\" ? 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 true,\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
|
+
}
|
|
@@ -27,7 +27,7 @@ async function withAgentSyncLock(ctx, options, run) {
|
|
|
27
27
|
request = error;
|
|
28
28
|
}
|
|
29
29
|
const { withAgentClaim, readAgentCandidate, saveAgentCandidate } = await import("./agent-store-A3F6IYWZ.ts");
|
|
30
|
-
const { parseAgentCandidate } = await import("./agent-candidate-
|
|
30
|
+
const { parseAgentCandidate } = await import("./agent-candidate-GNEYR225.ts");
|
|
31
31
|
const { runAgentResolver } = await import("./agent-runner-E2JU2VZ6.ts");
|
|
32
32
|
return withAgentClaim(async (assertOwner) => {
|
|
33
33
|
const clearStatus = () => setSyncStatus(ctx, void 0);
|
|
@@ -94,4 +94,4 @@ async function withAgentSyncLock(ctx, options, run) {
|
|
|
94
94
|
export {
|
|
95
95
|
withAgentSyncLock
|
|
96
96
|
};
|
|
97
|
-
//# sourceMappingURL=chunk-
|
|
97
|
+
//# sourceMappingURL=chunk-QOC47PBC.ts.map
|
|
@@ -74,6 +74,58 @@ async function resolveContentConflicts(config, state, local, remote, plan, prote
|
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
// src/sync/package-merge.ts
|
|
78
|
+
import { compare, valid } from "semver";
|
|
79
|
+
function entries(value) {
|
|
80
|
+
if (!Array.isArray(value)) return void 0;
|
|
81
|
+
const result = /* @__PURE__ */ new Map();
|
|
82
|
+
for (const item of value) {
|
|
83
|
+
if (typeof item !== "string" && (!item || Array.isArray(item) || typeof item !== "object")) return void 0;
|
|
84
|
+
const source = typeof item === "string" ? item : item.source;
|
|
85
|
+
if (typeof source !== "string") return void 0;
|
|
86
|
+
const spec = source.startsWith("npm:") ? source.slice(4).trim() : void 0;
|
|
87
|
+
const match = spec?.match(/^(@?[^@]+(?:\/[^@]+)?)(?:@(.+))?$/u);
|
|
88
|
+
const key = match ? `npm:${match[1]}` : source;
|
|
89
|
+
if (result.has(key)) return void 0;
|
|
90
|
+
const options = typeof item === "string" ? null : Object.fromEntries(Object.entries(item).filter(([key2]) => key2 !== "source"));
|
|
91
|
+
result.set(key, { value: item, options, version: match?.[2] });
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
function mergePackageVersions(base, local, remote) {
|
|
96
|
+
const ancestor = entries(base ?? []);
|
|
97
|
+
const left = entries(local);
|
|
98
|
+
const right = entries(remote);
|
|
99
|
+
if (!ancestor || !left || !right) return void 0;
|
|
100
|
+
const otherEntries = (items) => [...items].filter(([key]) => !key.startsWith("npm:")).map(([, entry]) => entry.value);
|
|
101
|
+
if (!jsonEqual(otherEntries(ancestor), otherEntries(left)) || !jsonEqual(otherEntries(ancestor), otherEntries(right)))
|
|
102
|
+
return void 0;
|
|
103
|
+
const baseKeys = [...ancestor.keys()];
|
|
104
|
+
const leftKeys = [...left.keys()];
|
|
105
|
+
const rightKeys = [...right.keys()];
|
|
106
|
+
const order = jsonEqual(leftKeys, rightKeys) ? leftKeys : jsonEqual(leftKeys, baseKeys) ? rightKeys : jsonEqual(rightKeys, baseKeys) ? leftKeys : void 0;
|
|
107
|
+
if (!order) return void 0;
|
|
108
|
+
const output = [];
|
|
109
|
+
for (const key of /* @__PURE__ */ new Set([...order, ...baseKeys])) {
|
|
110
|
+
const a = ancestor.get(key);
|
|
111
|
+
const l = left.get(key);
|
|
112
|
+
const r = right.get(key);
|
|
113
|
+
let chosen;
|
|
114
|
+
if (l && r && key.startsWith("npm:") && l.version && r.version && valid(l.version) && valid(r.version) && jsonEqual(l.options, r.options)) {
|
|
115
|
+
const precedence = compare(l.version, r.version);
|
|
116
|
+
if (precedence !== 0) chosen = precedence > 0 ? l : r;
|
|
117
|
+
}
|
|
118
|
+
if (!chosen) {
|
|
119
|
+
if (jsonEqual(l?.value, r?.value)) chosen = l;
|
|
120
|
+
else if (jsonEqual(l?.value, a?.value)) chosen = r;
|
|
121
|
+
else if (jsonEqual(r?.value, a?.value)) chosen = l;
|
|
122
|
+
else return void 0;
|
|
123
|
+
}
|
|
124
|
+
if (chosen) output.push(chosen.value);
|
|
125
|
+
}
|
|
126
|
+
return output;
|
|
127
|
+
}
|
|
128
|
+
|
|
77
129
|
// src/sync/settings-merge.ts
|
|
78
130
|
var COUPLED_FIELDS = [
|
|
79
131
|
["defaultProvider", "defaultModel"],
|
|
@@ -82,7 +134,34 @@ var COUPLED_FIELDS = [
|
|
|
82
134
|
["skills", "enableSkillCommands"],
|
|
83
135
|
["enableAnalytics", "trackingId"]
|
|
84
136
|
];
|
|
85
|
-
function
|
|
137
|
+
function selectPackages(base, left, right) {
|
|
138
|
+
const packages = mergePackageVersions(
|
|
139
|
+
base.root.members.get("packages")?.node.value,
|
|
140
|
+
left.root.members.get("packages")?.node.value,
|
|
141
|
+
right.root.members.get("packages")?.node.value
|
|
142
|
+
);
|
|
143
|
+
if (!packages) return void 0;
|
|
144
|
+
const document = [left, right].find((source) => jsonEqual(source.root.members.get("packages")?.node.value, packages)) ?? parseSettingsDocument(Buffer.from(JSON.stringify({ packages })));
|
|
145
|
+
const node = document.root.members.get("packages")?.node;
|
|
146
|
+
return node ? { document, node } : void 0;
|
|
147
|
+
}
|
|
148
|
+
function mergeAcceptedSettingsPackages(ancestor, local, remote, accepted) {
|
|
149
|
+
try {
|
|
150
|
+
const base = parseSettingsDocument(ancestor);
|
|
151
|
+
const left = parseSettingsDocument(local);
|
|
152
|
+
const right = parseSettingsDocument(remote);
|
|
153
|
+
const document = parseSettingsDocument(accepted);
|
|
154
|
+
const packages = selectPackages(base, left, right);
|
|
155
|
+
if (!packages || jsonEqual(packages.node.value, document.root.members.get("packages")?.node.value))
|
|
156
|
+
return void 0;
|
|
157
|
+
const selected = new Map([...document.root.members].map(([key, member]) => [key, { document, node: member.node }]));
|
|
158
|
+
selected.set("packages", packages);
|
|
159
|
+
return renderSettingsMembers(document, selected);
|
|
160
|
+
} catch {
|
|
161
|
+
return void 0;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function mergeSettingsJson(ancestor, local, remote, selections = /* @__PURE__ */ new Map(), preferHigherPackageVersions = false) {
|
|
86
165
|
let documents;
|
|
87
166
|
try {
|
|
88
167
|
documents = [ancestor, local, remote].map(parseSettingsDocument);
|
|
@@ -97,6 +176,13 @@ function mergeSettingsJson(ancestor, local, remote, selections = /* @__PURE__ */
|
|
|
97
176
|
units.push(...[...fields].filter((field) => !coupled.has(field)).map((field) => [field]));
|
|
98
177
|
const conflicts = [];
|
|
99
178
|
for (const unit of units) {
|
|
179
|
+
if (preferHigherPackageVersions && unit[0] === "packages") {
|
|
180
|
+
const packages = selectPackages(base, left, right);
|
|
181
|
+
if (packages) {
|
|
182
|
+
selected.set("packages", packages);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
100
186
|
const equal = (first, second) => unit.every(
|
|
101
187
|
(field) => jsonEqual(first.root.members.get(field)?.node.value, second.root.members.get(field)?.node.value)
|
|
102
188
|
);
|
|
@@ -123,6 +209,7 @@ export {
|
|
|
123
209
|
snapshotFile,
|
|
124
210
|
resolveContentConflicts,
|
|
125
211
|
COUPLED_FIELDS,
|
|
212
|
+
mergeAcceptedSettingsPackages,
|
|
126
213
|
mergeSettingsJson
|
|
127
214
|
};
|
|
128
|
-
//# sourceMappingURL=chunk-
|
|
215
|
+
//# sourceMappingURL=chunk-S7QS4AWK.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/sync/content-conflicts.ts", "../../src/sync/package-merge.ts", "../../src/sync/settings-merge.ts"],
|
|
4
|
+
"sourcesContent": ["import { createHash } from \"node:crypto\";\nimport type { AnySyncConfig } from \"../settings/settings-types.js\";\nimport type { Snapshot, SnapshotFile } from \"../snapshot/snapshot-types.js\";\nimport { readMergeAncestors } from \"../state/merge-baseline-store.js\";\nimport type { SyncState } from \"../state/state-types.js\";\nimport { type FileMergePlan, mergePathIdentity } from \"./file-merge-planner.js\";\nimport { mergeSession } from \"./session-merge.js\";\nimport { isMergeTextPath, mergeText, TEXT_MERGE_WORK_LIMIT, type TextMergeBudget } from \"./text-merge.js\";\nexport function snapshotFile(filePath: string, bytes: Buffer): SnapshotFile {\n return {\n path: filePath,\n contentBase64: bytes.toString(\"base64\"),\n sha256: createHash(\"sha256\").update(bytes).digest(\"hex\"),\n };\n}\nexport async function resolveContentConflicts(\n config: AnySyncConfig,\n state: SyncState,\n local: Snapshot,\n remote: Snapshot,\n plan: FileMergePlan,\n protectedPaths: ReadonlySet<string>,\n options: { textBudget?: TextMergeBudget; signal?: AbortSignal } = {},\n): Promise<FileMergePlan> {\n if (plan.kind !== \"planned\" || !config.mergeContent) return plan;\n options.signal?.throwIfAborted();\n const budget = options.textBudget ?? { remaining: TEXT_MERGE_WORK_LIMIT };\n const decisions = [...plan.decisions];\n let ancestors: SnapshotFile[] = [];\n try {\n ancestors = (await readMergeAncestors(config, state)) ?? [];\n } catch {\n options.signal?.throwIfAborted();\n return plan;\n }\n options.signal?.throwIfAborted();\n const oursByPath = new Map(local.files.map((file) => [file.path, file]));\n const theirsByPath = new Map(remote.files.map((file) => [file.path, file]));\n const ancestorsByPath = new Map(ancestors.map((file) => [file.path, file]));\n const decisionIndex = new Map(decisions.map((decision, index) => [decision.path, index]));\n const protectedKeys = new Set([...protectedPaths].map(mergePathIdentity));\n for (const conflict of plan.conflicts) {\n options.signal?.throwIfAborted();\n if (conflict.reason !== \"both-changed\" || protectedKeys.has(mergePathIdentity(conflict.path))) continue;\n const session = conflict.path.startsWith(\"sessions/\") && conflict.path.endsWith(\".jsonl\");\n if (!session && (!isMergeTextPath(conflict.path) || budget.remaining === 0)) continue;\n const ours = oursByPath.get(conflict.path);\n const theirs = theirsByPath.get(conflict.path);\n if (!ours || !theirs) continue;\n const ancestor = ancestorsByPath.get(conflict.path);\n const base = ancestor ? Buffer.from(ancestor.contentBase64, \"base64\") : undefined;\n if (!base) continue;\n const localBytes = Buffer.from(ours.contentBase64, \"base64\");\n const remoteBytes = Buffer.from(theirs.contentBase64, \"base64\");\n const bytes = session\n ? mergeSession(base, localBytes, remoteBytes)\n : mergeText(base, localBytes, remoteBytes, { budget, signal: options.signal });\n if (!bytes) continue;\n const index = decisionIndex.get(conflict.path);\n if (index === undefined) continue;\n decisions[index] = {\n kind: \"accepted\",\n path: conflict.path,\n source: \"merged\",\n file: snapshotFile(conflict.path, bytes),\n };\n }\n return {\n kind: \"planned\",\n decisions,\n conflicts: decisions.filter((item): item is Extract<typeof item, { kind: \"conflict\" }> => item.kind === \"conflict\"),\n };\n}\n", "import { compare, valid } from \"semver\";\nimport { type JsonValue, jsonEqual } from \"./json-document.js\";\n\ninterface Entry {\n value: JsonValue;\n version?: string;\n options: JsonValue;\n}\n\nfunction entries(value: JsonValue | undefined): Map<string, Entry> | undefined {\n if (!Array.isArray(value)) return undefined;\n const result = new Map<string, Entry>();\n for (const item of value) {\n if (typeof item !== \"string\" && (!item || Array.isArray(item) || typeof item !== \"object\")) return undefined;\n const source = typeof item === \"string\" ? item : item.source;\n if (typeof source !== \"string\") return undefined;\n const spec = source.startsWith(\"npm:\") ? source.slice(4).trim() : undefined;\n const match = spec?.match(/^(@?[^@]+(?:\\/[^@]+)?)(?:@(.+))?$/u);\n const key = match ? `npm:${match[1]}` : source;\n if (result.has(key)) return undefined;\n const options =\n typeof item === \"string\" ? null : Object.fromEntries(Object.entries(item).filter(([key]) => key !== \"source\"));\n result.set(key, { value: item, options, version: match?.[2] });\n }\n return result;\n}\n\n/** Compare only explicit npm versions; do not resolve ranges, tags, Git refs or registry state. */\nexport function mergePackageVersions(\n base: JsonValue | undefined,\n local: JsonValue | undefined,\n remote: JsonValue | undefined,\n): JsonValue[] | undefined {\n const ancestor = entries(base ?? []);\n const left = entries(local);\n const right = entries(remote);\n if (!ancestor || !left || !right) return undefined;\n // Raw Git refs and local path aliases do not prove distinct package identities.\n // Leave changes to those sources under the existing atomic field policy.\n const otherEntries = (items: Map<string, Entry>) =>\n [...items].filter(([key]) => !key.startsWith(\"npm:\")).map(([, entry]) => entry.value);\n if (!jsonEqual(otherEntries(ancestor), otherEntries(left)) || !jsonEqual(otherEntries(ancestor), otherEntries(right)))\n return undefined;\n\n // Package order affects resource precedence. Merge the ordered identity list\n // atomically, then select versions without changing that order.\n const baseKeys = [...ancestor.keys()];\n const leftKeys = [...left.keys()];\n const rightKeys = [...right.keys()];\n const order = jsonEqual(leftKeys, rightKeys)\n ? leftKeys\n : jsonEqual(leftKeys, baseKeys)\n ? rightKeys\n : jsonEqual(rightKeys, baseKeys)\n ? leftKeys\n : undefined;\n if (!order) return undefined;\n const output: JsonValue[] = [];\n for (const key of new Set([...order, ...baseKeys])) {\n const a = ancestor.get(key);\n const l = left.get(key);\n const r = right.get(key);\n let chosen: Entry | undefined;\n if (\n l &&\n r &&\n key.startsWith(\"npm:\") &&\n l.version &&\n r.version &&\n valid(l.version) &&\n valid(r.version) &&\n jsonEqual(l.options, r.options)\n ) {\n const precedence = compare(l.version, r.version);\n if (precedence !== 0) chosen = precedence > 0 ? l : r;\n }\n // Build metadata and equivalent version spelling do not establish a higher version.\n if (!chosen) {\n if (jsonEqual(l?.value, r?.value)) chosen = l;\n else if (jsonEqual(l?.value, a?.value)) chosen = r;\n else if (jsonEqual(r?.value, a?.value)) chosen = l;\n else return undefined;\n }\n if (chosen) output.push(chosen.value);\n }\n return output;\n}\n", "import {\n type JsonDocument,\n type JsonNode,\n jsonEqual,\n parseSettingsDocument,\n renderSettingsMembers,\n} from \"./json-document.js\";\n\nimport { mergePackageVersions } from \"./package-merge.js\";\n\nexport type SettingsMergeResult =\n | { kind: \"merged\"; content: Buffer }\n | { kind: \"review\"; reason: \"unsupported-format\" | \"field-conflict\"; fields: string[] };\n\nexport const COUPLED_FIELDS = [\n [\"defaultProvider\", \"defaultModel\"],\n [\"queueMode\", \"steeringMode\"],\n [\"websockets\", \"transport\"],\n [\"skills\", \"enableSkillCommands\"],\n [\"enableAnalytics\", \"trackingId\"],\n] as const;\n\nfunction selectPackages(base: JsonDocument, left: JsonDocument, right: JsonDocument) {\n const packages = mergePackageVersions(\n base.root.members.get(\"packages\")?.node.value,\n left.root.members.get(\"packages\")?.node.value,\n right.root.members.get(\"packages\")?.node.value,\n );\n if (!packages) return undefined;\n const document =\n [left, right].find((source) => jsonEqual(source.root.members.get(\"packages\")?.node.value, packages)) ??\n parseSettingsDocument(Buffer.from(JSON.stringify({ packages })));\n const node = document.root.members.get(\"packages\")?.node;\n return node ? { document, node } : undefined;\n}\n\n/** Accepted files need only a package override, not another full-document field merge. */\nexport function mergeAcceptedSettingsPackages(\n ancestor: Buffer,\n local: Buffer,\n remote: Buffer,\n accepted: Buffer,\n): Buffer | undefined {\n try {\n const base = parseSettingsDocument(ancestor);\n const left = parseSettingsDocument(local);\n const right = parseSettingsDocument(remote);\n const document = parseSettingsDocument(accepted);\n const packages = selectPackages(base, left, right);\n if (!packages || jsonEqual(packages.node.value, document.root.members.get(\"packages\")?.node.value))\n return undefined;\n const selected = new Map([...document.root.members].map(([key, member]) => [key, { document, node: member.node }]));\n selected.set(\"packages\", packages);\n return renderSettingsMembers(document, selected);\n } catch {\n // Unsupported settings must not replace an accepted file with a reconstructed document.\n return undefined;\n }\n}\n\n/** Only global settings.json; objects/arrays are atomic units until their invariants are proven. */\nexport function mergeSettingsJson(\n ancestor: Buffer,\n local: Buffer,\n remote: Buffer,\n selections: ReadonlyMap<string, \"local\" | \"remote\"> = new Map(),\n preferHigherPackageVersions = false,\n): SettingsMergeResult {\n let documents: JsonDocument[];\n try {\n documents = [ancestor, local, remote].map(parseSettingsDocument);\n } catch {\n return { kind: \"review\", reason: \"unsupported-format\", fields: [] };\n }\n const [base, left, right] = documents as [JsonDocument, JsonDocument, JsonDocument];\n const selected = new Map<string, { document: JsonDocument; node: JsonNode }>();\n const fields = new Set(documents.flatMap((document) => [...document.root.members.keys()]));\n const units: string[][] = COUPLED_FIELDS.map((group) => [...group]);\n const coupled = new Set(units.flat());\n units.push(...[...fields].filter((field) => !coupled.has(field)).map((field) => [field]));\n const conflicts: string[] = [];\n for (const unit of units) {\n if (preferHigherPackageVersions && unit[0] === \"packages\") {\n const packages = selectPackages(base, left, right);\n if (packages) {\n selected.set(\"packages\", packages);\n continue;\n }\n }\n const equal = (first: JsonDocument, second: JsonDocument) =>\n unit.every((field) =>\n jsonEqual(first.root.members.get(field)?.node.value, second.root.members.get(field)?.node.value),\n );\n const choice = selections.get(unit[0] ?? \"\");\n const source = equal(left, right)\n ? left\n : equal(left, base)\n ? right\n : equal(right, base)\n ? left\n : choice === \"local\"\n ? left\n : choice === \"remote\"\n ? right\n : undefined;\n if (!source) {\n conflicts.push(...unit.filter((field) => fields.has(field)));\n continue;\n }\n for (const field of unit) {\n const member = source.root.members.get(field);\n if (member) selected.set(field, { document: source, node: member.node });\n }\n }\n if (conflicts.length) return { kind: \"review\", reason: \"field-conflict\", fields: conflicts };\n try {\n return { kind: \"merged\", content: renderSettingsMembers(left, selected) };\n } catch {\n return { kind: \"review\", reason: \"unsupported-format\", fields: [] };\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["key"]
|
|
7
|
+
}
|
|
@@ -66,9 +66,10 @@ import {
|
|
|
66
66
|
expectedRemoteHead
|
|
67
67
|
} from "./chunk-YDVQYUHE.ts";
|
|
68
68
|
import {
|
|
69
|
+
mergeAcceptedSettingsPackages,
|
|
69
70
|
mergeSettingsJson,
|
|
70
71
|
resolveContentConflicts
|
|
71
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-S7QS4AWK.ts";
|
|
72
73
|
import {
|
|
73
74
|
agentDir,
|
|
74
75
|
createSnapshot,
|
|
@@ -1025,21 +1026,33 @@ async function resolveSettingsConflicts(config, state, local, remote, plan) {
|
|
|
1025
1026
|
const decisions = [...plan.decisions];
|
|
1026
1027
|
for (let index = 0; index < decisions.length; index++) {
|
|
1027
1028
|
const decision = decisions[index];
|
|
1028
|
-
if (decision?.
|
|
1029
|
-
|
|
1029
|
+
if (decision?.path !== "settings.json") continue;
|
|
1030
|
+
if (decision.kind === "conflict" ? decision.reason !== "both-changed" : !config.agentMode) continue;
|
|
1030
1031
|
const left = local.files.find((file) => file.path === decision.path);
|
|
1031
1032
|
const right = remote.files.find((file) => file.path === decision.path);
|
|
1032
1033
|
if (!left || !right) continue;
|
|
1033
1034
|
const ancestor = await readMergeAncestor(config, state, decision.path);
|
|
1034
1035
|
if (!ancestor) continue;
|
|
1035
|
-
const
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1036
|
+
const leftBytes = Buffer.from(left.contentBase64, "base64");
|
|
1037
|
+
const rightBytes = Buffer.from(right.contentBase64, "base64");
|
|
1038
|
+
let content;
|
|
1039
|
+
if (decision.kind === "accepted") {
|
|
1040
|
+
if (!decision.file) continue;
|
|
1041
|
+
const patched = mergeAcceptedSettingsPackages(
|
|
1042
|
+
ancestor,
|
|
1043
|
+
leftBytes,
|
|
1044
|
+
rightBytes,
|
|
1045
|
+
Buffer.from(decision.file.contentBase64, "base64")
|
|
1046
|
+
);
|
|
1047
|
+
if (!patched) continue;
|
|
1048
|
+
content = patched;
|
|
1049
|
+
} else {
|
|
1050
|
+
const merged = mergeSettingsJson(ancestor, leftBytes, rightBytes, void 0, Boolean(config.agentMode));
|
|
1051
|
+
if (merged.kind === "review") {
|
|
1052
|
+
fields.push(...merged.fields);
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
content = merged.content;
|
|
1043
1056
|
}
|
|
1044
1057
|
decisions[index] = {
|
|
1045
1058
|
kind: "accepted",
|
|
@@ -1047,8 +1060,8 @@ async function resolveSettingsConflicts(config, state, local, remote, plan) {
|
|
|
1047
1060
|
source: "merged",
|
|
1048
1061
|
file: {
|
|
1049
1062
|
path: decision.path,
|
|
1050
|
-
contentBase64:
|
|
1051
|
-
sha256: createHash3("sha256").update(
|
|
1063
|
+
contentBase64: content.toString("base64"),
|
|
1064
|
+
sha256: createHash3("sha256").update(content).digest("hex")
|
|
1052
1065
|
}
|
|
1053
1066
|
};
|
|
1054
1067
|
}
|
|
@@ -1409,7 +1422,7 @@ async function mergeSync(ctx, options, factory = createSyncBackend, resolution)
|
|
|
1409
1422
|
);
|
|
1410
1423
|
await validate();
|
|
1411
1424
|
let resolved = { plan, fields: [] };
|
|
1412
|
-
if (config.mergeSettings) {
|
|
1425
|
+
if (config.mergeSettings || config.agentMode) {
|
|
1413
1426
|
try {
|
|
1414
1427
|
resolved = await timings.measure(
|
|
1415
1428
|
"transfer settings merge",
|
|
@@ -1443,7 +1456,7 @@ async function mergeSync(ctx, options, factory = createSyncBackend, resolution)
|
|
|
1443
1456
|
await validate();
|
|
1444
1457
|
}
|
|
1445
1458
|
if (config.agentMode && !resolution) {
|
|
1446
|
-
const { resolveAgentPlan } = await import("./agent-plan-
|
|
1459
|
+
const { resolveAgentPlan } = await import("./agent-plan-K3CRYVXV.ts");
|
|
1447
1460
|
await validate();
|
|
1448
1461
|
plan = await resolveAgentPlan(
|
|
1449
1462
|
ctx,
|
|
@@ -1937,4 +1950,4 @@ export {
|
|
|
1937
1950
|
createSyncDecision,
|
|
1938
1951
|
mergeSync
|
|
1939
1952
|
};
|
|
1940
|
-
//# sourceMappingURL=chunk-
|
|
1953
|
+
//# sourceMappingURL=chunk-T3NC77TU.ts.map
|