@narumitw/pi-sync 0.57.11 → 0.58.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 (97) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +29 -21
  3. package/dist/chunks/{agent-candidate-OCDPRI5G.ts → agent-candidate-U2NU3GUX.ts} +2 -2
  4. package/dist/chunks/{agent-plan-P5B5RGZ7.ts → agent-plan-3YXDIJXO.ts} +13 -10
  5. package/dist/chunks/agent-plan-3YXDIJXO.ts.map +7 -0
  6. package/dist/chunks/{agent-runner-WMSCKSCB.ts → agent-runner-FCRV2Z75.ts} +11 -10
  7. package/dist/chunks/agent-runner-FCRV2Z75.ts.map +7 -0
  8. package/dist/chunks/{agent-store-RJSMNOYP.ts → agent-store-7GINKTS7.ts} +2 -2
  9. package/dist/chunks/{agent-store-RJSMNOYP.ts.map → agent-store-7GINKTS7.ts.map} +2 -2
  10. package/dist/chunks/{agent-sync-EUK2CPSS.ts → agent-sync-2R4GG2MF.ts} +3 -3
  11. package/dist/chunks/{chunk-67INJPKR.ts → chunk-CQD2TJHE.ts} +4 -4
  12. package/dist/chunks/{chunk-67INJPKR.ts.map → chunk-CQD2TJHE.ts.map} +2 -2
  13. package/dist/chunks/{chunk-ZDDBZH72.ts → chunk-JYILGR3K.ts} +2 -2
  14. package/dist/chunks/{chunk-OKPPZA4D.ts → chunk-MG66XM5U.ts} +3 -3
  15. package/dist/chunks/{chunk-OKPPZA4D.ts.map → chunk-MG66XM5U.ts.map} +2 -2
  16. package/dist/chunks/{chunk-7OLLPPLU.ts → chunk-MMQIEDZ6.ts} +6 -6
  17. package/dist/chunks/{chunk-7OLLPPLU.ts.map → chunk-MMQIEDZ6.ts.map} +2 -2
  18. package/dist/chunks/{chunk-Q23JJW7E.ts → chunk-MW6KUQYA.ts} +7 -5
  19. package/dist/chunks/chunk-MW6KUQYA.ts.map +7 -0
  20. package/dist/chunks/{chunk-NLWS55B6.ts → chunk-QPC5HT3A.ts} +2 -2
  21. package/dist/chunks/{chunk-NLWS55B6.ts.map → chunk-QPC5HT3A.ts.map} +1 -1
  22. package/dist/chunks/{chunk-DRTC4PKM.ts → chunk-S5OCYVPS.ts} +2 -2
  23. package/dist/chunks/{chunk-DRTC4PKM.ts.map → chunk-S5OCYVPS.ts.map} +2 -2
  24. package/dist/chunks/{chunk-3IYAPMST.ts → chunk-U6GXGY5G.ts} +2 -2
  25. package/dist/chunks/{chunk-F7E3VFXU.ts → chunk-WQMYV4DW.ts} +8 -8
  26. package/dist/chunks/chunk-WQMYV4DW.ts.map +7 -0
  27. package/dist/chunks/{chunk-CPT2BN6N.ts → chunk-XEODG2PE.ts} +2 -2
  28. package/dist/chunks/chunk-YIPK23JW.ts +171 -0
  29. package/dist/chunks/chunk-YIPK23JW.ts.map +7 -0
  30. package/dist/chunks/{command-handler-F2E7A5T5.ts → command-handler-KCB6MCWS.ts} +12 -12
  31. package/dist/chunks/{command-handler-F2E7A5T5.ts.map → command-handler-KCB6MCWS.ts.map} +2 -2
  32. package/dist/chunks/{conflict-review-2QMO2K6Q.ts → conflict-review-GXA6Y7KY.ts} +2 -2
  33. package/dist/chunks/{continuous-record-AH7JGYKM.ts → continuous-record-EPHGR2DU.ts} +2 -2
  34. package/dist/chunks/{continuous-sync-VRMXCA5X.ts → continuous-sync-FAFNLEUW.ts} +19 -11
  35. package/dist/chunks/continuous-sync-FAFNLEUW.ts.map +7 -0
  36. package/dist/chunks/{manager-result-dispatcher-6W52NM7F.ts → manager-result-dispatcher-XCYPW7RC.ts} +4 -4
  37. package/dist/chunks/{manager-ui-LM7DSJ2Y.ts → manager-ui-FMZXKPX2.ts} +153 -183
  38. package/dist/chunks/manager-ui-FMZXKPX2.ts.map +7 -0
  39. package/dist/chunks/{remote-selection-ui-TD4C4W3J.ts → remote-selection-ui-MYM2YCPF.ts} +3 -3
  40. package/dist/chunks/{setup-switch-PIPXYPCW.ts → setup-switch-KLGESLQX.ts} +3 -3
  41. package/dist/chunks/{setup-wizard-RR5U3SUB.ts → setup-wizard-ZQAK35BP.ts} +3 -3
  42. package/dist/chunks/{sync-operations-CV3TULLD.ts → sync-operations-45APGOL3.ts} +9 -9
  43. package/dist/chunks/sync-operations-45APGOL3.ts.map +7 -0
  44. package/dist/index.ts +80 -65
  45. package/dist/index.ts.map +2 -2
  46. package/docs/agent-continuous-hardening.md +5 -5
  47. package/docs/agent-mode-verification.md +3 -3
  48. package/docs/agent-quality-verification.md +3 -3
  49. package/docs/continuous-sync-verification.md +2 -2
  50. package/docs/settings.md +9 -5
  51. package/docs/tui-contract.md +57 -3
  52. package/package.json +3 -3
  53. package/skills/configure-pi-sync/SKILL.md +3 -1
  54. package/skills/configure-pi-sync/references/configuration.md +61 -38
  55. package/src/commands/command-execution.ts +3 -3
  56. package/src/commands/command.ts +1 -1
  57. package/src/settings/settings-types.ts +1 -1
  58. package/src/sync/agent-candidate.ts +3 -3
  59. package/src/sync/agent-plan.ts +11 -8
  60. package/src/sync/agent-runner.ts +10 -9
  61. package/src/sync/agent-store.ts +1 -1
  62. package/src/sync/agent-sync.ts +3 -3
  63. package/src/sync/continuous-record.ts +1 -1
  64. package/src/sync/continuous-sync.ts +15 -6
  65. package/src/sync/merged-sync.ts +4 -4
  66. package/src/sync/startup-check.ts +1 -1
  67. package/src/sync/sync-mutations.ts +3 -3
  68. package/src/sync/sync-queries.ts +2 -2
  69. package/src/sync-extension.ts +71 -57
  70. package/src/ui/agent-mode-setting.ts +6 -6
  71. package/src/ui/manager-state.ts +1 -1
  72. package/src/ui/remote-selection-ui.ts +1 -1
  73. package/src/ui/settings-ui.ts +19 -55
  74. package/src/ui/setup/setup-switcher.ts +2 -2
  75. package/src/ui/sync-attention.ts +5 -3
  76. package/src/ui/sync-setups-ui.ts +2 -2
  77. package/src/ui/sync-status.ts +214 -3
  78. package/dist/chunks/agent-plan-P5B5RGZ7.ts.map +0 -7
  79. package/dist/chunks/agent-runner-WMSCKSCB.ts.map +0 -7
  80. package/dist/chunks/chunk-F7E3VFXU.ts.map +0 -7
  81. package/dist/chunks/chunk-K3RIEL6J.ts +0 -20
  82. package/dist/chunks/chunk-K3RIEL6J.ts.map +0 -7
  83. package/dist/chunks/chunk-Q23JJW7E.ts.map +0 -7
  84. package/dist/chunks/continuous-sync-VRMXCA5X.ts.map +0 -7
  85. package/dist/chunks/manager-ui-LM7DSJ2Y.ts.map +0 -7
  86. package/dist/chunks/sync-operations-CV3TULLD.ts.map +0 -7
  87. /package/dist/chunks/{agent-candidate-OCDPRI5G.ts.map → agent-candidate-U2NU3GUX.ts.map} +0 -0
  88. /package/dist/chunks/{agent-sync-EUK2CPSS.ts.map → agent-sync-2R4GG2MF.ts.map} +0 -0
  89. /package/dist/chunks/{chunk-ZDDBZH72.ts.map → chunk-JYILGR3K.ts.map} +0 -0
  90. /package/dist/chunks/{chunk-3IYAPMST.ts.map → chunk-U6GXGY5G.ts.map} +0 -0
  91. /package/dist/chunks/{chunk-CPT2BN6N.ts.map → chunk-XEODG2PE.ts.map} +0 -0
  92. /package/dist/chunks/{conflict-review-2QMO2K6Q.ts.map → conflict-review-GXA6Y7KY.ts.map} +0 -0
  93. /package/dist/chunks/{continuous-record-AH7JGYKM.ts.map → continuous-record-EPHGR2DU.ts.map} +0 -0
  94. /package/dist/chunks/{manager-result-dispatcher-6W52NM7F.ts.map → manager-result-dispatcher-XCYPW7RC.ts.map} +0 -0
  95. /package/dist/chunks/{remote-selection-ui-TD4C4W3J.ts.map → remote-selection-ui-MYM2YCPF.ts.map} +0 -0
  96. /package/dist/chunks/{setup-switch-PIPXYPCW.ts.map → setup-switch-KLGESLQX.ts.map} +0 -0
  97. /package/dist/chunks/{setup-wizard-RR5U3SUB.ts.map → setup-wizard-ZQAK35BP.ts.map} +0 -0
@@ -84,7 +84,7 @@ export function createStartupCheck(
84
84
  const captured = config;
85
85
  const identity = syncCheckConfigFingerprint(captured);
86
86
  checking = true;
87
- setSyncStatus(ctx, "sync ...");
87
+ setSyncStatus(ctx, "sync ...", { activity: "checking" });
88
88
  const { inspectSync } = await timings.measure("inspection import", () => loaders.inspection());
89
89
  throwIfAborted(signal);
90
90
  const currentConfig = await loadConfigForCheck();
@@ -118,7 +118,7 @@ export async function push(
118
118
  const state = input?.state ?? (await readStateForConfig(config));
119
119
  validateMutation();
120
120
  requireResolvedDirectionalProgress(state, options);
121
- setSyncStatus(ctx, `pushing ${config.setupName}`);
121
+ setSyncStatus(ctx, `pushing ${config.setupName}`, { activity: "pushing" });
122
122
  const backend = input?.backend ?? (await factory(config));
123
123
  throwIfAborted(options.signal);
124
124
  const localRaw =
@@ -272,7 +272,7 @@ export async function pull(
272
272
  const config = await loadConfig(options.setup);
273
273
  throwIfAborted(options.signal);
274
274
  await requireNoMergeJournal(config, options);
275
- setSyncStatus(ctx, `pulling ${config.setupName}`);
275
+ setSyncStatus(ctx, `pulling ${config.setupName}`, { activity: "pulling" });
276
276
  const backend = await factory(config);
277
277
  const state = await readStateForConfig(config);
278
278
  validateMutation();
@@ -433,7 +433,7 @@ export async function syncBoth(
433
433
  options.background?.validate(config);
434
434
  if (options.agentRetryRefusal) {
435
435
  if (!config.agentMode || options.auto || options.background || !ctx.hasUI)
436
- throw new Error("Enable Agent mode and use the foreground manager to retry a cached refusal.");
436
+ throw new Error("Enable Autonomous sync and use the foreground manager to retry a cached refusal.");
437
437
  const state = await readStateForConfig(config);
438
438
  validateMutation();
439
439
  const pending = await readMergeJournal(config);
@@ -27,7 +27,7 @@ export async function status(
27
27
  ) {
28
28
  const config = await loadConfig(options.setup);
29
29
  throwIfAborted(options.signal);
30
- setSyncStatus(ctx, `checking ${config.setupName}`);
30
+ setSyncStatus(ctx, `checking ${config.setupName}`, { activity: "checking" });
31
31
  const { head, selectionState, localChanged, remoteChanged, localFiles, destination, capability } = await inspectSync(
32
32
  config,
33
33
  snapshotOptionsForContext(ctx, config),
@@ -70,7 +70,7 @@ export async function diff(
70
70
  ) {
71
71
  const config = await loadConfig(options.setup);
72
72
  throwIfAborted(options.signal);
73
- setSyncStatus(ctx, `checking ${config.setupName}`);
73
+ setSyncStatus(ctx, `checking ${config.setupName}`, { activity: "checking" });
74
74
  const backend = await factory(config);
75
75
  const local = portableSnapshot(
76
76
  await createSnapshot(config.snapshotIdentity, snapshotOptionsForContext(ctx, config)),
@@ -10,12 +10,13 @@ import { errorMessage } from "./sync/sync-errors.js";
10
10
  import { createSyncLoaders, type SyncDependencies } from "./sync/sync-loaders.js";
11
11
  import { registerStartupTimings } from "./ui/startup-timings-entry.js";
12
12
  import { createSyncAttentionController } from "./ui/sync-attention.js";
13
- import { setSyncStatus } from "./ui/sync-status.js";
13
+ import { createSyncStatusPublisher, setSyncStatus } from "./ui/sync-status.js";
14
14
  import { safeTerminalText } from "./ui/terminal-text.js";
15
15
 
16
16
  export default function sync(pi: ExtensionAPI, dependencies: Partial<SyncDependencies> = {}) {
17
17
  const loaders = createSyncLoaders(dependencies);
18
18
  const attention = createSyncAttentionController();
19
+ const status = createSyncStatusPublisher(pi.events);
19
20
  const publishTimings = registerStartupTimings(pi);
20
21
  const check = createStartupCheck(loaders, attention, 30_000, publishTimings);
21
22
  let sessionAbort = new AbortController();
@@ -60,6 +61,7 @@ export default function sync(pi: ExtensionAPI, dependencies: Partial<SyncDepende
60
61
  if (!ctx.hasUI) {
61
62
  throw new Error("/sync requires TUI or RPC mode so results and safety prompts are observable.");
62
63
  }
64
+ status.bind(ctx);
63
65
  const signal = sessionAbort.signal;
64
66
  const manager = ctx.sessionManager;
65
67
  const command = splitArgs(args)[0];
@@ -104,39 +106,46 @@ export default function sync(pi: ExtensionAPI, dependencies: Partial<SyncDepende
104
106
  });
105
107
 
106
108
  pi.on("session_start", async (_event, ctx) => {
107
- shutdownAbort?.abort(new DOMException("Session replaced", "AbortError"));
108
- shutdownAbort = undefined;
109
- sessionAbort.abort(new DOMException("Session replaced", "AbortError"));
110
- sessionAbort = new AbortController();
111
- const signal = sessionAbort.signal;
112
- owner = ctx.sessionManager;
113
- continuousPossible = false;
114
- startupDebug = false;
115
- attention.reset(ctx);
116
- const previous = initialization;
117
- initialization = (async () => {
118
- await previous;
119
- if (signal.aborted) return false;
120
- await continuous?.stop();
121
- if (signal.aborted) return false;
122
- await check.stop();
123
- if (signal.aborted) return false;
124
- try {
125
- const startup = await withStateDirectoryAccess(() => startSession(ctx, signal, publishTimings));
109
+ const releaseStatus = status.start(ctx);
110
+ try {
111
+ shutdownAbort?.abort(new DOMException("Session replaced", "AbortError"));
112
+ shutdownAbort = undefined;
113
+ sessionAbort.abort(new DOMException("Session replaced", "AbortError"));
114
+ sessionAbort = new AbortController();
115
+ const signal = sessionAbort.signal;
116
+ owner = ctx.sessionManager;
117
+ continuousPossible = false;
118
+ startupDebug = false;
119
+ attention.reset(ctx);
120
+ const previous = initialization;
121
+ initialization = (async () => {
122
+ await previous;
126
123
  if (signal.aborted) return false;
127
- startupDebug = startup?.debug ?? false;
128
- return startup?.config;
129
- } catch (error) {
130
- if (!signal.aborted && ctx.hasUI) {
131
- ctx.ui.notify(`pi-sync recovery required: ${safeTerminalText(errorMessage(error))}`, "error");
124
+ await continuous?.stop();
125
+ if (signal.aborted) return false;
126
+ await check.stop();
127
+ if (signal.aborted) return false;
128
+ try {
129
+ const startup = await withStateDirectoryAccess(() => startSession(ctx, signal, publishTimings));
130
+ if (signal.aborted) return false;
131
+ startupDebug = startup?.debug ?? false;
132
+ return startup?.config;
133
+ } catch (error) {
134
+ if (!signal.aborted && ctx.hasUI) {
135
+ ctx.ui.notify(`pi-sync recovery required: ${safeTerminalText(errorMessage(error))}`, "error");
136
+ }
137
+ return false;
132
138
  }
133
- return false;
139
+ })();
140
+ const ready = await initialization;
141
+ if (ready === false) releaseStatus();
142
+ if (ready && !signal.aborted) {
143
+ if (ready.continuous && ready.agentMode) await resumeContinuous(ctx, signal);
144
+ else if (ready.automatic || startupTransferEnabled(ready)) check.start(ctx, signal, ready);
134
145
  }
135
- })();
136
- const ready = await initialization;
137
- if (ready && !signal.aborted) {
138
- if (ready.continuous && ready.agentMode) await resumeContinuous(ctx, signal);
139
- else if (ready.automatic || startupTransferEnabled(ready)) check.start(ctx, signal, ready);
146
+ } catch (error) {
147
+ releaseStatus();
148
+ throw error;
140
149
  }
141
150
  });
142
151
 
@@ -154,36 +163,41 @@ export default function sync(pi: ExtensionAPI, dependencies: Partial<SyncDepende
154
163
  });
155
164
 
156
165
  pi.on("session_shutdown", async (event, ctx) => {
157
- if (continuous && owner && owner !== ctx.sessionManager) return;
158
- sessionAbort.abort(new DOMException("Session shut down", "AbortError"));
159
- attention.reset(ctx);
160
- shutdownAbort?.abort(new DOMException("Session shut down again", "AbortError"));
161
- const controller = new AbortController();
162
- shutdownAbort = controller;
163
- const signal = combineSignals(controller.signal, AbortSignal.timeout(30_000));
164
- const reason = typeof event === "object" && event ? (event as { reason?: string }).reason : undefined;
166
+ const releaseStatus = status.cleanupFor(ctx);
165
167
  try {
166
- const ready = await initialization;
167
- if (signal.aborted) return;
168
- await continuous?.stop(ctx.sessionManager);
169
- if (signal.aborted) return;
170
- await check.stop();
171
- if (signal.aborted) return;
172
- if (ready !== false && reason !== "reload") {
173
- await withStateDirectoryAccess(async () => {
174
- if (signal.aborted) return;
175
- await autoPushSessions(ctx, signal, loaders);
176
- });
177
- }
178
- } catch (error) {
179
- if (!signal.aborted) {
180
- ctx.ui.notify(`pi-sync session push skipped: ${errorMessage(error)}`, "warning");
168
+ if (continuous && owner && owner !== ctx.sessionManager) return;
169
+ sessionAbort.abort(new DOMException("Session shut down", "AbortError"));
170
+ attention.reset(ctx);
171
+ shutdownAbort?.abort(new DOMException("Session shut down again", "AbortError"));
172
+ const controller = new AbortController();
173
+ shutdownAbort = controller;
174
+ const signal = combineSignals(controller.signal, AbortSignal.timeout(30_000));
175
+ const reason = typeof event === "object" && event ? (event as { reason?: string }).reason : undefined;
176
+ try {
177
+ const ready = await initialization;
178
+ if (signal.aborted) return;
179
+ await continuous?.stop(ctx.sessionManager);
180
+ if (signal.aborted) return;
181
+ await check.stop();
182
+ if (signal.aborted) return;
183
+ if (ready !== false && reason !== "reload") {
184
+ await withStateDirectoryAccess(async () => {
185
+ if (signal.aborted) return;
186
+ await autoPushSessions(ctx, signal, loaders);
187
+ });
188
+ }
189
+ } catch (error) {
190
+ if (!signal.aborted) {
191
+ ctx.ui.notify(`pi-sync session push skipped: ${errorMessage(error)}`, "warning");
192
+ }
193
+ } finally {
194
+ if (shutdownAbort === controller) shutdownAbort = undefined;
181
195
  }
196
+ if (signal.aborted) return;
197
+ setSyncStatus(ctx, undefined);
182
198
  } finally {
183
- if (shutdownAbort === controller) shutdownAbort = undefined;
199
+ releaseStatus();
184
200
  }
185
- if (signal.aborted) return;
186
- setSyncStatus(ctx, undefined);
187
201
  });
188
202
  }
189
203
 
@@ -6,10 +6,10 @@ import { confirmMergeReview } from "./merge-review.js";
6
6
  import { safeTerminalText } from "./terminal-text.js";
7
7
 
8
8
  export const AGENT_MODE_DESCRIPTION =
9
- "This setup, experimental: one autonomous merged sync next startup and on Sync now. Sends eligible conflict text to the selected Pi model; may incur costs and replace/delete selected local and remote content without transfer prompts. No automatic reload. Unsupported conflicts pause.";
9
+ "This setup: sync eligible changes at session startup and on Sync now without transfer confirmation. Use the selected Pi model to resolve supported text and settings conflicts. Eligible conflict content may be sent to its provider and incur costs. Selected local and remote content can be replaced or deleted. Unresolved conflicts pause unless partial sync permits independent progress. Continuous sync is a separate opt-in. No automatic reload.";
10
10
 
11
11
  export const CONTINUOUS_DESCRIPTION =
12
- "This setup, experimental: recurring sync while Pi is open. Requires Agent mode. Watches selected files, checks the remote every 60–66 seconds, and retries temporary failures. Local changes and retries can cause extra requests between polls. May disclose conflict text and incur model/storage costs. No automatic reload.";
12
+ "This setup: recurring sync while Pi is open. Requires Autonomous sync. Watches selected files, checks the remote every 60–66 seconds, and retries temporary failures. Local changes and retries can cause extra requests between polls. May disclose conflict text and incur model/storage costs. No automatic reload.";
13
13
 
14
14
  export function saveContinuousMode(
15
15
  ctx: ExtensionCommandContext,
@@ -33,8 +33,8 @@ export async function saveAgentMode(
33
33
  const setup = settings.syncSetups[setupName];
34
34
  if (!setup || settings.activeSyncSetup !== setupName) throw new Error("Current sync setup changed; reopen Settings.");
35
35
  const continuous = preference === "continuous";
36
- const label = continuous ? "Continuous sync" : "Agent mode";
37
- if (enabled && continuous && !setup.sync.agentMode) throw new Error("Enable Agent mode before Continuous sync.");
36
+ const label = continuous ? "Continuous sync" : "Autonomous sync";
37
+ if (enabled && continuous && !setup.sync.agentMode) throw new Error("Enable Autonomous sync before Continuous sync.");
38
38
  if (Boolean(setup.sync[preference]) === enabled) return true;
39
39
  if (enabled) {
40
40
  const summary = [
@@ -59,7 +59,7 @@ export async function saveAgentMode(
59
59
  if (
60
60
  !(await confirmMergeReview(
61
61
  ctx,
62
- `Enable experimental ${label}?`,
62
+ `Enable ${label}?`,
63
63
  summary,
64
64
  signal,
65
65
  () => !signal?.aborted,
@@ -80,7 +80,7 @@ export async function saveAgentMode(
80
80
  settings.storageConnections[setup.storage.connection],
81
81
  )
82
82
  )
83
- throw new Error("Sync setup or connection changed during Agent mode review; reopen Settings.");
83
+ throw new Error("Sync setup or connection changed during Autonomous sync review; reopen Settings.");
84
84
  return {
85
85
  ...current,
86
86
  syncSetups: { ...current.syncSetups, [setupName]: { ...setup, sync: { ...setup.sync, [preference]: enabled } } },
@@ -165,7 +165,7 @@ export async function describeManagerState(
165
165
  `Included: ${selection.builtIns.length} built-in group${selection.builtIns.length === 1 ? "" : "s"} · ${selection.custom.length} extra path${selection.custom.length === 1 ? "" : "s"} · Sessions ${selection.sessions ? "on" : "off"}`,
166
166
  `Automatic sync: ${automaticSyncSummary(config.automatic)}`,
167
167
  `Startup transfer: ${startupTransferSummary(config)}`,
168
- `Agent mode: ${config.agentMode ? "On (experimental; autonomous merged sync)" : "Off"}`,
168
+ `Autonomous sync: ${config.agentMode ? "On (autonomous merged sync)" : "Off"}`,
169
169
  ...(config.continuous ? await (await import("../sync/continuous-record.js")).continuousStatusLines(config) : []),
170
170
  `Last applied: ${lastAppliedSnapshot}`,
171
171
  ...(noSyncedContent
@@ -489,7 +489,7 @@ async function inspectConfiguredRemoteSelection(
489
489
  const config = await loadConfig(setupName);
490
490
  if (signal?.aborted) return undefined;
491
491
  validateOwner();
492
- setSyncStatus(ctx, `checking synced content for ${safeTerminalText(config.setupName)}`);
492
+ setSyncStatus(ctx, `checking synced content for ${safeTerminalText(config.setupName)}`, { activity: "checking" });
493
493
  const backend = await factory(config);
494
494
  if (signal?.aborted) return undefined;
495
495
  validateOwner();
@@ -92,32 +92,20 @@ export async function showSyncSettings(
92
92
  | "storage-location"
93
93
  | "setups"
94
94
  | "connections";
95
- type Group = "content" | "behavior" | "storage" | "advanced" | "all";
96
- type Screen = "categories" | Group;
97
- const groups: Record<Group, { label: string; ids: readonly string[] }> = {
98
- content: { label: "Sync content", ids: ["include", "remoteInclude", "localFields"] },
99
- behavior: {
100
- label: "Sync behavior",
101
- ids: ["automatic", "automaticTransfer", "agentMode", "continuous", "onSwitch"],
102
- },
103
- storage: { label: "Storage & setups", ids: ["storageLocation", "setups", "connections"] },
104
- advanced: { label: "Advanced", ids: ["mergeSettings", "contentPolicy", "skipSecretScan", "showStatus", "debug"] },
105
- all: { label: "All settings (search)", ids: [] },
106
- };
107
95
  let saveStatus = "Simple choices save immediately. Back does not undo saved changes.";
108
96
  // Keep Kit's existing settings adapter: native SettingsList has no async save/rollback
109
97
  // contract or injected keybindings. Kit preserves these safety and accessibility behaviors.
110
- // Categories use public navigation; All settings retains cross-category search.
111
- const settingsScreen = (state: Awaited<ReturnType<typeof loadConfig>>, group: Group) =>
112
- ({
113
- kind: "settings",
114
- title: `Pi Sync Settings · ${groups[group].label}`,
115
- lines: [
116
- `Setup: ${safeTerminalText(state.setupName)} · Connection: ${safeTerminalText(state.connectionName)}`,
117
- saveStatus,
118
- ],
119
- items: (
120
- [
98
+ const menu = defineMenu<Awaited<ReturnType<typeof loadConfig>>, "settings", Action, ExtensionCommandContext>({
99
+ start: "settings",
100
+ screens: {
101
+ settings: ({ state }) => ({
102
+ kind: "settings",
103
+ title: "Pi Sync Settings",
104
+ lines: [
105
+ `Setup: ${safeTerminalText(state.setupName)} · Connection: ${safeTerminalText(state.connectionName)}`,
106
+ saveStatus,
107
+ ],
108
+ items: [
121
109
  {
122
110
  id: "include",
123
111
  label: "Included paths",
@@ -188,7 +176,7 @@ export async function showSyncSettings(
188
176
  },
189
177
  {
190
178
  id: "agentMode",
191
- label: "Agent mode (experimental)",
179
+ label: "Autonomous sync",
192
180
  description: AGENT_MODE_DESCRIPTION,
193
181
  currentValue: state.agentMode ? "On" : "Off",
194
182
  values: ["On", "Off"],
@@ -196,8 +184,8 @@ export async function showSyncSettings(
196
184
  },
197
185
  {
198
186
  id: "continuous",
199
- label: "Continuous sync (experimental)",
200
- description: `${CONTINUOUS_DESCRIPTION}${state.continuous && !state.agentMode ? " Saved On, but paused until Agent mode is enabled." : ""}`,
187
+ label: "Continuous sync",
188
+ description: `${CONTINUOUS_DESCRIPTION}${state.continuous && !state.agentMode ? " Saved On, but paused until Autonomous sync is enabled." : ""}`,
201
189
  currentValue: state.continuous ? "On" : "Off",
202
190
  values: ["On", "Off"],
203
191
  action: "continuous",
@@ -215,7 +203,7 @@ export async function showSyncSettings(
215
203
  id: "mergeSettings",
216
204
  label: "Merge settings fields",
217
205
  description:
218
- "This setup, experimental opt-in: merge independent global settings.json root fields with a verified private ancestor. Arrays and nested objects remain atomic. Applies to subsequent sync; no reload.",
206
+ "This setup, opt-in: merge independent global settings.json root fields with a verified private ancestor. Arrays and nested objects remain atomic. Applies to subsequent sync; no reload.",
219
207
  currentValue: state.mergeSettings ? "On" : "Off",
220
208
  values: ["On", "Off"],
221
209
  action: "merge-settings",
@@ -224,7 +212,7 @@ export async function showSyncSettings(
224
212
  id: "contentPolicy",
225
213
  label: "Content merge / partial progress",
226
214
  description:
227
- "This setup, experimental opt-in: review the version-5 upgrade before saving. Merge bounded text and prefix-only sessions; partial progress keeps withheld versions and baselines. Applies to subsequent sync; disabling does not clear recovery evidence.",
215
+ "This setup, opt-in: review the version-5 upgrade before saving. Merge bounded text and prefix-only sessions; partial progress keeps withheld versions and baselines. Applies to subsequent sync; disabling does not clear recovery evidence.",
228
216
  currentValue: state.mergeContent
229
217
  ? state.partialSync
230
218
  ? "Merge + partial"
@@ -262,32 +250,8 @@ export async function showSyncSettings(
262
250
  values: ["On", "Off"],
263
251
  action: "debug",
264
252
  },
265
- ] satisfies MenuSettingItem<Action>[]
266
- ).filter((item) => group === "all" || groups[group].ids.includes(item.id)),
267
- }) as const;
268
- const menu = defineMenu<Awaited<ReturnType<typeof loadConfig>>, Screen, Action, ExtensionCommandContext>({
269
- start: "categories",
270
- screens: {
271
- categories: ({ state }) => ({
272
- kind: "actions",
273
- title: "Pi Sync Settings",
274
- lines: [
275
- `Setup: ${safeTerminalText(state.setupName)} · Connection: ${safeTerminalText(state.connectionName)}`,
276
- "Choose a task, or search all settings. Scope is shown in each setting's description.",
277
- saveStatus,
278
- ],
279
- items: (Object.entries(groups) as [Group, (typeof groups)[Group]][]).map(([id, group]) => ({
280
- id,
281
- label: group.label,
282
- to: id,
283
- })),
284
- hint: "back",
253
+ ] satisfies MenuSettingItem<Action>[],
285
254
  }),
286
- content: ({ state }) => settingsScreen(state, "content"),
287
- behavior: ({ state }) => settingsScreen(state, "behavior"),
288
- storage: ({ state }) => settingsScreen(state, "storage"),
289
- advanced: ({ state }) => settingsScreen(state, "advanced"),
290
- all: ({ state }) => settingsScreen(state, "all"),
291
255
  },
292
256
  actions: {
293
257
  "storage-location": async ({ signal: actionSignal }) => {
@@ -315,7 +279,7 @@ export async function showSyncSettings(
315
279
  const partialSync = value === "Partial only" || value === "Merge + partial";
316
280
  if (
317
281
  !(await ctx.ui.confirm(
318
- "Save experimental content policy?",
282
+ "Save content policy?",
319
283
  "This explicitly upgrades settings to version 5; partial snapshots use version 3 and older clients must refuse them. Text/session merge is conservative. Partial sync preserves withheld versions and old baselines. Disabling does not clear unresolved groups or recovery evidence. Review a directional migration first if portable policy changes.",
320
284
  { signal: mutationSignal },
321
285
  ))
@@ -450,7 +414,7 @@ export async function showSyncSettings(
450
414
  try {
451
415
  validate();
452
416
  ctx.ui.notify(
453
- "Agent mode saved. Applies to subsequent sync and the next session start; no transfer started.",
417
+ "Autonomous sync saved. Applies to subsequent sync and the next session start; no transfer started.",
454
418
  "info",
455
419
  );
456
420
  } catch {
@@ -129,8 +129,8 @@ export async function showSetupSwitcher(
129
129
  `Included content: ${config.include.length} paths`,
130
130
  `Automatic sync: ${automaticSyncSummary(config.automatic)} · Sessions: ${config.include.includes("sessions") ? "On" : "Off"}`,
131
131
  `Automatic transfer at startup: ${config.automaticTransfer ? "On — may upload, replace, or delete selected files; no reload" : "Off"}`,
132
- `Agent mode: ${config.agentMode ? "On (experimental; autonomous merged sync)" : "Off"}`,
133
- `Continuous sync: ${config.continuous ? (config.agentMode ? "On (experimental; recurring requests and transfers)" : "Saved On; paused until Agent mode is enabled") : "Off"}`,
132
+ `Autonomous sync: ${config.agentMode ? "On (autonomous merged sync)" : "Off"}`,
133
+ `Continuous sync: ${config.continuous ? (config.agentMode ? "On (recurring requests and transfers)" : "Saved On; paused until Autonomous sync is enabled") : "Off"}`,
134
134
  "",
135
135
  switchEffect,
136
136
  ].join("\n"),
@@ -93,7 +93,9 @@ export function createSyncAttentionController(): SyncAttentionController {
93
93
  }
94
94
  if (!state && classification === "status") {
95
95
  ctx.ui.setWidget(WIDGET_KEY, undefined);
96
- setSyncStatus(ctx, observation?.inspection.remoteChanged ? "sync ⇣" : "sync ⇡");
96
+ setSyncStatus(ctx, observation?.inspection.remoteChanged ? "sync ⇣" : "sync ⇡", {
97
+ activity: observation?.inspection.remoteChanged ? "remote-change" : "local-change",
98
+ });
97
99
  return;
98
100
  }
99
101
  const presentation = state
@@ -104,7 +106,7 @@ export function createSyncAttentionController(): SyncAttentionController {
104
106
  };
105
107
  if (ctx.mode !== "tui") {
106
108
  ctx.ui.setWidget(WIDGET_KEY, undefined);
107
- setSyncStatus(ctx, presentation.status);
109
+ setSyncStatus(ctx, presentation.status, { activity: "review" });
108
110
  return;
109
111
  }
110
112
  // Keep Kit outside the eager startup graph; module loading owns no cancellable resources.
@@ -113,7 +115,7 @@ export function createSyncAttentionController(): SyncAttentionController {
113
115
  throw error;
114
116
  });
115
117
  if (generation !== currentGeneration || signal?.aborted) return;
116
- setSyncStatus(ctx, presentation.status);
118
+ setSyncStatus(ctx, presentation.status, { activity: "review" });
117
119
  ctx.ui.setWidget(
118
120
  WIDGET_KEY,
119
121
  (_tui, theme) =>
@@ -193,8 +193,8 @@ async function loadSetupMenuState(selectedName: string | undefined, signal?: Abo
193
193
  `Sessions: ${selection.sessions ? "On — privacy-sensitive" : "Off"}`,
194
194
  `Automatic sync: ${config.automatic ? "On" : "Off"}`,
195
195
  `Automatic transfer at startup: ${config.automaticTransfer ? "On — may upload, replace, or delete selected files; no reload" : "Off"}`,
196
- `Agent mode: ${config.agentMode ? "On (experimental; autonomous merged sync)" : "Off"}`,
197
- `Continuous sync: ${config.continuous ? (config.agentMode ? "On (experimental; recurring requests and transfers)" : "Saved On; paused until Agent mode is enabled") : "Off"}`,
196
+ `Autonomous sync: ${config.agentMode ? "On (autonomous merged sync)" : "Off"}`,
197
+ `Continuous sync: ${config.continuous ? (config.agentMode ? "On (recurring requests and transfers)" : "Saved On; paused until Autonomous sync is enabled") : "Off"}`,
198
198
  isCurrent
199
199
  ? "Edit included content and automatic sync in /sync → Settings."
200
200
  : "To edit included content or automatic sync, make this setup current, then open /sync → Settings.",