@narumitw/pi-sync 0.51.1 → 0.52.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.
Files changed (181) hide show
  1. package/README.md +30 -5
  2. package/dist/chunks/{chunk-7KHKWL3Q.ts → chunk-2CBF6SMT.ts} +8 -7
  3. package/dist/chunks/chunk-2CBF6SMT.ts.map +7 -0
  4. package/dist/chunks/{chunk-YFADF5ZQ.ts → chunk-2DI6PDZV.ts} +5 -1
  5. package/dist/chunks/chunk-2DI6PDZV.ts.map +7 -0
  6. package/dist/chunks/{chunk-4MPIH46N.ts → chunk-3B4Z2SKV.ts} +40 -6
  7. package/dist/chunks/chunk-3B4Z2SKV.ts.map +7 -0
  8. package/dist/chunks/{chunk-YPXGU5OR.ts → chunk-4QVPTIZB.ts} +16 -6
  9. package/dist/chunks/{chunk-YPXGU5OR.ts.map → chunk-4QVPTIZB.ts.map} +2 -2
  10. package/dist/chunks/{chunk-UA7RXOIT.ts → chunk-4SBTAVFL.ts} +6 -6
  11. package/dist/chunks/{chunk-6546TS54.ts → chunk-5RZPINUQ.ts} +7 -4
  12. package/dist/chunks/{chunk-6546TS54.ts.map → chunk-5RZPINUQ.ts.map} +2 -2
  13. package/dist/chunks/chunk-76NB4R7U.ts +107 -0
  14. package/dist/chunks/chunk-76NB4R7U.ts.map +7 -0
  15. package/dist/chunks/{chunk-DM4X4FIA.ts → chunk-7D6RDCVC.ts} +8 -10
  16. package/dist/chunks/{chunk-DM4X4FIA.ts.map → chunk-7D6RDCVC.ts.map} +2 -2
  17. package/dist/chunks/chunk-A6HU4ERM.ts +124 -0
  18. package/dist/chunks/chunk-A6HU4ERM.ts.map +7 -0
  19. package/dist/chunks/chunk-AMJRUXFT.ts +17 -0
  20. package/dist/chunks/chunk-AMJRUXFT.ts.map +7 -0
  21. package/dist/chunks/chunk-ANWNQIR7.ts +1013 -0
  22. package/dist/chunks/chunk-ANWNQIR7.ts.map +7 -0
  23. package/dist/chunks/{chunk-T56RNRZJ.ts → chunk-BDB4IDM3.ts} +19 -10
  24. package/dist/chunks/chunk-BDB4IDM3.ts.map +7 -0
  25. package/dist/chunks/{chunk-2RZSQWBH.ts → chunk-CHSIAC4N.ts} +5 -5
  26. package/dist/chunks/{chunk-2RZSQWBH.ts.map → chunk-CHSIAC4N.ts.map} +2 -2
  27. package/dist/chunks/chunk-HUZ5CR4W.ts +110 -0
  28. package/dist/chunks/chunk-HUZ5CR4W.ts.map +7 -0
  29. package/dist/chunks/{chunk-QLSN7AGX.ts → chunk-IJ6VV6FN.ts} +81 -19
  30. package/dist/chunks/chunk-IJ6VV6FN.ts.map +7 -0
  31. package/dist/chunks/chunk-NLS2NV3K.ts +1746 -0
  32. package/dist/chunks/chunk-NLS2NV3K.ts.map +7 -0
  33. package/dist/chunks/chunk-ORQRHEEO.ts +362 -0
  34. package/dist/chunks/chunk-ORQRHEEO.ts.map +7 -0
  35. package/dist/chunks/chunk-QE7NZZWT.ts +380 -0
  36. package/dist/chunks/chunk-QE7NZZWT.ts.map +7 -0
  37. package/dist/chunks/{chunk-HGJH56KR.ts → chunk-QHSQCK7L.ts} +4 -4
  38. package/dist/chunks/chunk-QOFMXPM5.ts +735 -0
  39. package/dist/chunks/chunk-QOFMXPM5.ts.map +7 -0
  40. package/dist/chunks/{chunk-DZUSTGQV.ts → chunk-UQ3ALBIB.ts} +4 -4
  41. package/dist/chunks/{chunk-Q32CQWPM.ts → chunk-V6VZ5TLG.ts} +13 -6
  42. package/dist/chunks/chunk-V6VZ5TLG.ts.map +7 -0
  43. package/dist/chunks/{chunk-E42GXD6D.ts → chunk-WKPX2SIC.ts} +4 -4
  44. package/dist/chunks/chunk-ZW5ZLGBI.ts +154 -0
  45. package/dist/chunks/chunk-ZW5ZLGBI.ts.map +7 -0
  46. package/dist/chunks/{chunk-6NT3YFGY.ts → chunk-ZX2Q6THB.ts} +8 -3
  47. package/dist/chunks/{chunk-6NT3YFGY.ts.map → chunk-ZX2Q6THB.ts.map} +2 -2
  48. package/dist/chunks/conflict-artifacts-BB2FZMIE.ts +27 -0
  49. package/dist/chunks/conflict-review-4WPGCXFB.ts +175 -0
  50. package/dist/chunks/conflict-review-4WPGCXFB.ts.map +7 -0
  51. package/dist/chunks/{file-selection-RAVHRYFG.ts → file-selection-WZX7HAY2.ts} +8 -7
  52. package/dist/chunks/{file-selection-RAVHRYFG.ts.map → file-selection-WZX7HAY2.ts.map} +1 -1
  53. package/dist/chunks/{git-backend-IAANDL3K.ts → git-backend-HLGKUQJT.ts} +19 -7
  54. package/dist/chunks/git-backend-HLGKUQJT.ts.map +7 -0
  55. package/dist/chunks/local-fields-6DGYOOIK.ts +22 -0
  56. package/dist/chunks/manager-result-dispatcher-VIDT3QBU.ts +23 -0
  57. package/dist/chunks/{manager-ui-763NEKJY.ts → manager-ui-HSMHS3OK.ts} +236 -57
  58. package/dist/chunks/manager-ui-HSMHS3OK.ts.map +7 -0
  59. package/dist/chunks/merge-baseline-store-EVQAAML6.ts +22 -0
  60. package/dist/chunks/remote-selection-ui-SMF5CLFO.ts +22 -0
  61. package/dist/chunks/{s3-backend-XGXUOPUN.ts → s3-backend-DJ2NT3SZ.ts} +16 -9
  62. package/dist/chunks/s3-backend-DJ2NT3SZ.ts.map +7 -0
  63. package/dist/chunks/{setup-switch-5G2VNJHL.ts → setup-switch-KTUWL6DE.ts} +9 -6
  64. package/dist/chunks/{setup-wizard-YIEWGX32.ts → setup-wizard-GSDPDFC4.ts} +10 -7
  65. package/dist/chunks/{setup-wizard-YIEWGX32.ts.map → setup-wizard-GSDPDFC4.ts.map} +1 -1
  66. package/dist/chunks/{snapshot-JL5DFSXI.ts → snapshot-THS5EY2L.ts} +6 -9
  67. package/dist/chunks/snapshot-THS5EY2L.ts.map +7 -0
  68. package/dist/chunks/{sync-inspection-P3ENJLNF.ts → sync-inspection-VBJF2FWC.ts} +10 -10
  69. package/dist/chunks/sync-inspection-VBJF2FWC.ts.map +7 -0
  70. package/dist/chunks/{sync-operations-AR76EB6C.ts → sync-operations-RHNECOOG.ts} +309 -508
  71. package/dist/chunks/sync-operations-RHNECOOG.ts.map +7 -0
  72. package/dist/chunks/{sync-state-VHJUZ4YG.ts → sync-state-CFL4RRRB.ts} +6 -6
  73. package/dist/chunks/sync-state-CFL4RRRB.ts.map +7 -0
  74. package/dist/chunks/{webdav-backend-3EGUONAT.ts → webdav-backend-ETQGSI6I.ts} +13 -7
  75. package/dist/chunks/webdav-backend-ETQGSI6I.ts.map +7 -0
  76. package/dist/index.ts +142 -43
  77. package/dist/index.ts.map +2 -2
  78. package/docs/content-merge-audit.md +156 -0
  79. package/docs/merge-implementation-audit.md +312 -0
  80. package/docs/settings-merge-audit.md +153 -0
  81. package/docs/settings.md +36 -0
  82. package/package.json +5 -5
  83. package/src/backends/git/git-backend.ts +2 -0
  84. package/src/backends/git/git-storage.ts +8 -3
  85. package/src/backends/s3/s3-backend.ts +2 -1
  86. package/src/backends/webdav/webdav-backend.ts +2 -1
  87. package/src/commands/command-execution.ts +10 -0
  88. package/src/commands/command.ts +4 -0
  89. package/src/settings/config.ts +12 -0
  90. package/src/settings/settings-management.ts +2 -2
  91. package/src/settings/settings-types.ts +14 -1
  92. package/src/settings/settings-validation.ts +24 -3
  93. package/src/snapshot/session-paths.ts +34 -7
  94. package/src/snapshot/snapshot-apply.ts +81 -32
  95. package/src/snapshot/snapshot-case-replacement.ts +80 -0
  96. package/src/snapshot/snapshot-codec.ts +6 -1
  97. package/src/snapshot/snapshot-root-transition.ts +154 -0
  98. package/src/snapshot/snapshot-transaction-plan.ts +72 -0
  99. package/src/snapshot/snapshot-transaction.ts +844 -102
  100. package/src/snapshot/snapshot-types.ts +5 -0
  101. package/src/state/json-file.ts +32 -5
  102. package/src/state/merge-baseline-store.ts +182 -0
  103. package/src/state/mutation-directory-sync.ts +34 -0
  104. package/src/state/state-types.ts +4 -0
  105. package/src/state/sync-state-store.ts +14 -1
  106. package/src/sync/automatic-sync.ts +11 -3
  107. package/src/sync/conflict-artifact-ownership.ts +63 -0
  108. package/src/sync/conflict-artifacts.ts +195 -0
  109. package/src/sync/conflict-groups.ts +68 -0
  110. package/src/sync/conflict-preview.ts +56 -0
  111. package/src/sync/conflict-resolution.ts +93 -0
  112. package/src/sync/conflict-retention.ts +72 -0
  113. package/src/sync/conflict-review.ts +95 -0
  114. package/src/sync/content-conflicts.ts +73 -0
  115. package/src/sync/field-migration.ts +39 -0
  116. package/src/sync/file-merge-planner.ts +143 -0
  117. package/src/sync/json-document.ts +184 -0
  118. package/src/sync/local-fields.ts +134 -0
  119. package/src/sync/merge-apply.ts +428 -0
  120. package/src/sync/merge-journal.ts +219 -0
  121. package/src/sync/merged-sync.ts +723 -0
  122. package/src/sync/partial-progress.ts +54 -0
  123. package/src/sync/remote-snapshot.ts +6 -0
  124. package/src/sync/session-merge.ts +194 -0
  125. package/src/sync/settings-conflicts.ts +52 -0
  126. package/src/sync/settings-merge.ts +57 -0
  127. package/src/sync/startup-check.ts +90 -5
  128. package/src/sync/sync-inspection.ts +1 -0
  129. package/src/sync/sync-local.ts +61 -0
  130. package/src/sync/sync-mutations.ts +256 -114
  131. package/src/sync/sync-queries.ts +6 -2
  132. package/src/sync/sync-state.ts +14 -5
  133. package/src/sync/text-merge.ts +133 -0
  134. package/src/sync-extension.ts +8 -0
  135. package/src/ui/manager-state.ts +12 -1
  136. package/src/ui/manager-ui.ts +10 -0
  137. package/src/ui/merge-review.ts +28 -0
  138. package/src/ui/remote-selection-ui.ts +1 -0
  139. package/src/ui/settings-ui.ts +183 -2
  140. package/src/ui/setup/setup-actions.ts +2 -2
  141. package/src/ui/setup/setup-switcher.ts +2 -1
  142. package/src/ui/storage-connections-ui.ts +11 -7
  143. package/src/ui/sync-attention.ts +3 -0
  144. package/src/ui/sync-format.ts +16 -12
  145. package/src/ui/sync-setups-ui.ts +1 -0
  146. package/src/ui/terminal-text.ts +9 -2
  147. package/dist/chunks/chunk-4AIHXRKE.ts +0 -197
  148. package/dist/chunks/chunk-4AIHXRKE.ts.map +0 -7
  149. package/dist/chunks/chunk-4MPIH46N.ts.map +0 -7
  150. package/dist/chunks/chunk-7KHKWL3Q.ts.map +0 -7
  151. package/dist/chunks/chunk-EEJPYTWE.ts +0 -348
  152. package/dist/chunks/chunk-EEJPYTWE.ts.map +0 -7
  153. package/dist/chunks/chunk-IVXEDPAD.ts +0 -370
  154. package/dist/chunks/chunk-IVXEDPAD.ts.map +0 -7
  155. package/dist/chunks/chunk-MSSW6F3R.ts +0 -12
  156. package/dist/chunks/chunk-MSSW6F3R.ts.map +0 -7
  157. package/dist/chunks/chunk-OVDTF5CH.ts +0 -90
  158. package/dist/chunks/chunk-OVDTF5CH.ts.map +0 -7
  159. package/dist/chunks/chunk-Q32CQWPM.ts.map +0 -7
  160. package/dist/chunks/chunk-QLSN7AGX.ts.map +0 -7
  161. package/dist/chunks/chunk-SWUXA3MH.ts +0 -18
  162. package/dist/chunks/chunk-SWUXA3MH.ts.map +0 -7
  163. package/dist/chunks/chunk-T56RNRZJ.ts.map +0 -7
  164. package/dist/chunks/chunk-YFADF5ZQ.ts.map +0 -7
  165. package/dist/chunks/git-backend-IAANDL3K.ts.map +0 -7
  166. package/dist/chunks/manager-result-dispatcher-OTUMQM26.ts +0 -23
  167. package/dist/chunks/manager-ui-763NEKJY.ts.map +0 -7
  168. package/dist/chunks/remote-selection-ui-TZTJF32Y.ts +0 -22
  169. package/dist/chunks/s3-backend-XGXUOPUN.ts.map +0 -7
  170. package/dist/chunks/sync-operations-AR76EB6C.ts.map +0 -7
  171. package/dist/chunks/webdav-backend-3EGUONAT.ts.map +0 -7
  172. /package/dist/chunks/{chunk-UA7RXOIT.ts.map → chunk-4SBTAVFL.ts.map} +0 -0
  173. /package/dist/chunks/{chunk-HGJH56KR.ts.map → chunk-QHSQCK7L.ts.map} +0 -0
  174. /package/dist/chunks/{chunk-DZUSTGQV.ts.map → chunk-UQ3ALBIB.ts.map} +0 -0
  175. /package/dist/chunks/{chunk-E42GXD6D.ts.map → chunk-WKPX2SIC.ts.map} +0 -0
  176. /package/dist/chunks/{manager-result-dispatcher-OTUMQM26.ts.map → conflict-artifacts-BB2FZMIE.ts.map} +0 -0
  177. /package/dist/chunks/{remote-selection-ui-TZTJF32Y.ts.map → local-fields-6DGYOOIK.ts.map} +0 -0
  178. /package/dist/chunks/{setup-switch-5G2VNJHL.ts.map → manager-result-dispatcher-VIDT3QBU.ts.map} +0 -0
  179. /package/dist/chunks/{snapshot-JL5DFSXI.ts.map → merge-baseline-store-EVQAAML6.ts.map} +0 -0
  180. /package/dist/chunks/{sync-inspection-P3ENJLNF.ts.map → remote-selection-ui-SMF5CLFO.ts.map} +0 -0
  181. /package/dist/chunks/{sync-state-VHJUZ4YG.ts.map → setup-switch-KTUWL6DE.ts.map} +0 -0
@@ -1,370 +0,0 @@
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
-
4
- // src/paths.ts
5
- import path from "node:path";
6
- function isDeniedPath(relativePath) {
7
- const normalized = toPosix(relativePath);
8
- const lower = normalized.toLowerCase();
9
- const segments = lower.split("/");
10
- const base = path.posix.basename(lower);
11
- return segments.includes("node_modules") || segments.includes(".git") || segments.includes(".pisync") || segments.includes("pi-sync") || segments.includes(".pi-sync-state-migration.lock") || base === ".env" || base.startsWith(".env.") || base.endsWith(".env") || base.includes("secret") || base.includes("token") || base === "pi-sync.json" || base.startsWith("pi-sync.json.") || base.startsWith(".pi-sync.json.") || base === "pi-sync.local.json" || base.startsWith("pi-sync.local.json.") || base.startsWith(".pi-sync.local.json.");
12
- }
13
- function isPathInside(parent, child) {
14
- const relative = path.relative(path.resolve(parent), path.resolve(child));
15
- return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
16
- }
17
- function safeJoin(root, relativePath) {
18
- const target = path.resolve(root, relativePath);
19
- assertWithinRoot(root, target, relativePath);
20
- return target;
21
- }
22
- function assertWithinRoot(root, target, label = target) {
23
- const resolvedRoot = path.resolve(root);
24
- const resolvedTarget = path.resolve(target);
25
- if (resolvedTarget !== resolvedRoot && !resolvedTarget.startsWith(`${resolvedRoot}${path.sep}`)) {
26
- throw new Error(`Unsafe path in snapshot: ${label}`);
27
- }
28
- }
29
- function encodeKey(key) {
30
- return key.split("/").map(encodeURIComponent).join("/");
31
- }
32
- function posixJoin(...parts) {
33
- return parts.map((part) => trimSlashes(part)).filter(Boolean).join("/");
34
- }
35
- function parentPaths(relativePath) {
36
- const results = [];
37
- let index = relativePath.lastIndexOf("/");
38
- while (index > 0) {
39
- results.push(relativePath.slice(0, index));
40
- index = relativePath.lastIndexOf("/", index - 1);
41
- }
42
- return results;
43
- }
44
- function toPosix(value) {
45
- return value.split(path.sep).join("/");
46
- }
47
- function trimSlashes(value) {
48
- return value.replace(/^\/+|\/+$/g, "");
49
- }
50
-
51
- // src/sync/sync-policy.ts
52
- import path2 from "node:path";
53
- var BUILT_IN_SYNC_ROOTS = [
54
- "settings.json",
55
- "keybindings.json",
56
- "models.json",
57
- "AGENTS.md",
58
- "APPEND_SYSTEM.md",
59
- "skills",
60
- "prompts",
61
- "themes",
62
- "extensions"
63
- ];
64
- var DEFAULT_SYNC_INCLUDE = [...BUILT_IN_SYNC_ROOTS];
65
- var SNAPSHOT_SELECTION_VERSION = 1;
66
- var MAX_SYNC_INCLUDE_ITEMS = 1024;
67
- var MAX_SYNC_INCLUDE_PATH_BYTES = 4096;
68
- var MAX_SYNC_INCLUDE_TOTAL_BYTES = 256 * 1024;
69
- var BUILT_IN_BY_LOWER = new Map(
70
- BUILT_IN_SYNC_ROOTS.map((fileName) => [fileName.toLowerCase(), fileName])
71
- );
72
- var TOP_LEVEL_FILE_PATHS = new Map(
73
- BUILT_IN_SYNC_ROOTS.filter((fileName) => fileName.includes(".")).map((fileName) => [
74
- fileName.toLowerCase(),
75
- fileName
76
- ])
77
- );
78
- var TOP_LEVEL_DIRS = new Set(BUILT_IN_SYNC_ROOTS.filter((fileName) => !fileName.includes(".")));
79
- var RESERVED_TOP_LEVEL_NAMES = /* @__PURE__ */ new Set([...BUILT_IN_BY_LOWER.keys(), "sessions"]);
80
- function normalizeSyncInclude(value) {
81
- if (!Array.isArray(value)) {
82
- throw new Error("Invalid pi-sync settings: sync.include must be an array.");
83
- }
84
- if (value.length > MAX_SYNC_INCLUDE_ITEMS) {
85
- throw new Error(`Invalid pi-sync settings: sync.include has too many items; limit: ${MAX_SYNC_INCLUDE_ITEMS}.`);
86
- }
87
- const result = [];
88
- const seen = /* @__PURE__ */ new Set();
89
- const pathRoot = { children: /* @__PURE__ */ new Map() };
90
- let totalBytes = 0;
91
- for (const item of value) {
92
- if (typeof item !== "string") {
93
- throw new Error("Invalid pi-sync settings: sync.include items must be strings.");
94
- }
95
- const itemBytes = Buffer.byteLength(item, "utf8");
96
- if (itemBytes > MAX_SYNC_INCLUDE_PATH_BYTES) {
97
- throw new Error(
98
- `Invalid pi-sync settings: sync.include item is too long; limit: ${MAX_SYNC_INCLUDE_PATH_BYTES} bytes.`
99
- );
100
- }
101
- totalBytes += itemBytes;
102
- if (totalBytes > MAX_SYNC_INCLUDE_TOTAL_BYTES) {
103
- throw new Error(
104
- `Invalid pi-sync settings: sync.include is too large; limit: ${MAX_SYNC_INCLUDE_TOTAL_BYTES} bytes.`
105
- );
106
- }
107
- const trimmed = item.trim();
108
- const builtIn = BUILT_IN_BY_LOWER.get(trimmed.toLowerCase());
109
- const normalized = builtIn ?? (trimmed.toLowerCase() === "sessions" ? "sessions" : trimmed);
110
- if (!builtIn && normalized !== "sessions") validateAgentRelativeInclude(normalized);
111
- const identity = normalized.toLowerCase();
112
- if (seen.has(identity)) {
113
- throw new Error(`Invalid pi-sync settings: duplicate sync.include item: ${item}`);
114
- }
115
- addIncludePath(pathRoot, identity, item);
116
- seen.add(identity);
117
- result.push(normalized);
118
- }
119
- return result;
120
- }
121
- function addIncludePath(root, identity, source) {
122
- let node = root;
123
- for (const segment of identity.split("/")) {
124
- if (node.selected !== void 0) throwOverlappingInclude(source);
125
- let child = node.children.get(segment);
126
- if (!child) {
127
- child = { children: /* @__PURE__ */ new Map() };
128
- node.children.set(segment, child);
129
- }
130
- node = child;
131
- }
132
- if (node.children.size > 0) throwOverlappingInclude(source);
133
- node.selected = identity;
134
- }
135
- function throwOverlappingInclude(item) {
136
- throw new Error(`Invalid pi-sync settings: overlapping sync.include items are ambiguous: ${item}`);
137
- }
138
- function validateAgentRelativeInclude(value) {
139
- const normalized = toPosix(value);
140
- const topLevel = normalized.split("/")[0]?.toLowerCase();
141
- if (!normalized || normalized === "." || normalized === ".." || normalized.startsWith("../") || path2.posix.isAbsolute(normalized) || normalized.includes("\\") || path2.posix.normalize(normalized) !== normalized || // biome-ignore lint/suspicious/noControlCharactersInRegex: Include paths cannot contain controls.
142
- /[\u0000-\u001f\u007f-\u009f]/u.test(normalized)) {
143
- throw new Error(`Invalid pi-sync settings: sync.include item must be a safe agent-relative path: ${value}`);
144
- }
145
- if (isDeniedPath(normalized)) {
146
- throw new Error(`Invalid pi-sync settings: ${value} cannot be synced.`);
147
- }
148
- if (topLevel && RESERVED_TOP_LEVEL_NAMES.has(topLevel)) {
149
- throw new Error(
150
- `Invalid pi-sync settings: use the canonical ${topLevel} root instead of a nested sync.include path.`
151
- );
152
- }
153
- }
154
- function portableSnapshotSelection(value) {
155
- const selection = value;
156
- if (!selection || typeof selection !== "object" || Array.isArray(selection) || selection.version !== SNAPSHOT_SELECTION_VERSION || !Object.hasOwn(selection, "include") || Object.keys(selection).some((key) => key !== "version" && key !== "include")) {
157
- throw new Error("Invalid snapshot selection policy.");
158
- }
159
- return {
160
- version: SNAPSHOT_SELECTION_VERSION,
161
- include: normalizeSyncInclude(selection.include)
162
- };
163
- }
164
- function snapshotSelectionInclude(snapshot) {
165
- return snapshot.selection === void 0 ? void 0 : portableSnapshotSelection(snapshot.selection).include;
166
- }
167
- function selectionForSnapshot(include) {
168
- return { version: SNAPSHOT_SELECTION_VERSION, include: normalizeSyncInclude(include) };
169
- }
170
- function sameSyncInclude(left, right) {
171
- const normalizedLeft = normalizeSyncInclude(left);
172
- const normalizedRight = normalizeSyncInclude(right);
173
- return normalizedLeft.length === normalizedRight.length && normalizedLeft.every((item, index) => item === normalizedRight[index]);
174
- }
175
- function compareSyncInclude(local, remote) {
176
- const localInclude = normalizeSyncInclude(local);
177
- const remoteInclude = normalizeSyncInclude(remote);
178
- const localSet = new Set(localInclude);
179
- const remoteSet = new Set(remoteInclude);
180
- return {
181
- same: sameSyncInclude(localInclude, remoteInclude),
182
- remoteOnly: remoteInclude.filter((item) => !localSet.has(item)),
183
- localOnly: localInclude.filter((item) => !remoteSet.has(item))
184
- };
185
- }
186
- function inspectRemoteSelection(localInclude, snapshot) {
187
- const remoteInclude = snapshotSelectionInclude(snapshot);
188
- if (!remoteInclude) {
189
- return { kind: "legacy", discovered: discoverLegacySnapshotInclude(snapshot) };
190
- }
191
- const comparison = compareSyncInclude(localInclude, remoteInclude);
192
- return comparison.same ? { kind: "same", include: remoteInclude } : { kind: "different", include: remoteInclude, ...comparison };
193
- }
194
- var RemoteSelectionMismatchError = class extends Error {
195
- decision;
196
- setupName;
197
- localInclude;
198
- remoteInclude;
199
- constructor(setupName, localInclude, remoteInclude, configIdentity = JSON.stringify([setupName, normalizeSyncInclude(localInclude)])) {
200
- const local = normalizeSyncInclude(localInclude);
201
- const remote = normalizeSyncInclude(remoteInclude);
202
- super(formatRemoteSelectionMismatch(setupName, local, remote));
203
- this.name = "RemoteSelectionMismatchError";
204
- this.setupName = setupName;
205
- this.localInclude = local;
206
- this.remoteInclude = remote;
207
- this.decision = {
208
- setupName,
209
- configIdentity,
210
- localInclude: [...local],
211
- remoteInclude: [...remote]
212
- };
213
- }
214
- };
215
- function remoteSelectionMismatch(config, remoteInclude, configIdentity) {
216
- return new RemoteSelectionMismatchError(config.setupName, config.include, remoteInclude, configIdentity);
217
- }
218
- function formatRemoteSelectionMismatch(setupName, localInclude, remoteInclude) {
219
- const comparison = compareSyncInclude(localInclude, remoteInclude);
220
- const lines = [
221
- `Synced content differs for sync setup \u201C${stripTerminalControls(setupName)}\u201D.`,
222
- `Remote-only: ${comparison.remoteOnly.join(", ") || "none"}`,
223
- `This-device-only: ${comparison.localOnly.join(", ") || "none"}`
224
- ];
225
- if (comparison.remoteOnly.length === 0 && comparison.localOnly.length === 0) {
226
- lines.push(
227
- "Only ordering differs.",
228
- `Remote order: ${remoteInclude.join(", ") || "none"}`,
229
- `This device order: ${localInclude.join(", ") || "none"}`
230
- );
231
- }
232
- lines.push("Run /sync in TUI to review both content lists and choose what happens next.");
233
- return lines.join("\n");
234
- }
235
- function stripTerminalControls(value) {
236
- return value.replace(/[\u0000-\u001f\u007f-\u009f]/gu, "?");
237
- }
238
- function discoverLegacySnapshotInclude(snapshot) {
239
- const builtIns = /* @__PURE__ */ new Set();
240
- const custom = /* @__PURE__ */ new Set();
241
- let sessions = false;
242
- for (const file of snapshot.files) {
243
- const normalized = toPosix(file.path);
244
- if (!normalized || file.path.includes("\\") || normalized.length > 4096 || normalized.startsWith("../") || path2.posix.isAbsolute(normalized) || path2.posix.normalize(normalized) !== normalized || // biome-ignore lint/suspicious/noControlCharactersInRegex: Ignore unsafe legacy paths.
245
- /[\u0000-\u001f\u007f-\u009f]/u.test(normalized) || isDeniedPath(normalized)) {
246
- continue;
247
- }
248
- const [topLevel, ...rest] = normalized.split("/");
249
- if (!topLevel) continue;
250
- if (topLevel === "sessions" && rest.length > 0) {
251
- sessions = true;
252
- continue;
253
- }
254
- const builtIn = BUILT_IN_BY_LOWER.get(topLevel.toLowerCase());
255
- if (builtIn && (TOP_LEVEL_DIRS.has(builtIn) && rest.length > 0 || !TOP_LEVEL_DIRS.has(builtIn) && rest.length === 0)) {
256
- builtIns.add(builtIn);
257
- continue;
258
- }
259
- if (isSafeCustomIncludePath(topLevel)) custom.add(topLevel);
260
- }
261
- return [
262
- ...BUILT_IN_SYNC_ROOTS.filter((item) => builtIns.has(item)),
263
- ...[...custom].sort((left, right) => left.localeCompare(right)),
264
- ...sessions ? ["sessions"] : []
265
- ];
266
- }
267
- function syncIncludeSelection(value) {
268
- const include = normalizeSyncInclude(value);
269
- const builtIns = include.filter((item) => BUILT_IN_BY_LOWER.has(item.toLowerCase()));
270
- const custom = include.filter((item) => item !== "sessions" && !BUILT_IN_BY_LOWER.has(item.toLowerCase()));
271
- return { include, builtIns, custom, sessions: include.includes("sessions") };
272
- }
273
- function customIncludePathsByLower(value) {
274
- return new Map(syncIncludeSelection(value).custom.map((relativePath) => [relativePath.toLowerCase(), relativePath]));
275
- }
276
- function includeFromSelectionConfig(config) {
277
- if (config.include !== void 0) return normalizeSyncInclude(config.include);
278
- return [
279
- ...normalizeSyncFiles(config.syncFiles),
280
- ...normalizeExtraFiles(config.extraFiles),
281
- ...config.syncSessions ? ["sessions"] : []
282
- ];
283
- }
284
- function isConfiguredSnapshotPath(relativePath, config, _legacyExtraFiles) {
285
- const normalized = toPosix(relativePath);
286
- const selection = syncIncludeSelection(includeFromSelectionConfig(config));
287
- if (normalized.startsWith("sessions/")) return selection.sessions;
288
- const lower = normalized.toLowerCase();
289
- if (!normalized.includes("/")) {
290
- const builtIn = BUILT_IN_BY_LOWER.get(lower);
291
- if (builtIn) return selection.builtIns.includes(builtIn) && !TOP_LEVEL_DIRS.has(builtIn);
292
- }
293
- const topLevel = normalized.slice(0, normalized.indexOf("/"));
294
- if (selection.builtIns.includes(topLevel) && TOP_LEVEL_DIRS.has(topLevel)) {
295
- return true;
296
- }
297
- return selection.custom.some((candidate) => {
298
- const candidateLower = candidate.toLowerCase();
299
- return lower === candidateLower || lower.startsWith(`${candidateLower}/`);
300
- });
301
- }
302
- function canonicalSnapshotPathForConfig(relativePath, includePaths) {
303
- const normalized = toPosix(relativePath);
304
- const lower = normalized.toLowerCase();
305
- return TOP_LEVEL_FILE_PATHS.get(lower) ?? includePaths.get(lower) ?? normalized;
306
- }
307
- function isPreservableUnmanagedSnapshotPath(relativePath) {
308
- const normalized = toPosix(relativePath);
309
- if (!normalized || isDeniedPath(normalized)) return false;
310
- if (normalized.startsWith("sessions/")) return normalized.endsWith(".jsonl");
311
- if (!normalized.includes("/")) {
312
- const lower = normalized.toLowerCase();
313
- return TOP_LEVEL_FILE_PATHS.has(lower) || !RESERVED_TOP_LEVEL_NAMES.has(lower);
314
- }
315
- return true;
316
- }
317
- function isSafeCustomIncludePath(relativePath) {
318
- try {
319
- validateAgentRelativeInclude(relativePath);
320
- return true;
321
- } catch {
322
- return false;
323
- }
324
- }
325
- function normalizeSyncFiles(value) {
326
- if (value === void 0) return [...DEFAULT_SYNC_INCLUDE];
327
- if (Array.isArray(value)) {
328
- return normalizeSyncInclude(value).filter(
329
- (item) => BUILT_IN_BY_LOWER.has(item.toLowerCase())
330
- );
331
- }
332
- throw new Error("Invalid pi-sync settings: expected an include array.");
333
- }
334
- function normalizeExtraFiles(value) {
335
- if (!Array.isArray(value)) return [];
336
- return value.filter((item) => typeof item === "string" && isSafeCustomIncludePath(item));
337
- }
338
-
339
- export {
340
- isDeniedPath,
341
- isPathInside,
342
- safeJoin,
343
- assertWithinRoot,
344
- encodeKey,
345
- posixJoin,
346
- parentPaths,
347
- toPosix,
348
- BUILT_IN_SYNC_ROOTS,
349
- DEFAULT_SYNC_INCLUDE,
350
- normalizeSyncInclude,
351
- portableSnapshotSelection,
352
- snapshotSelectionInclude,
353
- selectionForSnapshot,
354
- sameSyncInclude,
355
- compareSyncInclude,
356
- inspectRemoteSelection,
357
- RemoteSelectionMismatchError,
358
- remoteSelectionMismatch,
359
- formatRemoteSelectionMismatch,
360
- syncIncludeSelection,
361
- customIncludePathsByLower,
362
- includeFromSelectionConfig,
363
- isConfiguredSnapshotPath,
364
- canonicalSnapshotPathForConfig,
365
- isPreservableUnmanagedSnapshotPath,
366
- isSafeCustomIncludePath,
367
- normalizeSyncFiles,
368
- normalizeExtraFiles
369
- };
370
- //# sourceMappingURL=chunk-IVXEDPAD.ts.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/paths.ts", "../../src/sync/sync-policy.ts"],
4
- "sourcesContent": ["import path from \"node:path\";\n\nexport function isDeniedPath(relativePath: string) {\n const normalized = toPosix(relativePath);\n const lower = normalized.toLowerCase();\n const segments = lower.split(\"/\");\n const base = path.posix.basename(lower);\n return (\n segments.includes(\"node_modules\") ||\n segments.includes(\".git\") ||\n segments.includes(\".pisync\") ||\n segments.includes(\"pi-sync\") ||\n segments.includes(\".pi-sync-state-migration.lock\") ||\n base === \".env\" ||\n base.startsWith(\".env.\") ||\n base.endsWith(\".env\") ||\n base.includes(\"secret\") ||\n base.includes(\"token\") ||\n base === \"pi-sync.json\" ||\n base.startsWith(\"pi-sync.json.\") ||\n base.startsWith(\".pi-sync.json.\") ||\n base === \"pi-sync.local.json\" ||\n base.startsWith(\"pi-sync.local.json.\") ||\n base.startsWith(\".pi-sync.local.json.\")\n );\n}\n\nexport function isPathInside(parent: string, child: string) {\n const relative = path.relative(path.resolve(parent), path.resolve(child));\n return relative === \"\" || (!relative.startsWith(\"..\") && !path.isAbsolute(relative));\n}\n\nexport function safeJoin(root: string, relativePath: string) {\n const target = path.resolve(root, relativePath);\n assertWithinRoot(root, target, relativePath);\n return target;\n}\n\nexport function assertWithinRoot(root: string, target: string, label = target) {\n const resolvedRoot = path.resolve(root);\n const resolvedTarget = path.resolve(target);\n if (resolvedTarget !== resolvedRoot && !resolvedTarget.startsWith(`${resolvedRoot}${path.sep}`)) {\n throw new Error(`Unsafe path in snapshot: ${label}`);\n }\n}\n\nexport function encodeKey(key: string) {\n return key.split(\"/\").map(encodeURIComponent).join(\"/\");\n}\n\nexport function posixJoin(...parts: string[]) {\n return parts\n .map((part) => trimSlashes(part))\n .filter(Boolean)\n .join(\"/\");\n}\n\nexport function parentPaths(relativePath: string) {\n const results: string[] = [];\n let index = relativePath.lastIndexOf(\"/\");\n while (index > 0) {\n results.push(relativePath.slice(0, index));\n index = relativePath.lastIndexOf(\"/\", index - 1);\n }\n return results;\n}\n\nexport function toPosix(value: string) {\n return value.split(path.sep).join(\"/\");\n}\n\nfunction trimSlashes(value: string) {\n return value.replace(/^\\/+|\\/+$/g, \"\");\n}\n\nexport function safeName(value: string) {\n return value.replace(/[^A-Za-z0-9._-]/g, \"_\");\n}\n", "import path from \"node:path\";\nimport { isDeniedPath, toPosix } from \"../paths.js\";\nimport type { Snapshot, SnapshotSelection } from \"../snapshot/snapshot-types.js\";\n\nexport const BUILT_IN_SYNC_ROOTS = [\n \"settings.json\",\n \"keybindings.json\",\n \"models.json\",\n \"AGENTS.md\",\n \"APPEND_SYSTEM.md\",\n \"skills\",\n \"prompts\",\n \"themes\",\n \"extensions\",\n] as const;\n\nexport const DEFAULT_SYNC_INCLUDE = [...BUILT_IN_SYNC_ROOTS] as const;\nexport type BuiltInSyncFile = (typeof BUILT_IN_SYNC_ROOTS)[number];\nconst SNAPSHOT_SELECTION_VERSION = 1;\nconst MAX_SYNC_INCLUDE_ITEMS = 1_024;\nconst MAX_SYNC_INCLUDE_PATH_BYTES = 4_096;\nconst MAX_SYNC_INCLUDE_TOTAL_BYTES = 256 * 1_024;\n\ninterface IncludePathNode {\n selected?: string;\n children: Map<string, IncludePathNode>;\n}\n\nconst BUILT_IN_BY_LOWER = new Map<string, BuiltInSyncFile>(\n BUILT_IN_SYNC_ROOTS.map((fileName) => [fileName.toLowerCase(), fileName]),\n);\nconst TOP_LEVEL_FILE_PATHS = new Map<string, string>(\n BUILT_IN_SYNC_ROOTS.filter((fileName) => fileName.includes(\".\")).map((fileName) => [\n fileName.toLowerCase(),\n fileName,\n ]),\n);\nconst TOP_LEVEL_DIRS = new Set<string>(BUILT_IN_SYNC_ROOTS.filter((fileName) => !fileName.includes(\".\")));\nconst RESERVED_TOP_LEVEL_NAMES = new Set<string>([...BUILT_IN_BY_LOWER.keys(), \"sessions\"]);\n\nexport function normalizeSyncInclude(value: unknown): string[] {\n if (!Array.isArray(value)) {\n throw new Error(\"Invalid pi-sync settings: sync.include must be an array.\");\n }\n if (value.length > MAX_SYNC_INCLUDE_ITEMS) {\n throw new Error(`Invalid pi-sync settings: sync.include has too many items; limit: ${MAX_SYNC_INCLUDE_ITEMS}.`);\n }\n const result: string[] = [];\n const seen = new Set<string>();\n const pathRoot: IncludePathNode = { children: new Map() };\n let totalBytes = 0;\n for (const item of value) {\n if (typeof item !== \"string\") {\n throw new Error(\"Invalid pi-sync settings: sync.include items must be strings.\");\n }\n const itemBytes = Buffer.byteLength(item, \"utf8\");\n if (itemBytes > MAX_SYNC_INCLUDE_PATH_BYTES) {\n throw new Error(\n `Invalid pi-sync settings: sync.include item is too long; limit: ${MAX_SYNC_INCLUDE_PATH_BYTES} bytes.`,\n );\n }\n totalBytes += itemBytes;\n if (totalBytes > MAX_SYNC_INCLUDE_TOTAL_BYTES) {\n throw new Error(\n `Invalid pi-sync settings: sync.include is too large; limit: ${MAX_SYNC_INCLUDE_TOTAL_BYTES} bytes.`,\n );\n }\n const trimmed = item.trim();\n const builtIn = BUILT_IN_BY_LOWER.get(trimmed.toLowerCase());\n const normalized = builtIn ?? (trimmed.toLowerCase() === \"sessions\" ? \"sessions\" : trimmed);\n if (!builtIn && normalized !== \"sessions\") validateAgentRelativeInclude(normalized);\n const identity = normalized.toLowerCase();\n if (seen.has(identity)) {\n throw new Error(`Invalid pi-sync settings: duplicate sync.include item: ${item}`);\n }\n addIncludePath(pathRoot, identity, item);\n seen.add(identity);\n result.push(normalized);\n }\n return result;\n}\n\nfunction addIncludePath(root: IncludePathNode, identity: string, source: string) {\n let node = root;\n for (const segment of identity.split(\"/\")) {\n if (node.selected !== undefined) throwOverlappingInclude(source);\n let child = node.children.get(segment);\n if (!child) {\n child = { children: new Map() };\n node.children.set(segment, child);\n }\n node = child;\n }\n if (node.children.size > 0) throwOverlappingInclude(source);\n node.selected = identity;\n}\n\nfunction throwOverlappingInclude(item: string): never {\n throw new Error(`Invalid pi-sync settings: overlapping sync.include items are ambiguous: ${item}`);\n}\n\nfunction validateAgentRelativeInclude(value: string) {\n const normalized = toPosix(value);\n const topLevel = normalized.split(\"/\")[0]?.toLowerCase();\n if (\n !normalized ||\n normalized === \".\" ||\n normalized === \"..\" ||\n normalized.startsWith(\"../\") ||\n path.posix.isAbsolute(normalized) ||\n normalized.includes(\"\\\\\") ||\n path.posix.normalize(normalized) !== normalized ||\n // biome-ignore lint/suspicious/noControlCharactersInRegex: Include paths cannot contain controls.\n /[\\u0000-\\u001f\\u007f-\\u009f]/u.test(normalized)\n ) {\n throw new Error(`Invalid pi-sync settings: sync.include item must be a safe agent-relative path: ${value}`);\n }\n if (isDeniedPath(normalized)) {\n throw new Error(`Invalid pi-sync settings: ${value} cannot be synced.`);\n }\n if (topLevel && RESERVED_TOP_LEVEL_NAMES.has(topLevel)) {\n throw new Error(\n `Invalid pi-sync settings: use the canonical ${topLevel} root instead of a nested sync.include path.`,\n );\n }\n}\n\nexport function portableSnapshotSelection(value: unknown): SnapshotSelection {\n const selection = value as Partial<SnapshotSelection> | null;\n if (\n !selection ||\n typeof selection !== \"object\" ||\n Array.isArray(selection) ||\n selection.version !== SNAPSHOT_SELECTION_VERSION ||\n !Object.hasOwn(selection, \"include\") ||\n Object.keys(selection).some((key) => key !== \"version\" && key !== \"include\")\n ) {\n throw new Error(\"Invalid snapshot selection policy.\");\n }\n return {\n version: SNAPSHOT_SELECTION_VERSION,\n include: normalizeSyncInclude(selection.include),\n };\n}\n\nexport function snapshotSelectionInclude(snapshot: Pick<Snapshot, \"selection\">) {\n return snapshot.selection === undefined ? undefined : portableSnapshotSelection(snapshot.selection).include;\n}\n\nexport function selectionForSnapshot(include: unknown): SnapshotSelection {\n return { version: SNAPSHOT_SELECTION_VERSION, include: normalizeSyncInclude(include) };\n}\n\nexport function sameSyncInclude(left: unknown, right: unknown) {\n const normalizedLeft = normalizeSyncInclude(left);\n const normalizedRight = normalizeSyncInclude(right);\n return (\n normalizedLeft.length === normalizedRight.length &&\n normalizedLeft.every((item, index) => item === normalizedRight[index])\n );\n}\n\nexport function compareSyncInclude(local: unknown, remote: unknown) {\n const localInclude = normalizeSyncInclude(local);\n const remoteInclude = normalizeSyncInclude(remote);\n const localSet = new Set(localInclude);\n const remoteSet = new Set(remoteInclude);\n return {\n same: sameSyncInclude(localInclude, remoteInclude),\n remoteOnly: remoteInclude.filter((item) => !localSet.has(item)),\n localOnly: localInclude.filter((item) => !remoteSet.has(item)),\n };\n}\n\nexport type RemoteSelectionState =\n | { kind: \"legacy\"; discovered: string[] }\n | { kind: \"same\"; include: string[] }\n | {\n kind: \"different\";\n include: string[];\n remoteOnly: string[];\n localOnly: string[];\n };\n\nexport function inspectRemoteSelection(\n localInclude: unknown,\n snapshot: Pick<Snapshot, \"selection\" | \"files\">,\n): RemoteSelectionState {\n const remoteInclude = snapshotSelectionInclude(snapshot);\n if (!remoteInclude) {\n return { kind: \"legacy\", discovered: discoverLegacySnapshotInclude(snapshot) };\n }\n const comparison = compareSyncInclude(localInclude, remoteInclude);\n return comparison.same\n ? { kind: \"same\", include: remoteInclude }\n : { kind: \"different\", include: remoteInclude, ...comparison };\n}\n\nexport interface RemoteSelectionDecision {\n setupName: string;\n configIdentity: string;\n localInclude: readonly string[];\n remoteInclude: readonly string[];\n}\n\nexport class RemoteSelectionMismatchError extends Error {\n readonly decision: RemoteSelectionDecision;\n readonly setupName: string;\n readonly localInclude: string[];\n readonly remoteInclude: string[];\n\n constructor(\n setupName: string,\n localInclude: unknown,\n remoteInclude: unknown,\n configIdentity = JSON.stringify([setupName, normalizeSyncInclude(localInclude)]),\n ) {\n const local = normalizeSyncInclude(localInclude);\n const remote = normalizeSyncInclude(remoteInclude);\n super(formatRemoteSelectionMismatch(setupName, local, remote));\n this.name = \"RemoteSelectionMismatchError\";\n this.setupName = setupName;\n this.localInclude = local;\n this.remoteInclude = remote;\n this.decision = {\n setupName,\n configIdentity,\n localInclude: [...local],\n remoteInclude: [...remote],\n };\n }\n}\n\nexport function remoteSelectionMismatch(\n config: { setupName: string; include: unknown },\n remoteInclude: unknown,\n configIdentity?: string,\n) {\n return new RemoteSelectionMismatchError(config.setupName, config.include, remoteInclude, configIdentity);\n}\n\nexport function formatRemoteSelectionMismatch(\n setupName: string,\n localInclude: readonly string[],\n remoteInclude: readonly string[],\n) {\n const comparison = compareSyncInclude(localInclude, remoteInclude);\n const lines = [\n `Synced content differs for sync setup \u201C${stripTerminalControls(setupName)}\u201D.`,\n `Remote-only: ${comparison.remoteOnly.join(\", \") || \"none\"}`,\n `This-device-only: ${comparison.localOnly.join(\", \") || \"none\"}`,\n ];\n if (comparison.remoteOnly.length === 0 && comparison.localOnly.length === 0) {\n lines.push(\n \"Only ordering differs.\",\n `Remote order: ${remoteInclude.join(\", \") || \"none\"}`,\n `This device order: ${localInclude.join(\", \") || \"none\"}`,\n );\n }\n lines.push(\"Run /sync in TUI to review both content lists and choose what happens next.\");\n return lines.join(\"\\n\");\n}\n\nfunction stripTerminalControls(value: string) {\n // biome-ignore lint/suspicious/noControlCharactersInRegex: Escape untrusted terminal controls.\n return value.replace(/[\\u0000-\\u001f\\u007f-\\u009f]/gu, \"?\");\n}\n\nexport function discoverLegacySnapshotInclude(snapshot: Pick<Snapshot, \"files\">) {\n const builtIns = new Set<BuiltInSyncFile>();\n const custom = new Set<string>();\n let sessions = false;\n for (const file of snapshot.files) {\n const normalized = toPosix(file.path);\n if (\n !normalized ||\n file.path.includes(\"\\\\\") ||\n normalized.length > 4096 ||\n normalized.startsWith(\"../\") ||\n path.posix.isAbsolute(normalized) ||\n path.posix.normalize(normalized) !== normalized ||\n // biome-ignore lint/suspicious/noControlCharactersInRegex: Ignore unsafe legacy paths.\n /[\\u0000-\\u001f\\u007f-\\u009f]/u.test(normalized) ||\n isDeniedPath(normalized)\n ) {\n continue;\n }\n const [topLevel, ...rest] = normalized.split(\"/\");\n if (!topLevel) continue;\n if (topLevel === \"sessions\" && rest.length > 0) {\n sessions = true;\n continue;\n }\n const builtIn = BUILT_IN_BY_LOWER.get(topLevel.toLowerCase());\n if (\n builtIn &&\n ((TOP_LEVEL_DIRS.has(builtIn) && rest.length > 0) || (!TOP_LEVEL_DIRS.has(builtIn) && rest.length === 0))\n ) {\n builtIns.add(builtIn);\n continue;\n }\n if (isSafeCustomIncludePath(topLevel)) custom.add(topLevel);\n }\n return [\n ...BUILT_IN_SYNC_ROOTS.filter((item) => builtIns.has(item)),\n ...[...custom].sort((left, right) => left.localeCompare(right)),\n ...(sessions ? [\"sessions\"] : []),\n ];\n}\n\nexport function syncIncludeSelection(value: unknown) {\n const include = normalizeSyncInclude(value);\n const builtIns = include.filter((item): item is BuiltInSyncFile => BUILT_IN_BY_LOWER.has(item.toLowerCase()));\n const custom = include.filter((item) => item !== \"sessions\" && !BUILT_IN_BY_LOWER.has(item.toLowerCase()));\n return { include, builtIns, custom, sessions: include.includes(\"sessions\") };\n}\n\nexport function customIncludePathsByLower(value: unknown) {\n return new Map(syncIncludeSelection(value).custom.map((relativePath) => [relativePath.toLowerCase(), relativePath]));\n}\n\nexport interface SyncSelectionConfig {\n include?: unknown;\n syncFiles?: unknown;\n syncSessions?: boolean;\n extraFiles?: unknown;\n}\n\nexport function includeFromSelectionConfig(config: SyncSelectionConfig) {\n if (config.include !== undefined) return normalizeSyncInclude(config.include);\n return [\n ...normalizeSyncFiles(config.syncFiles),\n ...normalizeExtraFiles(config.extraFiles),\n ...(config.syncSessions ? [\"sessions\"] : []),\n ];\n}\n\nexport function isConfiguredSnapshotPath(\n relativePath: string,\n config: SyncSelectionConfig,\n _legacyExtraFiles?: Set<string>,\n) {\n const normalized = toPosix(relativePath);\n const selection = syncIncludeSelection(includeFromSelectionConfig(config));\n if (normalized.startsWith(\"sessions/\")) return selection.sessions;\n const lower = normalized.toLowerCase();\n if (!normalized.includes(\"/\")) {\n const builtIn = BUILT_IN_BY_LOWER.get(lower);\n if (builtIn) return selection.builtIns.includes(builtIn) && !TOP_LEVEL_DIRS.has(builtIn);\n }\n const topLevel = normalized.slice(0, normalized.indexOf(\"/\"));\n if (selection.builtIns.includes(topLevel as BuiltInSyncFile) && TOP_LEVEL_DIRS.has(topLevel)) {\n return true;\n }\n return selection.custom.some((candidate) => {\n const candidateLower = candidate.toLowerCase();\n return lower === candidateLower || lower.startsWith(`${candidateLower}/`);\n });\n}\n\nexport function canonicalSnapshotPathForConfig(relativePath: string, includePaths: Map<string, string>) {\n const normalized = toPosix(relativePath);\n const lower = normalized.toLowerCase();\n return TOP_LEVEL_FILE_PATHS.get(lower) ?? includePaths.get(lower) ?? normalized;\n}\n\nexport function isPreservableUnmanagedSnapshotPath(relativePath: string) {\n const normalized = toPosix(relativePath);\n if (!normalized || isDeniedPath(normalized)) return false;\n if (normalized.startsWith(\"sessions/\")) return normalized.endsWith(\".jsonl\");\n if (!normalized.includes(\"/\")) {\n const lower = normalized.toLowerCase();\n return TOP_LEVEL_FILE_PATHS.has(lower) || !RESERVED_TOP_LEVEL_NAMES.has(lower);\n }\n return true;\n}\n\nexport function isSafeCustomIncludePath(relativePath: string) {\n try {\n validateAgentRelativeInclude(relativePath);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function isBuiltInTopLevelFile(fileName: string) {\n return TOP_LEVEL_FILE_PATHS.has(path.posix.basename(fileName).toLowerCase());\n}\n\n/** Compatibility projection for backend-neutral output while callers move to sync.include. */\nexport function normalizeSyncFiles(value: unknown): BuiltInSyncFile[] {\n if (value === undefined) return [...DEFAULT_SYNC_INCLUDE];\n if (Array.isArray(value)) {\n return normalizeSyncInclude(value).filter((item): item is BuiltInSyncFile =>\n BUILT_IN_BY_LOWER.has(item.toLowerCase()),\n );\n }\n throw new Error(\"Invalid pi-sync settings: expected an include array.\");\n}\n\n/** Compatibility projection for old rendering helpers; v3 persistence never writes this split field. */\nexport function normalizeExtraFiles(value: unknown) {\n if (!Array.isArray(value)) return [];\n return value.filter((item): item is string => typeof item === \"string\" && isSafeCustomIncludePath(item));\n}\n\nexport const extraFilePathsByLower = customIncludePathsByLower;\nexport const isSafeExtraFileName = isSafeCustomIncludePath;\nexport const selectedSyncFileSet = (value: unknown) => new Set(normalizeSyncFiles(value));\n"],
5
- "mappings": "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",
6
- "names": ["path", "path"]
7
- }
@@ -1,12 +0,0 @@
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
-
4
- // src/ui/terminal-text.ts
5
- function safeTerminalText(value) {
6
- return value.replace(/[\u0000-\u001f\u007f-\u009f]/gu, "?");
7
- }
8
-
9
- export {
10
- safeTerminalText
11
- };
12
- //# sourceMappingURL=chunk-MSSW6F3R.ts.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/ui/terminal-text.ts"],
4
- "sourcesContent": ["export function safeTerminalText(value: string) {\n // biome-ignore lint/suspicious/noControlCharactersInRegex: Escape untrusted terminal controls.\n return value.replace(/[\\u0000-\\u001f\\u007f-\\u009f]/gu, \"?\");\n}\n"],
5
- "mappings": ";;;;AAAO,SAAS,iBAAiB,OAAe;AAE9C,SAAO,MAAM,QAAQ,kCAAkC,GAAG;AAC5D;",
6
- "names": []
7
- }
@@ -1,90 +0,0 @@
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
-
4
- // src/snapshot/session-paths.ts
5
- import os from "node:os";
6
- import path2 from "node:path";
7
- import { getAgentDir } from "@earendil-works/pi-coding-agent";
8
-
9
- // src/state/json-file.ts
10
- import { randomUUID } from "node:crypto";
11
- import fs from "node:fs/promises";
12
- import path from "node:path";
13
- async function readJsonIfExists(filePath) {
14
- try {
15
- return JSON.parse(await fs.readFile(filePath, "utf8"));
16
- } catch (error) {
17
- if (error.code === "ENOENT") return void 0;
18
- throw error;
19
- }
20
- }
21
- async function writeJson(filePath, value) {
22
- await fs.mkdir(path.dirname(filePath), { recursive: true });
23
- const temp = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
24
- await fs.writeFile(temp, `${JSON.stringify(value, null, " ")}
25
- `, { mode: 384 });
26
- if (process.platform !== "win32") await fs.chmod(temp, 384);
27
- await fs.rename(temp, filePath);
28
- }
29
-
30
- // src/snapshot/session-paths.ts
31
- function sessionDirFromContext(ctx) {
32
- const manager = ctx.sessionManager;
33
- const usesDefaultSessionDir = manager.usesDefaultSessionDir;
34
- if (typeof usesDefaultSessionDir === "function" && usesDefaultSessionDir.call(manager)) {
35
- return void 0;
36
- }
37
- const getSessionDir = manager.getSessionDir;
38
- return typeof getSessionDir === "function" ? getSessionDir.call(manager) : void 0;
39
- }
40
- async function configuredSessionDir() {
41
- const settings = await readJsonIfExists(path2.join(agentDir(), "settings.json"));
42
- return settings?.sessionDir ? expandHome(settings.sessionDir) : void 0;
43
- }
44
- async function sessionDirForApply(ctx, snapshot) {
45
- const contextSessionDir = sessionDirFromContext(ctx);
46
- const localSessionDir = await configuredSessionDir();
47
- if (contextSessionDir && path2.resolve(contextSessionDir) !== path2.resolve(localSessionDir ?? "")) {
48
- return contextSessionDir;
49
- }
50
- return sessionDirFromSnapshot(snapshot) ?? contextSessionDir;
51
- }
52
- function sessionDirFromSnapshot(snapshot) {
53
- const settingsFile = snapshot.files.find((file) => file.path === "settings.json");
54
- if (!settingsFile) return void 0;
55
- try {
56
- const settings = JSON.parse(decodeBase64Strict(settingsFile.contentBase64, settingsFile.path).toString("utf8"));
57
- return settings.sessionDir ? expandHome(settings.sessionDir) : void 0;
58
- } catch {
59
- return void 0;
60
- }
61
- }
62
- function decodeBase64Strict(value, filePath) {
63
- if (!/^[A-Za-z0-9+/]*={0,2}$/.test(value) || value.length % 4 !== 0) {
64
- throw new Error(`Invalid base64 content in snapshot file: ${filePath}`);
65
- }
66
- return Buffer.from(value, "base64");
67
- }
68
- function agentDir() {
69
- return getAgentDir();
70
- }
71
- function expandHome(value) {
72
- return value === "~" || value.startsWith("~/") ? path2.join(os.homedir(), value.slice(2)) : value;
73
- }
74
- function snapshotOptionsForContext(ctx, config) {
75
- return {
76
- include: config.include,
77
- sessionDir: sessionDirFromContext(ctx)
78
- };
79
- }
80
-
81
- export {
82
- readJsonIfExists,
83
- writeJson,
84
- sessionDirFromContext,
85
- configuredSessionDir,
86
- sessionDirForApply,
87
- agentDir,
88
- snapshotOptionsForContext
89
- };
90
- //# sourceMappingURL=chunk-OVDTF5CH.ts.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/snapshot/session-paths.ts", "../../src/state/json-file.ts"],
4
- "sourcesContent": ["import os from \"node:os\";\nimport path from \"node:path\";\nimport { type ExtensionCommandContext, type ExtensionContext, getAgentDir } from \"@earendil-works/pi-coding-agent\";\nimport type { AnySyncConfig } from \"../settings/settings-types.js\";\nimport { readJsonIfExists } from \"../state/json-file.js\";\nimport type { Snapshot, SnapshotOptions } from \"./snapshot-types.js\";\n\nexport function sessionDirFromContext(ctx: ExtensionCommandContext | ExtensionContext) {\n const manager = ctx.sessionManager as typeof ctx.sessionManager & {\n usesDefaultSessionDir?: () => boolean;\n };\n const usesDefaultSessionDir = manager.usesDefaultSessionDir;\n if (typeof usesDefaultSessionDir === \"function\" && usesDefaultSessionDir.call(manager)) {\n return undefined;\n }\n const getSessionDir = manager.getSessionDir;\n return typeof getSessionDir === \"function\" ? (getSessionDir.call(manager) as string | undefined) : undefined;\n}\n\nexport async function configuredSessionDir() {\n const settings = await readJsonIfExists<{ sessionDir?: string }>(path.join(agentDir(), \"settings.json\"));\n return settings?.sessionDir ? expandHome(settings.sessionDir) : undefined;\n}\n\nexport async function sessionDirForApply(ctx: ExtensionCommandContext | ExtensionContext, snapshot: Snapshot) {\n const contextSessionDir = sessionDirFromContext(ctx);\n const localSessionDir = await configuredSessionDir();\n if (contextSessionDir && path.resolve(contextSessionDir) !== path.resolve(localSessionDir ?? \"\")) {\n return contextSessionDir;\n }\n return sessionDirFromSnapshot(snapshot) ?? contextSessionDir;\n}\n\nfunction sessionDirFromSnapshot(snapshot: Snapshot) {\n const settingsFile = snapshot.files.find((file) => file.path === \"settings.json\");\n if (!settingsFile) return undefined;\n try {\n const settings = JSON.parse(decodeBase64Strict(settingsFile.contentBase64, settingsFile.path).toString(\"utf8\")) as {\n sessionDir?: string;\n };\n return settings.sessionDir ? expandHome(settings.sessionDir) : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction decodeBase64Strict(value: string, filePath: string) {\n if (!/^[A-Za-z0-9+/]*={0,2}$/.test(value) || value.length % 4 !== 0) {\n throw new Error(`Invalid base64 content in snapshot file: ${filePath}`);\n }\n return Buffer.from(value, \"base64\");\n}\n\nexport function agentDir() {\n return getAgentDir();\n}\n\nfunction expandHome(value: string) {\n return value === \"~\" || value.startsWith(\"~/\") ? path.join(os.homedir(), value.slice(2)) : value;\n}\n\nexport function snapshotOptionsForContext(\n ctx: ExtensionCommandContext | ExtensionContext,\n config: AnySyncConfig,\n): SnapshotOptions {\n return {\n include: config.include,\n sessionDir: sessionDirFromContext(ctx),\n };\n}\n", "import { randomUUID } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport async function readJsonIfExists<T>(filePath: string): Promise<T | undefined> {\n try {\n return JSON.parse(await fs.readFile(filePath, \"utf8\")) as T;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\n throw error;\n }\n}\n\nexport async function writeJson(filePath: string, value: unknown) {\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n const temp = `${filePath}.${process.pid}.${randomUUID()}.tmp`;\n await fs.writeFile(temp, `${JSON.stringify(value, null, \"\\t\")}\\n`, { mode: 0o600 });\n if (process.platform !== \"win32\") await fs.chmod(temp, 0o600);\n await fs.rename(temp, filePath);\n}\n"],
5
- "mappings": ";;;;AAAA,OAAO,QAAQ;AACf,OAAOA,WAAU;AACjB,SAA8D,mBAAmB;;;ACFjF,SAAS,kBAAkB;AAC3B,OAAO,QAAQ;AACf,OAAO,UAAU;AAEjB,eAAsB,iBAAoB,UAA0C;AAClF,MAAI;AACF,WAAO,KAAK,MAAM,MAAM,GAAG,SAAS,UAAU,MAAM,CAAC;AAAA,EACvD,SAAS,OAAO;AACd,QAAK,MAAgC,SAAS,SAAU,QAAO;AAC/D,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,UAAU,UAAkB,OAAgB;AAChE,QAAM,GAAG,MAAM,KAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAC1D,QAAM,OAAO,GAAG,QAAQ,IAAI,QAAQ,GAAG,IAAI,WAAW,CAAC;AACvD,QAAM,GAAG,UAAU,MAAM,GAAG,KAAK,UAAU,OAAO,MAAM,GAAI,CAAC;AAAA,GAAM,EAAE,MAAM,IAAM,CAAC;AAClF,MAAI,QAAQ,aAAa,QAAS,OAAM,GAAG,MAAM,MAAM,GAAK;AAC5D,QAAM,GAAG,OAAO,MAAM,QAAQ;AAChC;;;ADZO,SAAS,sBAAsB,KAAiD;AACrF,QAAM,UAAU,IAAI;AAGpB,QAAM,wBAAwB,QAAQ;AACtC,MAAI,OAAO,0BAA0B,cAAc,sBAAsB,KAAK,OAAO,GAAG;AACtF,WAAO;AAAA,EACT;AACA,QAAM,gBAAgB,QAAQ;AAC9B,SAAO,OAAO,kBAAkB,aAAc,cAAc,KAAK,OAAO,IAA2B;AACrG;AAEA,eAAsB,uBAAuB;AAC3C,QAAM,WAAW,MAAM,iBAA0CC,MAAK,KAAK,SAAS,GAAG,eAAe,CAAC;AACvG,SAAO,UAAU,aAAa,WAAW,SAAS,UAAU,IAAI;AAClE;AAEA,eAAsB,mBAAmB,KAAiD,UAAoB;AAC5G,QAAM,oBAAoB,sBAAsB,GAAG;AACnD,QAAM,kBAAkB,MAAM,qBAAqB;AACnD,MAAI,qBAAqBA,MAAK,QAAQ,iBAAiB,MAAMA,MAAK,QAAQ,mBAAmB,EAAE,GAAG;AAChG,WAAO;AAAA,EACT;AACA,SAAO,uBAAuB,QAAQ,KAAK;AAC7C;AAEA,SAAS,uBAAuB,UAAoB;AAClD,QAAM,eAAe,SAAS,MAAM,KAAK,CAAC,SAAS,KAAK,SAAS,eAAe;AAChF,MAAI,CAAC,aAAc,QAAO;AAC1B,MAAI;AACF,UAAM,WAAW,KAAK,MAAM,mBAAmB,aAAa,eAAe,aAAa,IAAI,EAAE,SAAS,MAAM,CAAC;AAG9G,WAAO,SAAS,aAAa,WAAW,SAAS,UAAU,IAAI;AAAA,EACjE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,mBAAmB,OAAe,UAAkB;AAC3D,MAAI,CAAC,yBAAyB,KAAK,KAAK,KAAK,MAAM,SAAS,MAAM,GAAG;AACnE,UAAM,IAAI,MAAM,4CAA4C,QAAQ,EAAE;AAAA,EACxE;AACA,SAAO,OAAO,KAAK,OAAO,QAAQ;AACpC;AAEO,SAAS,WAAW;AACzB,SAAO,YAAY;AACrB;AAEA,SAAS,WAAW,OAAe;AACjC,SAAO,UAAU,OAAO,MAAM,WAAW,IAAI,IAAIA,MAAK,KAAK,GAAG,QAAQ,GAAG,MAAM,MAAM,CAAC,CAAC,IAAI;AAC7F;AAEO,SAAS,0BACd,KACA,QACiB;AACjB,SAAO;AAAA,IACL,SAAS,OAAO;AAAA,IAChB,YAAY,sBAAsB,GAAG;AAAA,EACvC;AACF;",
6
- "names": ["path", "path"]
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/sync/remote-snapshot.ts"],
4
- "sourcesContent": ["import type { RemoteHead, SyncBackend } from \"../backends/sync-backend.js\";\nimport { syncConfigReviewFingerprint } from \"../settings/config.js\";\nimport type { AnySyncConfig } from \"../settings/settings-types.js\";\nimport { filterSnapshotForConfigPolicy } from \"../snapshot/snapshot.js\";\nimport type { Snapshot } from \"../snapshot/snapshot-types.js\";\nimport { safeTerminalText } from \"../ui/terminal-text.js\";\nimport {\n inspectRemoteSelection,\n type RemoteSelectionState,\n remoteSelectionMismatch,\n sameSyncInclude,\n snapshotSelectionInclude,\n} from \"./sync-policy.js\";\n\nexport async function readSnapshotForHead(backend: SyncBackend, head: RemoteHead, signal?: AbortSignal) {\n const snapshot = await backend.readSnapshot(head.snapshotRef, signal);\n if (signal?.aborted) {\n throw signal.reason instanceof Error ? signal.reason : new DOMException(\"The operation was aborted\", \"AbortError\");\n }\n if (snapshot.id !== head.snapshotId) {\n throw new Error(`Remote head ${head.snapshotId} resolved to unexpected snapshot ${snapshot.id}.`);\n }\n if (head.selection) {\n const snapshotInclude = snapshotSelectionInclude(snapshot);\n if (!snapshotInclude || !sameSyncInclude(head.selection.include, snapshotInclude)) {\n throw new Error(\"Remote head selection does not match its immutable snapshot.\");\n }\n }\n return snapshot;\n}\n\nexport function requireCompatibleRemoteSelection(config: AnySyncConfig, snapshot: Snapshot) {\n const state = inspectRemoteSelection(config.include, snapshot);\n if (state.kind === \"different\") {\n throw remoteSelectionMismatch(config, state.include, syncConfigReviewFingerprint(config));\n }\n}\n\nexport function formatRemoteSelectionStatus(state: RemoteSelectionState | undefined) {\n if (!state) return \"remote included content: unavailable (remote is empty)\";\n if (state.kind === \"same\") return \"remote included content: matches this setup\";\n if (state.kind === \"legacy\") {\n return `remote included content: unavailable in legacy snapshot (${state.discovered.length} path${state.discovered.length === 1 ? \"\" : \"s\"} discovered, partial)`;\n }\n return [\n \"remote included content: differs from this setup\",\n `remote-only selection: ${safeList(state.remoteOnly)}`,\n `local-only selection: ${safeList(state.localOnly)}`,\n ].join(\"\\n\");\n}\n\nfunction safeList(values: readonly string[]) {\n return values.length > 0 ? values.map(safeTerminalText).join(\", \") : \"none\";\n}\n\nexport async function readRemoteSnapshot(\n backend: SyncBackend,\n config: AnySyncConfig,\n signal?: AbortSignal,\n options: { allowSelectionDifference?: boolean } = {},\n) {\n const head = await backend.readHead(signal);\n if (!head) return { head: undefined, snapshot: undefined, selectionState: undefined };\n const snapshot = await readSnapshotForHead(backend, head, signal);\n const selectionState = inspectRemoteSelection(config.include, snapshot);\n if (!options.allowSelectionDifference && selectionState.kind === \"different\") {\n const configIdentity = syncConfigReviewFingerprint(config);\n throw remoteSelectionMismatch(config, selectionState.include, configIdentity);\n }\n return {\n head,\n snapshot: filterSnapshotForConfigPolicy(snapshot, config),\n selectionState,\n };\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;AAcA,eAAsB,oBAAoB,SAAsB,MAAkB,QAAsB;AACtG,QAAM,WAAW,MAAM,QAAQ,aAAa,KAAK,aAAa,MAAM;AACpE,MAAI,QAAQ,SAAS;AACnB,UAAM,OAAO,kBAAkB,QAAQ,OAAO,SAAS,IAAI,aAAa,6BAA6B,YAAY;AAAA,EACnH;AACA,MAAI,SAAS,OAAO,KAAK,YAAY;AACnC,UAAM,IAAI,MAAM,eAAe,KAAK,UAAU,oCAAoC,SAAS,EAAE,GAAG;AAAA,EAClG;AACA,MAAI,KAAK,WAAW;AAClB,UAAM,kBAAkB,yBAAyB,QAAQ;AACzD,QAAI,CAAC,mBAAmB,CAAC,gBAAgB,KAAK,UAAU,SAAS,eAAe,GAAG;AACjF,YAAM,IAAI,MAAM,8DAA8D;AAAA,IAChF;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,iCAAiC,QAAuB,UAAoB;AAC1F,QAAM,QAAQ,uBAAuB,OAAO,SAAS,QAAQ;AAC7D,MAAI,MAAM,SAAS,aAAa;AAC9B,UAAM,wBAAwB,QAAQ,MAAM,SAAS,4BAA4B,MAAM,CAAC;AAAA,EAC1F;AACF;AAEO,SAAS,4BAA4B,OAAyC;AACnF,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,MAAM,SAAS,OAAQ,QAAO;AAClC,MAAI,MAAM,SAAS,UAAU;AAC3B,WAAO,4DAA4D,MAAM,WAAW,MAAM,QAAQ,MAAM,WAAW,WAAW,IAAI,KAAK,GAAG;AAAA,EAC5I;AACA,SAAO;AAAA,IACL;AAAA,IACA,0BAA0B,SAAS,MAAM,UAAU,CAAC;AAAA,IACpD,yBAAyB,SAAS,MAAM,SAAS,CAAC;AAAA,EACpD,EAAE,KAAK,IAAI;AACb;AAEA,SAAS,SAAS,QAA2B;AAC3C,SAAO,OAAO,SAAS,IAAI,OAAO,IAAI,gBAAgB,EAAE,KAAK,IAAI,IAAI;AACvE;AAEA,eAAsB,mBACpB,SACA,QACA,QACA,UAAkD,CAAC,GACnD;AACA,QAAM,OAAO,MAAM,QAAQ,SAAS,MAAM;AAC1C,MAAI,CAAC,KAAM,QAAO,EAAE,MAAM,QAAW,UAAU,QAAW,gBAAgB,OAAU;AACpF,QAAM,WAAW,MAAM,oBAAoB,SAAS,MAAM,MAAM;AAChE,QAAM,iBAAiB,uBAAuB,OAAO,SAAS,QAAQ;AACtE,MAAI,CAAC,QAAQ,4BAA4B,eAAe,SAAS,aAAa;AAC5E,UAAM,iBAAiB,4BAA4B,MAAM;AACzD,UAAM,wBAAwB,QAAQ,eAAe,SAAS,cAAc;AAAA,EAC9E;AACA,SAAO;AAAA,IACL;AAAA,IACA,UAAU,8BAA8B,UAAU,MAAM;AAAA,IACxD;AAAA,EACF;AACF;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/state/lock.ts"],
4
- "sourcesContent": ["import { randomUUID } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport type { ExtensionCommandContext } from \"@earendil-works/pi-coding-agent\";\nimport lockfile from \"proper-lockfile\";\nimport type { CommandOptions } from \"../commands/command-types.js\";\nimport { LOCK_GUARD_STALE_MS, LOCK_GUARD_UPDATE_MS } from \"./lock-policy.js\";\nimport { LOCKFILE_FS_ADAPTER } from \"./lockfile-fs.js\";\nimport type { LockFile } from \"./state-types.js\";\nimport { ensureStateDir, lockPath } from \"./sync-state-store.js\";\n\nconst LOCK_STALE_MS = 30 * 60 * 1000;\nconst MAX_PROCESS_ID = 2_147_483_647;\n\ninterface Guard {\n release: () => Promise<void>;\n throwIfCompromised: () => void;\n isCompromised: () => boolean;\n}\n\nexport type LockInspection = { status: \"missing\" } | { status: \"unreadable\" } | { status: \"valid\"; lock: LockFile };\n\nexport async function withLock<T>(\n command: string,\n fn: () => Promise<T>,\n options: { reclaimStale?: boolean } = {},\n): Promise<T> {\n await ensureStateDir();\n const lock: LockFile = {\n id: randomUUID(),\n pid: process.pid,\n command,\n startedAt: new Date().toISOString(),\n };\n let guard: Guard | undefined;\n let result: T | undefined;\n let failed = false;\n let failure: unknown;\n try {\n try {\n guard = await acquireGuard();\n } catch (error) {\n if (!isLockHeldError(error)) throw error;\n throw await describeHeldLock();\n }\n\n let inspection = await inspectLock();\n if (inspection.status === \"valid\" && isStaleLock(inspection.lock)) {\n if (!options.reclaimStale) {\n throw new Error(`pi-sync lock is stale (pid ${inspection.lock.pid}). Run /sync unlock --stale, then retry.`);\n }\n guard.throwIfCompromised();\n const rechecked = await inspectLock();\n if (rechecked.status !== \"valid\" || rechecked.lock.id !== inspection.lock.id || !isStaleLock(rechecked.lock)) {\n throw new Error(\"pi-sync lock changed while preparing transaction recovery; retry.\");\n }\n await fs.rm(lockPath(), { force: true });\n inspection = { status: \"missing\" };\n }\n if (inspection.status === \"valid\") {\n throw new Error(\n `pi-sync is already running (${inspection.lock.command}, pid ${inspection.lock.pid}, started ${inspection.lock.startedAt}).`,\n );\n }\n if (inspection.status === \"unreadable\") {\n throw new Error(\n \"pi-sync lock metadata is unreadable. Run /sync unlock --stale after verifying no sync is running.\",\n );\n }\n\n await fs.writeFile(lockPath(), JSON.stringify(lock, null, \"\\t\"), { flag: \"wx\" });\n guard.throwIfCompromised();\n result = await fn();\n guard.throwIfCompromised();\n } catch (error) {\n failed = true;\n failure = error;\n }\n\n try {\n const current = await readLock();\n if (current?.id === lock.id) await fs.rm(lockPath(), { force: true });\n } catch (error) {\n if (!failed) {\n failed = true;\n failure = error;\n }\n }\n if (guard) {\n const releaseError = await releaseGuard(guard);\n if (releaseError && !failed) {\n failed = true;\n failure = releaseError;\n }\n }\n if (failed) throw failure;\n return result as T;\n}\n\nexport async function inspectLock(): Promise<LockInspection> {\n try {\n const text = await fs.readFile(lockPath(), \"utf8\");\n if (text.trim().length === 0) return { status: \"unreadable\" };\n const parsed = JSON.parse(text) as unknown;\n return isLockFile(parsed) ? { status: \"valid\", lock: parsed } : { status: \"unreadable\" };\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return { status: \"missing\" };\n if (error instanceof SyntaxError) return { status: \"unreadable\" };\n throw error;\n }\n}\n\nexport async function readLock(): Promise<LockFile | undefined> {\n const inspection = await inspectLock();\n return inspection.status === \"valid\" ? inspection.lock : undefined;\n}\n\nexport async function lockFileExists(): Promise<boolean> {\n try {\n await fs.stat(lockPath());\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return false;\n throw error;\n }\n}\n\nexport function isLockGuardHeld() {\n return lockfile.check(lockPath(), {\n fs: LOCKFILE_FS_ADAPTER,\n lockfilePath: `${lockPath()}.guard`,\n realpath: false,\n stale: LOCK_GUARD_STALE_MS,\n });\n}\n\nexport function isStaleLock(lock: LockFile) {\n try {\n process.kill(lock.pid, 0);\n return false;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ESRCH\") return true;\n return Date.now() - Date.parse(lock.startedAt) > LOCK_STALE_MS;\n }\n}\n\nexport async function unlock(ctx: ExtensionCommandContext, options: CommandOptions) {\n throwIfAborted(options.signal);\n await ensureStateDir();\n throwIfAborted(options.signal);\n let guard: Guard;\n try {\n guard = await acquireGuard();\n } catch (error) {\n if (!isLockHeldError(error)) throw error;\n ctx.ui.notify((await describeHeldLock()).message, \"warning\");\n return;\n }\n\n let failed = false;\n let failure: unknown;\n try {\n await unlockGuarded(ctx, options);\n } catch (error) {\n failed = true;\n failure = error;\n }\n const releaseError = await releaseGuard(guard);\n if (releaseError && !failed) {\n failed = true;\n failure = releaseError;\n }\n if (failed) throw failure;\n}\n\nasync function unlockGuarded(ctx: ExtensionCommandContext, options: CommandOptions) {\n throwIfAborted(options.signal);\n let inspection = await inspectLock();\n throwIfAborted(options.signal);\n if (inspection.status === \"missing\") {\n ctx.ui.notify(\"No pi-sync lock is present.\", \"info\");\n return;\n }\n if (inspection.status === \"unreadable\") {\n if (!options.stale) {\n ctx.ui.notify(\n \"Pi-sync lock metadata is unreadable. Use /sync unlock --stale only after verifying no sync is running.\",\n \"warning\",\n );\n return;\n }\n inspection = await inspectLock();\n throwIfAborted(options.signal);\n if (inspection.status === \"unreadable\") {\n // Legacy writers expose an empty file before writing owner metadata, so no\n // automatic test can prove this file is abandoned. The explicit --stale\n // flag is the user's confirmation that no legacy sync is still running.\n throwIfAborted(options.signal);\n await fs.rm(lockPath(), { force: true });\n if (!options.signal?.aborted) {\n ctx.ui.notify(\n \"Removed unreadable pi-sync lock. No settings, files, sync state, or remote data were changed.\",\n \"info\",\n );\n }\n return;\n }\n if (inspection.status === \"missing\") {\n ctx.ui.notify(\"No pi-sync lock is present.\", \"info\");\n return;\n }\n }\n if (!isStaleLock(inspection.lock)) {\n ctx.ui.notify(\"Lock owner is still live; refusing to remove it.\", \"warning\");\n return;\n }\n throwIfAborted(options.signal);\n await fs.rm(lockPath(), { force: true });\n if (!options.signal?.aborted) {\n ctx.ui.notify(\"Removed stale pi-sync lock. No settings, files, sync state, or remote data were changed.\", \"info\");\n }\n}\n\nfunction throwIfAborted(signal?: AbortSignal) {\n if (!signal?.aborted) return;\n throw signal.reason instanceof Error ? signal.reason : new DOMException(\"The operation was aborted\", \"AbortError\");\n}\n\nasync function releaseGuard(guard: Guard) {\n try {\n await guard.release();\n return undefined;\n } catch (error) {\n return guard.isCompromised() ? undefined : error;\n }\n}\n\nasync function acquireGuard(): Promise<Guard> {\n let compromisedError: Error | undefined;\n const release = await lockfile.lock(lockPath(), {\n fs: LOCKFILE_FS_ADAPTER,\n lockfilePath: `${lockPath()}.guard`,\n realpath: false,\n stale: LOCK_GUARD_STALE_MS,\n update: LOCK_GUARD_UPDATE_MS,\n onCompromised: (error) => {\n compromisedError = error;\n },\n });\n return {\n release,\n throwIfCompromised: () => {\n if (compromisedError) throw compromisedError;\n },\n isCompromised: () => compromisedError !== undefined,\n };\n}\n\nasync function describeHeldLock() {\n const current = await readLock();\n if (current && isStaleLock(current)) {\n return new Error(\"pi-sync lock owner exited; retry shortly while the lock guard expires.\");\n }\n if (current) {\n return new Error(\n `Pi-sync is currently running (${current.command}, pid ${current.pid}, started ${current.startedAt}).`,\n );\n }\n return new Error(\"Pi-sync is currently running (lock metadata is unreadable or still being written).\");\n}\n\nfunction isLockHeldError(error: unknown) {\n return (error as NodeJS.ErrnoException).code === \"ELOCKED\";\n}\n\nfunction isLockFile(value: unknown): value is LockFile {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n const lock = value as Partial<LockFile>;\n return (\n typeof lock.id === \"string\" &&\n lock.id.length > 0 &&\n Number.isInteger(lock.pid) &&\n (lock.pid ?? 0) > 0 &&\n (lock.pid ?? 0) <= MAX_PROCESS_ID &&\n typeof lock.command === \"string\" &&\n lock.command.length > 0 &&\n typeof lock.startedAt === \"string\" &&\n Number.isFinite(Date.parse(lock.startedAt))\n );\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
@@ -1,18 +0,0 @@
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
-
4
- // src/snapshot/snapshot-paths.ts
5
- import path from "node:path";
6
- function expandHome(value) {
7
- if (value === "~") return process.env.HOME ?? value;
8
- if (value.startsWith("~/")) return path.join(process.env.HOME ?? "~", value.slice(2));
9
- return value;
10
- }
11
- function sessionStorageRoot(root, configuredSessionDir) {
12
- return configuredSessionDir ? path.resolve(expandHome(configuredSessionDir)) : path.resolve(root, "sessions");
13
- }
14
-
15
- export {
16
- sessionStorageRoot
17
- };
18
- //# sourceMappingURL=chunk-SWUXA3MH.ts.map