@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.
- package/CHANGELOG.md +14 -0
- package/README.md +29 -21
- package/dist/chunks/{agent-candidate-OCDPRI5G.ts → agent-candidate-U2NU3GUX.ts} +2 -2
- package/dist/chunks/{agent-plan-P5B5RGZ7.ts → agent-plan-3YXDIJXO.ts} +13 -10
- package/dist/chunks/agent-plan-3YXDIJXO.ts.map +7 -0
- package/dist/chunks/{agent-runner-WMSCKSCB.ts → agent-runner-FCRV2Z75.ts} +11 -10
- package/dist/chunks/agent-runner-FCRV2Z75.ts.map +7 -0
- package/dist/chunks/{agent-store-RJSMNOYP.ts → agent-store-7GINKTS7.ts} +2 -2
- package/dist/chunks/{agent-store-RJSMNOYP.ts.map → agent-store-7GINKTS7.ts.map} +2 -2
- package/dist/chunks/{agent-sync-EUK2CPSS.ts → agent-sync-2R4GG2MF.ts} +3 -3
- package/dist/chunks/{chunk-67INJPKR.ts → chunk-CQD2TJHE.ts} +4 -4
- package/dist/chunks/{chunk-67INJPKR.ts.map → chunk-CQD2TJHE.ts.map} +2 -2
- package/dist/chunks/{chunk-ZDDBZH72.ts → chunk-JYILGR3K.ts} +2 -2
- package/dist/chunks/{chunk-OKPPZA4D.ts → chunk-MG66XM5U.ts} +3 -3
- package/dist/chunks/{chunk-OKPPZA4D.ts.map → chunk-MG66XM5U.ts.map} +2 -2
- package/dist/chunks/{chunk-7OLLPPLU.ts → chunk-MMQIEDZ6.ts} +6 -6
- package/dist/chunks/{chunk-7OLLPPLU.ts.map → chunk-MMQIEDZ6.ts.map} +2 -2
- package/dist/chunks/{chunk-Q23JJW7E.ts → chunk-MW6KUQYA.ts} +7 -5
- package/dist/chunks/chunk-MW6KUQYA.ts.map +7 -0
- package/dist/chunks/{chunk-NLWS55B6.ts → chunk-QPC5HT3A.ts} +2 -2
- package/dist/chunks/{chunk-NLWS55B6.ts.map → chunk-QPC5HT3A.ts.map} +1 -1
- package/dist/chunks/{chunk-DRTC4PKM.ts → chunk-S5OCYVPS.ts} +2 -2
- package/dist/chunks/{chunk-DRTC4PKM.ts.map → chunk-S5OCYVPS.ts.map} +2 -2
- package/dist/chunks/{chunk-3IYAPMST.ts → chunk-U6GXGY5G.ts} +2 -2
- package/dist/chunks/{chunk-F7E3VFXU.ts → chunk-WQMYV4DW.ts} +8 -8
- package/dist/chunks/chunk-WQMYV4DW.ts.map +7 -0
- package/dist/chunks/{chunk-CPT2BN6N.ts → chunk-XEODG2PE.ts} +2 -2
- package/dist/chunks/chunk-YIPK23JW.ts +171 -0
- package/dist/chunks/chunk-YIPK23JW.ts.map +7 -0
- package/dist/chunks/{command-handler-F2E7A5T5.ts → command-handler-KCB6MCWS.ts} +12 -12
- package/dist/chunks/{command-handler-F2E7A5T5.ts.map → command-handler-KCB6MCWS.ts.map} +2 -2
- package/dist/chunks/{conflict-review-2QMO2K6Q.ts → conflict-review-GXA6Y7KY.ts} +2 -2
- package/dist/chunks/{continuous-record-AH7JGYKM.ts → continuous-record-EPHGR2DU.ts} +2 -2
- package/dist/chunks/{continuous-sync-VRMXCA5X.ts → continuous-sync-FAFNLEUW.ts} +19 -11
- package/dist/chunks/continuous-sync-FAFNLEUW.ts.map +7 -0
- package/dist/chunks/{manager-result-dispatcher-6W52NM7F.ts → manager-result-dispatcher-XCYPW7RC.ts} +4 -4
- package/dist/chunks/{manager-ui-LM7DSJ2Y.ts → manager-ui-FMZXKPX2.ts} +153 -183
- package/dist/chunks/manager-ui-FMZXKPX2.ts.map +7 -0
- package/dist/chunks/{remote-selection-ui-TD4C4W3J.ts → remote-selection-ui-MYM2YCPF.ts} +3 -3
- package/dist/chunks/{setup-switch-PIPXYPCW.ts → setup-switch-KLGESLQX.ts} +3 -3
- package/dist/chunks/{setup-wizard-RR5U3SUB.ts → setup-wizard-ZQAK35BP.ts} +3 -3
- package/dist/chunks/{sync-operations-CV3TULLD.ts → sync-operations-45APGOL3.ts} +9 -9
- package/dist/chunks/sync-operations-45APGOL3.ts.map +7 -0
- package/dist/index.ts +80 -65
- package/dist/index.ts.map +2 -2
- package/docs/agent-continuous-hardening.md +5 -5
- package/docs/agent-mode-verification.md +3 -3
- package/docs/agent-quality-verification.md +3 -3
- package/docs/continuous-sync-verification.md +2 -2
- package/docs/settings.md +9 -5
- package/docs/tui-contract.md +57 -3
- package/package.json +3 -3
- package/skills/configure-pi-sync/SKILL.md +3 -1
- package/skills/configure-pi-sync/references/configuration.md +61 -38
- package/src/commands/command-execution.ts +3 -3
- package/src/commands/command.ts +1 -1
- package/src/settings/settings-types.ts +1 -1
- package/src/sync/agent-candidate.ts +3 -3
- package/src/sync/agent-plan.ts +11 -8
- package/src/sync/agent-runner.ts +10 -9
- package/src/sync/agent-store.ts +1 -1
- package/src/sync/agent-sync.ts +3 -3
- package/src/sync/continuous-record.ts +1 -1
- package/src/sync/continuous-sync.ts +15 -6
- package/src/sync/merged-sync.ts +4 -4
- package/src/sync/startup-check.ts +1 -1
- package/src/sync/sync-mutations.ts +3 -3
- package/src/sync/sync-queries.ts +2 -2
- package/src/sync-extension.ts +71 -57
- package/src/ui/agent-mode-setting.ts +6 -6
- package/src/ui/manager-state.ts +1 -1
- package/src/ui/remote-selection-ui.ts +1 -1
- package/src/ui/settings-ui.ts +19 -55
- package/src/ui/setup/setup-switcher.ts +2 -2
- package/src/ui/sync-attention.ts +5 -3
- package/src/ui/sync-setups-ui.ts +2 -2
- package/src/ui/sync-status.ts +214 -3
- package/dist/chunks/agent-plan-P5B5RGZ7.ts.map +0 -7
- package/dist/chunks/agent-runner-WMSCKSCB.ts.map +0 -7
- package/dist/chunks/chunk-F7E3VFXU.ts.map +0 -7
- package/dist/chunks/chunk-K3RIEL6J.ts +0 -20
- package/dist/chunks/chunk-K3RIEL6J.ts.map +0 -7
- package/dist/chunks/chunk-Q23JJW7E.ts.map +0 -7
- package/dist/chunks/continuous-sync-VRMXCA5X.ts.map +0 -7
- package/dist/chunks/manager-ui-LM7DSJ2Y.ts.map +0 -7
- package/dist/chunks/sync-operations-CV3TULLD.ts.map +0 -7
- /package/dist/chunks/{agent-candidate-OCDPRI5G.ts.map → agent-candidate-U2NU3GUX.ts.map} +0 -0
- /package/dist/chunks/{agent-sync-EUK2CPSS.ts.map → agent-sync-2R4GG2MF.ts.map} +0 -0
- /package/dist/chunks/{chunk-ZDDBZH72.ts.map → chunk-JYILGR3K.ts.map} +0 -0
- /package/dist/chunks/{chunk-3IYAPMST.ts.map → chunk-U6GXGY5G.ts.map} +0 -0
- /package/dist/chunks/{chunk-CPT2BN6N.ts.map → chunk-XEODG2PE.ts.map} +0 -0
- /package/dist/chunks/{conflict-review-2QMO2K6Q.ts.map → conflict-review-GXA6Y7KY.ts.map} +0 -0
- /package/dist/chunks/{continuous-record-AH7JGYKM.ts.map → continuous-record-EPHGR2DU.ts.map} +0 -0
- /package/dist/chunks/{manager-result-dispatcher-6W52NM7F.ts.map → manager-result-dispatcher-XCYPW7RC.ts.map} +0 -0
- /package/dist/chunks/{remote-selection-ui-TD4C4W3J.ts.map → remote-selection-ui-MYM2YCPF.ts.map} +0 -0
- /package/dist/chunks/{setup-switch-PIPXYPCW.ts.map → setup-switch-KLGESLQX.ts.map} +0 -0
- /package/dist/chunks/{setup-wizard-RR5U3SUB.ts.map → setup-wizard-ZQAK35BP.ts.map} +0 -0
|
@@ -7,8 +7,8 @@ The configuration skill provides guidance only; the user performs settings chang
|
|
|
7
7
|
- [Required backend shapes](#required-backend-shapes)
|
|
8
8
|
- [Status display](#status-display)
|
|
9
9
|
- [Secret scanning](#secret-scanning)
|
|
10
|
-
- [
|
|
11
|
-
- [
|
|
10
|
+
- [Autonomous sync](#autonomous-sync)
|
|
11
|
+
- [Continuous sync](#continuous-sync)
|
|
12
12
|
- [Included content](#included-content)
|
|
13
13
|
- [Unsupported old settings and recovery](#unsupported-old-settings-and-recovery)
|
|
14
14
|
|
|
@@ -55,14 +55,20 @@ Global settings apply to all setups on this device; they are not remote policy.
|
|
|
55
55
|
| Content/partial policy | `sync.mergeContent`, `sync.partialSync` | Enabling either requires version 5. |
|
|
56
56
|
|
|
57
57
|
Do not downgrade version 4 or 5 merely because an option is turned off.
|
|
58
|
-
The four automatic controls have separate effects:
|
|
58
|
+
The four automatic controls have separate effects:
|
|
59
|
+
|
|
60
|
+
- **Automatic sync** (`sync.automatic`) enables startup observation and selected-content shutdown pushes when sessions are included.
|
|
61
|
+
- **Startup transfer** (`sync.automaticTransfer`) enables one eligible startup transfer.
|
|
62
|
+
- **Autonomous sync** (`sync.agentMode`, unchanged) authorizes eligible startup and **Sync now** transfers without another transfer confirmation, plus model assistance and disclosure for supported conflicts.
|
|
63
|
+
- **Continuous sync** (`sync.continuous`) adds recurring work only with Autonomous sync enabled.
|
|
64
|
+
|
|
59
65
|
None of these controls is enabled by invoking the configuration skill.
|
|
60
66
|
|
|
61
67
|
### Editing in the manager
|
|
62
68
|
|
|
63
|
-
Open `/sync` → **Settings**
|
|
64
|
-
|
|
65
|
-
**Included paths** and **Compare included paths** are adjacent
|
|
69
|
+
Open `/sync` → **Settings** to go directly to **Pi Sync Settings**.
|
|
70
|
+
The screen has one searchable flat list with all 16 rows in their existing order and no category navigation or headings.
|
|
71
|
+
**Included paths** and **Compare included paths** are adjacent; the latter compares path policies, not file contents.
|
|
66
72
|
**Storage location** edits the current setup's coordinates, while **Manage sync setups** and **Manage storage connections** retain the catalog and affected-setup reviews.
|
|
67
73
|
Returning from catalog management refreshes the rows; switching or removing the owning setup closes its old Settings screen.
|
|
68
74
|
|
|
@@ -70,7 +76,7 @@ Descriptions identify the owner: this setup, all setups on this device, or share
|
|
|
70
76
|
Simple choices report Saving and the durable Saved or failed/rejected result, preserve the selected row and search during refresh, and restore their previous value if saving fails.
|
|
71
77
|
Escape closes the screen without undoing successful saves.
|
|
72
78
|
Complex values retain their input/review flows.
|
|
73
|
-
This navigation change does not alter schema, defaults, automatic-sync behavior, or
|
|
79
|
+
This navigation change does not alter schema, defaults, automatic-sync behavior, or explicit opt-ins.
|
|
74
80
|
|
|
75
81
|
The manager separates current state from transfer, inspection, and management actions.
|
|
76
82
|
**Check remote status** and **View differences** invoke the existing read/check routes only when selected.
|
|
@@ -179,7 +185,7 @@ This check does not contact the server or discover setups configured only on oth
|
|
|
179
185
|
Every setup requires `sync.include` and explicit `sync.automatic`.
|
|
180
186
|
Setup asks for automatic sync with Off first; existing values are not rewritten.
|
|
181
187
|
When enabled, the current setup receives one background startup check in TUI/RPC, including reload/new/resume/fork.
|
|
182
|
-
|
|
188
|
+
Automatic sync alone does not perform startup transfers or open dialogs; review changes through `/sync`.
|
|
183
189
|
Print/JSON skip startup checks.
|
|
184
190
|
Checks compare local hashes and remote metadata against the last sync baseline, so revision changes and missing baselines require review rather than proving a file conflict.
|
|
185
191
|
|
|
@@ -219,14 +225,25 @@ It accepts:
|
|
|
219
225
|
### Status display
|
|
220
226
|
|
|
221
227
|
The global `showStatus` setting accepts a boolean and defaults to `true` when omitted from an existing version 3 document.
|
|
222
|
-
Set it through **/sync → Settings →
|
|
228
|
+
Set it through **/sync → Settings → Show sync status**; changes are saved and applied immediately for every setup.
|
|
223
229
|
When `false`, pi-sync clears and suppresses status text for background checks, manual and automatic transfers, and review attention.
|
|
224
230
|
TUI review widgets and TUI/RPC notifications remain available, and lifecycle cleanup still clears any stale status owned by pi-sync.
|
|
225
231
|
|
|
232
|
+
Pi Sync also publishes optional semantic metadata through the [extension-neutral status protocol](https://github.com/narumiruna/pi-extensions/blob/main/packages/pi-statusline/docs/extension-status-protocol.md).
|
|
233
|
+
Pi Statusline uses that metadata for quiet display: waiting for changes and another local monitoring owner are hidden, while active work uses short static markers.
|
|
234
|
+
The existing configured icon identifies Sync without an extra duplicate label.
|
|
235
|
+
Native Pi footers, RPC observers, and older or absent metadata consumers still receive the original readable status value.
|
|
236
|
+
This is not permission to sync, change retry policy, or erase recovery evidence.
|
|
237
|
+
|
|
238
|
+
Retry and blocked states keep a short reason: `temp` (temporary service/network failure), `busy` (lock contention), `stale` (changed evidence), `auth` (authentication), `storage` (local storage), `review` (safety barrier), `model` (resolver/model failure or exhausted candidate attempts), `limit` (request-window allowance), or `unknown` (unclassified failure).
|
|
239
|
+
Settings or private-state repair uses `repair`.
|
|
240
|
+
A request-window limit is not a currency budget; exhausted attempts for one candidate remain a model block, not a timer-based allowance reset.
|
|
241
|
+
Use the existing manager and **Check remote status** guidance to inspect recovery requirements rather than treating a short marker as a complete diagnosis.
|
|
242
|
+
|
|
226
243
|
### Debug startup timings
|
|
227
244
|
|
|
228
245
|
The top-level user setting `debug` accepts only `true` or `false` and defaults to `false`.
|
|
229
|
-
Edit `<getAgentDir()>/pi-sync.json` (normally `~/.pi/agent/pi-sync.json`) or use **/sync → Settings →
|
|
246
|
+
Edit `<getAgentDir()>/pi-sync.json` (normally `~/.pi/agent/pi-sync.json`) or use **/sync → Settings → Debug startup timings**.
|
|
230
247
|
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.
|
|
231
248
|
There is no project or environment override.
|
|
232
249
|
|
|
@@ -255,7 +272,7 @@ Set `debug` to `false` or remove it, then start a new session to disable reports
|
|
|
255
272
|
### Secret scanning
|
|
256
273
|
|
|
257
274
|
The global `skipSecretScan` setting accepts a boolean and defaults to `false`, including when omitted from an existing version 3 document.
|
|
258
|
-
Set it through **/sync → Settings →
|
|
275
|
+
Set it through **/sync → Settings → Skip secret scan**; changes are saved immediately and apply to subsequent pushes for every sync setup, including automatic pushes.
|
|
259
276
|
When `true`, pushes skip the local secret scan; other safety checks and confirmations remain unchanged.
|
|
260
277
|
Enable it only after reviewing the destination and selected content because files containing secrets can be uploaded.
|
|
261
278
|
`/sync doctor` still scans and reports possible secrets regardless of this setting.
|
|
@@ -295,7 +312,7 @@ RPC remains a read-only summary with the manual `sync.include` path.
|
|
|
295
312
|
|
|
296
313
|
Every new snapshot stores the normalized included-content selection separately from the files that happened to exist.
|
|
297
314
|
This preserves selected-but-missing paths without syncing `pi-sync.json`, storage credentials, automatic-sync preferences, or setup names.
|
|
298
|
-
**Settings →
|
|
315
|
+
**Settings → Compare included paths** opens the same review-first flow as a manager operation when local and remote lists differ.
|
|
299
316
|
Adoption revalidates the remote head, immutable snapshot, and complete reviewed setup configuration, including the shared connection and credentials, before one atomic settings update.
|
|
300
317
|
Cancelling the network check leaves the content list unchanged.
|
|
301
318
|
A settings save that has started its atomic replacement can still finish; later cancellation does not undo it.
|
|
@@ -317,7 +334,7 @@ Pull protects the currently open session file; restart Pi or resume a pulled ses
|
|
|
317
334
|
### Automatic startup transfer
|
|
318
335
|
|
|
319
336
|
`sync.automaticTransfer` is optional and accepts only a boolean; absent means `false`.
|
|
320
|
-
Set it through **/sync → Settings →
|
|
337
|
+
Set it through **/sync → Settings → Startup transfer**.
|
|
321
338
|
It is separate from `sync.automatic`: the legacy setting retains observation and selected-content shutdown behavior.
|
|
322
339
|
|
|
323
340
|
When explicitly enabled, one startup transfer waits for an idle boundary in TUI/RPC.
|
|
@@ -345,27 +362,30 @@ Turning **Startup transfer** off does not revert transfers or remove backups/jou
|
|
|
345
362
|
For interrupted operations, follow [merge recovery](https://github.com/narumiruna/pi-extensions/blob/main/packages/pi-sync/docs/merge-implementation-audit.md).
|
|
346
363
|
Unknown fields remain preserved and malformed/invalid files block saves, just as for the existing policy.
|
|
347
364
|
|
|
348
|
-
|
|
365
|
+
<a id="experimental-agent-mode"></a>
|
|
366
|
+
|
|
367
|
+
### Autonomous sync
|
|
349
368
|
|
|
350
|
-
`sync.agentMode`
|
|
351
|
-
Open **Settings →
|
|
369
|
+
Autonomous sync uses the unchanged `sync.agentMode` key, an optional per-setup boolean, default `false`, in settings versions 3, 4, and 5.
|
|
370
|
+
Open **Settings → Autonomous sync** and review the setup, destination, selected paths, transfer authorization, model disclosure, costs, and autonomous writes before enabling.
|
|
352
371
|
RPC users can explicitly set the same field in the private settings file.
|
|
353
372
|
Setting it to `true` is authorization, not just a display preference.
|
|
354
373
|
|
|
355
|
-
|
|
356
|
-
It can publish
|
|
374
|
+
Autonomous sync authorizes one eligible merged sync at the next session startup and the same flow from **Sync now** or `/sync sync`, without another transfer confirmation.
|
|
375
|
+
It can publish, replace, or delete selected local and remote content.
|
|
376
|
+
The selected Pi model resolves supported text and settings conflicts; eligible conflict content is sent to its provider and may incur costs.
|
|
357
377
|
It is independent of `sync.automatic` and `sync.automaticTransfer`; neither existing preference is changed.
|
|
358
|
-
Disabling Startup transfer alone does not disable
|
|
378
|
+
Disabling Startup transfer alone does not disable Autonomous sync.
|
|
359
379
|
Saving never starts a transfer from Settings.
|
|
360
380
|
The atomic write cannot be cancelled after its final cancellation check.
|
|
361
381
|
A completed save remains in effect after cancellation or session replacement; reopen Settings to read the saved value.
|
|
362
382
|
TUI/RPC are supported; print/JSON and shutdown do not start a resolver.
|
|
363
|
-
|
|
383
|
+
Autonomous sync alone adds no polling or daemon.
|
|
364
384
|
Separately enabled Continuous sync provides recurring work while Pi remains open.
|
|
365
385
|
|
|
366
386
|
An accepted baseline, matching included-content/local-field policies, and conditional or lease-protected publication remain required.
|
|
367
387
|
First-source selection, policy migrations, explicit push/pull, and rollback keep their existing reviewed flows.
|
|
368
|
-
`--yes` does not enable
|
|
388
|
+
`--yes` does not enable Autonomous sync or authorize model disclosure.
|
|
369
389
|
Completed transfers never reload resources automatically.
|
|
370
390
|
|
|
371
391
|
The resolver uses the initiating session's selected model through Pi's public provider/authentication registry.
|
|
@@ -378,14 +398,14 @@ Parent model prompts and tools are not changed.
|
|
|
378
398
|
This is configuration isolation, not an OS sandbox or proof of semantic correctness.
|
|
379
399
|
|
|
380
400
|
Eligible text is global `AGENTS.md` and `.md`/`.txt` files under `prompts/` or `skills/`.
|
|
381
|
-
Global `settings.json` uses the existing strict JSON, numeric, coupled-field, unknown-field, and machine-local-field policies.
|
|
401
|
+
Global `settings.json` uses the existing strict JSON, numeric, coupled-field, unknown-field, and machine-local-field policies. With Autonomous sync enabled, `packages` merges by source identity and selects the higher explicit SemVer version for each npm package present on both sides, even when one side has not changed from the verified ancestor. No registry lookup occurs. Equal SemVer precedence does not select a winner: one-sided source edits are preserved, and divergent edits require atomic selection. Package filters must match. An existing local or remote array keeps its JSON spelling when it matches the merged result. Accepted settings files keep their bytes unless a package override is required; that override changes only the `packages` value. The ordered npm identity list can change on one side, or identically on both sides; conflicting order or membership changes retain atomic selection. This preserves insertion positions and resource precedence. Non-npm entries must remain unchanged across all three versions for per-package merging. Changed Git refs and local path aliases retain atomic selection instead of being combined as separate sources. Delete/upgrade conflicts, duplicate identities, incompatible filters, version ranges and tags also retain the existing atomic selection rules when deterministic merging is not possible. Other arrays and nested objects remain atomic.
|
|
382
402
|
Code/scripts, keybindings, binaries, unknown formats, session histories, deletion/modification conflicts, path collisions, and session-root transitions are not model-resolved.
|
|
383
403
|
Existing deterministic merges still follow their own explicit settings.
|
|
384
404
|
Unsupported or refused groups remain unresolved; only an explicitly enabled `partialSync` policy permits progress around them.
|
|
385
405
|
Preserving versions does not mean a conflict is resolved.
|
|
386
406
|
|
|
387
407
|
A verified accepted ancestor is required.
|
|
388
|
-
Enabling
|
|
408
|
+
Enabling Autonomous sync cannot recover historical ancestor bytes that were never retained.
|
|
389
409
|
If the original accepted bytes are still available on both sides, a conflict-free sync can seed the text ancestors for future runs; otherwise use the existing reviewed recovery flow rather than inventing a base from current edits.
|
|
390
410
|
|
|
391
411
|
Bounds are 8 KiB per input file, 16 conflict paths/units, 24 KiB of serialized model evidence, 16 KiB of candidate output, 8,192 maximum output tokens (or the selected model's smaller positive output limit), and one provider request per candidate attempt. A separately confirmed refusal retry can request the same candidate evidence again.
|
|
@@ -398,7 +418,7 @@ These limits are not an exact currency spending cap; in-flight disclosure or cha
|
|
|
398
418
|
|
|
399
419
|
The existing possible-secret scanner always checks model evidence, even when `skipSecretScan` permits uploads without scanning.
|
|
400
420
|
It is a heuristic, not a guarantee that all secrets are detected.
|
|
401
|
-
Do not enable
|
|
421
|
+
Do not enable Autonomous sync for content you cannot disclose to the selected provider.
|
|
402
422
|
Provider errors and model reasoning are not copied into status or persistent records.
|
|
403
423
|
|
|
404
424
|
Pi sessions sharing one agent directory use one local resolver claim.
|
|
@@ -417,7 +437,7 @@ They can contain merged text; retain them as private data.
|
|
|
417
437
|
Different candidates are retained without automatic retention pruning.
|
|
418
438
|
Ordinary **Sync now** and `/sync sync` reuse a valid cached refusal without another provider request.
|
|
419
439
|
The manager's **Retry refused model result…** action separately reviews the setup, destination, conflict paths, selected model, disclosure and cost before one fresh request for that exact refusal.
|
|
420
|
-
It requires foreground TUI/RPC,
|
|
440
|
+
It requires foreground TUI/RPC, Autonomous sync, an accepted baseline and no pending journal; use ordinary Sync now or reviewed recovery first when those prerequisites are absent.
|
|
421
441
|
Changed evidence, settings, model, session or cached bytes invalidate the review without disclosure.
|
|
422
442
|
A repeated refusal, invalid response or failed pre-publication replacement retains the original result.
|
|
423
443
|
A complete validated result replaces the active candidate only after the original refusal is archived privately in the same directory.
|
|
@@ -432,23 +452,25 @@ Pi-sync's cross-process operation lock excludes other pi-sync transfers, not arb
|
|
|
432
452
|
Pi file mutation queues coordinate participating writers only within one process.
|
|
433
453
|
Checks near rename reduce stale writes but do not provide an atomic compare-and-swap against another Pi process or editor.
|
|
434
454
|
Session JSONL is not rewritten by the model; other active session writers remain subject to the existing protection limitations.
|
|
435
|
-
Do not use
|
|
455
|
+
Do not use Autonomous sync as a guarantee of lossless live editing.
|
|
436
456
|
|
|
437
|
-
Turn
|
|
457
|
+
Turn Autonomous sync Off to prevent subsequent requests and invalidate pending authorization.
|
|
438
458
|
Keep backups, snapshots, conflicts and pending journals; disabling does not undo completed work or erase history.
|
|
439
459
|
Unknown publication outcomes use the existing journal reconciliation and never authorize blind republishing.
|
|
440
460
|
Pending journals can be reconciled on a read/check/write/verify backend because recovery does not publish.
|
|
441
|
-
New autonomous transfers still require conditional or lease-protected publication; turn
|
|
461
|
+
New autonomous transfers still require conditional or lease-protected publication; turn Autonomous sync Off before a reviewed manual transfer on a weaker backend.
|
|
442
462
|
If recovery is blocked, retain evidence and use the existing reviewed recovery routes.
|
|
443
463
|
Finish/reconcile pending merge journals before a downgrade.
|
|
444
464
|
No Pi core changes or other extension are required.
|
|
445
465
|
|
|
446
|
-
|
|
466
|
+
<a id="experimental-continuous-sync"></a>
|
|
467
|
+
|
|
468
|
+
### Continuous sync
|
|
447
469
|
|
|
448
470
|
`sync.continuous` is an optional per-setup boolean, default `false`, in settings versions 3, 4 and 5.
|
|
449
|
-
Open **Settings →
|
|
471
|
+
Open **Settings → Continuous sync** after enabling Autonomous sync.
|
|
450
472
|
RPC users can set the field explicitly in the private settings file.
|
|
451
|
-
Existing
|
|
473
|
+
Existing Autonomous sync consent does not enable recurring requests.
|
|
452
474
|
|
|
453
475
|
The separate review names the setup, storage connection, destination, included paths, recurring costs and automatic writes.
|
|
454
476
|
An elected idle session uses its selected Pi model; a different session can use a different provider/model.
|
|
@@ -460,8 +482,8 @@ Reopen Settings to read the saved value.
|
|
|
460
482
|
|
|
461
483
|
Monitoring starts at an eligible TUI/RPC session startup, after awaited local transaction recovery, or after a foreground `/sync` command or manager closes.
|
|
462
484
|
It replaces the duplicate one-shot startup transfer, not the legacy shutdown policy.
|
|
463
|
-
Turning Continuous sync off stops its work without disabling
|
|
464
|
-
Turning
|
|
485
|
+
Turning Continuous sync off stops its work without disabling Autonomous sync.
|
|
486
|
+
Turning Autonomous sync off also stops monitoring; a saved Continuous sync On value remains ineffective until Autonomous sync is enabled again.
|
|
465
487
|
|
|
466
488
|
A control-file change during monitoring is detected by a watcher or the five-second local control check.
|
|
467
489
|
If monitoring is already off, external enablement requires the next session start or a foreground `/sync` command.
|
|
@@ -543,7 +565,8 @@ Keep the directory as private evidence; do not delete it to repair a transfer.
|
|
|
543
565
|
|
|
544
566
|
#### Status, recovery and downgrade
|
|
545
567
|
|
|
546
|
-
The existing status key shows waiting, checking/syncing, scheduled retry, another local owner, or a stable blocked reason.
|
|
568
|
+
The existing raw status key shows waiting, checking/syncing, scheduled retry, another local owner, or a stable blocked reason.
|
|
569
|
+
A compatible Statusline uses the [quiet presentation described above](#status-display); normal hidden standby does not mean monitoring or another owner's work has stopped.
|
|
547
570
|
**Check remote status** and the manager show advisory ownership, the last successful background check/transfer and safe next actions.
|
|
548
571
|
Status also shows normalized rolling-window usage, the next window allowance, exhausted-candidate counts, serialized accounting bytes/capacity and on-disk bytes.
|
|
549
572
|
Window allowance is not dispatch permission: candidate, capacity, ownership and safety checks still apply.
|
|
@@ -560,14 +583,14 @@ Preserve journals, backups and unresolved versions when proof is unavailable.
|
|
|
560
583
|
|
|
561
584
|
Turn Continuous sync off and drain its owner before downgrade.
|
|
562
585
|
Reconcile pending transfers with the current release.
|
|
563
|
-
Existing
|
|
586
|
+
Existing Autonomous sync and shutdown preferences remain separately effective.
|
|
564
587
|
Private scheduler records are separate from baseline/journal formats and remain retained; no automatic filesystem rollback or state migration is performed.
|
|
565
588
|
|
|
566
589
|
For implementation evidence and test scope, see [Continuous sync verification](https://github.com/narumiruna/pi-extensions/blob/main/packages/pi-sync/docs/continuous-sync-verification.md).
|
|
567
590
|
|
|
568
591
|
### Settings field merge and machine-local fields
|
|
569
592
|
|
|
570
|
-
`sync.mergeSettings` is
|
|
593
|
+
`sync.mergeSettings` is an explicit opt-in, defaults to `false`, and is available through **Settings → Merge settings fields** for the current setup.
|
|
571
594
|
With a verified accepted ancestor, global `settings.json` root fields merge conservatively: absent differs from null, arrays/nested objects are atomic, and provider/model and other coupled fields form one unit.
|
|
572
595
|
Unknown fields are preserved.
|
|
573
596
|
Pi accepts plain UTF-8 JSON with an optional BOM, not JSONC; duplicate/prototype-sensitive keys, invalid encodings, migration-dependent syntax, and over-bound inputs require directional review.
|
|
@@ -616,8 +639,8 @@ Never reinterpret portable snapshots with an old binary.
|
|
|
616
639
|
|
|
617
640
|
### Content merge and partial progress
|
|
618
641
|
|
|
619
|
-
`sync.mergeContent` and `sync.partialSync` are independent
|
|
620
|
-
The owning **
|
|
642
|
+
`sync.mergeContent` and `sync.partialSync` are independent opt-in booleans, both default `false`.
|
|
643
|
+
The owning **Settings → Content merge / partial progress** setting offers Off, Merge only, Merge + partial, and Partial only and confirms an explicit settings-version-5 upgrade.
|
|
621
644
|
Manual configuration must also declare version 5.
|
|
622
645
|
Settings version 5 retains the portable-policy behavior of version 4; moving from a legacy setup can require the existing separately confirmed directional policy migration.
|
|
623
646
|
Disabling options retains the schema and recovery evidence.
|
|
@@ -266,11 +266,11 @@ async function showConfig(ctx: ExtensionCommandContext, options: CommandOptions)
|
|
|
266
266
|
...storageLines,
|
|
267
267
|
`storage path: ${config.storagePath}`,
|
|
268
268
|
`automatic sync: ${automaticSyncSummary(config.automatic)}`,
|
|
269
|
-
`settings field merge: ${config.mergeSettings ? "On
|
|
269
|
+
`settings field merge: ${config.mergeSettings ? "On" : "Off"}`,
|
|
270
270
|
`machine-local settings.json fields: ${(config.localFields ?? []).map(safeTerminalText).join(", ") || "none"}`,
|
|
271
271
|
`startup transfer: ${startupTransferSummary(config)}`,
|
|
272
|
-
`
|
|
273
|
-
`continuous sync: ${config.continuous ? (config.agentMode ? "On
|
|
272
|
+
`autonomous sync: ${config.agentMode ? "On" : "Off"}`,
|
|
273
|
+
`continuous sync: ${config.continuous ? (config.agentMode ? "On" : "paused; Autonomous sync is Off") : "Off"}`,
|
|
274
274
|
`included content: ${config.include.join(", ") || "none"}`,
|
|
275
275
|
`sessions: ${config.include.includes("sessions") ? "included" : "not included"}`,
|
|
276
276
|
`settings file: ${localConfigPath()}`,
|
package/src/commands/command.ts
CHANGED
|
@@ -236,7 +236,7 @@ export function usage() {
|
|
|
236
236
|
`Automatic sync: ${AUTOMATIC_SYNC_DESCRIPTION}`,
|
|
237
237
|
`Startup transfer: ${STARTUP_TRANSFER_DESCRIPTION}`,
|
|
238
238
|
"Automatic transfer at startup is a separate opt-in: one conflict-free established-baseline transfer at idle in TUI/RPC, with conditional/lease publication and no resource reload.",
|
|
239
|
-
"
|
|
239
|
+
"Continuous sync is a separate default-Off opt-in in Settings. It requires Autonomous sync, monitors while Pi is open, and can incur recurring network/model costs. Safety blocks and durable automatic request limits remain.",
|
|
240
240
|
"Settings: use /sync init or edit storage connections and sync setups in ~/.pi/agent/pi-sync.json (or the configured Pi agent directory). Version 1 and version 2 settings are unsupported and are never rewritten.",
|
|
241
241
|
].join("\n");
|
|
242
242
|
}
|
|
@@ -81,7 +81,7 @@ export interface SyncPolicySettings {
|
|
|
81
81
|
automatic: boolean;
|
|
82
82
|
/** Explicit opt-in, separate from the legacy observe/shutdown policy. */
|
|
83
83
|
automaticTransfer?: boolean;
|
|
84
|
-
/**
|
|
84
|
+
/** Autonomous merged-sync authorization. */
|
|
85
85
|
agentMode?: boolean;
|
|
86
86
|
/** Separate explicit authorization for recurring background work. */
|
|
87
87
|
continuous?: boolean;
|
|
@@ -93,7 +93,7 @@ export function buildAgentEvidence(
|
|
|
93
93
|
const evidence: AgentEvidence = { units: [], files: [], unsupported: [] };
|
|
94
94
|
if (plan.kind !== "planned") return evidence;
|
|
95
95
|
if (plan.conflicts.length > AGENT_MAX_UNITS)
|
|
96
|
-
throw new Error("
|
|
96
|
+
throw new Error("Autonomous sync paused: too many conflict paths for bounded analysis.");
|
|
97
97
|
for (const conflict of plan.conflicts) {
|
|
98
98
|
try {
|
|
99
99
|
if (conflict.reason !== "both-changed") throw new Error("unsupported conflict");
|
|
@@ -159,7 +159,7 @@ export function buildAgentEvidence(
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
if (evidence.units.length > AGENT_MAX_UNITS || Buffer.byteLength(JSON.stringify(evidence.units)) > AGENT_INPUT_BYTES)
|
|
162
|
-
throw new Error("
|
|
162
|
+
throw new Error("Autonomous sync paused: conflict evidence exceeds its input bound.");
|
|
163
163
|
// Scan only the actual disclosure image; skipSecretScan never authorizes model disclosure.
|
|
164
164
|
if (
|
|
165
165
|
scanSnapshot({
|
|
@@ -169,7 +169,7 @@ export function buildAgentEvidence(
|
|
|
169
169
|
),
|
|
170
170
|
}).length
|
|
171
171
|
)
|
|
172
|
-
throw new Error("
|
|
172
|
+
throw new Error("Autonomous sync paused: possible secrets in model evidence; nothing was disclosed.");
|
|
173
173
|
return evidence;
|
|
174
174
|
}
|
|
175
175
|
|
package/src/sync/agent-plan.ts
CHANGED
|
@@ -34,24 +34,27 @@ export async function resolveAgentPlan(
|
|
|
34
34
|
return plan;
|
|
35
35
|
}
|
|
36
36
|
if (!ctx.hasUI || (ctx.mode !== "tui" && ctx.mode !== "rpc"))
|
|
37
|
-
throw new Error("
|
|
37
|
+
throw new Error("Autonomous sync paused: TUI/RPC is required.");
|
|
38
38
|
const ancestors = (await readMergeAncestors(config, state)) ?? [];
|
|
39
39
|
await validate();
|
|
40
40
|
let evidence: ReturnType<typeof buildAgentEvidence>;
|
|
41
41
|
try {
|
|
42
42
|
evidence = buildAgentEvidence(plan, local, remote, ancestors);
|
|
43
43
|
} catch {
|
|
44
|
-
throw new ContinuousError("unsafe", "
|
|
44
|
+
throw new ContinuousError("unsafe", "Autonomous sync paused: unsafe or unsupported conflict evidence.");
|
|
45
45
|
}
|
|
46
46
|
if (!config.partialSync && evidence.unsupported.length)
|
|
47
|
-
throw new ContinuousError(
|
|
47
|
+
throw new ContinuousError(
|
|
48
|
+
"unsafe",
|
|
49
|
+
"Autonomous sync paused: unsupported conflicts prevent an all-or-nothing transfer.",
|
|
50
|
+
);
|
|
48
51
|
if (!evidence.files.length) {
|
|
49
52
|
if (options.agentRetryRefusal)
|
|
50
53
|
throw new ContinuousError("unsafe", "No eligible cached refusal for the current evidence. Use Sync now.");
|
|
51
54
|
if (config.partialSync) return plan;
|
|
52
55
|
throw new ContinuousError(
|
|
53
56
|
"unsafe",
|
|
54
|
-
"
|
|
57
|
+
"Autonomous sync paused: unsupported conflict or missing verified ancestor; evidence is unchanged.",
|
|
55
58
|
);
|
|
56
59
|
}
|
|
57
60
|
if (!evidence.units.length) {
|
|
@@ -59,7 +62,7 @@ export async function resolveAgentPlan(
|
|
|
59
62
|
throw new ContinuousError("unsafe", "No eligible cached refusal for the current evidence. Use Sync now.");
|
|
60
63
|
return applyAgentCandidate(plan, evidence, { version: 1, results: [] });
|
|
61
64
|
}
|
|
62
|
-
if (!ctx.model) throw new ContinuousError("model", "
|
|
65
|
+
if (!ctx.model) throw new ContinuousError("model", "Autonomous sync paused: select an available model first.");
|
|
63
66
|
const model = digest(ctx.model);
|
|
64
67
|
const key = digest([
|
|
65
68
|
1,
|
|
@@ -75,7 +78,7 @@ export async function resolveAgentPlan(
|
|
|
75
78
|
try {
|
|
76
79
|
return applyAgentCandidate(plan, evidence, options.agentAcceptance.candidate);
|
|
77
80
|
} catch {
|
|
78
|
-
throw new ContinuousError("unsafe", "
|
|
81
|
+
throw new ContinuousError("unsafe", "Autonomous sync paused: candidate refused or unsupported conflict remains.");
|
|
79
82
|
}
|
|
80
83
|
}
|
|
81
84
|
const validateEvidence = async (signal = options.signal) => {
|
|
@@ -85,7 +88,7 @@ export async function resolveAgentPlan(
|
|
|
85
88
|
signal?.throwIfAborted();
|
|
86
89
|
};
|
|
87
90
|
await check();
|
|
88
|
-
if (digest(ctx.model) !== model) throw new Error("
|
|
91
|
+
if (digest(ctx.model) !== model) throw new Error("Autonomous sync paused: selected model changed.");
|
|
89
92
|
const current = await createSnapshot(config.snapshotIdentity, { ...snapshotOptions, signal });
|
|
90
93
|
await check();
|
|
91
94
|
const currentState = await readStateForConfig(config);
|
|
@@ -98,7 +101,7 @@ export async function resolveAgentPlan(
|
|
|
98
101
|
!currentHead ||
|
|
99
102
|
!backend.sameRevision(head.revision, currentHead.revision)
|
|
100
103
|
)
|
|
101
|
-
throw new ContinuousError("stale", "
|
|
104
|
+
throw new ContinuousError("stale", "Autonomous sync paused: conflict evidence changed; retry a fresh sync.");
|
|
102
105
|
};
|
|
103
106
|
throw new AgentResolutionRequired(key, evidence, validateEvidence);
|
|
104
107
|
}
|
package/src/sync/agent-runner.ts
CHANGED
|
@@ -39,9 +39,9 @@ export async function runAgentResolver(
|
|
|
39
39
|
beforeRequest?: (signal: AbortSignal) => Promise<void>,
|
|
40
40
|
): Promise<string> {
|
|
41
41
|
const model = ctx.model;
|
|
42
|
-
if (!model) throw new ContinuousError("model", "
|
|
42
|
+
if (!model) throw new ContinuousError("model", "Autonomous sync paused: select an available model first.");
|
|
43
43
|
if (!Number.isSafeInteger(model.maxTokens) || model.maxTokens < 0)
|
|
44
|
-
throw new Error("
|
|
44
|
+
throw new Error("Autonomous sync paused: selected model has an invalid output limit.");
|
|
45
45
|
// Pi uses zero for unknown limits, including virtual models. Its registry caps routed requests.
|
|
46
46
|
const maxTokens = model.maxTokens > 0 ? Math.min(AGENT_MAX_TOKENS, model.maxTokens) : AGENT_MAX_TOKENS;
|
|
47
47
|
const controller = new AbortController();
|
|
@@ -74,7 +74,7 @@ export async function runAgentResolver(
|
|
|
74
74
|
},
|
|
75
75
|
});
|
|
76
76
|
const childModel = runtime.getModel(provider, "resolver");
|
|
77
|
-
if (!childModel) throw new Error("
|
|
77
|
+
if (!childModel) throw new Error("Autonomous sync paused: resolver model unavailable.");
|
|
78
78
|
({ session } = await createAgentSession({
|
|
79
79
|
cwd: ctx.cwd,
|
|
80
80
|
model: childModel,
|
|
@@ -104,7 +104,7 @@ export async function runAgentResolver(
|
|
|
104
104
|
});
|
|
105
105
|
child.agent.streamFunction = async (_model, context, options) => {
|
|
106
106
|
ownedSignal.throwIfAborted();
|
|
107
|
-
if (++calls > 1) throw new Error("
|
|
107
|
+
if (++calls > 1) throw new Error("Autonomous sync paused: one provider request per candidate is permitted.");
|
|
108
108
|
await validate(ownedSignal);
|
|
109
109
|
ownedSignal.throwIfAborted();
|
|
110
110
|
if (beforeRequest) {
|
|
@@ -146,15 +146,16 @@ export async function runAgentResolver(
|
|
|
146
146
|
last.stopReason !== "stop" ||
|
|
147
147
|
last.content.some((block) => block.type !== "text" && block.type !== "thinking")
|
|
148
148
|
)
|
|
149
|
-
throw new Error("
|
|
149
|
+
throw new Error("Autonomous sync paused: incomplete, failed or tool-bearing model output.");
|
|
150
150
|
const output = last.content
|
|
151
151
|
.filter((block) => block.type === "text")
|
|
152
152
|
.map((block) => block.text)
|
|
153
153
|
.join("");
|
|
154
|
-
if (Buffer.byteLength(output) > AGENT_OUTPUT_BYTES)
|
|
154
|
+
if (Buffer.byteLength(output) > AGENT_OUTPUT_BYTES)
|
|
155
|
+
throw new Error("Autonomous sync paused: output bound exceeded.");
|
|
155
156
|
if (ctx.hasUI)
|
|
156
157
|
ctx.ui.notify(
|
|
157
|
-
`Pi Sync
|
|
158
|
+
`Pi Sync resolver candidate: ${last.usage.totalTokens} tokens. Semantic correctness is not guaranteed.`,
|
|
158
159
|
"info",
|
|
159
160
|
);
|
|
160
161
|
return output;
|
|
@@ -169,13 +170,13 @@ export async function runAgentResolver(
|
|
|
169
170
|
if (failure.retry)
|
|
170
171
|
throw new ContinuousError(
|
|
171
172
|
failure.kind,
|
|
172
|
-
"
|
|
173
|
+
"Autonomous sync paused: transient resolver transport failure.",
|
|
173
174
|
failure.delay,
|
|
174
175
|
);
|
|
175
176
|
// Provider errors can quote submitted content or auth; never surface their payload.
|
|
176
177
|
throw new ContinuousError(
|
|
177
178
|
"model",
|
|
178
|
-
"
|
|
179
|
+
"Autonomous sync paused: resolver failed or returned unusable output. Check model access and retry later.",
|
|
179
180
|
);
|
|
180
181
|
} finally {
|
|
181
182
|
clearTimeout(timer);
|
package/src/sync/agent-store.ts
CHANGED
|
@@ -92,7 +92,7 @@ export async function withAgentClaim<T>(fn: (assertOwner: () => Promise<void>) =
|
|
|
92
92
|
await withClaimGuard(async () => {
|
|
93
93
|
const existing = await readClaim();
|
|
94
94
|
if (existing && alive(existing.pid))
|
|
95
|
-
throw new ContinuousError("contention", "
|
|
95
|
+
throw new ContinuousError("contention", "Autonomous sync deferred: another local resolver is running.");
|
|
96
96
|
// A durability failure can occur after rename: cleanup must own partial initialization too.
|
|
97
97
|
armed = true;
|
|
98
98
|
await writeJson(paths().claim, claim);
|
package/src/sync/agent-sync.ts
CHANGED
|
@@ -57,7 +57,7 @@ export async function withAgentSyncLock<T>(
|
|
|
57
57
|
signal?.throwIfAborted();
|
|
58
58
|
});
|
|
59
59
|
await validate();
|
|
60
|
-
setSyncStatus(ctx, "sync resolving");
|
|
60
|
+
setSyncStatus(ctx, "sync resolving", { activity: "resolving" });
|
|
61
61
|
const cached = await withLock("agent-cache-read", async () => {
|
|
62
62
|
await assertOwner();
|
|
63
63
|
options.signal?.throwIfAborted();
|
|
@@ -89,7 +89,7 @@ export async function withAgentSyncLock<T>(
|
|
|
89
89
|
try {
|
|
90
90
|
candidate = parseAgentCandidate(raw, request.evidence);
|
|
91
91
|
} catch {
|
|
92
|
-
throw new ContinuousError("model", "
|
|
92
|
+
throw new ContinuousError("model", "Autonomous sync paused: invalid resolver output; evidence retained.");
|
|
93
93
|
}
|
|
94
94
|
if (retry && candidate.results.some((row) => "refuse" in row))
|
|
95
95
|
throw new ContinuousError("model", "The model refused again. The previous refusal was retained.");
|
|
@@ -132,7 +132,7 @@ export async function withAgentSyncLock<T>(
|
|
|
132
132
|
request = error;
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
|
-
throw new ContinuousError("stale", "
|
|
135
|
+
throw new ContinuousError("stale", "Autonomous sync paused: content changed repeatedly; retry later.");
|
|
136
136
|
} finally {
|
|
137
137
|
options.signal?.removeEventListener("abort", onAbort);
|
|
138
138
|
if (!options.signal?.aborted) clearStatus();
|
|
@@ -210,7 +210,7 @@ function hasReservationCapacity(record: ContinuousRecord, now: number): boolean
|
|
|
210
210
|
/** Read-only status: no directory creation, lease, mutation, request reservation or provider call. */
|
|
211
211
|
export async function continuousStatusLines(config: AnySyncConfig, clock: () => number = Date.now): Promise<string[]> {
|
|
212
212
|
if (!config.continuous) return ["Continuous sync: Off"];
|
|
213
|
-
if (!config.agentMode) return ["Continuous sync: paused; enable
|
|
213
|
+
if (!config.agentMode) return ["Continuous sync: paused; enable Autonomous sync first"];
|
|
214
214
|
try {
|
|
215
215
|
const binding = await continuousBinding(config);
|
|
216
216
|
const document = await readContinuousPrivateDocument(`${binding}.json`);
|
|
@@ -5,7 +5,7 @@ import { snapshotOptionsForContext } from "../snapshot/session-paths.js";
|
|
|
5
5
|
import { withLock } from "../state/lock.js";
|
|
6
6
|
import { withStateDirectoryAccess } from "../state/state-directory.js";
|
|
7
7
|
import { readStateForConfig } from "../state/sync-state-store.js";
|
|
8
|
-
import { setSyncStatus } from "../ui/sync-status.js";
|
|
8
|
+
import { setSyncStatus, syncStatusFailureReason } from "../ui/sync-status.js";
|
|
9
9
|
import { withAgentSyncLock } from "./agent-sync.js";
|
|
10
10
|
import { ContinuousError, continuousFailure } from "./continuous-errors.js";
|
|
11
11
|
import { acquireContinuousLease, type ContinuousLease } from "./continuous-lease.js";
|
|
@@ -96,7 +96,10 @@ export function createContinuousSync(
|
|
|
96
96
|
if (signal.aborted) return;
|
|
97
97
|
try {
|
|
98
98
|
validateContext();
|
|
99
|
-
setSyncStatus(ctx, "sync blocked: private state or settings need repair"
|
|
99
|
+
setSyncStatus(ctx, "sync blocked: private state or settings need repair", {
|
|
100
|
+
activity: "blocked",
|
|
101
|
+
reason: "repair",
|
|
102
|
+
});
|
|
100
103
|
if (!repairNotices.has(ctx.sessionManager)) {
|
|
101
104
|
repairNotices.add(ctx.sessionManager);
|
|
102
105
|
ctx.ui.notify(
|
|
@@ -153,7 +156,7 @@ export function createContinuousSync(
|
|
|
153
156
|
if (lease) {
|
|
154
157
|
await monitor(ctx, config, binding, lease, signal, explicitRetry, startup);
|
|
155
158
|
explicitRetry = false;
|
|
156
|
-
} else setSyncStatus(ctx, "sync waiting: another local owner");
|
|
159
|
+
} else setSyncStatus(ctx, "sync waiting: another local owner", { activity: "idle" });
|
|
157
160
|
} finally {
|
|
158
161
|
await lease?.release();
|
|
159
162
|
}
|
|
@@ -162,7 +165,10 @@ export function createContinuousSync(
|
|
|
162
165
|
if (signal.aborted) return;
|
|
163
166
|
const failure = continuousFailure(error);
|
|
164
167
|
if (!failure.retry && !(error instanceof Error && error.name === "AbortError")) throw error;
|
|
165
|
-
setSyncStatus(ctx, `sync retry scheduled: ${failure.kind}
|
|
168
|
+
setSyncStatus(ctx, `sync retry scheduled: ${failure.kind}`, {
|
|
169
|
+
activity: "retry",
|
|
170
|
+
reason: syncStatusFailureReason(failure.kind),
|
|
171
|
+
});
|
|
166
172
|
}
|
|
167
173
|
validateOwner();
|
|
168
174
|
if (!continuousEnabled(await loadConfigForCheck())) return;
|
|
@@ -355,6 +361,9 @@ export function createContinuousSync(
|
|
|
355
361
|
: state.phase === "retry"
|
|
356
362
|
? `sync retry scheduled: ${state.error}`
|
|
357
363
|
: "sync waiting for changes",
|
|
364
|
+
state.phase === "blocked" || state.phase === "retry"
|
|
365
|
+
? { activity: state.phase, reason: syncStatusFailureReason(state.error) }
|
|
366
|
+
: { activity: "idle" },
|
|
358
367
|
);
|
|
359
368
|
if (state.phase === "retry") pollDue = Math.min(pollDue, state.next);
|
|
360
369
|
const next = Math.max(
|
|
@@ -400,7 +409,7 @@ export function createContinuousSync(
|
|
|
400
409
|
["authentication", "storage", "unknown"].includes(current.error ?? "")
|
|
401
410
|
)
|
|
402
411
|
return;
|
|
403
|
-
setSyncStatus(ctx, "sync checking");
|
|
412
|
+
setSyncStatus(ctx, "sync checking", { activity: "checking" });
|
|
404
413
|
const inspectionModule = await timings.measure("inspection import", () => loaders.inspection());
|
|
405
414
|
validate();
|
|
406
415
|
const observed = await timings.measure("inspection and guards", () =>
|
|
@@ -470,7 +479,7 @@ export function createContinuousSync(
|
|
|
470
479
|
if (observed.journal || observed.inspection.localChanged || observed.inspection.remoteChanged) {
|
|
471
480
|
const operations = await timings.measure("operations import", () => loaders.operations());
|
|
472
481
|
validate();
|
|
473
|
-
setSyncStatus(ctx, "sync syncing");
|
|
482
|
+
setSyncStatus(ctx, "sync syncing", { activity: "working" });
|
|
474
483
|
const result = await timings.measure("automatic transfer", () =>
|
|
475
484
|
withStateDirectoryAccess(() =>
|
|
476
485
|
withAgentSyncLock(
|
package/src/sync/merged-sync.ts
CHANGED
|
@@ -102,7 +102,7 @@ export async function mergeSync(
|
|
|
102
102
|
options.agentRetryRefusal &&
|
|
103
103
|
(!config.agentMode || options.auto || options.background || !ctx.hasUI || !["tui", "rpc"].includes(ctx.mode))
|
|
104
104
|
)
|
|
105
|
-
throw new Error("Refused-candidate retry requires foreground
|
|
105
|
+
throw new Error("Refused-candidate retry requires foreground Autonomous sync in TUI or RPC.");
|
|
106
106
|
};
|
|
107
107
|
const config = await loadConfig(options.setup);
|
|
108
108
|
validateOwner(config);
|
|
@@ -131,7 +131,7 @@ export async function mergeSync(
|
|
|
131
131
|
throw new Error("Session storage root changed during transfer; evidence retained for review.");
|
|
132
132
|
}
|
|
133
133
|
if (config.agentMode && JSON.stringify(ctx.model) !== modelToken)
|
|
134
|
-
throw new Error("
|
|
134
|
+
throw new Error("Autonomous sync paused: selected model changed during transfer.");
|
|
135
135
|
if (options.auto && (!startupTransferEnabled(latest) || !ctx.isIdle()))
|
|
136
136
|
throw new Error("Automatic transfer is no longer authorized at this idle boundary.");
|
|
137
137
|
});
|
|
@@ -171,7 +171,7 @@ export async function mergeSync(
|
|
|
171
171
|
// Recovery above never publishes. This gate applies only to new autonomous transfers.
|
|
172
172
|
if ((options.auto || config.agentMode) && backend.capability === "read-check-write-verify") {
|
|
173
173
|
throw new Error(
|
|
174
|
-
"Automatic transfer requires conditional publication or a lease; this backend supports only read/check/write/verify. Disable
|
|
174
|
+
"Automatic transfer requires conditional publication or a lease; this backend supports only read/check/write/verify. Disable Autonomous sync and use manual sync.",
|
|
175
175
|
);
|
|
176
176
|
}
|
|
177
177
|
const textBudget = { remaining: TEXT_MERGE_WORK_LIMIT };
|
|
@@ -353,7 +353,7 @@ export async function mergeSync(
|
|
|
353
353
|
if (plan.kind !== "planned" || (plan.conflicts.length && !config.partialSync))
|
|
354
354
|
throw new ContinuousError(
|
|
355
355
|
"unsafe",
|
|
356
|
-
"
|
|
356
|
+
"Autonomous sync paused: unsupported or refused conflict groups remain; no transfer was performed.",
|
|
357
357
|
);
|
|
358
358
|
}
|
|
359
359
|
if (
|