@narumitw/pi-sync 0.54.3 → 0.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -11
- 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-GC3QOL6J.ts → chunk-AAEEHR7H.ts} +31 -87
- package/dist/chunks/chunk-AAEEHR7H.ts.map +7 -0
- package/dist/chunks/{chunk-R35N4CL3.ts → chunk-B4QZBDBT.ts} +2 -2
- package/dist/chunks/{chunk-KGE47GCZ.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-HMXUKU43.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-YTHQEW35.ts → chunk-GP5DU62N.ts} +8 -9
- package/dist/chunks/chunk-GP5DU62N.ts.map +7 -0
- package/dist/chunks/{chunk-AAZDBIBJ.ts → chunk-GSNPASKB.ts} +2 -2
- package/dist/chunks/{chunk-E6ONFG57.ts → chunk-IWZH5KYS.ts} +11 -8
- package/dist/chunks/chunk-IWZH5KYS.ts.map +7 -0
- package/dist/chunks/{chunk-CAGJOKB7.ts → chunk-JJTARUSO.ts} +15 -14
- package/dist/chunks/chunk-JJTARUSO.ts.map +7 -0
- package/dist/chunks/{chunk-ITFR6NNQ.ts → chunk-NLWS55B6.ts} +2 -1
- package/dist/chunks/{chunk-ITFR6NNQ.ts.map → chunk-NLWS55B6.ts.map} +2 -2
- package/dist/chunks/{chunk-2FETCGGT.ts → chunk-OEWQVWLC.ts} +4 -4
- package/dist/chunks/{chunk-35GM6XKM.ts → chunk-OTZRCKYP.ts} +128 -382
- package/dist/chunks/chunk-OTZRCKYP.ts.map +7 -0
- package/dist/chunks/{chunk-XU2VXGQV.ts → chunk-OWBAZA6J.ts} +14 -8
- 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-KATKRR3D.ts → chunk-QO333NM4.ts} +32 -11
- package/dist/chunks/chunk-QO333NM4.ts.map +7 -0
- package/dist/chunks/{chunk-I4JYVFUS.ts → chunk-QSHMOL3I.ts} +21 -22
- 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-VCL7TZYW.ts → chunk-UPNVIUBN.ts} +3 -3
- 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-A3TFXY4O.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-4CZFB7WI.ts → conflict-artifacts-FXFIIBCB.ts} +8 -7
- package/dist/chunks/{conflict-review-ZOTOHL2E.ts → conflict-review-6WIEQLXW.ts} +31 -25
- package/dist/chunks/{conflict-review-ZOTOHL2E.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-UNHBVVON.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-HVRTPEVK.ts → manager-result-dispatcher-AGBBVDDS.ts} +10 -9
- package/dist/chunks/{manager-ui-ZIN4BUSF.ts → manager-ui-KRAW3NE2.ts} +184 -45
- package/dist/chunks/manager-ui-KRAW3NE2.ts.map +7 -0
- package/dist/chunks/{merge-baseline-store-ICXVTCML.ts → merge-baseline-store-RFCAH7KL.ts} +5 -5
- package/dist/chunks/{remote-selection-ui-H6AEVA3Q.ts → remote-selection-ui-5725JDNC.ts} +9 -8
- package/dist/chunks/remote-selection-ui-5725JDNC.ts.map +7 -0
- package/dist/chunks/{s3-backend-Y4P3O5B5.ts → s3-backend-Y7WSCLJI.ts} +38 -38
- package/dist/chunks/s3-backend-Y7WSCLJI.ts.map +7 -0
- package/dist/chunks/{setup-switch-ZMW5C4M7.ts → setup-switch-E7PHKI6J.ts} +7 -6
- package/dist/chunks/setup-switch-E7PHKI6J.ts.map +7 -0
- package/dist/chunks/{setup-wizard-VW7F3JN4.ts → setup-wizard-65DROCIJ.ts} +14 -13
- package/dist/chunks/setup-wizard-65DROCIJ.ts.map +7 -0
- package/dist/chunks/{sync-inspection-3EMS2J5B.ts → sync-inspection-XFJOAMJD.ts} +8 -8
- package/dist/chunks/sync-inspection-XFJOAMJD.ts.map +7 -0
- package/dist/chunks/{sync-operations-2CWST333.ts → sync-operations-HDUDT5PD.ts} +62 -63
- 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 +165 -85
- 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 +32 -247
- package/package.json +11 -5
- package/skills/configure-pi-sync/SKILL.md +54 -0
- package/skills/configure-pi-sync/references/configuration.md +650 -0
- 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 +5 -2
- package/src/commands/command-handler.ts +15 -3
- package/src/commands/command-types.ts +6 -0
- package/src/commands/command.ts +1 -0
- package/src/settings/config.ts +7 -0
- package/src/settings/settings-store.ts +0 -34
- package/src/settings/settings-types.ts +8 -0
- package/src/settings/settings-validation.ts +1 -1
- package/src/snapshot/snapshot-codec.ts +1 -5
- package/src/state/lock.ts +13 -10
- package/src/state/merge-baseline-store.ts +2 -2
- 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/automatic-sync.ts +2 -1
- 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 +70 -12
- 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 +15 -11
- package/src/sync/sync-inspection.ts +8 -1
- package/src/sync/sync-mutations.ts +6 -17
- package/src/sync/sync-queries.ts +5 -0
- 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 +2 -0
- package/src/ui/remote-selection-ui.ts +5 -5
- package/src/ui/settings-ui.ts +83 -1
- 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-switcher.ts +2 -0
- package/src/ui/sync-setups-ui.ts +3 -5
- package/dist/chunks/chunk-26PMXY7E.ts +0 -362
- package/dist/chunks/chunk-26PMXY7E.ts.map +0 -7
- package/dist/chunks/chunk-35GM6XKM.ts.map +0 -7
- package/dist/chunks/chunk-A3TFXY4O.ts.map +0 -7
- package/dist/chunks/chunk-BVVP33U5.ts.map +0 -7
- package/dist/chunks/chunk-CAGJOKB7.ts.map +0 -7
- package/dist/chunks/chunk-E6ONFG57.ts.map +0 -7
- package/dist/chunks/chunk-GC3QOL6J.ts.map +0 -7
- package/dist/chunks/chunk-I4JYVFUS.ts.map +0 -7
- package/dist/chunks/chunk-KATKRR3D.ts.map +0 -7
- package/dist/chunks/chunk-XU2VXGQV.ts.map +0 -7
- package/dist/chunks/chunk-YTHQEW35.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-ZIN4BUSF.ts.map +0 -7
- package/dist/chunks/s3-backend-Y4P3O5B5.ts.map +0 -7
- package/dist/chunks/setup-wizard-VW7F3JN4.ts.map +0 -7
- package/dist/chunks/sync-operations-2CWST333.ts.map +0 -7
- package/dist/chunks/webdav-backend-TBILVEQK.ts.map +0 -7
- /package/dist/chunks/{conflict-artifacts-4CZFB7WI.ts.map → agent-candidate-3JN6DJKT.ts.map} +0 -0
- /package/dist/chunks/{chunk-R35N4CL3.ts.map → chunk-B4QZBDBT.ts.map} +0 -0
- /package/dist/chunks/{chunk-KGE47GCZ.ts.map → chunk-CRKTAPSL.ts.map} +0 -0
- /package/dist/chunks/{chunk-HMXUKU43.ts.map → chunk-EUMINX7L.ts.map} +0 -0
- /package/dist/chunks/{chunk-AAZDBIBJ.ts.map → chunk-GSNPASKB.ts.map} +0 -0
- /package/dist/chunks/{chunk-2FETCGGT.ts.map → chunk-OEWQVWLC.ts.map} +0 -0
- /package/dist/chunks/{chunk-VCL7TZYW.ts.map → chunk-UPNVIUBN.ts.map} +0 -0
- /package/dist/chunks/{manager-result-dispatcher-HVRTPEVK.ts.map → config-KGIYTTXQ.ts.map} +0 -0
- /package/dist/chunks/{merge-baseline-store-ICXVTCML.ts.map → conflict-artifacts-FXFIIBCB.ts.map} +0 -0
- /package/dist/chunks/{remote-selection-ui-H6AEVA3Q.ts.map → continuous-record-LFRVXQBD.ts.map} +0 -0
- /package/dist/chunks/{file-selection-UNHBVVON.ts.map → file-selection-3JJYOVNR.ts.map} +0 -0
- /package/dist/chunks/{setup-switch-ZMW5C4M7.ts.map → manager-result-dispatcher-AGBBVDDS.ts.map} +0 -0
- /package/dist/chunks/{sync-inspection-3EMS2J5B.ts.map → merge-baseline-store-RFCAH7KL.ts.map} +0 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/sync/agent-store.ts"],
|
|
4
|
+
"sourcesContent": ["import { randomUUID } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport lockfile from \"proper-lockfile\";\nimport { writeJson } from \"../state/json-file.js\";\nimport { withLock } from \"../state/lock.js\";\nimport { LOCK_GUARD_STALE_MS, LOCK_GUARD_UPDATE_MS } from \"../state/lock-policy.js\";\nimport { LOCKFILE_FS_ADAPTER } from \"../state/lockfile-fs.js\";\nimport { stateDir } from \"../state/state-directory.js\";\nimport { AGENT_OUTPUT_BYTES } from \"./agent-policy.js\";\nimport { ContinuousError } from \"./continuous-errors.js\";\n\ninterface Claim {\n version: 1;\n id: string;\n pid: number;\n}\nfunction paths() {\n const root = path.join(stateDir(), \"agent-resolver\");\n return { root, claim: path.join(root, \"claim.json\") };\n}\nasync function directory() {\n const { root } = paths();\n await fs.mkdir(root, { mode: 0o700 });\n}\nasync function checkDirectory() {\n const stat = await fs.lstat(paths().root);\n if (!stat.isDirectory() || stat.isSymbolicLink() || (process.platform !== \"win32\" && stat.mode & 0o077))\n throw new Error(\"Unsafe agent resolver directory; evidence retained.\");\n}\nasync function readPrivate(file: string): Promise<unknown> {\n try {\n await checkDirectory();\n const stat = await fs.lstat(file);\n if (\n !stat.isFile() ||\n stat.isSymbolicLink() ||\n stat.nlink !== 1 ||\n stat.size > AGENT_OUTPUT_BYTES * 2 ||\n (process.platform !== \"win32\" && stat.mode & 0o077)\n )\n throw new Error(\"Unsafe agent resolver record; evidence retained.\");\n return JSON.parse(await fs.readFile(file, \"utf8\"));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\n throw new Error(\"Unreadable agent resolver record; evidence retained.\");\n }\n}\nasync function readClaim(): Promise<Claim | undefined> {\n const value = await readPrivate(paths().claim);\n if (value === undefined) return;\n const claim = value as Claim;\n if (\n claim?.version !== 1 ||\n typeof claim.id !== \"string\" ||\n !/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/u.test(claim.id) ||\n Object.keys(claim).sort().join(\",\") !== \"id,pid,version\" ||\n !Number.isSafeInteger(claim.pid) ||\n claim.pid < 1 ||\n claim.pid > 2_147_483_647\n )\n throw new Error(\"Invalid agent resolver claim; evidence retained.\");\n return claim;\n}\nfunction alive(pid: number) {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code !== \"ESRCH\";\n }\n}\n\n/** Caller retains the state-directory migration guard; claim changes use a separate short-lived guard. */\nexport async function withAgentClaim<T>(fn: (assertOwner: () => Promise<void>) => Promise<T>): Promise<T> {\n const claim: Claim = { version: 1, id: randomUUID(), pid: process.pid };\n let armed = false;\n const assertOwner = async () => {\n const current = await readClaim();\n if (current?.id !== claim.id || current.pid !== process.pid)\n throw new Error(\"Agent resolver ownership changed; candidate not applied.\");\n };\n try {\n await withLock(\"agent-claim\", async () => {\n try {\n await directory();\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n }\n await checkDirectory();\n await withClaimGuard(async () => {\n const existing = await readClaim();\n if (existing && alive(existing.pid))\n throw new ContinuousError(\"contention\", \"Agent mode deferred: another local resolver is running.\");\n // A durability failure can occur after rename: cleanup must own partial initialization too.\n armed = true;\n await writeJson(paths().claim, claim);\n });\n });\n return await fn(assertOwner);\n } finally {\n if (armed) await releaseClaim(claim);\n }\n}\nasync function releaseClaim(claim: Claim) {\n // Ordinary transfers may hold the operation lock for an arbitrary time.\n // Cleanup needs only the claim guard; it never waits for that operation lock.\n await withClaimGuard(async () => {\n const current = await readClaim();\n if (current?.id === claim.id && current.pid === claim.pid) await fs.rm(paths().claim);\n });\n}\nasync function withClaimGuard<T>(run: () => Promise<T>): Promise<T> {\n await checkDirectory();\n let compromisedError: Error | undefined;\n const release = await lockfile.lock(paths().claim, {\n fs: LOCKFILE_FS_ADAPTER,\n realpath: false,\n stale: LOCK_GUARD_STALE_MS,\n update: LOCK_GUARD_UPDATE_MS,\n retries: { retries: 100, factor: 1.2, minTimeout: 10, maxTimeout: 100 },\n onCompromised: (error) => {\n compromisedError = error;\n },\n });\n try {\n const result = await run();\n if (compromisedError) throw compromisedError;\n return result;\n } finally {\n await release();\n }\n}\nfunction candidatePath(key: string) {\n if (!/^[a-f0-9]{64}$/u.test(key)) throw new Error(\"Invalid agent candidate key.\");\n return path.join(paths().root, `${key}.json`);\n}\nexport async function readAgentCandidate(key: string): Promise<unknown> {\n return readPrivate(candidatePath(key));\n}\nexport async function saveAgentCandidate(key: string, candidate: unknown) {\n await checkDirectory();\n const target = candidatePath(key);\n const existing = await readPrivate(target);\n if (existing !== undefined) {\n if (JSON.stringify(existing) !== JSON.stringify(candidate))\n throw new Error(\"Agent candidate changed before save; existing evidence retained.\");\n return;\n }\n await writeJson(target, candidate, { maxBytes: AGENT_OUTPUT_BYTES * 2 });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
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
|
+
TEXT_MERGE_WORK_LIMIT,
|
|
5
|
+
isMergeTextPath,
|
|
6
|
+
mergeSession,
|
|
7
|
+
mergeText,
|
|
8
|
+
readMergeAncestors
|
|
9
|
+
} from "./chunk-IWZH5KYS.ts";
|
|
10
|
+
import {
|
|
11
|
+
mergePathIdentity
|
|
12
|
+
} from "./chunk-76NB4R7U.ts";
|
|
13
|
+
import {
|
|
14
|
+
jsonEqual,
|
|
15
|
+
parseSettingsDocument,
|
|
16
|
+
renderSettingsMembers
|
|
17
|
+
} from "./chunk-ZW5ZLGBI.ts";
|
|
18
|
+
|
|
19
|
+
// src/sync/content-conflicts.ts
|
|
20
|
+
import { createHash } from "node:crypto";
|
|
21
|
+
function snapshotFile(filePath, bytes) {
|
|
22
|
+
return {
|
|
23
|
+
path: filePath,
|
|
24
|
+
contentBase64: bytes.toString("base64"),
|
|
25
|
+
sha256: createHash("sha256").update(bytes).digest("hex")
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
async function resolveContentConflicts(config, state, local, remote, plan, protectedPaths, options = {}) {
|
|
29
|
+
if (plan.kind !== "planned" || !config.mergeContent) return plan;
|
|
30
|
+
options.signal?.throwIfAborted();
|
|
31
|
+
const budget = options.textBudget ?? { remaining: TEXT_MERGE_WORK_LIMIT };
|
|
32
|
+
const decisions = [...plan.decisions];
|
|
33
|
+
let ancestors = [];
|
|
34
|
+
try {
|
|
35
|
+
ancestors = await readMergeAncestors(config, state) ?? [];
|
|
36
|
+
} catch {
|
|
37
|
+
options.signal?.throwIfAborted();
|
|
38
|
+
return plan;
|
|
39
|
+
}
|
|
40
|
+
options.signal?.throwIfAborted();
|
|
41
|
+
const oursByPath = new Map(local.files.map((file) => [file.path, file]));
|
|
42
|
+
const theirsByPath = new Map(remote.files.map((file) => [file.path, file]));
|
|
43
|
+
const ancestorsByPath = new Map(ancestors.map((file) => [file.path, file]));
|
|
44
|
+
const decisionIndex = new Map(decisions.map((decision, index) => [decision.path, index]));
|
|
45
|
+
const protectedKeys = new Set([...protectedPaths].map(mergePathIdentity));
|
|
46
|
+
for (const conflict of plan.conflicts) {
|
|
47
|
+
options.signal?.throwIfAborted();
|
|
48
|
+
if (conflict.reason !== "both-changed" || protectedKeys.has(mergePathIdentity(conflict.path))) continue;
|
|
49
|
+
const session = conflict.path.startsWith("sessions/") && conflict.path.endsWith(".jsonl");
|
|
50
|
+
if (!session && (!isMergeTextPath(conflict.path) || budget.remaining === 0)) continue;
|
|
51
|
+
const ours = oursByPath.get(conflict.path);
|
|
52
|
+
const theirs = theirsByPath.get(conflict.path);
|
|
53
|
+
if (!ours || !theirs) continue;
|
|
54
|
+
const ancestor = ancestorsByPath.get(conflict.path);
|
|
55
|
+
const base = ancestor ? Buffer.from(ancestor.contentBase64, "base64") : void 0;
|
|
56
|
+
if (!base) continue;
|
|
57
|
+
const localBytes = Buffer.from(ours.contentBase64, "base64");
|
|
58
|
+
const remoteBytes = Buffer.from(theirs.contentBase64, "base64");
|
|
59
|
+
const bytes = session ? mergeSession(base, localBytes, remoteBytes) : mergeText(base, localBytes, remoteBytes, { budget, signal: options.signal });
|
|
60
|
+
if (!bytes) continue;
|
|
61
|
+
const index = decisionIndex.get(conflict.path);
|
|
62
|
+
if (index === void 0) continue;
|
|
63
|
+
decisions[index] = {
|
|
64
|
+
kind: "accepted",
|
|
65
|
+
path: conflict.path,
|
|
66
|
+
source: "merged",
|
|
67
|
+
file: snapshotFile(conflict.path, bytes)
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
kind: "planned",
|
|
72
|
+
decisions,
|
|
73
|
+
conflicts: decisions.filter((item) => item.kind === "conflict")
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/sync/settings-merge.ts
|
|
78
|
+
var COUPLED_FIELDS = [
|
|
79
|
+
["defaultProvider", "defaultModel"],
|
|
80
|
+
["queueMode", "steeringMode"],
|
|
81
|
+
["websockets", "transport"],
|
|
82
|
+
["skills", "enableSkillCommands"],
|
|
83
|
+
["enableAnalytics", "trackingId"]
|
|
84
|
+
];
|
|
85
|
+
function mergeSettingsJson(ancestor, local, remote, selections = /* @__PURE__ */ new Map()) {
|
|
86
|
+
let documents;
|
|
87
|
+
try {
|
|
88
|
+
documents = [ancestor, local, remote].map(parseSettingsDocument);
|
|
89
|
+
} catch {
|
|
90
|
+
return { kind: "review", reason: "unsupported-format", fields: [] };
|
|
91
|
+
}
|
|
92
|
+
const [base, left, right] = documents;
|
|
93
|
+
const selected = /* @__PURE__ */ new Map();
|
|
94
|
+
const fields = new Set(documents.flatMap((document) => [...document.root.members.keys()]));
|
|
95
|
+
const units = COUPLED_FIELDS.map((group) => [...group]);
|
|
96
|
+
const coupled = new Set(units.flat());
|
|
97
|
+
units.push(...[...fields].filter((field) => !coupled.has(field)).map((field) => [field]));
|
|
98
|
+
const conflicts = [];
|
|
99
|
+
for (const unit of units) {
|
|
100
|
+
const equal = (first, second) => unit.every(
|
|
101
|
+
(field) => jsonEqual(first.root.members.get(field)?.node.value, second.root.members.get(field)?.node.value)
|
|
102
|
+
);
|
|
103
|
+
const choice = selections.get(unit[0] ?? "");
|
|
104
|
+
const source = equal(left, right) ? left : equal(left, base) ? right : equal(right, base) ? left : choice === "local" ? left : choice === "remote" ? right : void 0;
|
|
105
|
+
if (!source) {
|
|
106
|
+
conflicts.push(...unit.filter((field) => fields.has(field)));
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
for (const field of unit) {
|
|
110
|
+
const member = source.root.members.get(field);
|
|
111
|
+
if (member) selected.set(field, { document: source, node: member.node });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (conflicts.length) return { kind: "review", reason: "field-conflict", fields: conflicts };
|
|
115
|
+
try {
|
|
116
|
+
return { kind: "merged", content: renderSettingsMembers(left, selected) };
|
|
117
|
+
} catch {
|
|
118
|
+
return { kind: "review", reason: "unsupported-format", fields: [] };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export {
|
|
123
|
+
snapshotFile,
|
|
124
|
+
resolveContentConflicts,
|
|
125
|
+
COUPLED_FIELDS,
|
|
126
|
+
mergeSettingsJson
|
|
127
|
+
};
|
|
128
|
+
//# sourceMappingURL=chunk-5Q5SSKNG.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/sync/content-conflicts.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 {\n type JsonDocument,\n type JsonNode,\n jsonEqual,\n parseSettingsDocument,\n renderSettingsMembers,\n} from \"./json-document.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\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): 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 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": []
|
|
7
|
+
}
|
|
@@ -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
|
+
safeTerminalText
|
|
5
|
+
} from "./chunk-AMJRUXFT.ts";
|
|
6
|
+
|
|
7
|
+
// src/ui/merge-review.ts
|
|
8
|
+
import { truncateToWidth as truncateToWidth2 } from "@earendil-works/pi-tui";
|
|
9
|
+
import { defineMenu, runCustomInteraction, runDocumentReview, runMenu } from "@narumitw/pi-tui-kit";
|
|
10
|
+
|
|
11
|
+
// src/ui/sync-confirmation.ts
|
|
12
|
+
import { Key, matchesKey, SelectList, truncateToWidth } from "@earendil-works/pi-tui";
|
|
13
|
+
import { HorizontalRule, hardWrapTerminalDocument, sanitizeTerminalText } from "@narumitw/pi-tui-kit";
|
|
14
|
+
function createSyncConfirmation(options) {
|
|
15
|
+
const { tui, theme, keybindings } = options;
|
|
16
|
+
const choices = ["cancel", "approve", "details"];
|
|
17
|
+
const list = new SelectList(
|
|
18
|
+
[
|
|
19
|
+
{ value: "cancel", label: "No, cancel" },
|
|
20
|
+
{ value: "approve", label: `Yes, ${sanitizeTerminalText(options.confirmationLabel)}` },
|
|
21
|
+
{ value: "details", label: "View details" }
|
|
22
|
+
],
|
|
23
|
+
3,
|
|
24
|
+
{
|
|
25
|
+
selectedPrefix: (text) => theme.fg("accent", text),
|
|
26
|
+
selectedText: (text) => theme.fg("accent", text),
|
|
27
|
+
description: (text) => theme.fg("muted", text),
|
|
28
|
+
scrollInfo: (text) => theme.fg("dim", text),
|
|
29
|
+
noMatch: (text) => theme.fg("muted", text)
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
const rule = new HorizontalRule({ ruleStyle: (text) => theme.fg("border", text) });
|
|
33
|
+
let selected = 0;
|
|
34
|
+
let offset = 0;
|
|
35
|
+
let maximumOffset = 0;
|
|
36
|
+
let lastWidth = 0;
|
|
37
|
+
let lastRows = 0;
|
|
38
|
+
let seen = /* @__PURE__ */ new Set();
|
|
39
|
+
let summary = [];
|
|
40
|
+
let reviewed = false;
|
|
41
|
+
let disposed = false;
|
|
42
|
+
let finished = false;
|
|
43
|
+
let summaryStart = 0;
|
|
44
|
+
let summaryEnd = 0;
|
|
45
|
+
let actionsStart = 0;
|
|
46
|
+
const finish = (choice) => {
|
|
47
|
+
if (disposed || finished) return;
|
|
48
|
+
finished = true;
|
|
49
|
+
options.complete(choice);
|
|
50
|
+
};
|
|
51
|
+
const select = (index) => {
|
|
52
|
+
selected = (index + choices.length) % choices.length;
|
|
53
|
+
list.setSelectedIndex(selected);
|
|
54
|
+
};
|
|
55
|
+
list.onSelectionChange = (item) => {
|
|
56
|
+
selected = Math.max(0, choices.indexOf(item.value));
|
|
57
|
+
};
|
|
58
|
+
list.onSelect = (item) => {
|
|
59
|
+
const choice = choices[selected] ?? "cancel";
|
|
60
|
+
if (item.value === choice && (choice !== "approve" || reviewed)) finish(choice);
|
|
61
|
+
};
|
|
62
|
+
const component = {
|
|
63
|
+
render(width, expose = true) {
|
|
64
|
+
if (!Number.isFinite(width) || width <= 0) {
|
|
65
|
+
reviewed = false;
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
const cells = Math.floor(width);
|
|
69
|
+
const rows = Math.max(1, Math.floor(tui.terminal.rows) - 3);
|
|
70
|
+
if (cells !== lastWidth || rows !== lastRows) {
|
|
71
|
+
summary = options.lines.flatMap((line) => hardWrapTerminalDocument(line, cells));
|
|
72
|
+
seen = /* @__PURE__ */ new Set();
|
|
73
|
+
offset = 0;
|
|
74
|
+
select(0);
|
|
75
|
+
lastWidth = cells;
|
|
76
|
+
lastRows = rows;
|
|
77
|
+
}
|
|
78
|
+
const framed = rows >= 9;
|
|
79
|
+
const actions = list.render(cells);
|
|
80
|
+
const viewport = Math.max(0, rows - actions.length - 3 - (framed ? 2 : 0));
|
|
81
|
+
maximumOffset = Math.max(0, summary.length - viewport);
|
|
82
|
+
offset = Math.min(offset, maximumOffset);
|
|
83
|
+
const visible = summary.slice(offset, offset + viewport);
|
|
84
|
+
if (expose) for (let index = offset; index < offset + visible.length; index++) seen.add(index);
|
|
85
|
+
reviewed = viewport > 0 && seen.size === summary.length;
|
|
86
|
+
const hint = reviewed ? "Select an action." : "Navigation keys scroll the summary before selecting Yes.";
|
|
87
|
+
const body = [
|
|
88
|
+
theme.fg("accent", theme.bold(hardWrapTerminalDocument(options.title, cells).join(" "))),
|
|
89
|
+
...visible.map((line) => theme.fg("text", line)),
|
|
90
|
+
theme.fg(
|
|
91
|
+
"muted",
|
|
92
|
+
`Summary ${visible.length ? offset + 1 : 0}-${offset + visible.length}/${summary.length}${reviewed ? "" : " \u2014 review before Yes"}`
|
|
93
|
+
),
|
|
94
|
+
...actions,
|
|
95
|
+
theme.fg("dim", hint)
|
|
96
|
+
];
|
|
97
|
+
summaryStart = Number(framed) + 1;
|
|
98
|
+
summaryEnd = summaryStart + visible.length;
|
|
99
|
+
actionsStart = summaryEnd + 1;
|
|
100
|
+
const frame = framed ? [...rule.render(cells), ...body, ...rule.render(cells)] : body;
|
|
101
|
+
return frame.slice(0, rows).map((line) => truncateToWidth(line, cells, ""));
|
|
102
|
+
},
|
|
103
|
+
invalidate() {
|
|
104
|
+
list.invalidate();
|
|
105
|
+
rule.invalidate();
|
|
106
|
+
},
|
|
107
|
+
handleInput(data) {
|
|
108
|
+
if (disposed || finished) return;
|
|
109
|
+
if (matchesKey(data, Key.ctrl("c")) || keybindings.matches(data, "tui.select.cancel")) {
|
|
110
|
+
finish("cancel");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
component.render(tui.terminal.columns ?? lastWidth, false);
|
|
114
|
+
if (keybindings.matches(data, "tui.select.up")) {
|
|
115
|
+
if (!reviewed || selected === 0 && offset > 0) offset = Math.max(0, offset - 1);
|
|
116
|
+
else select(selected - 1);
|
|
117
|
+
} else if (keybindings.matches(data, "tui.select.down")) {
|
|
118
|
+
if (!reviewed || selected === 0 && offset < maximumOffset) offset = Math.min(maximumOffset, offset + 1);
|
|
119
|
+
else select(selected + 1);
|
|
120
|
+
} else if (keybindings.matches(data, "tui.select.confirm")) {
|
|
121
|
+
const choice = choices[selected] ?? "cancel";
|
|
122
|
+
if (choice !== "approve" || reviewed) finish(choice);
|
|
123
|
+
}
|
|
124
|
+
tui.requestRender();
|
|
125
|
+
},
|
|
126
|
+
handleMouse(event) {
|
|
127
|
+
if (disposed || finished || event.width !== lastWidth) return void 0;
|
|
128
|
+
component.render(event.width, false);
|
|
129
|
+
if (event.y >= summaryStart && event.y < summaryEnd && event.type === "wheel" && event.wheelDelta) {
|
|
130
|
+
offset = Math.max(0, Math.min(maximumOffset, offset + (event.wheelDelta < 0 ? -1 : 1)));
|
|
131
|
+
select(0);
|
|
132
|
+
tui.requestRender();
|
|
133
|
+
return { handled: true, render: true };
|
|
134
|
+
}
|
|
135
|
+
if (event.y < actionsStart || event.y >= actionsStart + choices.length || event.y >= lastRows) return void 0;
|
|
136
|
+
return list.handleMouse({ ...event, y: event.y - actionsStart, height: choices.length });
|
|
137
|
+
},
|
|
138
|
+
dispose() {
|
|
139
|
+
disposed = true;
|
|
140
|
+
seen.clear();
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
return component;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/ui/merge-review.ts
|
|
147
|
+
async function confirmMergeReview(ctx, title, content, signal, isCurrent, confirmationLabel = "apply changes", summaryLines) {
|
|
148
|
+
const lines = summaryLines ?? [
|
|
149
|
+
...content.split("\n").slice(0, 6).map((line) => truncateToWidth2(safeTerminalText(line), 100)),
|
|
150
|
+
"View details for the complete review."
|
|
151
|
+
];
|
|
152
|
+
while (isCurrent() && !signal?.aborted) {
|
|
153
|
+
const choice = { value: "cancel" };
|
|
154
|
+
const menu = defineMenu({
|
|
155
|
+
start: "confirm",
|
|
156
|
+
screens: {
|
|
157
|
+
confirm: () => ({
|
|
158
|
+
kind: "choice",
|
|
159
|
+
title,
|
|
160
|
+
lines,
|
|
161
|
+
initialItemId: "cancel",
|
|
162
|
+
items: [
|
|
163
|
+
{ id: "cancel", label: "No, cancel" },
|
|
164
|
+
{ id: "approve", label: `Yes, ${confirmationLabel}` },
|
|
165
|
+
{ id: "details", label: "View details" }
|
|
166
|
+
],
|
|
167
|
+
action: "choose",
|
|
168
|
+
hint: "close"
|
|
169
|
+
})
|
|
170
|
+
},
|
|
171
|
+
actions: {
|
|
172
|
+
choose: ({ itemId }) => {
|
|
173
|
+
if (itemId === "approve" || itemId === "details") choice.value = itemId;
|
|
174
|
+
return { kind: "close" };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
const result = ctx.mode === "tui" && ctx.hasUI ? await runCustomInteraction(ctx, {
|
|
179
|
+
create: ({ tui, theme, keybindings, complete }) => createSyncConfirmation({ title, lines, confirmationLabel, tui, theme, keybindings, complete }),
|
|
180
|
+
signal,
|
|
181
|
+
isCurrent,
|
|
182
|
+
onError: () => {
|
|
183
|
+
}
|
|
184
|
+
}) : await runMenu(ctx, menu, { getState: () => void 0, signal, isCurrent, onError: () => {
|
|
185
|
+
} });
|
|
186
|
+
if (!isCurrent() || signal?.aborted) return false;
|
|
187
|
+
if (result.kind === "error") throw new Error("Sync review failed; no transfer was performed.");
|
|
188
|
+
if (result.kind === "unsupported")
|
|
189
|
+
throw new Error("Sync review requires observable TUI or RPC; review the plan before using --yes.");
|
|
190
|
+
if (result.kind === "completed") choice.value = result.value;
|
|
191
|
+
else if (result.kind !== "closed") return false;
|
|
192
|
+
if (choice.value === "approve") return true;
|
|
193
|
+
if (choice.value !== "details") return false;
|
|
194
|
+
const detail = await runDocumentReview(ctx, {
|
|
195
|
+
title: "Sync change details",
|
|
196
|
+
content,
|
|
197
|
+
format: { kind: "text" },
|
|
198
|
+
viewportSize: "adaptive",
|
|
199
|
+
hint: "back",
|
|
200
|
+
signal,
|
|
201
|
+
isCurrent,
|
|
202
|
+
onError: () => {
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
if (!isCurrent() || signal?.aborted) return false;
|
|
206
|
+
if (detail.kind === "error") throw new Error("Sync review failed; no transfer was performed.");
|
|
207
|
+
if (detail.kind !== "cancelled" || detail.reason !== "back") return false;
|
|
208
|
+
}
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export {
|
|
213
|
+
confirmMergeReview
|
|
214
|
+
};
|
|
215
|
+
//# sourceMappingURL=chunk-5XZOC7FD.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/ui/merge-review.ts", "../../src/ui/sync-confirmation.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ExtensionCommandContext, ExtensionContext } from \"@earendil-works/pi-coding-agent\";\nimport { truncateToWidth } from \"@earendil-works/pi-tui\";\nimport { defineMenu, runCustomInteraction, runDocumentReview, runMenu } from \"@narumitw/pi-tui-kit\";\nimport { createSyncConfirmation, type SyncConfirmationChoice } from \"./sync-confirmation.js\";\nimport { safeTerminalText } from \"./terminal-text.js\";\n\ntype ReviewContext = ExtensionCommandContext | ExtensionContext;\n\n/** One approval screen; full details are optional and never authorize a transfer. */\nexport async function confirmMergeReview(\n ctx: ReviewContext,\n title: string,\n content: string,\n signal: AbortSignal | undefined,\n isCurrent: () => boolean,\n confirmationLabel = \"apply changes\",\n summaryLines?: readonly string[],\n) {\n const lines = summaryLines ?? [\n ...content\n .split(\"\\n\")\n .slice(0, 6)\n .map((line) => truncateToWidth(safeTerminalText(line), 100)),\n \"View details for the complete review.\",\n ];\n while (isCurrent() && !signal?.aborted) {\n const choice: { value: \"cancel\" | \"approve\" | \"details\" } = { value: \"cancel\" };\n const menu = defineMenu<undefined, \"confirm\", \"choose\", ReviewContext>({\n start: \"confirm\",\n screens: {\n confirm: () => ({\n kind: \"choice\",\n title,\n lines,\n initialItemId: \"cancel\",\n items: [\n { id: \"cancel\", label: \"No, cancel\" },\n { id: \"approve\", label: `Yes, ${confirmationLabel}` },\n { id: \"details\", label: \"View details\" },\n ],\n action: \"choose\",\n hint: \"close\",\n }),\n },\n actions: {\n choose: ({ itemId }) => {\n if (itemId === \"approve\" || itemId === \"details\") choice.value = itemId;\n return { kind: \"close\" };\n },\n },\n });\n const result =\n ctx.mode === \"tui\" && ctx.hasUI\n ? await runCustomInteraction<SyncConfirmationChoice, ReviewContext>(ctx, {\n create: ({ tui, theme, keybindings, complete }) =>\n createSyncConfirmation({ title, lines, confirmationLabel, tui, theme, keybindings, complete }),\n signal,\n isCurrent,\n onError: () => {},\n })\n : await runMenu(ctx, menu, { getState: () => undefined, signal, isCurrent, onError: () => {} });\n if (!isCurrent() || signal?.aborted) return false;\n if (result.kind === \"error\") throw new Error(\"Sync review failed; no transfer was performed.\");\n if (result.kind === \"unsupported\")\n throw new Error(\"Sync review requires observable TUI or RPC; review the plan before using --yes.\");\n if (result.kind === \"completed\") choice.value = result.value;\n else if (result.kind !== \"closed\") return false;\n // Keep authorization separate from UI termination in both adapters.\n if (choice.value === \"approve\") return true;\n if (choice.value !== \"details\") return false;\n const detail = await runDocumentReview(ctx, {\n title: \"Sync change details\",\n content,\n format: { kind: \"text\" },\n viewportSize: \"adaptive\",\n hint: \"back\",\n signal,\n isCurrent,\n onError: () => {},\n });\n if (!isCurrent() || signal?.aborted) return false;\n if (detail.kind === \"error\") throw new Error(\"Sync review failed; no transfer was performed.\");\n if (detail.kind !== \"cancelled\" || detail.reason !== \"back\") return false;\n // A fresh menu discards remembered selection: returning from details always selects No.\n // Unlike Pi's native Yes-first dialog, approval requires moving off the safe default.\n }\n return false;\n}\n", "import type { KeybindingsManager, Theme } from \"@earendil-works/pi-coding-agent\";\nimport { Key, matchesKey, SelectList, type TUI, type TuiMouseEvent, truncateToWidth } from \"@earendil-works/pi-tui\";\nimport { HorizontalRule, hardWrapTerminalDocument, sanitizeTerminalText } from \"@narumitw/pi-tui-kit\";\n\nexport type SyncConfirmationChoice = \"cancel\" | \"approve\" | \"details\";\n\n/** Kit choice context is clipped on short terminals; this summary must remain reviewable. */\nexport function createSyncConfirmation(options: {\n title: string;\n lines: readonly string[];\n confirmationLabel: string;\n tui: TUI;\n theme: Theme;\n keybindings: KeybindingsManager;\n complete(choice: SyncConfirmationChoice): void;\n}) {\n const { tui, theme, keybindings } = options;\n const choices: SyncConfirmationChoice[] = [\"cancel\", \"approve\", \"details\"];\n const list = new SelectList(\n [\n { value: \"cancel\", label: \"No, cancel\" },\n { value: \"approve\", label: `Yes, ${sanitizeTerminalText(options.confirmationLabel)}` },\n { value: \"details\", label: \"View details\" },\n ],\n 3,\n {\n selectedPrefix: (text) => theme.fg(\"accent\", text),\n selectedText: (text) => theme.fg(\"accent\", text),\n description: (text) => theme.fg(\"muted\", text),\n scrollInfo: (text) => theme.fg(\"dim\", text),\n noMatch: (text) => theme.fg(\"muted\", text),\n },\n );\n const rule = new HorizontalRule({ ruleStyle: (text) => theme.fg(\"border\", text) });\n let selected = 0;\n let offset = 0;\n let maximumOffset = 0;\n let lastWidth = 0;\n let lastRows = 0;\n let seen = new Set<number>();\n let summary: readonly string[] = [];\n let reviewed = false;\n let disposed = false;\n let finished = false;\n let summaryStart = 0;\n let summaryEnd = 0;\n let actionsStart = 0;\n const finish = (choice: SyncConfirmationChoice) => {\n if (disposed || finished) return;\n finished = true;\n options.complete(choice);\n };\n const select = (index: number) => {\n selected = (index + choices.length) % choices.length;\n list.setSelectedIndex(selected);\n };\n list.onSelectionChange = (item) => {\n selected = Math.max(0, choices.indexOf(item.value as SyncConfirmationChoice));\n };\n list.onSelect = (item) => {\n const choice = choices[selected] ?? \"cancel\";\n if (item.value === choice && (choice !== \"approve\" || reviewed)) finish(choice);\n };\n const component = {\n render(width: number, expose = true): string[] {\n if (!Number.isFinite(width) || width <= 0) {\n reviewed = false;\n return [];\n }\n const cells = Math.floor(width);\n const rows = Math.max(1, Math.floor(tui.terminal.rows) - 3);\n if (cells !== lastWidth || rows !== lastRows) {\n // Reflow can expose previously unseen rows or hide the selected action.\n // Reset proof and selection rather than carry approval across a resize.\n summary = options.lines.flatMap((line) => hardWrapTerminalDocument(line, cells));\n seen = new Set();\n offset = 0;\n select(0);\n lastWidth = cells;\n lastRows = rows;\n }\n const framed = rows >= 9;\n const actions = list.render(cells);\n const viewport = Math.max(0, rows - actions.length - 3 - (framed ? 2 : 0));\n maximumOffset = Math.max(0, summary.length - viewport);\n offset = Math.min(offset, maximumOffset);\n const visible = summary.slice(offset, offset + viewport);\n if (expose) for (let index = offset; index < offset + visible.length; index++) seen.add(index);\n reviewed = viewport > 0 && seen.size === summary.length;\n // Describe actions without advertising configured keys that can be invalid,\n // aliased, or shadowed by an earlier cancellation/navigation binding.\n const hint = reviewed ? \"Select an action.\" : \"Navigation keys scroll the summary before selecting Yes.\";\n const body = [\n theme.fg(\"accent\", theme.bold(hardWrapTerminalDocument(options.title, cells).join(\" \"))),\n ...visible.map((line) => theme.fg(\"text\", line)),\n theme.fg(\n \"muted\",\n `Summary ${visible.length ? offset + 1 : 0}-${offset + visible.length}/${summary.length}${reviewed ? \"\" : \" \u2014 review before Yes\"}`,\n ),\n ...actions,\n theme.fg(\"dim\", hint),\n ];\n summaryStart = Number(framed) + 1;\n summaryEnd = summaryStart + visible.length;\n actionsStart = summaryEnd + 1;\n const frame = framed ? [...rule.render(cells), ...body, ...rule.render(cells)] : body;\n // Very small terminals cannot display the summary and all choices together.\n // Keep cancellation available, but never approve a zero-row summary viewport.\n return frame.slice(0, rows).map((line) => truncateToWidth(line, cells, \"\"));\n },\n invalidate() {\n list.invalidate();\n rule.invalidate();\n },\n handleInput(data: string) {\n if (disposed || finished) return;\n if (matchesKey(data, Key.ctrl(\"c\")) || keybindings.matches(data, \"tui.select.cancel\")) {\n finish(\"cancel\");\n return;\n }\n // Refresh height/width proof before handling an input racing with terminal resize.\n component.render(tui.terminal.columns ?? lastWidth, false);\n if (keybindings.matches(data, \"tui.select.up\")) {\n if (!reviewed || (selected === 0 && offset > 0)) offset = Math.max(0, offset - 1);\n else select(selected - 1);\n } else if (keybindings.matches(data, \"tui.select.down\")) {\n if (!reviewed || (selected === 0 && offset < maximumOffset)) offset = Math.min(maximumOffset, offset + 1);\n else select(selected + 1);\n } else if (keybindings.matches(data, \"tui.select.confirm\")) {\n const choice = choices[selected] ?? \"cancel\";\n if (choice !== \"approve\" || reviewed) finish(choice);\n }\n tui.requestRender();\n },\n handleMouse(event: TuiMouseEvent) {\n if (disposed || finished || event.width !== lastWidth) return undefined;\n component.render(event.width, false);\n if (event.y >= summaryStart && event.y < summaryEnd && event.type === \"wheel\" && event.wheelDelta) {\n offset = Math.max(0, Math.min(maximumOffset, offset + (event.wheelDelta < 0 ? -1 : 1)));\n select(0);\n tui.requestRender();\n return { handled: true, render: true };\n }\n if (event.y < actionsStart || event.y >= actionsStart + choices.length || event.y >= lastRows) return undefined;\n return list.handleMouse({ ...event, y: event.y - actionsStart, height: choices.length });\n },\n dispose() {\n disposed = true;\n seen.clear();\n },\n };\n return component;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["truncateToWidth", "truncateToWidth"]
|
|
7
|
+
}
|
|
@@ -6,72 +6,16 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
ensureStateDir,
|
|
8
8
|
lockPath
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-UPNVIUBN.ts";
|
|
10
10
|
import {
|
|
11
11
|
LOCKFILE_FS_ADAPTER,
|
|
12
12
|
LOCK_GUARD_STALE_MS,
|
|
13
|
-
LOCK_GUARD_UPDATE_MS
|
|
14
|
-
|
|
15
|
-
} from "./chunk-BVVP33U5.ts";
|
|
16
|
-
import {
|
|
17
|
-
encodeSnapshot
|
|
18
|
-
} from "./chunk-ZX2Q6THB.ts";
|
|
19
|
-
import {
|
|
20
|
-
agentDir,
|
|
21
|
-
createSnapshot,
|
|
22
|
-
sessionDirFromContext,
|
|
23
|
-
sessionSnapshotPathFromAbsolute
|
|
24
|
-
} from "./chunk-QOFMXPM5.ts";
|
|
25
|
-
import {
|
|
26
|
-
syncDirectory
|
|
27
|
-
} from "./chunk-HUZ5CR4W.ts";
|
|
28
|
-
|
|
29
|
-
// src/sync/sync-local.ts
|
|
30
|
-
import fs from "node:fs/promises";
|
|
31
|
-
import path from "node:path";
|
|
32
|
-
function protectedSessionPaths(ctx, sessionRoot = sessionDirFromContext(ctx)) {
|
|
33
|
-
const getSessionFile = ctx.sessionManager.getSessionFile;
|
|
34
|
-
if (typeof getSessionFile !== "function") return /* @__PURE__ */ new Set();
|
|
35
|
-
const sessionFile = getSessionFile.call(ctx.sessionManager);
|
|
36
|
-
const snapshotPath = sessionFile ? sessionSnapshotPathFromAbsolute(sessionFile, sessionRoot) : void 0;
|
|
37
|
-
return snapshotPath ? /* @__PURE__ */ new Set([snapshotPath]) : /* @__PURE__ */ new Set();
|
|
38
|
-
}
|
|
39
|
-
function captureMutationOwner(ctx, signal) {
|
|
40
|
-
const manager = ctx.sessionManager;
|
|
41
|
-
const file = manager.getSessionFile?.();
|
|
42
|
-
const id = manager.getSessionId?.();
|
|
43
|
-
const cwd = manager.getCwd?.();
|
|
44
|
-
const root = agentDir();
|
|
45
|
-
return () => {
|
|
46
|
-
throwIfAborted(signal);
|
|
47
|
-
if (ctx.sessionManager !== manager || manager.getSessionFile?.() !== file || manager.getSessionId?.() !== id || manager.getCwd?.() !== cwd || agentDir() !== root)
|
|
48
|
-
throw new Error("Session changed during file mutation; evidence retained for review.");
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
async function backupLocal(profile, options = {}, signal) {
|
|
52
|
-
throwIfAborted(signal);
|
|
53
|
-
const snapshot = await createSnapshot(profile, { ...options, signal });
|
|
54
|
-
throwIfAborted(signal);
|
|
55
|
-
const backupDirectory = path.join(stateDir(), "backups");
|
|
56
|
-
await fs.mkdir(backupDirectory, { recursive: true, mode: 448 });
|
|
57
|
-
throwIfAborted(signal);
|
|
58
|
-
const backupPath = path.join(backupDirectory, `${snapshot.id}.json.gz`);
|
|
59
|
-
const encoded = await encodeSnapshot(snapshot);
|
|
60
|
-
throwIfAborted(signal);
|
|
61
|
-
const handle = await fs.open(backupPath, "wx", 384);
|
|
62
|
-
try {
|
|
63
|
-
await handle.writeFile(encoded);
|
|
64
|
-
await handle.sync();
|
|
65
|
-
} finally {
|
|
66
|
-
await handle.close();
|
|
67
|
-
}
|
|
68
|
-
await syncDirectory(backupDirectory);
|
|
69
|
-
return backupPath;
|
|
70
|
-
}
|
|
13
|
+
LOCK_GUARD_UPDATE_MS
|
|
14
|
+
} from "./chunk-SWRVXVFD.ts";
|
|
71
15
|
|
|
72
16
|
// src/state/lock.ts
|
|
73
17
|
import { randomUUID } from "node:crypto";
|
|
74
|
-
import
|
|
18
|
+
import fs from "node:fs/promises";
|
|
75
19
|
import lockfile from "proper-lockfile";
|
|
76
20
|
var LOCK_STALE_MS = 30 * 60 * 1e3;
|
|
77
21
|
var MAX_PROCESS_ID = 2147483647;
|
|
@@ -104,12 +48,15 @@ async function withLock(command, fn, options = {}) {
|
|
|
104
48
|
if (rechecked.status !== "valid" || rechecked.lock.id !== inspection.lock.id || !isStaleLock(rechecked.lock)) {
|
|
105
49
|
throw new Error("pi-sync lock changed while preparing transaction recovery; retry.");
|
|
106
50
|
}
|
|
107
|
-
await
|
|
51
|
+
await fs.rm(lockPath(), { force: true });
|
|
108
52
|
inspection = { status: "missing" };
|
|
109
53
|
}
|
|
110
54
|
if (inspection.status === "valid") {
|
|
111
|
-
throw
|
|
112
|
-
|
|
55
|
+
throw Object.assign(
|
|
56
|
+
new Error(
|
|
57
|
+
`pi-sync is already running (${inspection.lock.command}, pid ${inspection.lock.pid}, started ${inspection.lock.startedAt}).`
|
|
58
|
+
),
|
|
59
|
+
{ code: "ELOCKED" }
|
|
113
60
|
);
|
|
114
61
|
}
|
|
115
62
|
if (inspection.status === "unreadable") {
|
|
@@ -117,7 +64,7 @@ async function withLock(command, fn, options = {}) {
|
|
|
117
64
|
"pi-sync lock metadata is unreadable. Run /sync unlock --stale after verifying no sync is running."
|
|
118
65
|
);
|
|
119
66
|
}
|
|
120
|
-
await
|
|
67
|
+
await fs.writeFile(lockPath(), JSON.stringify(lock, null, " "), { flag: "wx" });
|
|
121
68
|
guard.throwIfCompromised();
|
|
122
69
|
result = await fn();
|
|
123
70
|
guard.throwIfCompromised();
|
|
@@ -127,7 +74,7 @@ async function withLock(command, fn, options = {}) {
|
|
|
127
74
|
}
|
|
128
75
|
try {
|
|
129
76
|
const current = await readLock();
|
|
130
|
-
if (current?.id === lock.id) await
|
|
77
|
+
if (current?.id === lock.id) await fs.rm(lockPath(), { force: true });
|
|
131
78
|
} catch (error) {
|
|
132
79
|
if (!failed) {
|
|
133
80
|
failed = true;
|
|
@@ -146,7 +93,7 @@ async function withLock(command, fn, options = {}) {
|
|
|
146
93
|
}
|
|
147
94
|
async function inspectLock() {
|
|
148
95
|
try {
|
|
149
|
-
const text = await
|
|
96
|
+
const text = await fs.readFile(lockPath(), "utf8");
|
|
150
97
|
if (text.trim().length === 0) return { status: "unreadable" };
|
|
151
98
|
const parsed = JSON.parse(text);
|
|
152
99
|
return isLockFile(parsed) ? { status: "valid", lock: parsed } : { status: "unreadable" };
|
|
@@ -178,9 +125,9 @@ function isStaleLock(lock) {
|
|
|
178
125
|
}
|
|
179
126
|
}
|
|
180
127
|
async function unlock(ctx, options) {
|
|
181
|
-
|
|
128
|
+
throwIfAborted(options.signal);
|
|
182
129
|
await ensureStateDir();
|
|
183
|
-
|
|
130
|
+
throwIfAborted(options.signal);
|
|
184
131
|
let guard;
|
|
185
132
|
try {
|
|
186
133
|
guard = await acquireGuard();
|
|
@@ -205,9 +152,9 @@ async function unlock(ctx, options) {
|
|
|
205
152
|
if (failed) throw failure;
|
|
206
153
|
}
|
|
207
154
|
async function unlockGuarded(ctx, options) {
|
|
208
|
-
|
|
155
|
+
throwIfAborted(options.signal);
|
|
209
156
|
let inspection = await inspectLock();
|
|
210
|
-
|
|
157
|
+
throwIfAborted(options.signal);
|
|
211
158
|
if (inspection.status === "missing") {
|
|
212
159
|
ctx.ui.notify("No pi-sync lock is present.", "info");
|
|
213
160
|
return;
|
|
@@ -221,10 +168,10 @@ async function unlockGuarded(ctx, options) {
|
|
|
221
168
|
return;
|
|
222
169
|
}
|
|
223
170
|
inspection = await inspectLock();
|
|
224
|
-
|
|
171
|
+
throwIfAborted(options.signal);
|
|
225
172
|
if (inspection.status === "unreadable") {
|
|
226
|
-
|
|
227
|
-
await
|
|
173
|
+
throwIfAborted(options.signal);
|
|
174
|
+
await fs.rm(lockPath(), { force: true });
|
|
228
175
|
if (!options.signal?.aborted) {
|
|
229
176
|
ctx.ui.notify(
|
|
230
177
|
"Removed unreadable pi-sync lock. No settings, files, sync state, or remote data were changed.",
|
|
@@ -242,16 +189,12 @@ async function unlockGuarded(ctx, options) {
|
|
|
242
189
|
ctx.ui.notify("Lock owner is still live; refusing to remove it.", "warning");
|
|
243
190
|
return;
|
|
244
191
|
}
|
|
245
|
-
|
|
246
|
-
await
|
|
192
|
+
throwIfAborted(options.signal);
|
|
193
|
+
await fs.rm(lockPath(), { force: true });
|
|
247
194
|
if (!options.signal?.aborted) {
|
|
248
195
|
ctx.ui.notify("Removed stale pi-sync lock. No settings, files, sync state, or remote data were changed.", "info");
|
|
249
196
|
}
|
|
250
197
|
}
|
|
251
|
-
function throwIfAborted2(signal) {
|
|
252
|
-
if (!signal?.aborted) return;
|
|
253
|
-
throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError");
|
|
254
|
-
}
|
|
255
198
|
async function releaseGuard(guard) {
|
|
256
199
|
try {
|
|
257
200
|
await guard.release();
|
|
@@ -286,11 +229,15 @@ async function describeHeldLock() {
|
|
|
286
229
|
return new Error("pi-sync lock owner exited; retry shortly while the lock guard expires.");
|
|
287
230
|
}
|
|
288
231
|
if (current) {
|
|
289
|
-
return
|
|
290
|
-
`Pi-sync is currently running (${current.command}, pid ${current.pid}, started ${current.startedAt}).`
|
|
232
|
+
return Object.assign(
|
|
233
|
+
new Error(`Pi-sync is currently running (${current.command}, pid ${current.pid}, started ${current.startedAt}).`),
|
|
234
|
+
{ code: "ELOCKED" }
|
|
291
235
|
);
|
|
292
236
|
}
|
|
293
|
-
return
|
|
237
|
+
return Object.assign(
|
|
238
|
+
new Error("Pi-sync is currently running (lock metadata is unreadable or still being written)."),
|
|
239
|
+
{ code: "ELOCKED" }
|
|
240
|
+
);
|
|
294
241
|
}
|
|
295
242
|
function isLockHeldError(error) {
|
|
296
243
|
return error.code === "ELOCKED";
|
|
@@ -306,9 +253,6 @@ export {
|
|
|
306
253
|
inspectLock,
|
|
307
254
|
isLockGuardHeld,
|
|
308
255
|
isStaleLock,
|
|
309
|
-
unlock
|
|
310
|
-
protectedSessionPaths,
|
|
311
|
-
captureMutationOwner,
|
|
312
|
-
backupLocal
|
|
256
|
+
unlock
|
|
313
257
|
};
|
|
314
|
-
//# sourceMappingURL=chunk-
|
|
258
|
+
//# sourceMappingURL=chunk-AAEEHR7H.ts.map
|