@narumitw/pi-sync 0.50.2 → 0.51.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 (218) hide show
  1. package/README.md +4 -1
  2. package/dist/chunks/{chunk-6LP3MCR2.ts → chunk-2JZFHNLM.ts} +3 -5
  3. package/dist/chunks/chunk-2JZFHNLM.ts.map +7 -0
  4. package/dist/chunks/{chunk-LCRI3ABW.ts → chunk-2RZSQWBH.ts} +8 -19
  5. package/dist/chunks/chunk-2RZSQWBH.ts.map +7 -0
  6. package/dist/chunks/{chunk-IJ6FJFA5.ts → chunk-4AIHXRKE.ts} +11 -17
  7. package/dist/chunks/chunk-4AIHXRKE.ts.map +7 -0
  8. package/dist/chunks/{chunk-C7XY5NF5.ts → chunk-4MPIH46N.ts} +29 -51
  9. package/dist/chunks/chunk-4MPIH46N.ts.map +7 -0
  10. package/dist/chunks/{chunk-5B3FXBLE.ts → chunk-6546TS54.ts} +16 -20
  11. package/dist/chunks/chunk-6546TS54.ts.map +7 -0
  12. package/dist/chunks/{chunk-Q2TUM5X6.ts → chunk-6NT3YFGY.ts} +2 -2
  13. package/dist/chunks/chunk-6NT3YFGY.ts.map +7 -0
  14. package/dist/chunks/{chunk-2B2ELUF5.ts → chunk-7D5SF7P3.ts} +1 -1
  15. package/dist/chunks/chunk-7D5SF7P3.ts.map +7 -0
  16. package/dist/chunks/{chunk-4CHTZMEO.ts → chunk-7KHKWL3Q.ts} +37 -55
  17. package/dist/chunks/chunk-7KHKWL3Q.ts.map +7 -0
  18. package/dist/chunks/{chunk-ZI5MXMEX.ts → chunk-BVVP33U5.ts} +2 -4
  19. package/dist/chunks/chunk-BVVP33U5.ts.map +7 -0
  20. package/dist/chunks/{chunk-SZWBGPID.ts → chunk-DM4X4FIA.ts} +8 -13
  21. package/dist/chunks/chunk-DM4X4FIA.ts.map +7 -0
  22. package/dist/chunks/{chunk-SL64RLXR.ts → chunk-DZUSTGQV.ts} +8 -16
  23. package/dist/chunks/chunk-DZUSTGQV.ts.map +7 -0
  24. package/dist/chunks/{chunk-G4RNQQZS.ts → chunk-E42GXD6D.ts} +13 -19
  25. package/dist/chunks/chunk-E42GXD6D.ts.map +7 -0
  26. package/dist/chunks/{chunk-Z4DQF6L3.ts → chunk-ECYCNZOE.ts} +1 -1
  27. package/dist/chunks/chunk-ECYCNZOE.ts.map +7 -0
  28. package/dist/chunks/{chunk-OEX4ABY6.ts → chunk-EEJPYTWE.ts} +11 -21
  29. package/dist/chunks/chunk-EEJPYTWE.ts.map +7 -0
  30. package/dist/chunks/{chunk-CPEHGXJV.ts → chunk-G4RUXRWZ.ts} +3 -5
  31. package/dist/chunks/chunk-G4RUXRWZ.ts.map +7 -0
  32. package/dist/chunks/{chunk-T2B5IU74.ts → chunk-HGJH56KR.ts} +4 -4
  33. package/dist/chunks/chunk-HGJH56KR.ts.map +7 -0
  34. package/dist/chunks/{chunk-JAAKLL6M.ts → chunk-IVXEDPAD.ts} +10 -34
  35. package/dist/chunks/chunk-IVXEDPAD.ts.map +7 -0
  36. package/dist/chunks/chunk-K3RIEL6J.ts +20 -0
  37. package/dist/chunks/chunk-K3RIEL6J.ts.map +7 -0
  38. package/dist/chunks/{chunk-LTBWGSBV.ts → chunk-MSSW6F3R.ts} +1 -1
  39. package/dist/chunks/chunk-MSSW6F3R.ts.map +7 -0
  40. package/dist/chunks/{chunk-T3HH3UEJ.ts → chunk-OVDTF5CH.ts} +4 -10
  41. package/dist/chunks/chunk-OVDTF5CH.ts.map +7 -0
  42. package/dist/chunks/{chunk-NJFUIVYL.ts → chunk-PWMWOJP4.ts} +1 -1
  43. package/dist/chunks/chunk-PWMWOJP4.ts.map +7 -0
  44. package/dist/chunks/{chunk-HUDQATCL.ts → chunk-Q32CQWPM.ts} +6 -8
  45. package/dist/chunks/chunk-Q32CQWPM.ts.map +7 -0
  46. package/dist/chunks/{chunk-ETTRJDNP.ts → chunk-QLSN7AGX.ts} +6 -13
  47. package/dist/chunks/chunk-QLSN7AGX.ts.map +7 -0
  48. package/dist/chunks/{chunk-THMF4BVL.ts → chunk-SWUXA3MH.ts} +1 -1
  49. package/dist/chunks/chunk-SWUXA3MH.ts.map +7 -0
  50. package/dist/chunks/{chunk-TEN2ZBJV.ts → chunk-T2SJD3N7.ts} +2 -4
  51. package/dist/chunks/chunk-T2SJD3N7.ts.map +7 -0
  52. package/dist/chunks/{chunk-AUSDLD6F.ts → chunk-T56RNRZJ.ts} +10 -26
  53. package/dist/chunks/chunk-T56RNRZJ.ts.map +7 -0
  54. package/dist/chunks/{chunk-KW7YQEZN.ts → chunk-UA7RXOIT.ts} +48 -100
  55. package/dist/chunks/chunk-UA7RXOIT.ts.map +7 -0
  56. package/dist/chunks/{chunk-D5X27LCA.ts → chunk-UETBKTDI.ts} +1 -1
  57. package/dist/chunks/chunk-UETBKTDI.ts.map +7 -0
  58. package/dist/chunks/{chunk-CHH26D4F.ts → chunk-YFADF5ZQ.ts} +7 -6
  59. package/dist/chunks/chunk-YFADF5ZQ.ts.map +7 -0
  60. package/dist/chunks/{chunk-CCSN3F47.ts → chunk-YPXGU5OR.ts} +4 -4
  61. package/dist/chunks/chunk-YPXGU5OR.ts.map +7 -0
  62. package/dist/chunks/{file-selection-FTTKAOYX.ts → file-selection-RAVHRYFG.ts} +14 -26
  63. package/dist/chunks/file-selection-RAVHRYFG.ts.map +7 -0
  64. package/dist/chunks/{git-backend-GMVS4PD4.ts → git-backend-IAANDL3K.ts} +41 -104
  65. package/dist/chunks/git-backend-IAANDL3K.ts.map +7 -0
  66. package/dist/chunks/manager-result-dispatcher-OTUMQM26.ts +23 -0
  67. package/dist/chunks/{manager-ui-5KLKVMRC.ts → manager-ui-763NEKJY.ts} +124 -251
  68. package/dist/chunks/manager-ui-763NEKJY.ts.map +7 -0
  69. package/dist/chunks/remote-selection-ui-TZTJF32Y.ts +22 -0
  70. package/dist/chunks/{s3-backend-66O4FGBH.ts → s3-backend-XGXUOPUN.ts} +23 -54
  71. package/dist/chunks/s3-backend-XGXUOPUN.ts.map +7 -0
  72. package/dist/chunks/{setup-switch-IS2PEZ3J.ts → setup-switch-5G2VNJHL.ts} +9 -9
  73. package/dist/chunks/{setup-wizard-IUB6ESEU.ts → setup-wizard-YIEWGX32.ts} +12 -12
  74. package/dist/chunks/setup-wizard-YIEWGX32.ts.map +7 -0
  75. package/dist/chunks/{snapshot-HKEWCVK6.ts → snapshot-JL5DFSXI.ts} +5 -5
  76. package/dist/chunks/sync-inspection-P3ENJLNF.ts +20 -0
  77. package/dist/chunks/{sync-operations-LBX3GKCM.ts → sync-operations-AR76EB6C.ts} +85 -201
  78. package/dist/chunks/sync-operations-AR76EB6C.ts.map +7 -0
  79. package/dist/chunks/{sync-state-Q2FTXTW6.ts → sync-state-VHJUZ4YG.ts} +6 -6
  80. package/dist/chunks/{webdav-backend-ECIKD7SO.ts → webdav-backend-3EGUONAT.ts} +20 -51
  81. package/dist/chunks/webdav-backend-3EGUONAT.ts.map +7 -0
  82. package/dist/index.ts +49 -74
  83. package/dist/index.ts.map +3 -3
  84. package/docs/settings.md +9 -0
  85. package/package.json +61 -62
  86. package/src/backends/backend-factory.ts +15 -15
  87. package/src/backends/backend-identity.ts +31 -31
  88. package/src/backends/backend-types.ts +42 -42
  89. package/src/backends/git/git-backend.ts +714 -788
  90. package/src/backends/git/git-config.ts +104 -111
  91. package/src/backends/git/git-runner.ts +230 -247
  92. package/src/backends/git/git-storage.ts +228 -245
  93. package/src/backends/s3/s3-backend.ts +408 -441
  94. package/src/backends/s3/s3-client.ts +288 -333
  95. package/src/backends/sync-backend.ts +65 -65
  96. package/src/backends/webdav/webdav-backend.ts +542 -589
  97. package/src/backends/webdav/webdav-client.ts +292 -316
  98. package/src/backends/webdav/webdav-config.ts +57 -68
  99. package/src/commands/command-execution.ts +270 -289
  100. package/src/commands/command-handler.ts +160 -163
  101. package/src/commands/command-types.ts +13 -13
  102. package/src/commands/command.ts +190 -196
  103. package/src/commands/setup-completions.ts +1 -1
  104. package/src/paths.ts +47 -47
  105. package/src/settings/config-errors.ts +4 -5
  106. package/src/settings/config-file.ts +473 -506
  107. package/src/settings/config.ts +171 -174
  108. package/src/settings/settings-management.ts +191 -216
  109. package/src/settings/settings-store.ts +63 -77
  110. package/src/settings/settings-types.ts +78 -75
  111. package/src/settings/settings-validation.ts +328 -364
  112. package/src/snapshot/session-paths.ts +41 -55
  113. package/src/snapshot/snapshot-apply.ts +197 -229
  114. package/src/snapshot/snapshot-codec.ts +29 -31
  115. package/src/snapshot/snapshot-paths.ts +4 -6
  116. package/src/snapshot/snapshot-transaction.ts +161 -170
  117. package/src/snapshot/snapshot-types.ts +24 -24
  118. package/src/snapshot/snapshot.ts +310 -343
  119. package/src/state/json-file.ts +11 -11
  120. package/src/state/lock.ts +226 -242
  121. package/src/state/lockfile-fs.ts +20 -20
  122. package/src/state/operation-availability.ts +36 -41
  123. package/src/state/state-directory.ts +171 -173
  124. package/src/state/state-types.ts +15 -15
  125. package/src/state/sync-state-store.ts +23 -23
  126. package/src/sync/automatic-sync.ts +75 -80
  127. package/src/sync/remote-snapshot.ts +52 -60
  128. package/src/sync/setup-switch.ts +99 -114
  129. package/src/sync/signals.ts +3 -5
  130. package/src/sync/startup-check.ts +97 -111
  131. package/src/sync/sync-decision.ts +76 -80
  132. package/src/sync/sync-error-guidance.ts +41 -48
  133. package/src/sync/sync-errors.ts +10 -10
  134. package/src/sync/sync-inspection.ts +29 -33
  135. package/src/sync/sync-loaders.ts +28 -36
  136. package/src/sync/sync-mutations.ts +510 -622
  137. package/src/sync/sync-operations.ts +7 -7
  138. package/src/sync/sync-policy.ts +296 -326
  139. package/src/sync/sync-queries.ts +175 -194
  140. package/src/sync/sync-state.ts +74 -100
  141. package/src/sync-extension.ts +89 -101
  142. package/src/sync.ts +26 -26
  143. package/src/types.ts +37 -37
  144. package/src/ui/automatic-sync-summary.ts +1 -3
  145. package/src/ui/cancellable-operation.ts +92 -112
  146. package/src/ui/file-selection.ts +250 -274
  147. package/src/ui/manager-attention.ts +51 -63
  148. package/src/ui/manager-recovery.ts +48 -57
  149. package/src/ui/manager-result-dispatcher.ts +62 -76
  150. package/src/ui/manager-state.ts +200 -232
  151. package/src/ui/manager-ui.ts +264 -362
  152. package/src/ui/remote-selection-ui.ts +547 -588
  153. package/src/ui/secret-input.ts +198 -214
  154. package/src/ui/settings-ui.ts +175 -172
  155. package/src/ui/setup/git-ui.ts +232 -261
  156. package/src/ui/setup/s3-credentials-ui.ts +58 -65
  157. package/src/ui/setup/s3-ui.ts +171 -195
  158. package/src/ui/setup/setup-actions.ts +91 -101
  159. package/src/ui/setup/setup-location-ui.ts +121 -137
  160. package/src/ui/setup/setup-name-ui.ts +27 -27
  161. package/src/ui/setup/setup-prompts.ts +65 -65
  162. package/src/ui/setup/setup-review.ts +58 -59
  163. package/src/ui/setup/setup-switcher.ts +156 -160
  164. package/src/ui/setup/setup-wizard.ts +28 -28
  165. package/src/ui/setup/text-input.ts +54 -58
  166. package/src/ui/setup/webdav-ui.ts +246 -293
  167. package/src/ui/storage-connections-ui.ts +317 -369
  168. package/src/ui/sync-attention.ts +148 -164
  169. package/src/ui/sync-format.ts +110 -121
  170. package/src/ui/sync-resolution-ui.ts +147 -153
  171. package/src/ui/sync-setups-ui.ts +218 -238
  172. package/src/ui/sync-status.ts +17 -0
  173. package/src/ui/terminal-text.ts +2 -2
  174. package/dist/chunks/chunk-2B2ELUF5.ts.map +0 -7
  175. package/dist/chunks/chunk-4CHTZMEO.ts.map +0 -7
  176. package/dist/chunks/chunk-5B3FXBLE.ts.map +0 -7
  177. package/dist/chunks/chunk-6LP3MCR2.ts.map +0 -7
  178. package/dist/chunks/chunk-AUSDLD6F.ts.map +0 -7
  179. package/dist/chunks/chunk-C7XY5NF5.ts.map +0 -7
  180. package/dist/chunks/chunk-CCSN3F47.ts.map +0 -7
  181. package/dist/chunks/chunk-CHH26D4F.ts.map +0 -7
  182. package/dist/chunks/chunk-CPEHGXJV.ts.map +0 -7
  183. package/dist/chunks/chunk-D5X27LCA.ts.map +0 -7
  184. package/dist/chunks/chunk-ETTRJDNP.ts.map +0 -7
  185. package/dist/chunks/chunk-G4RNQQZS.ts.map +0 -7
  186. package/dist/chunks/chunk-HUDQATCL.ts.map +0 -7
  187. package/dist/chunks/chunk-IJ6FJFA5.ts.map +0 -7
  188. package/dist/chunks/chunk-JAAKLL6M.ts.map +0 -7
  189. package/dist/chunks/chunk-KW7YQEZN.ts.map +0 -7
  190. package/dist/chunks/chunk-LCRI3ABW.ts.map +0 -7
  191. package/dist/chunks/chunk-LTBWGSBV.ts.map +0 -7
  192. package/dist/chunks/chunk-NJFUIVYL.ts.map +0 -7
  193. package/dist/chunks/chunk-OEX4ABY6.ts.map +0 -7
  194. package/dist/chunks/chunk-Q2TUM5X6.ts.map +0 -7
  195. package/dist/chunks/chunk-SL64RLXR.ts.map +0 -7
  196. package/dist/chunks/chunk-SZWBGPID.ts.map +0 -7
  197. package/dist/chunks/chunk-T2B5IU74.ts.map +0 -7
  198. package/dist/chunks/chunk-T3HH3UEJ.ts.map +0 -7
  199. package/dist/chunks/chunk-TEN2ZBJV.ts.map +0 -7
  200. package/dist/chunks/chunk-THMF4BVL.ts.map +0 -7
  201. package/dist/chunks/chunk-Z4DQF6L3.ts.map +0 -7
  202. package/dist/chunks/chunk-ZI5MXMEX.ts.map +0 -7
  203. package/dist/chunks/file-selection-FTTKAOYX.ts.map +0 -7
  204. package/dist/chunks/git-backend-GMVS4PD4.ts.map +0 -7
  205. package/dist/chunks/manager-result-dispatcher-FKIBM3GO.ts +0 -22
  206. package/dist/chunks/manager-ui-5KLKVMRC.ts.map +0 -7
  207. package/dist/chunks/remote-selection-ui-D7RBRJNL.ts +0 -21
  208. package/dist/chunks/s3-backend-66O4FGBH.ts.map +0 -7
  209. package/dist/chunks/setup-wizard-IUB6ESEU.ts.map +0 -7
  210. package/dist/chunks/sync-inspection-CST2KL65.ts +0 -20
  211. package/dist/chunks/sync-operations-LBX3GKCM.ts.map +0 -7
  212. package/dist/chunks/webdav-backend-ECIKD7SO.ts.map +0 -7
  213. /package/dist/chunks/{manager-result-dispatcher-FKIBM3GO.ts.map → manager-result-dispatcher-OTUMQM26.ts.map} +0 -0
  214. /package/dist/chunks/{remote-selection-ui-D7RBRJNL.ts.map → remote-selection-ui-TZTJF32Y.ts.map} +0 -0
  215. /package/dist/chunks/{setup-switch-IS2PEZ3J.ts.map → setup-switch-5G2VNJHL.ts.map} +0 -0
  216. /package/dist/chunks/{snapshot-HKEWCVK6.ts.map → snapshot-JL5DFSXI.ts.map} +0 -0
  217. /package/dist/chunks/{sync-inspection-CST2KL65.ts.map → sync-inspection-P3ENJLNF.ts.map} +0 -0
  218. /package/dist/chunks/{sync-state-Q2FTXTW6.ts.map → sync-state-VHJUZ4YG.ts.map} +0 -0
@@ -6,10 +6,10 @@ import { localConfigPath } from "../settings/config-file.js";
6
6
  import { updateSyncSetup } from "../settings/settings-management.js";
7
7
  import { agentDir } from "../snapshot/session-paths.js";
8
8
  import {
9
- BUILT_IN_SYNC_ROOTS,
10
- isSafeCustomIncludePath,
11
- normalizeSyncInclude,
12
- syncIncludeSelection,
9
+ BUILT_IN_SYNC_ROOTS,
10
+ isSafeCustomIncludePath,
11
+ normalizeSyncInclude,
12
+ syncIncludeSelection,
13
13
  } from "../sync/sync-policy.js";
14
14
 
15
15
  const BUILT_IN_PREFIX = "builtin:";
@@ -18,310 +18,286 @@ const SESSIONS_ID = "sessions";
18
18
  const ADD_CUSTOM_ID = "add-custom";
19
19
 
20
20
  interface SelectionDraft {
21
- builtIns: Set<string>;
22
- custom: Set<string>;
23
- sessions: boolean;
21
+ builtIns: Set<string>;
22
+ custom: Set<string>;
23
+ sessions: boolean;
24
24
  }
25
25
 
26
- export async function showFileSelection(
27
- ctx: ExtensionCommandContext,
28
- setupName?: string,
29
- signal?: AbortSignal,
30
- ) {
31
- const config = await loadConfig(setupName);
32
- if (signal?.aborted) return;
33
- const selection = syncIncludeSelection(config.include);
34
- const original: SelectionDraft = {
35
- builtIns: new Set(selection.builtIns),
36
- custom: new Set(selection.custom),
37
- sessions: selection.sessions,
38
- };
39
- const draft = cloneDraft(original);
40
- const customCandidates = await listCustomCandidates(draft.custom);
26
+ export async function showFileSelection(ctx: ExtensionCommandContext, setupName?: string, signal?: AbortSignal) {
27
+ const config = await loadConfig(setupName);
28
+ if (signal?.aborted) return;
29
+ const selection = syncIncludeSelection(config.include);
30
+ const original: SelectionDraft = {
31
+ builtIns: new Set(selection.builtIns),
32
+ custom: new Set(selection.custom),
33
+ sessions: selection.sessions,
34
+ };
35
+ const draft = cloneDraft(original);
36
+ const customCandidates = await listCustomCandidates(draft.custom);
41
37
 
42
- if (ctx.mode !== "tui") {
43
- ctx.ui.notify(
44
- [
45
- `pi-sync included content for sync setup ${safeTerminalText(config.setupName)}:`,
46
- `include: ${config.include.map(safeTerminalText).join(", ") || "none"}`,
47
- `Edit sync.include in ${safeTerminalText(localConfigPath())}.`,
48
- ].join("\n"),
49
- "info",
50
- );
51
- return;
52
- }
38
+ if (ctx.mode !== "tui") {
39
+ ctx.ui.notify(
40
+ [
41
+ `pi-sync included content for sync setup ${safeTerminalText(config.setupName)}:`,
42
+ `include: ${config.include.map(safeTerminalText).join(", ") || "none"}`,
43
+ `Edit sync.include in ${safeTerminalText(localConfigPath())}.`,
44
+ ].join("\n"),
45
+ "info",
46
+ );
47
+ return;
48
+ }
53
49
 
54
- while (!signal?.aborted) {
55
- await showDraftEditor(ctx, config.setupName, draft, customCandidates, signal);
56
- if (signal?.aborted || sameDraft(original, draft)) return;
57
- const choice = await showDraftReview(ctx, original, draft, signal);
58
- if (signal?.aborted) return;
59
- if (choice === "Continue editing") continue;
60
- if (choice !== "Save changes") {
61
- ctx.ui.notify("Included-content changes discarded.", "info");
62
- return;
63
- }
64
- try {
65
- if (!original.sessions && draft.sessions) {
66
- const acknowledged = await ctx.ui.confirm(
67
- "Include session conversations?",
68
- "Session JSONL may contain prompts, tool output, file paths, images, and secrets. Continue only with storage you trust.",
69
- { signal },
70
- );
71
- if (signal?.aborted) return;
72
- if (!acknowledged) {
73
- ctx.ui.notify("Session inclusion was not saved.", "info");
74
- return;
75
- }
76
- }
77
- const include = [
78
- ...BUILT_IN_SYNC_ROOTS.filter((candidate) => draft.builtIns.has(candidate)),
79
- ...draft.custom,
80
- ...(draft.sessions ? ["sessions"] : []),
81
- ];
82
- if (signal?.aborted) return;
83
- await updateSyncSetup(
84
- config.setupName,
85
- (setup) => ({
86
- ...setup,
87
- sync: { ...setup.sync, include },
88
- }),
89
- { expectedInclude: config.include, signal },
90
- );
91
- if (signal?.aborted) return;
92
- ctx.ui.notify(
93
- `Saved included content for sync setup “${safeTerminalText(config.setupName)}”. It applies to the next manual or automatic sync.`,
94
- "info",
95
- );
96
- } catch (error) {
97
- if (signal?.aborted) return;
98
- ctx.ui.notify(
99
- `Could not save pi-sync file selection: ${safeTerminalText(error instanceof Error ? error.message : String(error))}`,
100
- "error",
101
- );
102
- }
103
- return;
104
- }
50
+ while (!signal?.aborted) {
51
+ await showDraftEditor(ctx, config.setupName, draft, customCandidates, signal);
52
+ if (signal?.aborted || sameDraft(original, draft)) return;
53
+ const choice = await showDraftReview(ctx, original, draft, signal);
54
+ if (signal?.aborted) return;
55
+ if (choice === "Continue editing") continue;
56
+ if (choice !== "Save changes") {
57
+ ctx.ui.notify("Included-content changes discarded.", "info");
58
+ return;
59
+ }
60
+ try {
61
+ if (!original.sessions && draft.sessions) {
62
+ const acknowledged = await ctx.ui.confirm(
63
+ "Include session conversations?",
64
+ "Session JSONL may contain prompts, tool output, file paths, images, and secrets. Continue only with storage you trust.",
65
+ { signal },
66
+ );
67
+ if (signal?.aborted) return;
68
+ if (!acknowledged) {
69
+ ctx.ui.notify("Session inclusion was not saved.", "info");
70
+ return;
71
+ }
72
+ }
73
+ const include = [
74
+ ...BUILT_IN_SYNC_ROOTS.filter((candidate) => draft.builtIns.has(candidate)),
75
+ ...draft.custom,
76
+ ...(draft.sessions ? ["sessions"] : []),
77
+ ];
78
+ if (signal?.aborted) return;
79
+ await updateSyncSetup(
80
+ config.setupName,
81
+ (setup) => ({
82
+ ...setup,
83
+ sync: { ...setup.sync, include },
84
+ }),
85
+ { expectedInclude: config.include, signal },
86
+ );
87
+ if (signal?.aborted) return;
88
+ ctx.ui.notify(
89
+ `Saved included content for sync setup “${safeTerminalText(config.setupName)}”. It applies to the next manual or automatic sync.`,
90
+ "info",
91
+ );
92
+ } catch (error) {
93
+ if (signal?.aborted) return;
94
+ ctx.ui.notify(
95
+ `Could not save pi-sync file selection: ${safeTerminalText(error instanceof Error ? error.message : String(error))}`,
96
+ "error",
97
+ );
98
+ }
99
+ return;
100
+ }
105
101
  }
106
102
 
107
103
  async function showDraftEditor(
108
- ctx: ExtensionCommandContext,
109
- setupName: string,
110
- draft: SelectionDraft,
111
- customCandidates: string[],
112
- signal?: AbortSignal,
104
+ ctx: ExtensionCommandContext,
105
+ setupName: string,
106
+ draft: SelectionDraft,
107
+ customCandidates: string[],
108
+ signal?: AbortSignal,
113
109
  ) {
114
- const menu = defineMenu<undefined, "editor", "toggle" | "addCustom", ExtensionCommandContext>({
115
- start: "editor",
116
- screens: {
117
- editor: () => ({
118
- kind: "multiSelect",
119
- title: `Included Content · ${safeTerminalText(setupName)}`,
120
- lines: ["Draft only · leaving this screen opens Save, Discard, or Continue editing."],
121
- viewportSize: 12,
122
- items: [
123
- ...BUILT_IN_SYNC_ROOTS.map((relativePath) => ({
124
- id: `${BUILT_IN_PREFIX}${relativePath}`,
125
- label: relativePath,
126
- description: relativePath.includes(".")
127
- ? `Sync the top-level ${relativePath} file when present.`
128
- : `Recursively sync every safe file under ${relativePath}/.`,
129
- selected: draft.builtIns.has(relativePath),
130
- })),
131
- ...customCandidates.map((relativePath) => ({
132
- id: `${CUSTOM_PREFIX}${relativePath}`,
133
- label: safeTerminalText(relativePath),
134
- description: "Additional safe agent-relative file or directory.",
135
- selected: draft.custom.has(relativePath),
136
- })),
137
- {
138
- id: SESSIONS_ID,
139
- label: "sessions",
140
- description:
141
- "Session JSONL may contain prompts, tool output, paths, images, and secrets. Sync only to storage you trust.",
142
- selected: draft.sessions,
143
- },
144
- ],
145
- action: "toggle",
146
- actions: [
147
- {
148
- id: ADD_CUSTOM_ID,
149
- label: "Add custom path…",
150
- description:
151
- "Include an agent-relative file or directory even when it exists only remotely.",
152
- action: "addCustom",
153
- },
154
- ],
155
- hint: "close",
156
- doneLabel: "Review changes",
157
- }),
158
- },
159
- actions: {
160
- toggle: async ({ itemId, selected }) => {
161
- updateDraft(draft, itemId, selected === true);
162
- return { kind: "stay" };
163
- },
164
- addCustom: async ({ ctx: actionCtx, signal: actionSignal }) => {
165
- const entered = await actionCtx.ui.input(
166
- "Add included content",
167
- "Agent-relative path, for example custom.toml or snippets",
168
- { signal: actionSignal },
169
- );
170
- if (actionSignal.aborted) return { kind: "stay" };
171
- const requested = entered?.trim();
172
- if (!requested) return { kind: "stay" };
173
- const existing = customCandidates.find(
174
- (candidate) => candidate.toLowerCase() === requested.toLowerCase(),
175
- );
176
- const relativePath = existing ?? requested;
177
- if (draft.custom.has(relativePath)) return { kind: "stay" };
178
- try {
179
- if (!isSafeCustomIncludePath(relativePath)) {
180
- throw new Error("Enter a safe agent-relative file or directory path.");
181
- }
182
- normalizeSyncInclude([
183
- ...draft.builtIns,
184
- ...draft.custom,
185
- ...(draft.sessions ? ["sessions"] : []),
186
- relativePath,
187
- ]);
188
- } catch (error) {
189
- if (actionSignal.aborted) return { kind: "stay" };
190
- actionCtx.ui.notify(
191
- `Could not add included content: ${safeTerminalText(error instanceof Error ? error.message : String(error))}`,
192
- "error",
193
- );
194
- return { kind: "stay" };
195
- }
196
- if (!existing) {
197
- customCandidates.push(relativePath);
198
- customCandidates.sort((left, right) => left.localeCompare(right));
199
- }
200
- draft.custom.add(relativePath);
201
- return { kind: "stay" };
202
- },
203
- },
204
- });
205
- await runMenu(ctx, menu, {
206
- getState: () => undefined,
207
- signal,
208
- isCurrent: () => !signal?.aborted,
209
- });
110
+ const menu = defineMenu<undefined, "editor", "toggle" | "addCustom", ExtensionCommandContext>({
111
+ start: "editor",
112
+ screens: {
113
+ editor: () => ({
114
+ kind: "multiSelect",
115
+ title: `Included Content · ${safeTerminalText(setupName)}`,
116
+ lines: ["Draft only · leaving this screen opens Save, Discard, or Continue editing."],
117
+ viewportSize: 12,
118
+ items: [
119
+ ...BUILT_IN_SYNC_ROOTS.map((relativePath) => ({
120
+ id: `${BUILT_IN_PREFIX}${relativePath}`,
121
+ label: relativePath,
122
+ description: relativePath.includes(".")
123
+ ? `Sync the top-level ${relativePath} file when present.`
124
+ : `Recursively sync every safe file under ${relativePath}/.`,
125
+ selected: draft.builtIns.has(relativePath),
126
+ })),
127
+ ...customCandidates.map((relativePath) => ({
128
+ id: `${CUSTOM_PREFIX}${relativePath}`,
129
+ label: safeTerminalText(relativePath),
130
+ description: "Additional safe agent-relative file or directory.",
131
+ selected: draft.custom.has(relativePath),
132
+ })),
133
+ {
134
+ id: SESSIONS_ID,
135
+ label: "sessions",
136
+ description:
137
+ "Session JSONL may contain prompts, tool output, paths, images, and secrets. Sync only to storage you trust.",
138
+ selected: draft.sessions,
139
+ },
140
+ ],
141
+ action: "toggle",
142
+ actions: [
143
+ {
144
+ id: ADD_CUSTOM_ID,
145
+ label: "Add custom path…",
146
+ description: "Include an agent-relative file or directory even when it exists only remotely.",
147
+ action: "addCustom",
148
+ },
149
+ ],
150
+ hint: "close",
151
+ doneLabel: "Review changes",
152
+ }),
153
+ },
154
+ actions: {
155
+ toggle: async ({ itemId, selected }) => {
156
+ updateDraft(draft, itemId, selected === true);
157
+ return { kind: "stay" };
158
+ },
159
+ addCustom: async ({ ctx: actionCtx, signal: actionSignal }) => {
160
+ const entered = await actionCtx.ui.input(
161
+ "Add included content",
162
+ "Agent-relative path, for example custom.toml or snippets",
163
+ { signal: actionSignal },
164
+ );
165
+ if (actionSignal.aborted) return { kind: "stay" };
166
+ const requested = entered?.trim();
167
+ if (!requested) return { kind: "stay" };
168
+ const existing = customCandidates.find((candidate) => candidate.toLowerCase() === requested.toLowerCase());
169
+ const relativePath = existing ?? requested;
170
+ if (draft.custom.has(relativePath)) return { kind: "stay" };
171
+ try {
172
+ if (!isSafeCustomIncludePath(relativePath)) {
173
+ throw new Error("Enter a safe agent-relative file or directory path.");
174
+ }
175
+ normalizeSyncInclude([
176
+ ...draft.builtIns,
177
+ ...draft.custom,
178
+ ...(draft.sessions ? ["sessions"] : []),
179
+ relativePath,
180
+ ]);
181
+ } catch (error) {
182
+ if (actionSignal.aborted) return { kind: "stay" };
183
+ actionCtx.ui.notify(
184
+ `Could not add included content: ${safeTerminalText(error instanceof Error ? error.message : String(error))}`,
185
+ "error",
186
+ );
187
+ return { kind: "stay" };
188
+ }
189
+ if (!existing) {
190
+ customCandidates.push(relativePath);
191
+ customCandidates.sort((left, right) => left.localeCompare(right));
192
+ }
193
+ draft.custom.add(relativePath);
194
+ return { kind: "stay" };
195
+ },
196
+ },
197
+ });
198
+ await runMenu(ctx, menu, {
199
+ getState: () => undefined,
200
+ signal,
201
+ isCurrent: () => !signal?.aborted,
202
+ });
210
203
  }
211
204
 
212
205
  async function showDraftReview(
213
- ctx: ExtensionCommandContext,
214
- original: SelectionDraft,
215
- draft: SelectionDraft,
216
- signal?: AbortSignal,
206
+ ctx: ExtensionCommandContext,
207
+ original: SelectionDraft,
208
+ draft: SelectionDraft,
209
+ signal?: AbortSignal,
217
210
  ) {
218
- let choice: "Save changes" | "Discard changes" | "Continue editing" | undefined;
219
- const menu = defineMenu<undefined, "review", "choose", ExtensionCommandContext>({
220
- start: "review",
221
- screens: {
222
- review: () => ({
223
- kind: "actions",
224
- title: "Review included-content changes",
225
- lines: formatDraftPreview(original, draft).split("\n").slice(2),
226
- items: [
227
- { id: "save", label: "Save changes", action: "choose" },
228
- { id: "discard", label: "Discard changes", action: "choose" },
229
- { id: "continue", label: "Continue editing", action: "choose" },
230
- ],
231
- hint: "close",
232
- }),
233
- },
234
- actions: {
235
- choose: async ({ itemId }) => {
236
- choice =
237
- itemId === "save"
238
- ? "Save changes"
239
- : itemId === "continue"
240
- ? "Continue editing"
241
- : "Discard changes";
242
- return { kind: "close" };
243
- },
244
- },
245
- });
246
- await runMenu(ctx, menu, {
247
- getState: () => undefined,
248
- signal,
249
- isCurrent: () => !signal?.aborted,
250
- });
251
- return choice;
211
+ let choice: "Save changes" | "Discard changes" | "Continue editing" | undefined;
212
+ const menu = defineMenu<undefined, "review", "choose", ExtensionCommandContext>({
213
+ start: "review",
214
+ screens: {
215
+ review: () => ({
216
+ kind: "actions",
217
+ title: "Review included-content changes",
218
+ lines: formatDraftPreview(original, draft).split("\n").slice(2),
219
+ items: [
220
+ { id: "save", label: "Save changes", action: "choose" },
221
+ { id: "discard", label: "Discard changes", action: "choose" },
222
+ { id: "continue", label: "Continue editing", action: "choose" },
223
+ ],
224
+ hint: "close",
225
+ }),
226
+ },
227
+ actions: {
228
+ choose: async ({ itemId }) => {
229
+ choice = itemId === "save" ? "Save changes" : itemId === "continue" ? "Continue editing" : "Discard changes";
230
+ return { kind: "close" };
231
+ },
232
+ },
233
+ });
234
+ await runMenu(ctx, menu, {
235
+ getState: () => undefined,
236
+ signal,
237
+ isCurrent: () => !signal?.aborted,
238
+ });
239
+ return choice;
252
240
  }
253
241
 
254
242
  function updateDraft(draft: SelectionDraft, id: string, included: boolean) {
255
- if (id.startsWith(BUILT_IN_PREFIX))
256
- return updateSet(draft.builtIns, id.slice(BUILT_IN_PREFIX.length), included);
257
- if (id.startsWith(CUSTOM_PREFIX))
258
- return updateSet(draft.custom, id.slice(CUSTOM_PREFIX.length), included);
259
- if (id === SESSIONS_ID) {
260
- draft.sessions = included;
261
- return;
262
- }
263
- throw new Error(`Unknown file selection: ${id}`);
243
+ if (id.startsWith(BUILT_IN_PREFIX)) return updateSet(draft.builtIns, id.slice(BUILT_IN_PREFIX.length), included);
244
+ if (id.startsWith(CUSTOM_PREFIX)) return updateSet(draft.custom, id.slice(CUSTOM_PREFIX.length), included);
245
+ if (id === SESSIONS_ID) {
246
+ draft.sessions = included;
247
+ return;
248
+ }
249
+ throw new Error(`Unknown file selection: ${id}`);
264
250
  }
265
251
 
266
252
  function updateSet(set: Set<string>, value: string, included: boolean) {
267
- if (included) set.add(value);
268
- else set.delete(value);
253
+ if (included) set.add(value);
254
+ else set.delete(value);
269
255
  }
270
256
 
271
257
  function formatDraftPreview(original: SelectionDraft, draft: SelectionDraft) {
272
- const lines = ["Review included-content changes", ""];
273
- for (const item of new Set([
274
- ...original.builtIns,
275
- ...draft.builtIns,
276
- ...original.custom,
277
- ...draft.custom,
278
- ])) {
279
- const before = original.builtIns.has(item) || original.custom.has(item);
280
- const after = draft.builtIns.has(item) || draft.custom.has(item);
281
- if (before !== after) lines.push(`${after ? "Include" : "Exclude"}: ${safeTerminalText(item)}`);
282
- }
283
- if (original.sessions !== draft.sessions)
284
- lines.push(`${draft.sessions ? "Include" : "Exclude"}: sessions`);
285
- lines.push("", "Saving does not start a network sync.");
286
- return lines.join("\n");
258
+ const lines = ["Review included-content changes", ""];
259
+ for (const item of new Set([...original.builtIns, ...draft.builtIns, ...original.custom, ...draft.custom])) {
260
+ const before = original.builtIns.has(item) || original.custom.has(item);
261
+ const after = draft.builtIns.has(item) || draft.custom.has(item);
262
+ if (before !== after) lines.push(`${after ? "Include" : "Exclude"}: ${safeTerminalText(item)}`);
263
+ }
264
+ if (original.sessions !== draft.sessions) lines.push(`${draft.sessions ? "Include" : "Exclude"}: sessions`);
265
+ lines.push("", "Saving does not start a network sync.");
266
+ return lines.join("\n");
287
267
  }
288
268
 
289
269
  async function listCustomCandidates(configured: Set<string>) {
290
- const candidates = new Map([...configured].map((item) => [item.toLowerCase(), item]));
291
- try {
292
- for (const entry of await fs.readdir(agentDir(), { withFileTypes: true })) {
293
- if ((!entry.isFile() && !entry.isDirectory()) || !isSafeCustomIncludePath(entry.name))
294
- continue;
295
- if (!candidates.has(entry.name.toLowerCase()))
296
- candidates.set(entry.name.toLowerCase(), entry.name);
297
- }
298
- } catch (error) {
299
- if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
300
- }
301
- return [...candidates.values()].sort((left, right) => left.localeCompare(right));
270
+ const candidates = new Map([...configured].map((item) => [item.toLowerCase(), item]));
271
+ try {
272
+ for (const entry of await fs.readdir(agentDir(), { withFileTypes: true })) {
273
+ if ((!entry.isFile() && !entry.isDirectory()) || !isSafeCustomIncludePath(entry.name)) continue;
274
+ if (!candidates.has(entry.name.toLowerCase())) candidates.set(entry.name.toLowerCase(), entry.name);
275
+ }
276
+ } catch (error) {
277
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
278
+ }
279
+ return [...candidates.values()].sort((left, right) => left.localeCompare(right));
302
280
  }
303
281
 
304
282
  function cloneDraft(value: SelectionDraft): SelectionDraft {
305
- return {
306
- builtIns: new Set(value.builtIns),
307
- custom: new Set(value.custom),
308
- sessions: value.sessions,
309
- };
283
+ return {
284
+ builtIns: new Set(value.builtIns),
285
+ custom: new Set(value.custom),
286
+ sessions: value.sessions,
287
+ };
310
288
  }
311
289
 
312
290
  function sameDraft(left: SelectionDraft, right: SelectionDraft) {
313
- return (
314
- left.sessions === right.sessions &&
315
- sameSet(left.builtIns, right.builtIns) &&
316
- sameSet(left.custom, right.custom)
317
- );
291
+ return (
292
+ left.sessions === right.sessions && sameSet(left.builtIns, right.builtIns) && sameSet(left.custom, right.custom)
293
+ );
318
294
  }
319
295
 
320
296
  function sameSet(left: Set<string>, right: Set<string>) {
321
- return left.size === right.size && [...left].every((item) => right.has(item));
297
+ return left.size === right.size && [...left].every((item) => right.has(item));
322
298
  }
323
299
 
324
300
  function safeTerminalText(value: string) {
325
- // biome-ignore lint/suspicious/noControlCharactersInRegex: Escape untrusted terminal controls.
326
- return value.replace(/[\u0000-\u001f\u007f-\u009f]/gu, "?");
301
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Escape untrusted terminal controls.
302
+ return value.replace(/[\u0000-\u001f\u007f-\u009f]/gu, "?");
327
303
  }