@narumitw/pi-sync 0.57.6 → 0.57.7

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 (31) hide show
  1. package/README.md +1 -1
  2. package/dist/chunks/{chunk-UZPMDLTL.ts → chunk-M6VQJIOU.ts} +5 -5
  3. package/dist/chunks/chunk-M6VQJIOU.ts.map +7 -0
  4. package/dist/chunks/{chunk-R75IHAOJ.ts → chunk-N5TFMPB5.ts} +2 -2
  5. package/dist/chunks/{chunk-DT6TOCIG.ts → chunk-PQW6GS4J.ts} +2 -2
  6. package/dist/chunks/{conflict-review-T7GSOKNG.ts → conflict-review-UC5AGVER.ts} +3 -3
  7. package/dist/chunks/{continuous-sync-U4HIKPQK.ts → continuous-sync-POEQXR25.ts} +73 -46
  8. package/dist/chunks/continuous-sync-POEQXR25.ts.map +7 -0
  9. package/dist/chunks/{manager-ui-FXXO6TW4.ts → manager-ui-26LEINEX.ts} +2 -2
  10. package/dist/chunks/{manager-ui-FXXO6TW4.ts.map → manager-ui-26LEINEX.ts.map} +1 -1
  11. package/dist/chunks/{sync-inspection-5MY2HI4P.ts → sync-inspection-J25EEKZK.ts} +3 -3
  12. package/dist/chunks/{sync-operations-GNPS6BLY.ts → sync-operations-ZHA6HQDB.ts} +4 -4
  13. package/dist/index.ts +52 -18
  14. package/dist/index.ts.map +3 -3
  15. package/docs/continuous-sync-verification.md +10 -0
  16. package/package.json +1 -1
  17. package/skills/configure-pi-sync/references/configuration.md +8 -4
  18. package/src/sync/automatic-sync.ts +11 -6
  19. package/src/sync/continuous-sync.ts +78 -41
  20. package/src/sync/startup-check.ts +8 -3
  21. package/src/sync/startup-timings.ts +8 -4
  22. package/src/sync-extension.ts +18 -6
  23. package/src/ui/settings-ui.ts +1 -1
  24. package/src/ui/startup-timings-entry.ts +36 -0
  25. package/dist/chunks/chunk-UZPMDLTL.ts.map +0 -7
  26. package/dist/chunks/continuous-sync-U4HIKPQK.ts.map +0 -7
  27. /package/dist/chunks/{chunk-R75IHAOJ.ts.map → chunk-N5TFMPB5.ts.map} +0 -0
  28. /package/dist/chunks/{chunk-DT6TOCIG.ts.map → chunk-PQW6GS4J.ts.map} +0 -0
  29. /package/dist/chunks/{conflict-review-T7GSOKNG.ts.map → conflict-review-UC5AGVER.ts.map} +0 -0
  30. /package/dist/chunks/{sync-inspection-5MY2HI4P.ts.map → sync-inspection-J25EEKZK.ts.map} +0 -0
  31. /package/dist/chunks/{sync-operations-GNPS6BLY.ts.map → sync-operations-ZHA6HQDB.ts.map} +0 -0
@@ -58,6 +58,16 @@ Final verification:
58
58
  | README heading audit | Required headings/order checked across all 36 active package READMEs. |
59
59
  | Package build, RPC and archive | Passed as described below. |
60
60
 
61
+ ## Startup timing report verification
62
+
63
+ Startup timing reports use native custom session entries in TUI and retain RPC notifications. Entry renderers and live append events were checked at the declared Pi peer floor. Tests verify transcript retention across renderer replacement and ordinary notifications, unchanged model context, control sanitization, and zero/narrow/resized rendering.
64
+
65
+ Continuous timing tests cover the initial check, nested transfer stages, failure, timeout, later polls, foreground pauses, idle resume, replacement, and shutdown. Debug is captured during awaited session initialization, including when Continuous sync is initially Off or no setup exists; both directions of a later debug edit retain the session-start value. Automatic sync and startup transfer remain Off when not authorized. No new watcher, timer, setting, command, provider request, or model-visible prefix transition is added.
66
+
67
+ The package-directory RPC smoke now waits for both local and initial Continuous timing notifications before making a synthetic edit. It verifies exactly two timing reports, conditional loopback publication, zero provider requests, and released ownership. The startup timing fix passed both root gates, the package pack workflow, archive inspection, generated-runtime tests, and the fenced-code-aware README heading audit.
68
+
69
+ Physical-terminal visual acceptance and production storage/provider behavior remain unverified by this fix. Automated native rendering and RPC tests do not replace that acceptance. Existing installed npm copies are not modified by a source checkout; the user must explicitly load the local build or install a later release. No package was published, tagged, or installed into the user's managed npm root.
70
+
61
71
  ## CLI, package and load checks
62
72
 
63
73
  The required package build and dry-run pack use:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-sync",
3
- "version": "0.57.6",
3
+ "version": "0.57.7",
4
4
  "description": "Pi extension that syncs Pi configuration through Git, WebDAV, Cloudflare R2, or S3-compatible storage.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -225,7 +225,10 @@ Edit `<getAgentDir()>/pi-sync.json` (normally `~/.pi/agent/pi-sync.json`) or use
225
225
  UI changes save immediately through the existing ordered, atomic settings writer; both manual and UI changes take effect on the next session start, including `/reload`, new, resume, and fork.
226
226
  There is no project or environment override.
227
227
 
228
- With `debug: true`, TUI/RPC notifications report local initialization after recovery and settings resolution, then background work when it ends.
228
+ With `debug: true`, reports show local initialization after recovery and settings resolution, then background work when it ends.
229
+ TUI reports are stored as custom session entries, so they remain in the transcript after `/reload` and later notifications without entering model context.
230
+ RPC reports remain notifications.
231
+ When Continuous sync is enabled, the background report covers its first cycle rather than the separate one-shot startup check; later polls and same-session idle resumes do not publish more timing reports.
229
232
  Stages include transaction recovery, settings reads, inspection import, backend initialization, local snapshot, sync state read, remote head, operations import, and automatic transfer when those stages run.
230
233
  Automatic-transfer details separate authorization, journal recovery, state and local reads, remote-head reads, snapshot downloads, merge planning, backup, publication, local apply, baseline writes, and cleanup.
231
234
  Repeated stages aggregate elapsed time and show a call count.
@@ -237,10 +240,11 @@ Recovery failures before valid settings can be read cannot enable a timing repor
237
240
 
238
241
  Interrupted stages are marked `interrupted`; cancelled or failed background work is marked `stopped`.
239
242
  Session replacement and shutdown suppress late reports from the old session.
243
+ Pausing Continuous sync cancels its active cycle and suppresses that cycle’s late report; it does not schedule another startup report on idle resume.
240
244
  Labels and durations contain no credentials, paths, file contents, remote addresses, or error text.
241
- No log file or model message is created.
242
- Print/JSON produce no timing notifications.
243
- The setting does not enable automatic sync or transfer, change safety checks, or measure manual/shutdown operations.
245
+ No separate log file or model message is created; TUI reports remain in session history even after debugging is disabled.
246
+ Print/JSON produce no timing reports.
247
+ The setting does not enable automatic sync or transfer, change safety checks, or report manual/shutdown operations.
244
248
  Set `debug` to `false` or remove it, then start a new session to disable reports.
245
249
 
246
250
  ### Secret scanning
@@ -5,6 +5,7 @@ import { loadConfig, loadPartialConfig } from "../settings/config.js";
5
5
  import { isMissingConfigError } from "../settings/config-errors.js";
6
6
  import { consumeLocalConfigMigrationNotice } from "../settings/config-file.js";
7
7
  import { readLocalConfigObject } from "../settings/settings-store.js";
8
+ import type { AnySyncConfig } from "../settings/settings-types.js";
8
9
  import { sessionDirFromContext, snapshotOptionsForContext } from "../snapshot/session-paths.js";
9
10
  import { recoverSnapshotTransactionsOnStartup } from "../snapshot/snapshot-transaction.js";
10
11
  import { withLock } from "../state/lock.js";
@@ -12,9 +13,8 @@ import { stateDirectoryMigrationNotice } from "../state/state-directory.js";
12
13
  import { ensureStateDir, readStateForConfig } from "../state/sync-state-store.js";
13
14
  import { configureSyncStatus, setSyncStatus } from "../ui/sync-status.js";
14
15
  import { safeTerminalText } from "../ui/terminal-text.js";
15
- import { startupTransferEnabled } from "./agent-policy.js";
16
16
  import { throwIfAborted } from "./signals.js";
17
- import { createStartupTimings } from "./startup-timings.js";
17
+ import { createStartupTimings, type StartupTimingPublisher } from "./startup-timings.js";
18
18
  import { errorMessage } from "./sync-errors.js";
19
19
  import type { SyncLoaders } from "./sync-loaders.js";
20
20
  import { captureMutationOwner } from "./sync-local.js";
@@ -29,8 +29,12 @@ const AUTO_SYNC_OPTIONS: CommandOptions = {
29
29
  args: [],
30
30
  };
31
31
 
32
- export async function startSession(ctx: ExtensionContext, signal: AbortSignal) {
33
- const timings = createStartupTimings();
32
+ export async function startSession(
33
+ ctx: ExtensionContext,
34
+ signal: AbortSignal,
35
+ publish?: StartupTimingPublisher,
36
+ ): Promise<{ debug: boolean; config?: AnySyncConfig } | undefined> {
37
+ const timings = createStartupTimings(publish);
34
38
  let debug = false;
35
39
  let outcome: "completed" | "stopped" = "stopped";
36
40
  try {
@@ -64,19 +68,20 @@ export async function startSession(ctx: ExtensionContext, signal: AbortSignal) {
64
68
  const migrationNotice = consumeLocalConfigMigrationNotice();
65
69
  if (migrationNotice && ctx.hasUI) ctx.ui.notify(migrationNotice, "warning");
66
70
  throwIfAborted(signal);
67
- if (!ctx.hasUI) return;
71
+ if (!ctx.hasUI) return { debug };
68
72
  try {
69
73
  const config = await timings.measure("config resolution", () => loadConfig());
70
74
  throwIfAborted(signal);
71
75
  configureSyncStatus(ctx, config.showStatus);
72
76
  outcome = "completed";
73
- return config.automatic || startupTransferEnabled(config) ? config : undefined;
77
+ return { debug, config };
74
78
  } catch (error) {
75
79
  throwIfAborted(signal);
76
80
  if (!isMissingConfigError(error)) {
77
81
  ctx.ui.notify(`pi-sync startup check skipped: ${safeTerminalText(errorMessage(error))}`, "warning");
78
82
  }
79
83
  }
84
+ return { debug };
80
85
  } finally {
81
86
  timings.publish(ctx, debug, signal, "local initialization", outcome);
82
87
  }
@@ -18,6 +18,7 @@ import {
18
18
  import { continuousBinding, hashContinuous } from "./continuous-storage.js";
19
19
  import { watchContinuous } from "./continuous-watch.js";
20
20
  import { readMergeJournal } from "./merge-journal.js";
21
+ import { createStartupTimings, type StartupTimingPublisher, type StartupTimings } from "./startup-timings.js";
21
22
  import type { SyncLoaders } from "./sync-loaders.js";
22
23
  import { captureMutationOwner } from "./sync-local.js";
23
24
  import { fileHashMap } from "./sync-state.js";
@@ -43,18 +44,34 @@ const LOCAL_CHECK_MS = 5_000;
43
44
  const POLL_MS = 60_000;
44
45
  const DEBOUNCE_MS = 1_000;
45
46
 
47
+ interface ContinuousStartup {
48
+ signal: AbortSignal;
49
+ debug: boolean;
50
+ reported: boolean;
51
+ }
52
+
46
53
  /** Created on first enabled session. No module/factory-owned timers or watchers. */
47
- export function createContinuousSync(loaders: SyncLoaders, clock: ContinuousClock = systemClock) {
54
+ export function createContinuousSync(
55
+ loaders: SyncLoaders,
56
+ clock: ContinuousClock = systemClock,
57
+ publish?: StartupTimingPublisher,
58
+ ) {
59
+ const startups = new WeakMap<ExtensionContext["sessionManager"], ContinuousStartup>();
48
60
  const repairNotices = new WeakSet<ExtensionContext["sessionManager"]>();
49
61
  let task:
50
62
  | { owner: ExtensionContext["sessionManager"]; controller: AbortController; promise: Promise<void>; clear(): void }
51
63
  | undefined;
52
64
  return {
53
- start(ctx: ExtensionContext, sessionSignal: AbortSignal, explicitRetry = false) {
65
+ start(ctx: ExtensionContext, sessionSignal: AbortSignal, explicitRetry = false, debug = false) {
54
66
  if (!ctx.hasUI || (ctx.mode !== "tui" && ctx.mode !== "rpc") || sessionSignal.aborted || task) return;
55
67
  const controller = new AbortController();
56
68
  const signal = AbortSignal.any([sessionSignal, controller.signal]);
57
69
  const validateContext = captureMutationOwner(ctx);
70
+ // The caller supplies the session-start debug value, including first activation after settings edits.
71
+ const previousStartup = startups.get(ctx.sessionManager);
72
+ const startup: ContinuousStartup =
73
+ previousStartup?.signal === sessionSignal ? previousStartup : { signal: sessionSignal, debug, reported: false };
74
+ startups.set(ctx.sessionManager, startup);
58
75
  const current = {
59
76
  owner: ctx.sessionManager,
60
77
  controller,
@@ -73,7 +90,7 @@ export function createContinuousSync(loaders: SyncLoaders, clock: ContinuousCloc
73
90
  let explicit = explicitRetry;
74
91
  while (!signal.aborted) {
75
92
  try {
76
- await run(ctx, signal, explicit);
93
+ await run(ctx, signal, explicit, startup);
77
94
  return;
78
95
  } catch {
79
96
  if (signal.aborted) return;
@@ -113,7 +130,7 @@ export function createContinuousSync(loaders: SyncLoaders, clock: ContinuousCloc
113
130
  },
114
131
  };
115
132
 
116
- async function run(ctx: ExtensionContext, signal: AbortSignal, explicitRetry: boolean) {
133
+ async function run(ctx: ExtensionContext, signal: AbortSignal, explicitRetry: boolean, startup: ContinuousStartup) {
117
134
  const validateOwner = captureMutationOwner(ctx, signal);
118
135
  try {
119
136
  while (!signal.aborted) {
@@ -134,7 +151,7 @@ export function createContinuousSync(loaders: SyncLoaders, clock: ContinuousCloc
134
151
  validateOwner();
135
152
  });
136
153
  if (lease) {
137
- await monitor(ctx, config, binding, lease, signal, explicitRetry);
154
+ await monitor(ctx, config, binding, lease, signal, explicitRetry, startup);
138
155
  explicitRetry = false;
139
156
  } else setSyncStatus(ctx, "sync waiting: another local owner");
140
157
  } finally {
@@ -172,6 +189,7 @@ export function createContinuousSync(loaders: SyncLoaders, clock: ContinuousCloc
172
189
  lease: ContinuousLease,
173
190
  parent: AbortSignal,
174
191
  explicitRetry: boolean,
192
+ startup: ContinuousStartup,
175
193
  ) {
176
194
  const signal = AbortSignal.any([parent, lease.signal]);
177
195
  const validateOwner = captureMutationOwner(ctx, signal);
@@ -230,6 +248,10 @@ export function createContinuousSync(loaders: SyncLoaders, clock: ContinuousCloc
230
248
  const hintDue = dirtyAt === undefined ? Number.POSITIVE_INFINITY : dirtyAt + DEBOUNCE_MS;
231
249
  const due = Math.max(record.error === "budget" ? 0 : record.next, Math.min(pollDue, hintDue));
232
250
  if (now >= due) {
251
+ const timings = createStartupTimings(publish);
252
+ const report = !startup.reported && startup.debug;
253
+ startup.reported = true;
254
+ let outcome: "completed" | "stopped" = "stopped";
233
255
  const force = explicitRetry || controlChanged;
234
256
  explicitRetry = false;
235
257
  const expectedModel = hashContinuous(ctx.model);
@@ -277,7 +299,9 @@ export function createContinuousSync(loaders: SyncLoaders, clock: ContinuousCloc
277
299
  validateCycle,
278
300
  configuration,
279
301
  force,
302
+ timings,
280
303
  );
304
+ outcome = "completed";
281
305
  } catch (error) {
282
306
  if (signal.aborted || !ctx.isIdle()) return;
283
307
  // A settings hint interrupts the old authorization; load it afresh, not as a failed transfer.
@@ -313,6 +337,7 @@ export function createContinuousSync(loaders: SyncLoaders, clock: ContinuousCloc
313
337
  guard.abort();
314
338
  await guardTask;
315
339
  cycle = undefined;
340
+ timings.publish(ctx, report, signal, "continuous startup", outcome);
316
341
  }
317
342
  pollDue = clock.now() + POLL_MS + Math.floor(clock.random() * 6_000);
318
343
  closeWatch();
@@ -356,6 +381,7 @@ export function createContinuousSync(loaders: SyncLoaders, clock: ContinuousCloc
356
381
  validateOwner: (config?: AnySyncConfig) => void,
357
382
  configuration: string,
358
383
  force: boolean,
384
+ timings: StartupTimings,
359
385
  ) {
360
386
  const validate = (latest?: AnySyncConfig) => {
361
387
  signal.throwIfAborted();
@@ -375,27 +401,35 @@ export function createContinuousSync(loaders: SyncLoaders, clock: ContinuousCloc
375
401
  )
376
402
  return;
377
403
  setSyncStatus(ctx, "sync checking");
378
- const inspectionModule = await loaders.inspection();
404
+ const inspectionModule = await timings.measure("inspection import", () => loaders.inspection());
379
405
  validate();
380
- const observed = await guardedLock("continuous-check", async () => {
381
- validate();
382
- const inspection = await inspectionModule.inspectSync(config, snapshotOptionsForContext(ctx, config), signal);
383
- validate();
384
- let localIdentity = inspection.localIdentity;
385
- if (!localIdentity) {
386
- const snapshotModule = await loaders.snapshot();
406
+ const observed = await timings.measure("inspection and guards", () =>
407
+ guardedLock("continuous-check", async () => {
387
408
  validate();
388
- const local = await snapshotModule.createSnapshot(config.snapshotIdentity, {
389
- ...snapshotOptionsForContext(ctx, config),
409
+ const inspection = await inspectionModule.inspectSync(
410
+ config,
411
+ snapshotOptionsForContext(ctx, config),
390
412
  signal,
391
- });
413
+ undefined,
414
+ timings,
415
+ );
392
416
  validate();
393
- localIdentity = hashContinuous(fileHashMap(local));
394
- }
395
- const journal = await readMergeJournal(config);
396
- validate();
397
- return { inspection, journal, localIdentity };
398
- });
417
+ let localIdentity = inspection.localIdentity;
418
+ if (!localIdentity) {
419
+ const snapshotModule = await loaders.snapshot();
420
+ validate();
421
+ const local = await snapshotModule.createSnapshot(config.snapshotIdentity, {
422
+ ...snapshotOptionsForContext(ctx, config),
423
+ signal,
424
+ });
425
+ validate();
426
+ localIdentity = hashContinuous(fileHashMap(local));
427
+ }
428
+ const journal = await readMergeJournal(config);
429
+ validate();
430
+ return { inspection, journal, localIdentity };
431
+ }),
432
+ );
399
433
  validate();
400
434
  const evidence = hashContinuous([
401
435
  configuration,
@@ -434,28 +468,31 @@ export function createContinuousSync(loaders: SyncLoaders, clock: ContinuousCloc
434
468
  throw new ContinuousError("unsafe", "Continuous sync needs an accepted baseline and matching remote selection.");
435
469
  let transferred = false;
436
470
  if (observed.journal || observed.inspection.localChanged || observed.inspection.remoteChanged) {
437
- const operations = await loaders.operations();
471
+ const operations = await timings.measure("operations import", () => loaders.operations());
438
472
  validate();
439
473
  setSyncStatus(ctx, "sync syncing");
440
- const result = await withStateDirectoryAccess(() =>
441
- withAgentSyncLock(
442
- ctx,
443
- {
444
- args: [],
445
- yes: true,
446
- force: false,
447
- stale: false,
448
- silent: true,
449
- reload: false,
450
- auto: true,
451
- signal,
452
- background: {
453
- validate,
454
- reserve: (key, requestSignal) =>
455
- reserveContinuousRequest(binding, lease, key, () => clock.now(), requestSignal),
474
+ const result = await timings.measure("automatic transfer", () =>
475
+ withStateDirectoryAccess(() =>
476
+ withAgentSyncLock(
477
+ ctx,
478
+ {
479
+ args: [],
480
+ yes: true,
481
+ force: false,
482
+ stale: false,
483
+ silent: true,
484
+ reload: false,
485
+ auto: true,
486
+ signal,
487
+ startupTimings: timings,
488
+ background: {
489
+ validate,
490
+ reserve: (key, requestSignal) =>
491
+ reserveContinuousRequest(binding, lease, key, () => clock.now(), requestSignal),
492
+ },
456
493
  },
457
- },
458
- (options) => operations.syncBoth(ctx, options),
494
+ (options) => operations.syncBoth(ctx, options),
495
+ ),
459
496
  ),
460
497
  );
461
498
  validate();
@@ -12,12 +12,17 @@ import { safeTerminalText } from "../ui/terminal-text.js";
12
12
  import { startupTransferEnabled } from "./agent-policy.js";
13
13
  import { withAgentSyncLock } from "./agent-sync.js";
14
14
  import { combineSignals, throwIfAborted } from "./signals.js";
15
- import { createStartupTimings } from "./startup-timings.js";
15
+ import { createStartupTimings, type StartupTimingPublisher } from "./startup-timings.js";
16
16
  import { errorMessage } from "./sync-errors.js";
17
17
  import type { SyncLoaders } from "./sync-loaders.js";
18
18
 
19
19
  /** One owned task per extension session, never keyed by the shared headless UI. */
20
- export function createStartupCheck(loaders: SyncLoaders, attention: SyncAttentionController, timeoutMs = 30_000) {
20
+ export function createStartupCheck(
21
+ loaders: SyncLoaders,
22
+ attention: SyncAttentionController,
23
+ timeoutMs = 30_000,
24
+ publish?: StartupTimingPublisher,
25
+ ) {
21
26
  let active:
22
27
  | {
23
28
  controller: AbortController;
@@ -49,7 +54,7 @@ export function createStartupCheck(loaders: SyncLoaders, attention: SyncAttentio
49
54
  return;
50
55
  }
51
56
  if (active) throw new Error("Previous startup check has not been drained.");
52
- const timings = createStartupTimings();
57
+ const timings = createStartupTimings(publish);
53
58
  const controller = new AbortController();
54
59
  const signal = combineSignals(sessionSignal, controller.signal);
55
60
  const task = {
@@ -33,8 +33,12 @@ type StartupStage =
33
33
  | "transfer baseline write"
34
34
  | "transfer cleanup";
35
35
 
36
+ export type StartupTimingPublisher = (ctx: ExtensionContext, report: string) => void;
37
+
36
38
  /** Fixed labels and durations only: never retain errors, paths, settings, or payloads. */
37
- export function createStartupTimings() {
39
+ export function createStartupTimings(
40
+ publishReport: StartupTimingPublisher = (ctx, report) => ctx.ui.notify(report, "info"),
41
+ ) {
38
42
  const started = performance.now();
39
43
  const stages = new Map<StartupStage, { ms: number; calls: number; interrupted: boolean }>();
40
44
  let unchanged = false;
@@ -62,12 +66,13 @@ export function createStartupTimings() {
62
66
  ctx: ExtensionContext,
63
67
  enabled: boolean,
64
68
  sessionSignal: AbortSignal,
65
- scope: "local initialization" | "background startup",
69
+ scope: "local initialization" | "background startup" | "continuous startup",
66
70
  outcome: "completed" | "stopped" = "completed",
67
71
  ) {
68
72
  if (!enabled || !ctx.hasUI || sessionSignal.aborted) return;
69
73
  try {
70
- ctx.ui.notify(
74
+ publishReport(
75
+ ctx,
71
76
  [
72
77
  `pi-sync debug: ${scope} (${outcome})`,
73
78
  ...Array.from(
@@ -78,7 +83,6 @@ export function createStartupTimings() {
78
83
  ...(unchanged ? ["transfer result: unchanged (fast path)"] : []),
79
84
  `total: ${(performance.now() - started).toFixed(1)} ms`,
80
85
  ].join("\n"),
81
- "info",
82
86
  );
83
87
  } catch {
84
88
  // Diagnostics must not change sync behavior if the UI is unavailable during teardown.
@@ -3,11 +3,13 @@ import { completeSyncArguments, splitArgs } from "./commands/command.js";
3
3
  import { handleCommand } from "./commands/command-handler.js";
4
4
  import type { AnySyncConfig } from "./settings/settings-types.js";
5
5
  import { withStateDirectoryAccess } from "./state/state-directory.js";
6
+ import { startupTransferEnabled } from "./sync/agent-policy.js";
6
7
  import { autoPushSessions, startSession } from "./sync/automatic-sync.js";
7
8
  import { combineSignals } from "./sync/signals.js";
8
9
  import { createStartupCheck } from "./sync/startup-check.js";
9
10
  import { errorMessage } from "./sync/sync-errors.js";
10
11
  import { createSyncLoaders, type SyncDependencies } from "./sync/sync-loaders.js";
12
+ import { registerStartupTimings } from "./ui/startup-timings-entry.js";
11
13
  import { createSyncAttentionController } from "./ui/sync-attention.js";
12
14
  import { setSyncStatus } from "./ui/sync-status.js";
13
15
  import { safeTerminalText } from "./ui/terminal-text.js";
@@ -15,13 +17,15 @@ import { safeTerminalText } from "./ui/terminal-text.js";
15
17
  export default function sync(pi: ExtensionAPI, dependencies: Partial<SyncDependencies> = {}) {
16
18
  const loaders = createSyncLoaders(dependencies);
17
19
  const attention = createSyncAttentionController();
18
- const check = createStartupCheck(loaders, attention);
20
+ const publishTimings = registerStartupTimings(pi);
21
+ const check = createStartupCheck(loaders, attention, 30_000, publishTimings);
19
22
  let sessionAbort = new AbortController();
20
23
  let shutdownAbort: AbortController | undefined;
21
24
  let initialization = Promise.resolve<AnySyncConfig | false | undefined>(undefined);
22
25
  let continuous: ReturnType<typeof import("./sync/continuous-sync.js")["createContinuousSync"]> | undefined;
23
26
  let owner: ExtensionContext["sessionManager"] | undefined;
24
27
  let continuousPossible = false;
28
+ let startupDebug = false;
25
29
  const foreground = new Map<ExtensionContext["sessionManager"], number>();
26
30
  async function resumeContinuous(ctx: ExtensionContext, signal: AbortSignal, retry?: { setup?: string }) {
27
31
  const isCurrent = () => !signal.aborted && !foreground.has(ctx.sessionManager) && owner === ctx.sessionManager;
@@ -38,8 +42,13 @@ export default function sync(pi: ExtensionAPI, dependencies: Partial<SyncDepende
38
42
  }
39
43
  const { createContinuousSync } = await import("./sync/continuous-sync.js");
40
44
  if (!isCurrent()) return;
41
- continuous ??= createContinuousSync(loaders);
42
- continuous.start(ctx, signal, Boolean(retry && (retry.setup === undefined || retry.setup === config.setupName)));
45
+ continuous ??= createContinuousSync(loaders, undefined, publishTimings);
46
+ continuous.start(
47
+ ctx,
48
+ signal,
49
+ Boolean(retry && (retry.setup === undefined || retry.setup === config.setupName)),
50
+ startupDebug,
51
+ );
43
52
  } catch {
44
53
  /* Existing initialization/command paths report settings errors. */
45
54
  }
@@ -97,6 +106,7 @@ export default function sync(pi: ExtensionAPI, dependencies: Partial<SyncDepende
97
106
  const signal = sessionAbort.signal;
98
107
  owner = ctx.sessionManager;
99
108
  continuousPossible = false;
109
+ startupDebug = false;
100
110
  attention.reset(ctx);
101
111
  const previous = initialization;
102
112
  initialization = (async () => {
@@ -107,8 +117,10 @@ export default function sync(pi: ExtensionAPI, dependencies: Partial<SyncDepende
107
117
  await check.stop();
108
118
  if (signal.aborted) return false;
109
119
  try {
110
- const config = await withStateDirectoryAccess(() => startSession(ctx, signal));
111
- return signal.aborted ? false : config;
120
+ const startup = await withStateDirectoryAccess(() => startSession(ctx, signal, publishTimings));
121
+ if (signal.aborted) return false;
122
+ startupDebug = startup?.debug ?? false;
123
+ return startup?.config;
112
124
  } catch (error) {
113
125
  if (!signal.aborted && ctx.hasUI) {
114
126
  ctx.ui.notify(`pi-sync recovery required: ${safeTerminalText(errorMessage(error))}`, "error");
@@ -119,7 +131,7 @@ export default function sync(pi: ExtensionAPI, dependencies: Partial<SyncDepende
119
131
  const ready = await initialization;
120
132
  if (ready && !signal.aborted) {
121
133
  if (ready.continuous && ready.agentMode) await resumeContinuous(ctx, signal);
122
- else check.start(ctx, signal, ready);
134
+ else if (ready.automatic || startupTransferEnabled(ready)) check.start(ctx, signal, ready);
123
135
  }
124
136
  });
125
137
 
@@ -256,7 +256,7 @@ export async function showSyncSettings(
256
256
  id: "debug",
257
257
  label: "Debug startup timings",
258
258
  description:
259
- "All setups on this device: show startup durations in TUI/RPC notifications. Applies next session start; no credentials or file contents.",
259
+ "All setups on this device: show startup durations in the TUI transcript or RPC notifications, including initial Continuous sync. Applies next session start; no credentials or file contents.",
260
260
  currentValue: state.debug ? "On" : "Off",
261
261
  values: ["On", "Off"],
262
262
  action: "debug",
@@ -0,0 +1,36 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { Text, truncateToWidth } from "@earendil-works/pi-tui";
3
+ import type { StartupTimingPublisher } from "../sync/startup-timings.js";
4
+ import { safeTerminalText } from "./terminal-text.js";
5
+
6
+ export const STARTUP_TIMING_ENTRY = "pi-sync-startup-timings";
7
+
8
+ export interface StartupTimingEntry {
9
+ version: 1;
10
+ report: string;
11
+ }
12
+
13
+ export function registerStartupTimings(pi: ExtensionAPI): StartupTimingPublisher {
14
+ pi.registerEntryRenderer<StartupTimingEntry>(STARTUP_TIMING_ENTRY, (entry, _options, theme) => {
15
+ const data = entry.data;
16
+ if (data?.version !== 1 || typeof data.report !== "string") return undefined;
17
+ const text = new Text(
18
+ data.report
19
+ .split("\n")
20
+ .map((line, index) => theme.fg(index === 0 ? "accent" : "muted", safeTerminalText(line)))
21
+ .join("\n"),
22
+ 0,
23
+ 0,
24
+ );
25
+ return {
26
+ render: (width) => (width > 0 ? text.render(width).map((line) => truncateToWidth(line, width)) : []),
27
+ invalidate: () => text.invalidate(),
28
+ };
29
+ });
30
+ return (ctx, report) => {
31
+ // Pi coalesces consecutive info notifications, including the final /reload notice.
32
+ // Custom entries remain visible without adding messages to model context.
33
+ if (ctx.mode === "tui") pi.appendEntry<StartupTimingEntry>(STARTUP_TIMING_ENTRY, { version: 1, report });
34
+ else ctx.ui.notify(report, "info");
35
+ };
36
+ }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/sync/startup-timings.ts"],
4
- "sourcesContent": ["import { performance } from \"node:perf_hooks\";\nimport type { ExtensionContext } from \"@earendil-works/pi-coding-agent\";\n\ntype StartupStage =\n | \"transaction recovery\"\n | \"settings read\"\n | \"config resolution\"\n | \"inspection import\"\n | \"inspection and guards\"\n | \"backend initialization\"\n | \"local snapshot\"\n | \"sync state read\"\n | \"remote head\"\n | \"operations import\"\n | \"automatic transfer\"\n | \"transfer authorization\"\n | \"transfer backend initialization\"\n | \"transfer journal read\"\n | \"transfer journal recovery\"\n | \"transfer state read\"\n | \"transfer local snapshot\"\n | \"transfer remote head\"\n | \"transfer snapshot download\"\n | \"transfer unchanged verification\"\n | \"transfer merge planning\"\n | \"transfer settings merge\"\n | \"transfer content merge\"\n | \"transfer preflight\"\n | \"transfer backup\"\n | \"transfer journal write\"\n | \"transfer remote publication\"\n | \"transfer local apply\"\n | \"transfer baseline write\"\n | \"transfer cleanup\";\n\n/** Fixed labels and durations only: never retain errors, paths, settings, or payloads. */\nexport function createStartupTimings() {\n const started = performance.now();\n const stages = new Map<StartupStage, { ms: number; calls: number; interrupted: boolean }>();\n let unchanged = false;\n return {\n markUnchanged() {\n unchanged = true;\n },\n async measure<T>(stage: StartupStage, run: () => T | Promise<T>): Promise<T> {\n const start = performance.now();\n let completed = false;\n try {\n const result = await run();\n completed = true;\n return result;\n } finally {\n const previous = stages.get(stage);\n stages.set(stage, {\n ms: (previous?.ms ?? 0) + performance.now() - start,\n calls: (previous?.calls ?? 0) + 1,\n interrupted: Boolean(previous?.interrupted || !completed),\n });\n }\n },\n publish(\n ctx: ExtensionContext,\n enabled: boolean,\n sessionSignal: AbortSignal,\n scope: \"local initialization\" | \"background startup\",\n outcome: \"completed\" | \"stopped\" = \"completed\",\n ) {\n if (!enabled || !ctx.hasUI || sessionSignal.aborted) return;\n try {\n ctx.ui.notify(\n [\n `pi-sync debug: ${scope} (${outcome})`,\n ...Array.from(\n stages,\n ([stage, value]) =>\n `${stage}: ${value.ms.toFixed(1)} ms${value.calls > 1 ? ` (${value.calls} calls)` : \"\"}${value.interrupted ? \" (interrupted)\" : \"\"}`,\n ),\n ...(unchanged ? [\"transfer result: unchanged (fast path)\"] : []),\n `total: ${(performance.now() - started).toFixed(1)} ms`,\n ].join(\"\\n\"),\n \"info\",\n );\n } catch {\n // Diagnostics must not change sync behavior if the UI is unavailable during teardown.\n }\n },\n };\n}\n\nexport type StartupTimings = ReturnType<typeof createStartupTimings>;\n"],
5
- "mappings": ";;;;AAAA,SAAS,mBAAmB;AAoCrB,SAAS,uBAAuB;AACrC,QAAM,UAAU,YAAY,IAAI;AAChC,QAAM,SAAS,oBAAI,IAAuE;AAC1F,MAAI,YAAY;AAChB,SAAO;AAAA,IACL,gBAAgB;AACd,kBAAY;AAAA,IACd;AAAA,IACA,MAAM,QAAW,OAAqB,KAAuC;AAC3E,YAAM,QAAQ,YAAY,IAAI;AAC9B,UAAI,YAAY;AAChB,UAAI;AACF,cAAM,SAAS,MAAM,IAAI;AACzB,oBAAY;AACZ,eAAO;AAAA,MACT,UAAE;AACA,cAAM,WAAW,OAAO,IAAI,KAAK;AACjC,eAAO,IAAI,OAAO;AAAA,UAChB,KAAK,UAAU,MAAM,KAAK,YAAY,IAAI,IAAI;AAAA,UAC9C,QAAQ,UAAU,SAAS,KAAK;AAAA,UAChC,aAAa,QAAQ,UAAU,eAAe,CAAC,SAAS;AAAA,QAC1D,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IACA,QACE,KACA,SACA,eACA,OACA,UAAmC,aACnC;AACA,UAAI,CAAC,WAAW,CAAC,IAAI,SAAS,cAAc,QAAS;AACrD,UAAI;AACF,YAAI,GAAG;AAAA,UACL;AAAA,YACE,kBAAkB,KAAK,KAAK,OAAO;AAAA,YACnC,GAAG,MAAM;AAAA,cACP;AAAA,cACA,CAAC,CAAC,OAAO,KAAK,MACZ,GAAG,KAAK,KAAK,MAAM,GAAG,QAAQ,CAAC,CAAC,MAAM,MAAM,QAAQ,IAAI,KAAK,MAAM,KAAK,YAAY,EAAE,GAAG,MAAM,cAAc,mBAAmB,EAAE;AAAA,YACtI;AAAA,YACA,GAAI,YAAY,CAAC,wCAAwC,IAAI,CAAC;AAAA,YAC9D,WAAW,YAAY,IAAI,IAAI,SAAS,QAAQ,CAAC,CAAC;AAAA,UACpD,EAAE,KAAK,IAAI;AAAA,UACX;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/sync/continuous-watch.ts", "../../src/sync/continuous-sync.ts"],
4
- "sourcesContent": ["import { type FSWatcher, watch } from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { getAgentDir } from \"@earendil-works/pi-coding-agent\";\nimport { isDeniedPath } from \"../paths.js\";\nimport type { AnySyncConfig } from \"../settings/settings-types.js\";\n\n/** Hints only. The scheduler's periodic snapshot scan remains required. */\nexport async function watchContinuous(\n config: AnySyncConfig,\n changed: (settings: boolean) => void,\n): Promise<() => void> {\n const root = getAgentDir();\n const watchers: FSWatcher[] = [];\n const selected = config.include.map((item) => path.resolve(root, item));\n const relevant = (target: string) =>\n selected.some(\n (item) => target === item || target.startsWith(`${item}${path.sep}`) || item.startsWith(`${target}${path.sep}`),\n );\n const add = (directory: string, recursive: boolean) => {\n try {\n const watcher = watch(directory, { recursive, persistent: false }, (_event, file) => {\n // Unknown names can be our own private writes. Periodic scans cover them.\n if (!file) return;\n const target = path.resolve(directory, String(file));\n if (target === path.join(root, \"pi-sync.json\")) changed(true);\n else if (isDeniedPath(path.relative(root, target))) return;\n else if (relevant(target)) changed(false);\n });\n watcher.on(\"error\", () => watcher.close());\n watchers.push(watcher);\n } catch {\n /* Periodic scans cover unavailable or unsupported watchers. */\n }\n };\n add(root, false);\n const directories = new Set<string>();\n try {\n for (const target of selected) {\n try {\n const stat = await fs.lstat(target);\n if (stat.isSymbolicLink()) continue;\n const directory = stat.isDirectory() ? target : path.dirname(target);\n if (directory === root || directories.has(directory)) continue;\n directories.add(directory);\n add(directory, stat.isDirectory());\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") continue;\n // Missing targets are found by the parent hint or the next periodic scan.\n }\n }\n return () => {\n for (const watcher of watchers) watcher.close();\n };\n } catch (error) {\n for (const watcher of watchers) watcher.close();\n throw error;\n }\n}\n", "import type { ExtensionContext } from \"@earendil-works/pi-coding-agent\";\nimport { loadConfigForCheck, syncCheckConfigFingerprint } from \"../settings/config.js\";\nimport type { AnySyncConfig } from \"../settings/settings-types.js\";\nimport { snapshotOptionsForContext } from \"../snapshot/session-paths.js\";\nimport { withLock } from \"../state/lock.js\";\nimport { withStateDirectoryAccess } from \"../state/state-directory.js\";\nimport { readStateForConfig } from \"../state/sync-state-store.js\";\nimport { setSyncStatus } from \"../ui/sync-status.js\";\nimport { withAgentSyncLock } from \"./agent-sync.js\";\nimport { ContinuousError, continuousFailure } from \"./continuous-errors.js\";\nimport { acquireContinuousLease, type ContinuousLease } from \"./continuous-lease.js\";\nimport {\n normalizeContinuousClock,\n readContinuousRecord,\n reserveContinuousRequest,\n updateContinuousRecord,\n} from \"./continuous-record.js\";\nimport { continuousBinding, hashContinuous } from \"./continuous-storage.js\";\nimport { watchContinuous } from \"./continuous-watch.js\";\nimport { readMergeJournal } from \"./merge-journal.js\";\nimport type { SyncLoaders } from \"./sync-loaders.js\";\nimport { captureMutationOwner } from \"./sync-local.js\";\nimport { fileHashMap } from \"./sync-state.js\";\n\nexport function continuousEnabled(config: Pick<AnySyncConfig, \"continuous\" | \"agentMode\">) {\n return Boolean(config.continuous && config.agentMode);\n}\nexport interface ContinuousClock {\n now(): number;\n random(): number;\n schedule(ms: number, run: () => void, kind?: \"wait\" | \"authorization\"): () => void;\n}\nconst systemClock: ContinuousClock = {\n now: () => Date.now(),\n random: () => Math.random(),\n schedule: (ms, run) => {\n const timer = setTimeout(run, ms);\n timer.unref?.();\n return () => clearTimeout(timer);\n },\n};\nconst LOCAL_CHECK_MS = 5_000;\nconst POLL_MS = 60_000;\nconst DEBOUNCE_MS = 1_000;\n\n/** Created on first enabled session. No module/factory-owned timers or watchers. */\nexport function createContinuousSync(loaders: SyncLoaders, clock: ContinuousClock = systemClock) {\n const repairNotices = new WeakSet<ExtensionContext[\"sessionManager\"]>();\n let task:\n | { owner: ExtensionContext[\"sessionManager\"]; controller: AbortController; promise: Promise<void>; clear(): void }\n | undefined;\n return {\n start(ctx: ExtensionContext, sessionSignal: AbortSignal, explicitRetry = false) {\n if (!ctx.hasUI || (ctx.mode !== \"tui\" && ctx.mode !== \"rpc\") || sessionSignal.aborted || task) return;\n const controller = new AbortController();\n const signal = AbortSignal.any([sessionSignal, controller.signal]);\n const validateContext = captureMutationOwner(ctx);\n const current = {\n owner: ctx.sessionManager,\n controller,\n promise: Promise.resolve(),\n clear: () => {\n try {\n validateContext();\n setSyncStatus(ctx, undefined);\n } catch {\n /* replaced session */\n }\n },\n };\n task = current;\n current.promise = (async () => {\n let explicit = explicitRetry;\n while (!signal.aborted) {\n try {\n await run(ctx, signal, explicit);\n return;\n } catch {\n if (signal.aborted) return;\n try {\n validateContext();\n setSyncStatus(ctx, \"sync blocked: private state or settings need repair\");\n if (!repairNotices.has(ctx.sessionManager)) {\n repairNotices.add(ctx.sessionManager);\n ctx.ui.notify(\n \"Continuous sync blocked: private state or settings need repair. Use /sync. Evidence was retained.\",\n \"warning\",\n );\n }\n } catch {\n return;\n }\n }\n explicit = false;\n // Only local prerequisites are retried here. No remote/model request can\n // pass a malformed settings/state read. A repair can resume monitoring.\n await wait(clock, LOCAL_CHECK_MS, signal);\n }\n })()\n .catch(() => {\n /* Cancellation/UI disposal must not leak a detached rejection. */\n })\n .finally(() => {\n if (task === current) task = undefined;\n });\n },\n async stop(owner?: ExtensionContext[\"sessionManager\"]) {\n const current = task;\n if (!current || (owner && current.owner !== owner)) return;\n current.controller.abort(new DOMException(\"Continuous sync interrupted\", \"AbortError\"));\n current.clear();\n await current.promise;\n },\n };\n\n async function run(ctx: ExtensionContext, signal: AbortSignal, explicitRetry: boolean) {\n const validateOwner = captureMutationOwner(ctx, signal);\n try {\n while (!signal.aborted) {\n validateOwner();\n const config = await loadConfigForCheck();\n validateOwner();\n if (!continuousEnabled(config)) return;\n try {\n if (ctx.isIdle()) {\n let lease: ContinuousLease | undefined;\n let binding = \"\";\n try {\n await withStateDirectoryAccess(async () => {\n validateOwner();\n binding = await continuousBinding(config);\n validateOwner();\n lease = await acquireContinuousLease(binding);\n validateOwner();\n });\n if (lease) {\n await monitor(ctx, config, binding, lease, signal, explicitRetry);\n explicitRetry = false;\n } else setSyncStatus(ctx, \"sync waiting: another local owner\");\n } finally {\n await lease?.release();\n }\n }\n } catch (error) {\n if (signal.aborted) return;\n const failure = continuousFailure(error);\n if (!failure.retry && !(error instanceof Error && error.name === \"AbortError\")) throw error;\n setSyncStatus(ctx, `sync retry scheduled: ${failure.kind}`);\n }\n validateOwner();\n if (!continuousEnabled(await loadConfigForCheck())) return;\n validateOwner();\n await wait(clock, LOCAL_CHECK_MS, signal);\n }\n } finally {\n // Shutdown/replacement owns clearing the old UI; do not call it from a stale continuation.\n if (!signal.aborted) {\n try {\n validateOwner();\n setSyncStatus(ctx, undefined);\n } catch {\n /* stale context */\n }\n }\n }\n }\n\n async function monitor(\n ctx: ExtensionContext,\n initial: AnySyncConfig,\n binding: string,\n lease: ContinuousLease,\n parent: AbortSignal,\n explicitRetry: boolean,\n ) {\n const signal = AbortSignal.any([parent, lease.signal]);\n const validateOwner = captureMutationOwner(ctx, signal);\n let config = initial;\n let closeWatch = () => {};\n let wake = () => {};\n let dirtyAt: number | undefined;\n let cycle: AbortController | undefined;\n const changed = (settings: boolean) => {\n dirtyAt = clock.now();\n if (settings) cycle?.abort(new DOMException(\"Continuous settings changed\", \"AbortError\"));\n wake();\n };\n const validate = () => {\n validateOwner();\n lease.assert();\n if (!ctx.isIdle()) throw new DOMException(\"Owning session became busy\", \"AbortError\");\n };\n const stopWatch = () => {\n closeWatch();\n wake();\n };\n signal.addEventListener(\"abort\", stopWatch, { once: true });\n let pollDue = 0;\n try {\n closeWatch = await watchContinuous(config, changed);\n validate();\n while (!signal.aborted && ctx.isIdle()) {\n validate();\n const latest = await loadConfigForCheck();\n validate();\n if (!continuousEnabled(latest) || (await continuousBinding(latest)) !== binding) return;\n validate();\n if (syncCheckConfigFingerprint(latest) !== syncCheckConfigFingerprint(config)) {\n closeWatch();\n config = latest;\n closeWatch = await watchContinuous(config, changed);\n validate();\n pollDue = 0;\n }\n const record = await readContinuousRecord(binding);\n validate();\n const now = clock.now();\n const oldClock = JSON.stringify([record.next, record.calls]);\n normalizeContinuousClock(record, now);\n if (oldClock !== JSON.stringify([record.next, record.calls])) {\n await updateContinuousRecord(binding, lease, (current) => normalizeContinuousClock(current, now));\n validate();\n }\n const configuration = hashContinuous([{ ...config, showStatus: undefined, debug: undefined }, ctx.model]);\n const controlChanged = record.configuration !== configuration;\n if (controlChanged || explicitRetry) {\n record.next = 0;\n pollDue = 0;\n }\n const hintDue = dirtyAt === undefined ? Number.POSITIVE_INFINITY : dirtyAt + DEBOUNCE_MS;\n const due = Math.max(record.error === \"budget\" ? 0 : record.next, Math.min(pollDue, hintDue));\n if (now >= due) {\n const force = explicitRetry || controlChanged;\n explicitRetry = false;\n const expectedModel = hashContinuous(ctx.model);\n const expectedConfig = syncCheckConfigFingerprint(config);\n dirtyAt = undefined;\n cycle = new AbortController();\n const cancelDeadline = clock.schedule(30_000, () =>\n cycle?.abort(new DOMException(\"Continuous cycle timed out\", \"TimeoutError\")),\n );\n const guard = new AbortController();\n const guardSignal = AbortSignal.any([signal, cycle.signal, guard.signal]);\n const activeCycle = cycle;\n // Transfer entrypoints supply each fresh settings read. Never let a new\n // transfer adopt authorization that changed after this cycle's inspection.\n const validateCycle = (latest?: AnySyncConfig) => {\n validate();\n if (\n (latest && syncCheckConfigFingerprint(latest) !== expectedConfig) ||\n hashContinuous(ctx.model) !== expectedModel\n )\n activeCycle.abort(new DOMException(\"Continuous authorization changed\", \"AbortError\"));\n activeCycle.signal.throwIfAborted();\n };\n const guardTask = (async () => {\n while (!guardSignal.aborted) {\n await wait(clock, LOCAL_CHECK_MS, guardSignal, undefined, \"authorization\");\n if (guardSignal.aborted) return;\n try {\n validate();\n const latest = await loadConfigForCheck();\n if (guardSignal.aborted) return;\n validateCycle(latest);\n } catch {\n activeCycle.abort(new DOMException(\"Continuous authorization unavailable\", \"AbortError\"));\n }\n }\n })().catch(() => {});\n try {\n await performCycle(\n ctx,\n config,\n binding,\n lease,\n AbortSignal.any([signal, cycle.signal]),\n validateCycle,\n configuration,\n force,\n );\n } catch (error) {\n if (signal.aborted || !ctx.isIdle()) return;\n // A settings hint interrupts the old authorization; load it afresh, not as a failed transfer.\n if (cycle.signal.aborted && cycle.signal.reason?.name !== \"TimeoutError\") {\n pollDue = 0;\n continue;\n }\n const failure = continuousFailure(error);\n let announce = false;\n await updateContinuousRecord(binding, lease, (current) => {\n const now = clock.now();\n normalizeContinuousClock(current, now);\n const notice = hashContinuous([failure.kind, current.evidence, configuration]);\n announce = current.notice !== notice;\n if (!failure.retry) current.notice = notice;\n current.configuration = configuration;\n current.pending = true;\n current.error = failure.kind;\n current.phase = failure.retry ? \"retry\" : \"blocked\";\n current.failures = Math.min(30, current.failures + 1);\n const base = Math.min(300_000, 5_000 * 2 ** Math.min(6, current.failures - 1));\n const delay = Math.min(300_000, base + Math.floor(clock.random() * base * 0.2));\n current.next = now + Math.max(delay, failure.delay ?? 0);\n });\n validate();\n if (announce && !failure.retry)\n ctx.ui.notify(\n `Continuous sync blocked (${failure.kind}). Review /sync or repair the setup. Evidence was retained.`,\n \"warning\",\n );\n } finally {\n cancelDeadline();\n guard.abort();\n await guardTask;\n cycle = undefined;\n }\n pollDue = clock.now() + POLL_MS + Math.floor(clock.random() * 6_000);\n closeWatch();\n closeWatch = () => {};\n closeWatch = await watchContinuous(config, changed);\n validate();\n }\n validate();\n const state = await readContinuousRecord(binding);\n validate();\n setSyncStatus(\n ctx,\n state.phase === \"blocked\"\n ? `sync blocked: ${state.error}`\n : state.phase === \"retry\"\n ? `sync retry scheduled: ${state.error}`\n : \"sync waiting for changes\",\n );\n if (state.phase === \"retry\") pollDue = Math.min(pollDue, state.next);\n const next = Math.max(\n state.error === \"budget\" ? 0 : state.next,\n Math.min(pollDue, dirtyAt === undefined ? Number.POSITIVE_INFINITY : dirtyAt + DEBOUNCE_MS),\n );\n await wait(clock, Math.max(1, Math.min(LOCAL_CHECK_MS, next - clock.now())), signal, (callback) => {\n wake = callback;\n });\n }\n } finally {\n signal.removeEventListener(\"abort\", stopWatch);\n closeWatch();\n cycle?.abort();\n }\n }\n\n async function performCycle(\n ctx: ExtensionContext,\n config: AnySyncConfig,\n binding: string,\n lease: ContinuousLease,\n signal: AbortSignal,\n validateOwner: (config?: AnySyncConfig) => void,\n configuration: string,\n force: boolean,\n ) {\n const validate = (latest?: AnySyncConfig) => {\n signal.throwIfAborted();\n validateOwner(latest);\n };\n validate();\n const current = await readContinuousRecord(binding);\n validate();\n // A newly pending journal may prove an uncertain commit. It is never authority to republish.\n const pending = await guardedLock(\"continuous-journal\", () => readMergeJournal(config));\n validate();\n if (\n !force &&\n !pending &&\n current.phase === \"blocked\" &&\n [\"authentication\", \"storage\", \"unknown\"].includes(current.error ?? \"\")\n )\n return;\n setSyncStatus(ctx, \"sync checking\");\n const inspectionModule = await loaders.inspection();\n validate();\n const observed = await guardedLock(\"continuous-check\", async () => {\n validate();\n const inspection = await inspectionModule.inspectSync(config, snapshotOptionsForContext(ctx, config), signal);\n validate();\n let localIdentity = inspection.localIdentity;\n if (!localIdentity) {\n const snapshotModule = await loaders.snapshot();\n validate();\n const local = await snapshotModule.createSnapshot(config.snapshotIdentity, {\n ...snapshotOptionsForContext(ctx, config),\n signal,\n });\n validate();\n localIdentity = hashContinuous(fileHashMap(local));\n }\n const journal = await readMergeJournal(config);\n validate();\n return { inspection, journal, localIdentity };\n });\n validate();\n const evidence = hashContinuous([\n configuration,\n observed.localIdentity,\n observed.inspection.head?.revision,\n observed.inspection.stateIdentity,\n ]);\n const recovery = observed.journal ? hashContinuous([evidence, observed.journal]) : undefined;\n const blocked =\n !force &&\n current.evidence === evidence &&\n ((current.phase === \"blocked\" && (!recovery || recovery === current.recovery)) ||\n (current.error === \"budget\" && current.next > clock.now() && !observed.journal));\n await updateContinuousRecord(binding, lease, (record) => {\n if (record.configuration !== configuration) {\n record.failures = 0;\n record.next = 0;\n }\n record.configuration = configuration;\n record.checked = clock.now();\n record.evidence = evidence;\n if (!blocked) {\n if (record.phase !== \"blocked\") record.phase = \"syncing\";\n record.pending = true;\n if (recovery) record.recovery = recovery;\n }\n });\n validate();\n if (blocked) return;\n if (\n !observed.journal &&\n (observed.inspection.firstSync ||\n !observed.inspection.head ||\n observed.inspection.selectionState?.kind !== \"same\")\n )\n throw new ContinuousError(\"unsafe\", \"Continuous sync needs an accepted baseline and matching remote selection.\");\n let transferred = false;\n if (observed.journal || observed.inspection.localChanged || observed.inspection.remoteChanged) {\n const operations = await loaders.operations();\n validate();\n setSyncStatus(ctx, \"sync syncing\");\n const result = await withStateDirectoryAccess(() =>\n withAgentSyncLock(\n ctx,\n {\n args: [],\n yes: true,\n force: false,\n stale: false,\n silent: true,\n reload: false,\n auto: true,\n signal,\n background: {\n validate,\n reserve: (key, requestSignal) =>\n reserveContinuousRequest(binding, lease, key, () => clock.now(), requestSignal),\n },\n },\n (options) => operations.syncBoth(ctx, options),\n ),\n );\n validate();\n transferred = result === \"applied\";\n }\n const state = await guardedLock(\"continuous-result\", () => readStateForConfig(config));\n validate();\n const unresolved = state.unresolved;\n let announcePartial = false;\n await updateContinuousRecord(binding, lease, (record) => {\n record.phase = unresolved?.length ? \"blocked\" : \"waiting\";\n record.pending = Boolean(unresolved?.length);\n record.failures = 0;\n record.next = 0;\n if (unresolved?.length) {\n record.error = \"unsafe\";\n const notice = hashContinuous([\"partial\", unresolved, configuration]);\n announcePartial = record.notice !== notice;\n record.notice = notice;\n } else {\n delete record.error;\n delete record.notice;\n }\n if (transferred) record.transferred = clock.now();\n });\n validate();\n if (announcePartial)\n ctx.ui.notify(\n \"Continuous sync retained unresolved groups. Review /sync conflicts; independent partial progress was preserved.\",\n \"warning\",\n );\n }\n}\n\nfunction guardedLock<T>(command: string, run: () => Promise<T>) {\n return withStateDirectoryAccess(() => withLock(command, run));\n}\n\nfunction wait(\n clock: ContinuousClock,\n ms: number,\n signal: AbortSignal,\n expose?: (wake: () => void) => void,\n kind: \"wait\" | \"authorization\" = \"wait\",\n) {\n signal.throwIfAborted();\n return new Promise<void>((resolve) => {\n const done = () => {\n cancel();\n signal.removeEventListener(\"abort\", done);\n expose?.(() => {});\n resolve();\n };\n const cancel = clock.schedule(ms, done, kind);\n signal.addEventListener(\"abort\", done, { once: true });\n expose?.(done);\n if (signal.aborted) done();\n });\n}\n"],
5
- "mappings": "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",
6
- "names": ["latest", "now"]
7
- }