@phnx-labs/agents-cli 1.20.92 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/CHANGELOG.md +385 -0
  2. package/README.md +3 -0
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/beta.js +3 -5
  5. package/dist/commands/doctor.js +23 -0
  6. package/dist/commands/events.js +91 -1
  7. package/dist/commands/exec.js +13 -0
  8. package/dist/commands/mailboxes.js +39 -1
  9. package/dist/commands/message.js +12 -1
  10. package/dist/commands/packages.js +14 -5
  11. package/dist/commands/projects.d.ts +38 -0
  12. package/dist/commands/projects.js +355 -48
  13. package/dist/commands/routines.js +27 -1
  14. package/dist/commands/rules.js +6 -0
  15. package/dist/commands/secrets.d.ts +17 -0
  16. package/dist/commands/secrets.js +198 -7
  17. package/dist/commands/send.d.ts +14 -12
  18. package/dist/commands/send.js +105 -35
  19. package/dist/commands/sessions.d.ts +17 -0
  20. package/dist/commands/sessions.js +16 -8
  21. package/dist/commands/setup-browser.js +5 -1
  22. package/dist/commands/setup-fleet.js +5 -0
  23. package/dist/commands/setup-preferences.d.ts +53 -0
  24. package/dist/commands/setup-preferences.js +142 -0
  25. package/dist/commands/setup.js +8 -9
  26. package/dist/commands/ssh.js +220 -10
  27. package/dist/commands/sync.d.ts +2 -2
  28. package/dist/commands/sync.js +13 -12
  29. package/dist/commands/view.js +4 -0
  30. package/dist/commands/watchdog.js +26 -3
  31. package/dist/index.js +38 -6
  32. package/dist/lib/activity.d.ts +15 -0
  33. package/dist/lib/activity.js +26 -2
  34. package/dist/lib/auto-dispatch.d.ts +6 -1
  35. package/dist/lib/auto-dispatch.js +7 -2
  36. package/dist/lib/beta.d.ts +1 -1
  37. package/dist/lib/beta.js +1 -1
  38. package/dist/lib/channels/send.d.ts +83 -0
  39. package/dist/lib/channels/send.js +112 -0
  40. package/dist/lib/daemon.d.ts +13 -0
  41. package/dist/lib/daemon.js +80 -60
  42. package/dist/lib/device-config.d.ts +82 -0
  43. package/dist/lib/device-config.js +296 -0
  44. package/dist/lib/event-provenance.d.ts +19 -0
  45. package/dist/lib/event-provenance.js +48 -0
  46. package/dist/lib/events-ingest.d.ts +46 -0
  47. package/dist/lib/events-ingest.js +182 -0
  48. package/dist/lib/events.d.ts +17 -5
  49. package/dist/lib/events.js +59 -56
  50. package/dist/lib/feed.d.ts +1 -1
  51. package/dist/lib/feed.js +2 -0
  52. package/dist/lib/git.d.ts +13 -2
  53. package/dist/lib/git.js +38 -6
  54. package/dist/lib/linear-project-counts.d.ts +129 -0
  55. package/dist/lib/linear-project-counts.js +206 -0
  56. package/dist/lib/linear-projects.d.ts +62 -0
  57. package/dist/lib/linear-projects.js +144 -0
  58. package/dist/lib/mailbox-gc.js +30 -7
  59. package/dist/lib/mailbox.d.ts +14 -1
  60. package/dist/lib/mailbox.js +35 -3
  61. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  62. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  63. package/dist/lib/menubar/install-menubar.d.ts +5 -21
  64. package/dist/lib/menubar/install-menubar.js +35 -44
  65. package/dist/lib/menubar/notify-desktop.d.ts +17 -2
  66. package/dist/lib/menubar/notify-desktop.js +8 -2
  67. package/dist/lib/migrate.js +1 -1
  68. package/dist/lib/models.js +21 -11
  69. package/dist/lib/overdue.d.ts +1 -1
  70. package/dist/lib/overdue.js +54 -19
  71. package/dist/lib/project-import.d.ts +96 -0
  72. package/dist/lib/project-import.js +171 -0
  73. package/dist/lib/project-probe.d.ts +75 -0
  74. package/dist/lib/project-probe.js +160 -0
  75. package/dist/lib/project-resources.d.ts +8 -0
  76. package/dist/lib/project-resources.js +31 -3
  77. package/dist/lib/project-status.d.ts +32 -1
  78. package/dist/lib/project-status.js +82 -1
  79. package/dist/lib/projects.d.ts +6 -0
  80. package/dist/lib/projects.js +12 -0
  81. package/dist/lib/remote-agents-json.d.ts +14 -1
  82. package/dist/lib/remote-agents-json.js +21 -3
  83. package/dist/lib/routine-notify.d.ts +11 -0
  84. package/dist/lib/routine-notify.js +22 -0
  85. package/dist/lib/routines.d.ts +53 -0
  86. package/dist/lib/routines.js +105 -4
  87. package/dist/lib/rules/run-sync.d.ts +18 -0
  88. package/dist/lib/rules/run-sync.js +92 -0
  89. package/dist/lib/run-notify.js +3 -0
  90. package/dist/lib/scheduler.js +12 -2
  91. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  92. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  93. package/dist/lib/secrets/audit.d.ts +1 -1
  94. package/dist/lib/secrets/audit.js +53 -10
  95. package/dist/lib/secrets/index.d.ts +4 -5
  96. package/dist/lib/secrets/index.js +4 -5
  97. package/dist/lib/secrets/list-filter.d.ts +20 -5
  98. package/dist/lib/secrets/list-filter.js +22 -6
  99. package/dist/lib/secrets/usage-db.d.ts +106 -0
  100. package/dist/lib/secrets/usage-db.js +236 -0
  101. package/dist/lib/session/active.d.ts +16 -2
  102. package/dist/lib/session/active.js +8 -4
  103. package/dist/lib/session/bundle.js +5 -1
  104. package/dist/lib/session/remote-active.d.ts +5 -1
  105. package/dist/lib/session/remote-active.js +4 -1
  106. package/dist/lib/session/remote-list.d.ts +1 -15
  107. package/dist/lib/session/remote-list.js +22 -111
  108. package/dist/lib/session/sync/config.d.ts +13 -5
  109. package/dist/lib/session/sync/config.js +21 -13
  110. package/dist/lib/sqlite.js +28 -1
  111. package/dist/lib/startup/command-registry.d.ts +0 -2
  112. package/dist/lib/startup/command-registry.js +1 -5
  113. package/dist/lib/state.d.ts +19 -2
  114. package/dist/lib/state.js +99 -10
  115. package/dist/lib/sync-umbrella.d.ts +11 -23
  116. package/dist/lib/sync-umbrella.js +14 -45
  117. package/dist/lib/teams/agents.d.ts +35 -3
  118. package/dist/lib/teams/agents.js +52 -8
  119. package/dist/lib/teams/scheduler.d.ts +29 -2
  120. package/dist/lib/teams/scheduler.js +60 -16
  121. package/dist/lib/types.d.ts +22 -5
  122. package/dist/lib/versions.d.ts +6 -0
  123. package/dist/lib/versions.js +6 -4
  124. package/package.json +1 -2
  125. package/dist/commands/drive.d.ts +0 -10
  126. package/dist/commands/drive.js +0 -183
  127. package/dist/commands/hq.d.ts +0 -2
  128. package/dist/commands/hq.js +0 -58
  129. package/dist/commands/sessions-sync.d.ts +0 -17
  130. package/dist/commands/sessions-sync.js +0 -135
  131. package/dist/commands/sync-provision.d.ts +0 -23
  132. package/dist/commands/sync-provision.js +0 -107
  133. package/dist/lib/drive-sync.d.ts +0 -45
  134. package/dist/lib/drive-sync.js +0 -238
  135. package/dist/lib/hq/floor.d.ts +0 -87
  136. package/dist/lib/hq/floor.js +0 -243
  137. package/dist/lib/session/sync/crdt.d.ts +0 -44
  138. package/dist/lib/session/sync/crdt.js +0 -119
  139. package/dist/lib/session/sync/manifest.d.ts +0 -62
  140. package/dist/lib/session/sync/manifest.js +0 -100
  141. package/dist/lib/session/sync/provision.d.ts +0 -49
  142. package/dist/lib/session/sync/provision.js +0 -91
  143. package/dist/lib/session/sync/r2.d.ts +0 -32
  144. package/dist/lib/session/sync/r2.js +0 -121
  145. package/dist/lib/session/sync/sync.d.ts +0 -106
  146. package/dist/lib/session/sync/sync.js +0 -374
@@ -1,49 +0,0 @@
1
- /**
2
- * Provisioning for cross-machine session sync: write the `r2.backups` secrets
3
- * bundle and probe R2 connectivity. Pure logic — no prompts, no Commander — so
4
- * it is unit-testable against the in-memory keychain seam. The interactive flow
5
- * that collects the values lives in the command layer
6
- * (`commands/sync-provision.ts`).
7
- */
8
- /** Values a caller has already collected for the sync bundle. */
9
- export interface ProvisionInput {
10
- accountId: string;
11
- bucketName: string;
12
- accessKeyId: string;
13
- secretAccessKey: string;
14
- /** Optional S3 endpoint override (MinIO / non-R2). Default derived from accountId. */
15
- endpoint?: string;
16
- /**
17
- * Shared transcript-encryption key for a machine JOINING an existing fabric —
18
- * paste the key the first machine generated. Omit on the first machine to mint
19
- * a fresh one. Ignored if the bundle already carries a key (never overwritten).
20
- */
21
- encKey?: string;
22
- }
23
- export type EncKeyAction = 'generated' | 'reused' | 'provided';
24
- /**
25
- * Create or update the `r2.backups` bundle from already-collected values.
26
- *
27
- * The four R2 credentials are always (over)written. The encryption key is
28
- * handled carefully: an existing key is REUSED, never overwritten (overwriting
29
- * would orphan every transcript peers already encrypted under it); otherwise a
30
- * caller-supplied `encKey` is stored, or a fresh one minted on the first machine.
31
- */
32
- export declare function writeSyncBundle(input: ProvisionInput): {
33
- encKeyAction: EncKeyAction;
34
- };
35
- /** The shared encryption key currently stored (for display when handing it to peers). */
36
- export declare function readStoredEncKey(): string | null;
37
- /**
38
- * Prove the configured bundle can read AND write its bucket: put a throwaway
39
- * object, read it back, delete it. Returns a structured result instead of
40
- * throwing so the caller (setup) never crashes on a bad credential. The probe
41
- * key lives OUTSIDE the `sessions/` prefix so it can never be mistaken for a
42
- * machine manifest by the pull path.
43
- */
44
- export declare function probeR2Connectivity(): Promise<{
45
- ok: true;
46
- } | {
47
- ok: false;
48
- error: string;
49
- }>;
@@ -1,91 +0,0 @@
1
- /**
2
- * Provisioning for cross-machine session sync: write the `r2.backups` secrets
3
- * bundle and probe R2 connectivity. Pure logic — no prompts, no Commander — so
4
- * it is unit-testable against the in-memory keychain seam. The interactive flow
5
- * that collects the values lives in the command layer
6
- * (`commands/sync-provision.ts`).
7
- */
8
- import { bundleExists, readBundle, writeBundle, bundleItemStore, keychainRef, } from '../../secrets/bundles.js';
9
- import { secretsKeychainItem } from '../../secrets/index.js';
10
- import { SYNC_BUNDLE, loadR2Config, clearR2ConfigCache } from './config.js';
11
- import { R2Client } from './r2.js';
12
- import { generateSyncEncKey, resolveSyncEncKey } from './transcript-crypto.js';
13
- import { machineId } from '../../machine-id.js';
14
- /**
15
- * Create or update the `r2.backups` bundle from already-collected values.
16
- *
17
- * The four R2 credentials are always (over)written. The encryption key is
18
- * handled carefully: an existing key is REUSED, never overwritten (overwriting
19
- * would orphan every transcript peers already encrypted under it); otherwise a
20
- * caller-supplied `encKey` is stored, or a fresh one minted on the first machine.
21
- */
22
- export function writeSyncBundle(input) {
23
- // Validate a joining machine's pasted key up front so a bad paste fails here,
24
- // not silently at the next sync.
25
- if (input.encKey)
26
- resolveSyncEncKey({ syncEncKey: input.encKey });
27
- const bundle = bundleExists(SYNC_BUNDLE)
28
- ? readBundle(SYNC_BUNDLE)
29
- : { name: SYNC_BUNDLE, description: 'Cross-machine session-sync R2 credentials', vars: {} };
30
- const store = bundleItemStore(bundle.backend);
31
- const setKey = (key, value) => {
32
- store.set(secretsKeychainItem(SYNC_BUNDLE, key), value);
33
- bundle.vars[key] = keychainRef(key);
34
- };
35
- setKey('R2_ACCOUNT_ID', input.accountId);
36
- setKey('R2_BUCKET_NAME', input.bucketName);
37
- setKey('R2_ACCESS_KEY_ID', input.accessKeyId);
38
- setKey('R2_SECRET_ACCESS_KEY', input.secretAccessKey);
39
- if (input.endpoint)
40
- setKey('R2_ENDPOINT', input.endpoint);
41
- let encKeyAction;
42
- if (bundle.vars['R2_SYNC_ENC_KEY']) {
43
- encKeyAction = 'reused';
44
- }
45
- else if (input.encKey) {
46
- setKey('R2_SYNC_ENC_KEY', input.encKey);
47
- encKeyAction = 'provided';
48
- }
49
- else {
50
- setKey('R2_SYNC_ENC_KEY', generateSyncEncKey());
51
- encKeyAction = 'generated';
52
- }
53
- writeBundle(bundle);
54
- clearR2ConfigCache(); // so the next loadR2Config() sees the just-written values
55
- return { encKeyAction };
56
- }
57
- /** The shared encryption key currently stored (for display when handing it to peers). */
58
- export function readStoredEncKey() {
59
- clearR2ConfigCache();
60
- try {
61
- return loadR2Config().syncEncKey ?? null;
62
- }
63
- catch {
64
- return null;
65
- }
66
- }
67
- /**
68
- * Prove the configured bundle can read AND write its bucket: put a throwaway
69
- * object, read it back, delete it. Returns a structured result instead of
70
- * throwing so the caller (setup) never crashes on a bad credential. The probe
71
- * key lives OUTSIDE the `sessions/` prefix so it can never be mistaken for a
72
- * machine manifest by the pull path.
73
- */
74
- export async function probeR2Connectivity() {
75
- try {
76
- clearR2ConfigCache();
77
- const cfg = loadR2Config();
78
- const r2 = new R2Client(cfg);
79
- const key = `.agents-provision-probe/${machineId()}.txt`;
80
- const token = `agents-sync-probe-${machineId()}`;
81
- await r2.put(key, token, 'text/plain');
82
- const got = await r2.get(key);
83
- await r2.delete(key);
84
- if (got !== token)
85
- return { ok: false, error: 'wrote a probe object but read back different bytes' };
86
- return { ok: true };
87
- }
88
- catch (err) {
89
- return { ok: false, error: err.message };
90
- }
91
- }
@@ -1,32 +0,0 @@
1
- /**
2
- * Minimal S3-compatible client for Cloudflare R2, built on aws4fetch (SigV4
3
- * over the platform `fetch` + WebCrypto — works identically under Bun and
4
- * Node >= 22). Only the verbs sync needs: put / get / head / list / delete.
5
- *
6
- * No mounting, no FUSE: this is a plain object-store client driven by a polling
7
- * loop, which is the only approach that is seamless on both macOS and Linux
8
- * (Mountpoint for S3 is Linux-only; rclone-mount needs macFUSE).
9
- */
10
- import type { R2Config } from './config.js';
11
- export interface HeadResult {
12
- size: number;
13
- etag: string;
14
- }
15
- export declare class R2Client {
16
- private aws;
17
- private base;
18
- constructor(cfg: R2Config);
19
- private url;
20
- /** Upload an object. Overwrites unconditionally. */
21
- put(key: string, body: string | Uint8Array, contentType?: string): Promise<void>;
22
- /** Fetch an object as text, or null if it does not exist (404). */
23
- get(key: string): Promise<string | null>;
24
- /** HEAD an object for size + etag, or null if it does not exist. */
25
- head(key: string): Promise<HeadResult | null>;
26
- /** Delete an object (no error if it is already absent). */
27
- delete(key: string): Promise<void>;
28
- /** List immediate sub-prefixes under a prefix (delimiter '/'), e.g. machine dirs. */
29
- listPrefixes(prefix: string): Promise<string[]>;
30
- /** List all object keys under a prefix (handles pagination). */
31
- list(prefix: string): Promise<string[]>;
32
- }
@@ -1,121 +0,0 @@
1
- /**
2
- * Minimal S3-compatible client for Cloudflare R2, built on aws4fetch (SigV4
3
- * over the platform `fetch` + WebCrypto — works identically under Bun and
4
- * Node >= 22). Only the verbs sync needs: put / get / head / list / delete.
5
- *
6
- * No mounting, no FUSE: this is a plain object-store client driven by a polling
7
- * loop, which is the only approach that is seamless on both macOS and Linux
8
- * (Mountpoint for S3 is Linux-only; rclone-mount needs macFUSE).
9
- */
10
- import { AwsClient } from 'aws4fetch';
11
- export class R2Client {
12
- aws;
13
- base;
14
- constructor(cfg) {
15
- this.aws = new AwsClient({
16
- accessKeyId: cfg.accessKeyId,
17
- secretAccessKey: cfg.secretAccessKey,
18
- service: 's3',
19
- region: 'auto',
20
- });
21
- this.base = `${cfg.endpoint}/${encodeURIComponent(cfg.bucket)}`;
22
- }
23
- url(key) {
24
- const encoded = key.split('/').map(encodeURIComponent).join('/');
25
- return `${this.base}/${encoded}`;
26
- }
27
- /** Upload an object. Overwrites unconditionally. */
28
- async put(key, body, contentType = 'application/octet-stream') {
29
- const res = await this.aws.fetch(this.url(key), {
30
- method: 'PUT',
31
- body,
32
- headers: { 'content-type': contentType },
33
- });
34
- if (!res.ok)
35
- throw new Error(`R2 PUT ${key} failed: ${res.status} ${await safeText(res)}`);
36
- }
37
- /** Fetch an object as text, or null if it does not exist (404). */
38
- async get(key) {
39
- const res = await this.aws.fetch(this.url(key), { method: 'GET' });
40
- if (res.status === 404)
41
- return null;
42
- if (!res.ok)
43
- throw new Error(`R2 GET ${key} failed: ${res.status} ${await safeText(res)}`);
44
- return await res.text();
45
- }
46
- /** HEAD an object for size + etag, or null if it does not exist. */
47
- async head(key) {
48
- const res = await this.aws.fetch(this.url(key), { method: 'HEAD' });
49
- if (res.status === 404)
50
- return null;
51
- if (!res.ok)
52
- throw new Error(`R2 HEAD ${key} failed: ${res.status}`);
53
- return {
54
- size: Number(res.headers.get('content-length') ?? '0'),
55
- etag: (res.headers.get('etag') ?? '').replace(/"/g, ''),
56
- };
57
- }
58
- /** Delete an object (no error if it is already absent). */
59
- async delete(key) {
60
- const res = await this.aws.fetch(this.url(key), { method: 'DELETE' });
61
- if (!res.ok && res.status !== 404) {
62
- throw new Error(`R2 DELETE ${key} failed: ${res.status}`);
63
- }
64
- }
65
- /** List immediate sub-prefixes under a prefix (delimiter '/'), e.g. machine dirs. */
66
- async listPrefixes(prefix) {
67
- const prefixes = [];
68
- let token;
69
- do {
70
- const params = new URLSearchParams({ 'list-type': '2', prefix, delimiter: '/' });
71
- if (token)
72
- params.set('continuation-token', token);
73
- const res = await this.aws.fetch(`${this.base}?${params.toString()}`, { method: 'GET' });
74
- if (!res.ok)
75
- throw new Error(`R2 LIST(prefixes) ${prefix} failed: ${res.status} ${await safeText(res)}`);
76
- const xml = await res.text();
77
- for (const m of xml.matchAll(/<CommonPrefixes><Prefix>([^<]+)<\/Prefix><\/CommonPrefixes>/g)) {
78
- prefixes.push(decodeXml(m[1]));
79
- }
80
- const truncated = /<IsTruncated>true<\/IsTruncated>/.test(xml);
81
- token = truncated ? xml.match(/<NextContinuationToken>([^<]+)<\/NextContinuationToken>/)?.[1] : undefined;
82
- } while (token);
83
- return prefixes;
84
- }
85
- /** List all object keys under a prefix (handles pagination). */
86
- async list(prefix) {
87
- const keys = [];
88
- let token;
89
- do {
90
- const params = new URLSearchParams({ 'list-type': '2', prefix });
91
- if (token)
92
- params.set('continuation-token', token);
93
- const res = await this.aws.fetch(`${this.base}?${params.toString()}`, { method: 'GET' });
94
- if (!res.ok)
95
- throw new Error(`R2 LIST ${prefix} failed: ${res.status} ${await safeText(res)}`);
96
- const xml = await res.text();
97
- for (const m of xml.matchAll(/<Key>([^<]+)<\/Key>/g)) {
98
- keys.push(decodeXml(m[1]));
99
- }
100
- const truncated = /<IsTruncated>true<\/IsTruncated>/.test(xml);
101
- token = truncated ? xml.match(/<NextContinuationToken>([^<]+)<\/NextContinuationToken>/)?.[1] : undefined;
102
- } while (token);
103
- return keys;
104
- }
105
- }
106
- function decodeXml(s) {
107
- return s
108
- .replace(/&amp;/g, '&')
109
- .replace(/&lt;/g, '<')
110
- .replace(/&gt;/g, '>')
111
- .replace(/&quot;/g, '"')
112
- .replace(/&apos;/g, "'");
113
- }
114
- async function safeText(res) {
115
- try {
116
- return (await res.text()).slice(0, 200);
117
- }
118
- catch {
119
- return '';
120
- }
121
- }
@@ -1,106 +0,0 @@
1
- /**
2
- * Cross-machine session sync orchestration.
3
- *
4
- * PUSH this machine's transcripts (changed since last tick) to its own R2
5
- * prefix, then publish a manifest. Single-writer prefixes mean no two
6
- * machines ever write the same object — zero remote contention.
7
- *
8
- * PULL every other machine's manifest, fetch changed transcripts, CRDT-union
9
- * copies of the same session, and write the result into the local mirror
10
- * (a scan root). The existing scanner indexes it; sessions present in the
11
- * live home always win, so the mirror only fills in remote-origin sessions.
12
- *
13
- * Invariants:
14
- * - Live home transcripts are only ever READ + uploaded, never rewritten.
15
- * - Union is idempotent, so once sets match nothing is transferred (quiescence).
16
- */
17
- import { type SyncAgentSpec } from './agents.js';
18
- import { type ManifestEntry, type PullState } from './manifest.js';
19
- export interface SyncResult {
20
- machine: string;
21
- pushed: number;
22
- pushSkipped: number;
23
- pulled: number;
24
- merged: number;
25
- pullSkipped: number;
26
- errors: string[];
27
- /** Non-fatal advisories (e.g. transcripts uploaded unencrypted). Unlike
28
- * `errors` these do not set a failing exit code. */
29
- warnings: string[];
30
- }
31
- export interface SyncOptions {
32
- verbose?: boolean;
33
- log?: (msg: string) => void;
34
- /** Upload this machine's transcripts (default true). */
35
- push?: boolean;
36
- /** Download + merge other machines' transcripts (default true). A machine
37
- * with read-only R2 credentials can still pull. */
38
- pull?: boolean;
39
- }
40
- export interface RemoteCopy {
41
- machine: string;
42
- entry: ManifestEntry;
43
- }
44
- export interface PendingSession {
45
- agentId: string;
46
- sessionId: string;
47
- copies: RemoteCopy[];
48
- /** Signature of source hashes — stored in pull state once applied. */
49
- sig: string;
50
- }
51
- /**
52
- * Decide which remote sessions need fetching this tick. Pure: no I/O.
53
- * Skips sessions we hold locally (home wins) and sessions whose source set is
54
- * unchanged since we last materialized them (pull-state hit → quiescence).
55
- */
56
- export declare function selectSessionsToFetch(copies: Map<string, Map<string, RemoteCopy[]>>, localIdsByAgent: Map<string, Set<string>>, pullState: PullState): PendingSession[];
57
- /**
58
- * Resolve the mirror destination + reconciled content for ONE file across its
59
- * copies (every copy here is the same file — same relKey — held by a different
60
- * machine). Pure.
61
- *
62
- * The canonical path comes from the lexicographically-smallest machine so every
63
- * puller derives an identical location. The content depends on the file's kind
64
- * (see `isMergeableFile`):
65
- * - append-only logs (a transcript `.jsonl`) take the CRDT G-Set union — every
66
- * machine converges to byte-identical output regardless of order.
67
- * - mutable blobs (Kimi `state.json`) can't be line-unioned without corruption,
68
- * so they resolve **last-writer-wins**: the copy with the latest event
69
- * timestamp, tie-broken by content hash so the pick is deterministic fleet-wide.
70
- */
71
- /**
72
- * The `lastTs` a manifest entry carries for one file. Append-only logs (a
73
- * conversation `.jsonl`) embed per-line event timestamps, so their recency is
74
- * the latest line timestamp (`transcriptStats`). Mutable blobs (Kimi
75
- * `state.json`, the per-tool `tasks/*.json` sidecars) carry no event timestamp —
76
- * their own `updatedAt`/`createdAt` fields are agent-specific and unreliable — so
77
- * their "last written" signal is the file mtime. Without this, `transcriptStats`
78
- * returns `''` for every blob and the last-writer-wins branch in
79
- * `resolveMirrorWrite` silently degrades to "highest-hash-wins", which can pick a
80
- * stale copy over the genuinely newer one.
81
- */
82
- export declare function deriveLastTs(spec: SyncAgentSpec, relKey: string, content: string, mtimeMs: number): string;
83
- export declare function resolveMirrorWrite(spec: SyncAgentSpec, copies: RemoteCopy[], contents: string[]): {
84
- dest: string;
85
- content: string;
86
- merged: boolean;
87
- };
88
- /**
89
- * Decide how to reconcile one session's fetched copies. Pure: no I/O.
90
- *
91
- * `fetched` is positionally aligned to `copies` — a `null` slot means that
92
- * copy's object wasn't retrievable this tick (R2 404 / LIST→GET consistency
93
- * lag / a transient get error). If ANY listed copy is missing, returns `null`:
94
- * the caller must then skip the mirror write AND skip stamping pull-state, so
95
- * the session is retried next tick. Writing a partial set instead would
96
- * materialize a non-converged union and — because pull-state would record the
97
- * full source signature — abandon the missing branch forever (the bug this
98
- * guards: a signature match in selectSessionsToFetch never re-fetches it).
99
- */
100
- export declare function reconcileCopies(spec: SyncAgentSpec, copies: RemoteCopy[], fetched: Array<string | null>): {
101
- dest: string;
102
- content: string;
103
- merged: boolean;
104
- } | null;
105
- /** Run one full sync cycle: push this machine's changes, pull everyone else's. */
106
- export declare function syncSessions(opts?: SyncOptions): Promise<SyncResult>;