@narumitw/pi-sync 0.51.0 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/README.md +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-2GUXI2XT.ts +1702 -0
  7. package/dist/chunks/chunk-2GUXI2XT.ts.map +7 -0
  8. package/dist/chunks/{chunk-4MPIH46N.ts → chunk-3B4Z2SKV.ts} +40 -6
  9. package/dist/chunks/chunk-3B4Z2SKV.ts.map +7 -0
  10. package/dist/chunks/{chunk-YPXGU5OR.ts → chunk-4QVPTIZB.ts} +16 -6
  11. package/dist/chunks/{chunk-YPXGU5OR.ts.map → chunk-4QVPTIZB.ts.map} +2 -2
  12. package/dist/chunks/{chunk-UA7RXOIT.ts → chunk-4SBTAVFL.ts} +6 -6
  13. package/dist/chunks/{chunk-6546TS54.ts → chunk-5RZPINUQ.ts} +7 -4
  14. package/dist/chunks/{chunk-6546TS54.ts.map → chunk-5RZPINUQ.ts.map} +2 -2
  15. package/dist/chunks/chunk-76NB4R7U.ts +107 -0
  16. package/dist/chunks/chunk-76NB4R7U.ts.map +7 -0
  17. package/dist/chunks/{chunk-DM4X4FIA.ts → chunk-7D6RDCVC.ts} +8 -10
  18. package/dist/chunks/{chunk-DM4X4FIA.ts.map → chunk-7D6RDCVC.ts.map} +2 -2
  19. package/dist/chunks/chunk-A6HU4ERM.ts +124 -0
  20. package/dist/chunks/chunk-A6HU4ERM.ts.map +7 -0
  21. package/dist/chunks/chunk-AMJRUXFT.ts +17 -0
  22. package/dist/chunks/chunk-AMJRUXFT.ts.map +7 -0
  23. package/dist/chunks/chunk-ANWNQIR7.ts +1013 -0
  24. package/dist/chunks/chunk-ANWNQIR7.ts.map +7 -0
  25. package/dist/chunks/{chunk-T56RNRZJ.ts → chunk-BDB4IDM3.ts} +19 -10
  26. package/dist/chunks/chunk-BDB4IDM3.ts.map +7 -0
  27. package/dist/chunks/{chunk-2RZSQWBH.ts → chunk-CHSIAC4N.ts} +5 -5
  28. package/dist/chunks/{chunk-2RZSQWBH.ts.map → chunk-CHSIAC4N.ts.map} +2 -2
  29. package/dist/chunks/chunk-HUZ5CR4W.ts +110 -0
  30. package/dist/chunks/chunk-HUZ5CR4W.ts.map +7 -0
  31. package/dist/chunks/{chunk-QLSN7AGX.ts → chunk-IJ6VV6FN.ts} +81 -19
  32. package/dist/chunks/chunk-IJ6VV6FN.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-T3PB7ZSD.ts +175 -0
  50. package/dist/chunks/conflict-review-T3PB7ZSD.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-27VQUDH2.ts} +309 -508
  71. package/dist/chunks/sync-operations-27VQUDH2.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 +6 -6
  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 +380 -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
@@ -0,0 +1,735 @@
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
+ isDeniedPath,
5
+ posixJoin,
6
+ readJsonIfExists,
7
+ safeJoin,
8
+ toPosix
9
+ } from "./chunk-HUZ5CR4W.ts";
10
+
11
+ // src/snapshot/snapshot.ts
12
+ import { createHash, randomUUID } from "node:crypto";
13
+ import fs from "node:fs/promises";
14
+ import os2 from "node:os";
15
+ import path4 from "node:path";
16
+
17
+ // src/snapshot/session-paths.ts
18
+ import os from "node:os";
19
+ import path2 from "node:path";
20
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
21
+
22
+ // src/snapshot/snapshot-paths.ts
23
+ import path from "node:path";
24
+ function expandHome(value) {
25
+ if (value === "~") return process.env.HOME ?? value;
26
+ if (value.startsWith("~/")) return path.join(process.env.HOME ?? "~", value.slice(2));
27
+ return value;
28
+ }
29
+ function sessionStorageRoot(root, configuredSessionDir2) {
30
+ return configuredSessionDir2 ? path.resolve(expandHome(configuredSessionDir2)) : path.resolve(root, "sessions");
31
+ }
32
+
33
+ // src/snapshot/session-paths.ts
34
+ function sessionDirFromContext(ctx) {
35
+ const manager = ctx.sessionManager;
36
+ const usesDefaultSessionDir = manager.usesDefaultSessionDir;
37
+ if (typeof usesDefaultSessionDir === "function" && usesDefaultSessionDir.call(manager)) {
38
+ return void 0;
39
+ }
40
+ const getSessionDir = manager.getSessionDir;
41
+ return typeof getSessionDir === "function" ? getSessionDir.call(manager) : void 0;
42
+ }
43
+ async function configuredSessionDir() {
44
+ const settings = await readJsonIfExists(path2.join(agentDir(), "settings.json"));
45
+ return settings?.sessionDir ? expandSessionDir(settings.sessionDir) : void 0;
46
+ }
47
+ async function effectiveSessionRoot(ctx) {
48
+ return path2.resolve(sessionStorageRoot(agentDir(), sessionDirFromContext(ctx) ?? await configuredSessionDir()));
49
+ }
50
+ async function sessionDirForApply(ctx, snapshot) {
51
+ const contextSessionDir = sessionDirFromContext(ctx);
52
+ const localSessionDir = await configuredSessionDir();
53
+ if (contextSessionDir && path2.resolve(contextSessionDir) !== path2.resolve(localSessionDir ?? "")) {
54
+ return contextSessionDir;
55
+ }
56
+ return sessionDirFromSnapshot(snapshot) ?? contextSessionDir;
57
+ }
58
+ function requireStableMergeSessionRoot(before, after) {
59
+ const root = agentDir();
60
+ if (path2.resolve(sessionStorageRoot(root, sessionDirFromSnapshot(before))) !== path2.resolve(sessionStorageRoot(root, sessionDirFromSnapshot(after)))) {
61
+ throw new Error(
62
+ "Merged transfer changes the session root; no local apply or baseline acceptance is allowed. Review /sync diff and choose an explicit push or pull direction."
63
+ );
64
+ }
65
+ }
66
+ function sessionDirFromSnapshot(snapshot) {
67
+ const settingsFile = snapshot.files.find((file) => file.path === "settings.json");
68
+ if (!settingsFile) return void 0;
69
+ let settings;
70
+ try {
71
+ settings = JSON.parse(
72
+ new TextDecoder("utf-8", { fatal: true }).decode(
73
+ decodeBase64Strict(settingsFile.contentBase64, settingsFile.path)
74
+ )
75
+ );
76
+ } catch {
77
+ throw new Error("Merged settings cannot be parsed; review a directional recovery.");
78
+ }
79
+ if (!settings || typeof settings !== "object" || Array.isArray(settings))
80
+ throw new Error("Merged settings must be a JSON object; review a directional recovery.");
81
+ const sessionDir = settings.sessionDir;
82
+ if (sessionDir !== void 0 && typeof sessionDir !== "string")
83
+ throw new Error("Merged sessionDir must be a string; review a directional recovery.");
84
+ return sessionDir ? expandSessionDir(sessionDir) : void 0;
85
+ }
86
+ function decodeBase64Strict(value, filePath) {
87
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(value) || value.length % 4 !== 0) {
88
+ throw new Error(`Invalid base64 content in snapshot file: ${filePath}`);
89
+ }
90
+ return Buffer.from(value, "base64");
91
+ }
92
+ function agentDir() {
93
+ return getAgentDir();
94
+ }
95
+ function expandSessionDir(value) {
96
+ return value === "~" || value.startsWith("~/") ? path2.join(os.homedir(), value.slice(2)) : value;
97
+ }
98
+ function snapshotOptionsForContext(ctx, config) {
99
+ return {
100
+ include: config.include,
101
+ sessionDir: sessionDirFromContext(ctx)
102
+ };
103
+ }
104
+
105
+ // src/sync/sync-policy.ts
106
+ import path3 from "node:path";
107
+ var BUILT_IN_SYNC_ROOTS = [
108
+ "settings.json",
109
+ "keybindings.json",
110
+ "models.json",
111
+ "AGENTS.md",
112
+ "APPEND_SYSTEM.md",
113
+ "skills",
114
+ "prompts",
115
+ "themes",
116
+ "extensions"
117
+ ];
118
+ var DEFAULT_SYNC_INCLUDE = [...BUILT_IN_SYNC_ROOTS];
119
+ var SNAPSHOT_SELECTION_VERSION = 1;
120
+ var MAX_SYNC_INCLUDE_ITEMS = 1024;
121
+ var MAX_SYNC_INCLUDE_PATH_BYTES = 4096;
122
+ var MAX_SYNC_INCLUDE_TOTAL_BYTES = 256 * 1024;
123
+ var BUILT_IN_BY_LOWER = new Map(
124
+ BUILT_IN_SYNC_ROOTS.map((fileName) => [fileName.toLowerCase(), fileName])
125
+ );
126
+ var TOP_LEVEL_FILE_PATHS = new Map(
127
+ BUILT_IN_SYNC_ROOTS.filter((fileName) => fileName.includes(".")).map((fileName) => [
128
+ fileName.toLowerCase(),
129
+ fileName
130
+ ])
131
+ );
132
+ var TOP_LEVEL_DIRS = new Set(BUILT_IN_SYNC_ROOTS.filter((fileName) => !fileName.includes(".")));
133
+ var RESERVED_TOP_LEVEL_NAMES = /* @__PURE__ */ new Set([...BUILT_IN_BY_LOWER.keys(), "sessions"]);
134
+ function normalizeSyncInclude(value) {
135
+ if (!Array.isArray(value)) {
136
+ throw new Error("Invalid pi-sync settings: sync.include must be an array.");
137
+ }
138
+ if (value.length > MAX_SYNC_INCLUDE_ITEMS) {
139
+ throw new Error(`Invalid pi-sync settings: sync.include has too many items; limit: ${MAX_SYNC_INCLUDE_ITEMS}.`);
140
+ }
141
+ const result = [];
142
+ const seen = /* @__PURE__ */ new Set();
143
+ const pathRoot = { children: /* @__PURE__ */ new Map() };
144
+ let totalBytes = 0;
145
+ for (const item of value) {
146
+ if (typeof item !== "string") {
147
+ throw new Error("Invalid pi-sync settings: sync.include items must be strings.");
148
+ }
149
+ const itemBytes = Buffer.byteLength(item, "utf8");
150
+ if (itemBytes > MAX_SYNC_INCLUDE_PATH_BYTES) {
151
+ throw new Error(
152
+ `Invalid pi-sync settings: sync.include item is too long; limit: ${MAX_SYNC_INCLUDE_PATH_BYTES} bytes.`
153
+ );
154
+ }
155
+ totalBytes += itemBytes;
156
+ if (totalBytes > MAX_SYNC_INCLUDE_TOTAL_BYTES) {
157
+ throw new Error(
158
+ `Invalid pi-sync settings: sync.include is too large; limit: ${MAX_SYNC_INCLUDE_TOTAL_BYTES} bytes.`
159
+ );
160
+ }
161
+ const trimmed = item.trim();
162
+ const builtIn = BUILT_IN_BY_LOWER.get(trimmed.toLowerCase());
163
+ const normalized = builtIn ?? (trimmed.toLowerCase() === "sessions" ? "sessions" : trimmed);
164
+ if (!builtIn && normalized !== "sessions") validateAgentRelativeInclude(normalized);
165
+ const identity = normalized.toLowerCase();
166
+ if (seen.has(identity)) {
167
+ throw new Error(`Invalid pi-sync settings: duplicate sync.include item: ${item}`);
168
+ }
169
+ addIncludePath(pathRoot, identity, item);
170
+ seen.add(identity);
171
+ result.push(normalized);
172
+ }
173
+ return result;
174
+ }
175
+ function addIncludePath(root, identity, source) {
176
+ let node = root;
177
+ for (const segment of identity.split("/")) {
178
+ if (node.selected !== void 0) throwOverlappingInclude(source);
179
+ let child = node.children.get(segment);
180
+ if (!child) {
181
+ child = { children: /* @__PURE__ */ new Map() };
182
+ node.children.set(segment, child);
183
+ }
184
+ node = child;
185
+ }
186
+ if (node.children.size > 0) throwOverlappingInclude(source);
187
+ node.selected = identity;
188
+ }
189
+ function throwOverlappingInclude(item) {
190
+ throw new Error(`Invalid pi-sync settings: overlapping sync.include items are ambiguous: ${item}`);
191
+ }
192
+ function validateAgentRelativeInclude(value) {
193
+ const normalized = toPosix(value);
194
+ const topLevel = normalized.split("/")[0]?.toLowerCase();
195
+ if (!normalized || normalized === "." || normalized === ".." || normalized.startsWith("../") || path3.posix.isAbsolute(normalized) || normalized.includes("\\") || path3.posix.normalize(normalized) !== normalized || // biome-ignore lint/suspicious/noControlCharactersInRegex: Include paths cannot contain controls.
196
+ /[\u0000-\u001f\u007f-\u009f]/u.test(normalized)) {
197
+ throw new Error(`Invalid pi-sync settings: sync.include item must be a safe agent-relative path: ${value}`);
198
+ }
199
+ if (isDeniedPath(normalized)) {
200
+ throw new Error(`Invalid pi-sync settings: ${value} cannot be synced.`);
201
+ }
202
+ if (topLevel && RESERVED_TOP_LEVEL_NAMES.has(topLevel)) {
203
+ throw new Error(
204
+ `Invalid pi-sync settings: use the canonical ${topLevel} root instead of a nested sync.include path.`
205
+ );
206
+ }
207
+ }
208
+ function portableSnapshotSelection(value) {
209
+ const selection = value;
210
+ 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")) {
211
+ throw new Error("Invalid snapshot selection policy.");
212
+ }
213
+ return {
214
+ version: SNAPSHOT_SELECTION_VERSION,
215
+ include: normalizeSyncInclude(selection.include)
216
+ };
217
+ }
218
+ function snapshotSelectionInclude(snapshot) {
219
+ return snapshot.selection === void 0 ? void 0 : portableSnapshotSelection(snapshot.selection).include;
220
+ }
221
+ function selectionForSnapshot(include) {
222
+ return { version: SNAPSHOT_SELECTION_VERSION, include: normalizeSyncInclude(include) };
223
+ }
224
+ function sameSyncInclude(left, right) {
225
+ const normalizedLeft = normalizeSyncInclude(left);
226
+ const normalizedRight = normalizeSyncInclude(right);
227
+ return normalizedLeft.length === normalizedRight.length && normalizedLeft.every((item, index) => item === normalizedRight[index]);
228
+ }
229
+ function compareSyncInclude(local, remote) {
230
+ const localInclude = normalizeSyncInclude(local);
231
+ const remoteInclude = normalizeSyncInclude(remote);
232
+ const localSet = new Set(localInclude);
233
+ const remoteSet = new Set(remoteInclude);
234
+ return {
235
+ same: sameSyncInclude(localInclude, remoteInclude),
236
+ remoteOnly: remoteInclude.filter((item) => !localSet.has(item)),
237
+ localOnly: localInclude.filter((item) => !remoteSet.has(item))
238
+ };
239
+ }
240
+ function inspectRemoteSelection(localInclude, snapshot) {
241
+ const remoteInclude = snapshotSelectionInclude(snapshot);
242
+ if (!remoteInclude) {
243
+ return { kind: "legacy", discovered: discoverLegacySnapshotInclude(snapshot) };
244
+ }
245
+ const comparison = compareSyncInclude(localInclude, remoteInclude);
246
+ return comparison.same ? { kind: "same", include: remoteInclude } : { kind: "different", include: remoteInclude, ...comparison };
247
+ }
248
+ var RemoteSelectionMismatchError = class extends Error {
249
+ decision;
250
+ setupName;
251
+ localInclude;
252
+ remoteInclude;
253
+ constructor(setupName, localInclude, remoteInclude, configIdentity = JSON.stringify([setupName, normalizeSyncInclude(localInclude)])) {
254
+ const local = normalizeSyncInclude(localInclude);
255
+ const remote = normalizeSyncInclude(remoteInclude);
256
+ super(formatRemoteSelectionMismatch(setupName, local, remote));
257
+ this.name = "RemoteSelectionMismatchError";
258
+ this.setupName = setupName;
259
+ this.localInclude = local;
260
+ this.remoteInclude = remote;
261
+ this.decision = {
262
+ setupName,
263
+ configIdentity,
264
+ localInclude: [...local],
265
+ remoteInclude: [...remote]
266
+ };
267
+ }
268
+ };
269
+ function remoteSelectionMismatch(config, remoteInclude, configIdentity) {
270
+ return new RemoteSelectionMismatchError(config.setupName, config.include, remoteInclude, configIdentity);
271
+ }
272
+ function formatRemoteSelectionMismatch(setupName, localInclude, remoteInclude) {
273
+ const comparison = compareSyncInclude(localInclude, remoteInclude);
274
+ const lines = [
275
+ `Synced content differs for sync setup \u201C${stripTerminalControls(setupName)}\u201D.`,
276
+ `Remote-only: ${comparison.remoteOnly.join(", ") || "none"}`,
277
+ `This-device-only: ${comparison.localOnly.join(", ") || "none"}`
278
+ ];
279
+ if (comparison.remoteOnly.length === 0 && comparison.localOnly.length === 0) {
280
+ lines.push(
281
+ "Only ordering differs.",
282
+ `Remote order: ${remoteInclude.join(", ") || "none"}`,
283
+ `This device order: ${localInclude.join(", ") || "none"}`
284
+ );
285
+ }
286
+ lines.push("Run /sync in TUI to review both content lists and choose what happens next.");
287
+ return lines.join("\n");
288
+ }
289
+ function stripTerminalControls(value) {
290
+ return value.replace(/[\u0000-\u001f\u007f-\u009f]/gu, "?");
291
+ }
292
+ function discoverLegacySnapshotInclude(snapshot) {
293
+ const builtIns = /* @__PURE__ */ new Set();
294
+ const custom = /* @__PURE__ */ new Set();
295
+ let sessions = false;
296
+ for (const file of snapshot.files) {
297
+ const normalized = toPosix(file.path);
298
+ if (!normalized || file.path.includes("\\") || normalized.length > 4096 || normalized.startsWith("../") || path3.posix.isAbsolute(normalized) || path3.posix.normalize(normalized) !== normalized || // biome-ignore lint/suspicious/noControlCharactersInRegex: Ignore unsafe legacy paths.
299
+ /[\u0000-\u001f\u007f-\u009f]/u.test(normalized) || isDeniedPath(normalized)) {
300
+ continue;
301
+ }
302
+ const [topLevel, ...rest] = normalized.split("/");
303
+ if (!topLevel) continue;
304
+ if (topLevel === "sessions" && rest.length > 0) {
305
+ sessions = true;
306
+ continue;
307
+ }
308
+ const builtIn = BUILT_IN_BY_LOWER.get(topLevel.toLowerCase());
309
+ if (builtIn && (TOP_LEVEL_DIRS.has(builtIn) && rest.length > 0 || !TOP_LEVEL_DIRS.has(builtIn) && rest.length === 0)) {
310
+ builtIns.add(builtIn);
311
+ continue;
312
+ }
313
+ if (isSafeCustomIncludePath(topLevel)) custom.add(topLevel);
314
+ }
315
+ return [
316
+ ...BUILT_IN_SYNC_ROOTS.filter((item) => builtIns.has(item)),
317
+ ...[...custom].sort((left, right) => left.localeCompare(right)),
318
+ ...sessions ? ["sessions"] : []
319
+ ];
320
+ }
321
+ function syncIncludeSelection(value) {
322
+ const include = normalizeSyncInclude(value);
323
+ const builtIns = include.filter((item) => BUILT_IN_BY_LOWER.has(item.toLowerCase()));
324
+ const custom = include.filter((item) => item !== "sessions" && !BUILT_IN_BY_LOWER.has(item.toLowerCase()));
325
+ return { include, builtIns, custom, sessions: include.includes("sessions") };
326
+ }
327
+ function customIncludePathsByLower(value) {
328
+ return new Map(syncIncludeSelection(value).custom.map((relativePath) => [relativePath.toLowerCase(), relativePath]));
329
+ }
330
+ function includeFromSelectionConfig(config) {
331
+ if (config.include !== void 0) return normalizeSyncInclude(config.include);
332
+ return [
333
+ ...normalizeSyncFiles(config.syncFiles),
334
+ ...normalizeExtraFiles(config.extraFiles),
335
+ ...config.syncSessions ? ["sessions"] : []
336
+ ];
337
+ }
338
+ function isConfiguredSnapshotPath(relativePath, config, _legacyExtraFiles) {
339
+ const normalized = toPosix(relativePath);
340
+ const selection = syncIncludeSelection(includeFromSelectionConfig(config));
341
+ if (normalized.startsWith("sessions/")) return selection.sessions;
342
+ const lower = normalized.toLowerCase();
343
+ if (!normalized.includes("/")) {
344
+ const builtIn = BUILT_IN_BY_LOWER.get(lower);
345
+ if (builtIn) return selection.builtIns.includes(builtIn) && !TOP_LEVEL_DIRS.has(builtIn);
346
+ }
347
+ const topLevel = normalized.slice(0, normalized.indexOf("/"));
348
+ if (selection.builtIns.includes(topLevel) && TOP_LEVEL_DIRS.has(topLevel)) {
349
+ return true;
350
+ }
351
+ return selection.custom.some((candidate) => {
352
+ const candidateLower = candidate.toLowerCase();
353
+ return lower === candidateLower || lower.startsWith(`${candidateLower}/`);
354
+ });
355
+ }
356
+ function canonicalSnapshotPathForConfig(relativePath, includePaths) {
357
+ const normalized = toPosix(relativePath);
358
+ const lower = normalized.toLowerCase();
359
+ return TOP_LEVEL_FILE_PATHS.get(lower) ?? includePaths.get(lower) ?? normalized;
360
+ }
361
+ function isPreservableUnmanagedSnapshotPath(relativePath) {
362
+ const normalized = toPosix(relativePath);
363
+ if (!normalized || isDeniedPath(normalized)) return false;
364
+ if (normalized.startsWith("sessions/")) return normalized.endsWith(".jsonl");
365
+ if (!normalized.includes("/")) {
366
+ const lower = normalized.toLowerCase();
367
+ return TOP_LEVEL_FILE_PATHS.has(lower) || !RESERVED_TOP_LEVEL_NAMES.has(lower);
368
+ }
369
+ return true;
370
+ }
371
+ function isSafeCustomIncludePath(relativePath) {
372
+ try {
373
+ validateAgentRelativeInclude(relativePath);
374
+ return true;
375
+ } catch {
376
+ return false;
377
+ }
378
+ }
379
+ function normalizeSyncFiles(value) {
380
+ if (value === void 0) return [...DEFAULT_SYNC_INCLUDE];
381
+ if (Array.isArray(value)) {
382
+ return normalizeSyncInclude(value).filter(
383
+ (item) => BUILT_IN_BY_LOWER.has(item.toLowerCase())
384
+ );
385
+ }
386
+ throw new Error("Invalid pi-sync settings: expected an include array.");
387
+ }
388
+ function normalizeExtraFiles(value) {
389
+ if (!Array.isArray(value)) return [];
390
+ return value.filter((item) => typeof item === "string" && isSafeCustomIncludePath(item));
391
+ }
392
+
393
+ // src/snapshot/snapshot.ts
394
+ var VERSION = 1;
395
+ var TOP_LEVEL_FILES = DEFAULT_SYNC_INCLUDE.filter((name) => name.includes("."));
396
+ var TOP_LEVEL_DIRS2 = new Set(DEFAULT_SYNC_INCLUDE.filter((name) => !name.includes(".")));
397
+ var SECRET_PATTERNS = [
398
+ /AWS_SECRET_ACCESS_KEY\s*[=:]\s*['"]?[A-Za-z0-9/+]{35,}/i,
399
+ /(ANTHROPIC|OPENAI|GEMINI|GOOGLE|FIRECRAWL|GITHUB|CLOUDFLARE|R2|S3)_[A-Z0-9_]*(KEY|TOKEN|SECRET)\s*[=:]\s*['"]?[^\s'"]{12,}/i,
400
+ /sk-ant-[A-Za-z0-9_-]{20,}/,
401
+ /sk-[A-Za-z0-9]{20,}/,
402
+ /gh[pousr]_[A-Za-z0-9_]{20,}/
403
+ ];
404
+ function selectTopLevelFileEntry(entries, fileName) {
405
+ const exact = entries.find((entry) => entry.isFile() && entry.name === fileName);
406
+ if (exact) return exact;
407
+ const lower = fileName.toLowerCase();
408
+ return entries.filter((entry) => entry.isFile() && entry.name.toLowerCase() === lower).sort((left, right) => left.name.localeCompare(right.name))[0];
409
+ }
410
+ function sha256(value) {
411
+ return createHash("sha256").update(value).digest("hex");
412
+ }
413
+ function isSafeSnapshotPath(relativePath) {
414
+ if (relativePath.includes("\\")) return false;
415
+ const normalized = toPosix(relativePath);
416
+ return Boolean(normalized) && normalized !== "." && normalized !== ".." && !normalized.startsWith("../") && !path4.posix.isAbsolute(normalized) && path4.posix.normalize(normalized) === normalized && !isDeniedPath(normalized);
417
+ }
418
+ function snapshotsMatch(left, right) {
419
+ const leftHashes = new Map(left.files.map((file) => [file.path, file.sha256]));
420
+ const rightHashes = new Map(right.files.map((file) => [file.path, file.sha256]));
421
+ return left.syncSessions === right.syncSessions && sameOptionalInclude(snapshotSelectionInclude(left), snapshotSelectionInclude(right)) && leftHashes.size === rightHashes.size && [...leftHashes].every(([filePath, hash]) => rightHashes.get(filePath) === hash);
422
+ }
423
+ function snapshotId() {
424
+ return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`;
425
+ }
426
+ function regenerateSnapshotIdentity(snapshot) {
427
+ return {
428
+ ...snapshot,
429
+ id: snapshotId(),
430
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
431
+ machine: os2.hostname()
432
+ };
433
+ }
434
+ async function createSnapshot(profile, options = {}) {
435
+ options.signal?.throwIfAborted();
436
+ const include = effectiveInclude(options);
437
+ const syncSessions = include.includes("sessions");
438
+ const sessionDir = options.sessionDir ?? await configuredSessionDir();
439
+ options.signal?.throwIfAborted();
440
+ const files = await collectFiles(agentDir(), { include, sessionDir, signal: options.signal });
441
+ options.signal?.throwIfAborted();
442
+ return {
443
+ version: VERSION,
444
+ id: snapshotId(),
445
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
446
+ machine: os2.hostname(),
447
+ profile,
448
+ syncSessions,
449
+ selection: selectionForSnapshot(include),
450
+ files
451
+ };
452
+ }
453
+ function effectiveInclude(options) {
454
+ if (options.include) return options.include;
455
+ return [
456
+ ...normalizeSyncFiles(options.syncFiles),
457
+ ...normalizeExtraFiles(options.extraFiles),
458
+ ...options.syncSessions ? ["sessions"] : []
459
+ ];
460
+ }
461
+ async function collectFiles(root, options = {}) {
462
+ const signal = options.signal;
463
+ signal?.throwIfAborted();
464
+ const results = [];
465
+ const entries = await fs.readdir(root, { withFileTypes: true });
466
+ signal?.throwIfAborted();
467
+ const selection = syncIncludeSelection(effectiveInclude(options));
468
+ const selectedFiles = new Set(selection.builtIns);
469
+ for (const entry of entries) {
470
+ if (entry.isDirectory() && TOP_LEVEL_DIRS2.has(entry.name) && selectedFiles.has(entry.name)) {
471
+ await collectDirectory(results, root, entry.name, { signal });
472
+ }
473
+ }
474
+ for (const fileName of TOP_LEVEL_FILES) {
475
+ if (!selectedFiles.has(fileName)) continue;
476
+ const entry = selectTopLevelFileEntry(entries, fileName);
477
+ if (entry) await addFile(results, root, entry.name, fileName, signal);
478
+ }
479
+ for (const relativePath of selection.custom) {
480
+ await collectIncludedPath(results, root, relativePath, signal);
481
+ }
482
+ if (selection.sessions) {
483
+ try {
484
+ await collectDirectory(results, sessionStorageRoot(root, options.sessionDir), "", {
485
+ sessionsOnly: true,
486
+ virtualPrefix: "sessions",
487
+ signal
488
+ });
489
+ } catch (error) {
490
+ if (error.code !== "ENOENT") throw error;
491
+ }
492
+ }
493
+ signal?.throwIfAborted();
494
+ return results.sort((left, right) => left.path.localeCompare(right.path));
495
+ }
496
+ async function collectIncludedPath(results, root, relativePath, signal) {
497
+ signal?.throwIfAborted();
498
+ const absolutePath = safeJoin(root, relativePath);
499
+ try {
500
+ const stat = await fs.lstat(absolutePath);
501
+ signal?.throwIfAborted();
502
+ if (stat.isFile()) await addFile(results, root, relativePath, relativePath, signal);
503
+ else if (stat.isDirectory()) await collectDirectory(results, root, relativePath, { signal });
504
+ } catch (error) {
505
+ if (error.code !== "ENOENT") throw error;
506
+ if (!relativePath.includes("/")) {
507
+ const entries = await fs.readdir(root, { withFileTypes: true });
508
+ signal?.throwIfAborted();
509
+ const entry = selectTopLevelFileEntry(entries, relativePath);
510
+ if (entry) await addFile(results, root, entry.name, relativePath, signal);
511
+ }
512
+ }
513
+ }
514
+ async function collectDirectory(results, root, relativeDirectory, options = {}) {
515
+ options.signal?.throwIfAborted();
516
+ const absoluteDirectory = path4.join(root, relativeDirectory);
517
+ for (const entry of await fs.readdir(absoluteDirectory, { withFileTypes: true })) {
518
+ options.signal?.throwIfAborted();
519
+ const relativePath = relativeDirectory ? posixJoin(relativeDirectory, entry.name) : entry.name;
520
+ const snapshotPath = options.virtualPrefix ? posixJoin(options.virtualPrefix, relativePath) : relativePath;
521
+ if (isDeniedPath(snapshotPath)) continue;
522
+ if (entry.isDirectory()) {
523
+ await collectDirectory(results, root, relativePath, options);
524
+ } else if (entry.isFile() && (!options.sessionsOnly || isSessionFilePath(snapshotPath))) {
525
+ await addFile(results, root, relativePath, snapshotPath, options.signal);
526
+ }
527
+ }
528
+ }
529
+ async function addFile(results, root, relativePath, snapshotPath = relativePath, signal) {
530
+ signal?.throwIfAborted();
531
+ if (!isSafeSnapshotPath(snapshotPath)) return;
532
+ const absolutePath = safeJoin(root, relativePath);
533
+ const content = await fs.readFile(absolutePath, { signal });
534
+ signal?.throwIfAborted();
535
+ results.push({
536
+ path: snapshotPath,
537
+ contentBase64: content.toString("base64"),
538
+ sha256: sha256(content)
539
+ });
540
+ }
541
+ function isSessionPath(relativePath) {
542
+ return toPosix(relativePath).startsWith("sessions/");
543
+ }
544
+ function isSessionFilePath(relativePath) {
545
+ const normalized = toPosix(relativePath);
546
+ return isSessionPath(normalized) && normalized.endsWith(".jsonl");
547
+ }
548
+ function sessionSnapshotPathFromAbsolute(sessionFile, configuredSessionDir2) {
549
+ const relativePath = toPosix(path4.relative(sessionStorageRoot(agentDir(), configuredSessionDir2), sessionFile));
550
+ if (!relativePath || relativePath.startsWith("../") || path4.posix.isAbsolute(relativePath)) {
551
+ return void 0;
552
+ }
553
+ const snapshotPath = posixJoin("sessions", relativePath);
554
+ return isSessionFilePath(snapshotPath) ? snapshotPath : void 0;
555
+ }
556
+ function snapshotTarget(root, relativePath, configuredSessionDir2) {
557
+ if (isSessionPath(relativePath)) {
558
+ return safeJoin(sessionStorageRoot(root, configuredSessionDir2), relativePath.slice("sessions/".length));
559
+ }
560
+ return safeJoin(root, relativePath);
561
+ }
562
+ function snapshotIncludesSessions(snapshot) {
563
+ return snapshot.syncSessions === true || snapshotSelectionInclude(snapshot)?.includes("sessions") === true || snapshot.files.some((file) => isSessionPath(file.path));
564
+ }
565
+ function filterSnapshotForConfigPolicy(snapshot, config, options = {}) {
566
+ const include = includeFromSelectionConfig(config);
567
+ const includePaths = customIncludePathsByLower(include);
568
+ const filtered = {
569
+ ...snapshot,
570
+ syncSessions: include.includes("sessions") ? snapshot.syncSessions : false,
571
+ selection: selectionForSnapshot(include),
572
+ files: canonicalizeSnapshotFilesForConfig(snapshot.files, config, includePaths)
573
+ };
574
+ if (!options.regenerateId || snapshotsMatch(snapshot, filtered)) return filtered;
575
+ return {
576
+ ...filtered,
577
+ id: snapshotId(),
578
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
579
+ machine: os2.hostname()
580
+ };
581
+ }
582
+ function canonicalizeSnapshotFilesForConfig(files, config, includePaths) {
583
+ const configuredFiles = [];
584
+ const extraCandidates = /* @__PURE__ */ new Map();
585
+ for (const file of files) {
586
+ const normalized = toPosix(file.path);
587
+ if (!isSafeSnapshotPath(file.path) || !isConfiguredSnapshotPath(normalized, config)) {
588
+ continue;
589
+ }
590
+ if (normalized.includes("/")) {
591
+ configuredFiles.push(normalized === file.path ? file : { ...file, path: normalized });
592
+ continue;
593
+ }
594
+ const topLevelPath = canonicalSnapshotPathForConfig(normalized, includePaths);
595
+ const candidate = {
596
+ exact: normalized === topLevelPath,
597
+ file: { ...file, path: topLevelPath },
598
+ originalPath: normalized
599
+ };
600
+ const current = extraCandidates.get(topLevelPath.toLowerCase());
601
+ if (!current || isPreferredExtraCandidate(candidate, current)) {
602
+ extraCandidates.set(topLevelPath.toLowerCase(), candidate);
603
+ }
604
+ }
605
+ return [...configuredFiles, ...[...extraCandidates.values()].map((candidate) => candidate.file)].sort(
606
+ (left, right) => left.path.localeCompare(right.path)
607
+ );
608
+ }
609
+ function isPreferredExtraCandidate(left, right) {
610
+ if (left.exact !== right.exact) return left.exact;
611
+ return left.originalPath.localeCompare(right.originalPath) < 0;
612
+ }
613
+ function snapshotWithoutSessions(snapshot) {
614
+ const files = snapshot.files.filter((file) => !isSessionPath(file.path));
615
+ const include = snapshotSelectionInclude(snapshot)?.filter((item) => item !== "sessions");
616
+ if (files.length === snapshot.files.length && snapshot.syncSessions !== true && include?.length === snapshot.selection?.include.length) {
617
+ return snapshot;
618
+ }
619
+ return {
620
+ ...snapshot,
621
+ id: snapshotId(),
622
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
623
+ machine: os2.hostname(),
624
+ syncSessions: false,
625
+ ...include ? { selection: selectionForSnapshot(include) } : {},
626
+ files
627
+ };
628
+ }
629
+ function scanSnapshot(snapshot) {
630
+ const findings = [];
631
+ for (const file of snapshot.files) {
632
+ const content = Buffer.from(file.contentBase64, "base64");
633
+ if (content.includes(0)) continue;
634
+ const text = content.toString("utf8");
635
+ for (const pattern of SECRET_PATTERNS) {
636
+ if (pattern.test(text)) {
637
+ findings.push(file.path);
638
+ break;
639
+ }
640
+ }
641
+ }
642
+ return findings;
643
+ }
644
+ function mergeRemotePreservedFiles(local, remote, config) {
645
+ const localPathNames = new Set(local.files.map((file) => file.path.toLowerCase()));
646
+ const preservedPathNames = /* @__PURE__ */ new Set();
647
+ const preserved = remote.files.filter((file) => {
648
+ const normalized = toPosix(file.path);
649
+ const lower = normalized.toLowerCase();
650
+ if (localPathNames.has(lower) || preservedPathNames.has(lower) || !isSafeSnapshotPath(file.path) || isConfiguredSnapshotPath(normalized, config) || !isPreservableUnmanagedSnapshotPath(normalized)) {
651
+ return false;
652
+ }
653
+ preservedPathNames.add(lower);
654
+ return true;
655
+ });
656
+ if (preserved.length === 0) return local;
657
+ return {
658
+ ...local,
659
+ id: snapshotId(),
660
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
661
+ machine: os2.hostname(),
662
+ syncSessions: snapshotIncludesSessions(local) || snapshotIncludesSessions(remote),
663
+ files: [...local.files, ...preserved].sort((left, right) => left.path.localeCompare(right.path))
664
+ };
665
+ }
666
+ function mergeRemoteSessionFiles(local, remote) {
667
+ const remoteSessions = remote.files.filter((file) => {
668
+ const normalized = toPosix(file.path);
669
+ return isSessionFilePath(normalized) && isSafeSnapshotPath(file.path);
670
+ });
671
+ if (remoteSessions.length === 0 && !snapshotIncludesSessions(remote)) return local;
672
+ const localInclude = snapshotSelectionInclude(local);
673
+ return {
674
+ ...local,
675
+ id: snapshotId(),
676
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
677
+ machine: os2.hostname(),
678
+ syncSessions: true,
679
+ ...localInclude ? {
680
+ selection: selectionForSnapshot(
681
+ localInclude.includes("sessions") ? localInclude : [...localInclude, "sessions"]
682
+ )
683
+ } : {},
684
+ files: [...local.files.filter((file) => !isSessionPath(file.path)), ...remoteSessions].sort(
685
+ (left, right) => left.path.localeCompare(right.path)
686
+ )
687
+ };
688
+ }
689
+ function sameOptionalInclude(left, right) {
690
+ if (!left || !right) return left === right;
691
+ return left.length === right.length && left.every((item, index) => item === right[index]);
692
+ }
693
+
694
+ export {
695
+ sessionStorageRoot,
696
+ sessionDirFromContext,
697
+ configuredSessionDir,
698
+ effectiveSessionRoot,
699
+ sessionDirForApply,
700
+ requireStableMergeSessionRoot,
701
+ agentDir,
702
+ expandSessionDir,
703
+ snapshotOptionsForContext,
704
+ BUILT_IN_SYNC_ROOTS,
705
+ DEFAULT_SYNC_INCLUDE,
706
+ normalizeSyncInclude,
707
+ portableSnapshotSelection,
708
+ snapshotSelectionInclude,
709
+ sameSyncInclude,
710
+ compareSyncInclude,
711
+ inspectRemoteSelection,
712
+ RemoteSelectionMismatchError,
713
+ remoteSelectionMismatch,
714
+ formatRemoteSelectionMismatch,
715
+ syncIncludeSelection,
716
+ customIncludePathsByLower,
717
+ includeFromSelectionConfig,
718
+ isConfiguredSnapshotPath,
719
+ canonicalSnapshotPathForConfig,
720
+ isSafeCustomIncludePath,
721
+ regenerateSnapshotIdentity,
722
+ createSnapshot,
723
+ collectFiles,
724
+ isSessionPath,
725
+ isSessionFilePath,
726
+ sessionSnapshotPathFromAbsolute,
727
+ snapshotTarget,
728
+ snapshotIncludesSessions,
729
+ filterSnapshotForConfigPolicy,
730
+ snapshotWithoutSessions,
731
+ scanSnapshot,
732
+ mergeRemotePreservedFiles,
733
+ mergeRemoteSessionFiles
734
+ };
735
+ //# sourceMappingURL=chunk-QOFMXPM5.ts.map