@farmslot/protocol 0.74.9 → 0.75.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 (170) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/agents/roles.d.ts +2 -0
  3. package/dist/agents/roles.d.ts.map +1 -1
  4. package/dist/agents/roles.js +8 -1
  5. package/dist/agents/roles.js.map +1 -1
  6. package/dist/client-cache/artifact-response.d.ts +15 -0
  7. package/dist/client-cache/artifact-response.d.ts.map +1 -0
  8. package/dist/client-cache/artifact-response.js +103 -0
  9. package/dist/client-cache/artifact-response.js.map +1 -0
  10. package/dist/client-cache/cache.d.ts +6 -1
  11. package/dist/client-cache/cache.d.ts.map +1 -1
  12. package/dist/client-cache/cache.js +207 -33
  13. package/dist/client-cache/cache.js.map +1 -1
  14. package/dist/client-cache/command-method.d.ts +17 -4
  15. package/dist/client-cache/command-method.d.ts.map +1 -1
  16. package/dist/client-cache/command-method.js +221 -1
  17. package/dist/client-cache/command-method.js.map +1 -1
  18. package/dist/client-cache/conformance.d.ts +2 -5
  19. package/dist/client-cache/conformance.d.ts.map +1 -1
  20. package/dist/client-cache/conformance.js +4 -0
  21. package/dist/client-cache/conformance.js.map +1 -1
  22. package/dist/client-cache/content.d.ts +11 -0
  23. package/dist/client-cache/content.d.ts.map +1 -1
  24. package/dist/client-cache/content.js +43 -0
  25. package/dist/client-cache/content.js.map +1 -1
  26. package/dist/client-cache/index.d.ts +5 -2
  27. package/dist/client-cache/index.d.ts.map +1 -1
  28. package/dist/client-cache/index.js +5 -2
  29. package/dist/client-cache/index.js.map +1 -1
  30. package/dist/client-cache/native-history.d.ts +22 -1
  31. package/dist/client-cache/native-history.d.ts.map +1 -1
  32. package/dist/client-cache/native-history.js +59 -4
  33. package/dist/client-cache/native-history.js.map +1 -1
  34. package/dist/client-cache/network-cost.d.ts +4 -0
  35. package/dist/client-cache/network-cost.d.ts.map +1 -0
  36. package/dist/client-cache/network-cost.js +24 -0
  37. package/dist/client-cache/network-cost.js.map +1 -0
  38. package/dist/client-cache/prefetch.d.ts +17 -0
  39. package/dist/client-cache/prefetch.d.ts.map +1 -1
  40. package/dist/client-cache/prefetch.js +110 -47
  41. package/dist/client-cache/prefetch.js.map +1 -1
  42. package/dist/client-cache/read-stored.d.ts +7 -0
  43. package/dist/client-cache/read-stored.d.ts.map +1 -0
  44. package/dist/client-cache/read-stored.js +18 -0
  45. package/dist/client-cache/read-stored.js.map +1 -0
  46. package/dist/client-cache/registry.d.ts +309 -16
  47. package/dist/client-cache/registry.d.ts.map +1 -1
  48. package/dist/client-cache/registry.js +422 -27
  49. package/dist/client-cache/registry.js.map +1 -1
  50. package/dist/client-cache/run-details.d.ts +29 -0
  51. package/dist/client-cache/run-details.d.ts.map +1 -0
  52. package/dist/client-cache/run-details.js +145 -0
  53. package/dist/client-cache/run-details.js.map +1 -0
  54. package/dist/client-cache/run-list-versions.d.ts +2 -0
  55. package/dist/client-cache/run-list-versions.d.ts.map +1 -1
  56. package/dist/client-cache/run-list-versions.js +9 -0
  57. package/dist/client-cache/run-list-versions.js.map +1 -1
  58. package/dist/client-cache/store.d.ts +6 -0
  59. package/dist/client-cache/store.d.ts.map +1 -1
  60. package/dist/client-cache/store.js +25 -0
  61. package/dist/client-cache/store.js.map +1 -1
  62. package/dist/client-cache/transport.d.ts +8 -1
  63. package/dist/client-cache/transport.d.ts.map +1 -1
  64. package/dist/client-cache/transport.js +8 -0
  65. package/dist/client-cache/transport.js.map +1 -1
  66. package/dist/client-cache/types.d.ts +11 -1
  67. package/dist/client-cache/types.d.ts.map +1 -1
  68. package/dist/contracts/agents.d.ts +2 -0
  69. package/dist/contracts/agents.d.ts.map +1 -1
  70. package/dist/contracts/agents.js.map +1 -1
  71. package/dist/contracts/config.d.ts +10 -0
  72. package/dist/contracts/config.d.ts.map +1 -1
  73. package/dist/contracts/config.js +7 -0
  74. package/dist/contracts/config.js.map +1 -1
  75. package/dist/contracts/dispatch.d.ts +18 -0
  76. package/dist/contracts/dispatch.d.ts.map +1 -1
  77. package/dist/contracts/run-lifecycle-view.d.ts +9 -0
  78. package/dist/contracts/run-lifecycle-view.d.ts.map +1 -1
  79. package/dist/contracts/run-lifecycle-view.js +8 -0
  80. package/dist/contracts/run-lifecycle-view.js.map +1 -1
  81. package/dist/contracts/runs.d.ts +76 -7
  82. package/dist/contracts/runs.d.ts.map +1 -1
  83. package/dist/contracts/runs.js +33 -2
  84. package/dist/contracts/runs.js.map +1 -1
  85. package/dist/contracts/slot-recovery.d.ts +11 -0
  86. package/dist/contracts/slot-recovery.d.ts.map +1 -1
  87. package/dist/contracts/slot-recovery.js +11 -1
  88. package/dist/contracts/slot-recovery.js.map +1 -1
  89. package/dist/contracts/slots.d.ts +17 -0
  90. package/dist/contracts/slots.d.ts.map +1 -1
  91. package/dist/index.d.ts +5 -0
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +5 -0
  94. package/dist/index.js.map +1 -1
  95. package/dist/markdown-fence.d.ts +6 -0
  96. package/dist/markdown-fence.d.ts.map +1 -0
  97. package/dist/markdown-fence.js +12 -0
  98. package/dist/markdown-fence.js.map +1 -0
  99. package/dist/recipe/recording.d.ts +1 -1
  100. package/dist/recipe/recording.d.ts.map +1 -1
  101. package/dist/recipe/recording.js +2 -2
  102. package/dist/recipe/recording.js.map +1 -1
  103. package/dist/rpc/auth.d.ts +5 -2
  104. package/dist/rpc/auth.d.ts.map +1 -1
  105. package/dist/rpc/auth.js.map +1 -1
  106. package/dist/rpc/dispatch.d.ts +8 -0
  107. package/dist/rpc/dispatch.d.ts.map +1 -1
  108. package/dist/rpc/dispatch.js +8 -0
  109. package/dist/rpc/dispatch.js.map +1 -1
  110. package/dist/rpc/fleet.d.ts +3 -1
  111. package/dist/rpc/fleet.d.ts.map +1 -1
  112. package/dist/rpc/fleet.js.map +1 -1
  113. package/dist/rpc/index.d.ts +1 -0
  114. package/dist/rpc/index.d.ts.map +1 -1
  115. package/dist/rpc/index.js +1 -0
  116. package/dist/rpc/index.js.map +1 -1
  117. package/dist/rpc/native-session.d.ts +11 -0
  118. package/dist/rpc/native-session.d.ts.map +1 -1
  119. package/dist/rpc/native-session.js +9 -0
  120. package/dist/rpc/native-session.js.map +1 -1
  121. package/dist/rpc/nodes.d.ts +2 -0
  122. package/dist/rpc/nodes.d.ts.map +1 -1
  123. package/dist/rpc/nodes.js.map +1 -1
  124. package/dist/rpc/run.d.ts +4 -0
  125. package/dist/rpc/run.d.ts.map +1 -1
  126. package/dist/rpc/run.js.map +1 -1
  127. package/dist/rpc/runner-models.d.ts +14 -3
  128. package/dist/rpc/runner-models.d.ts.map +1 -1
  129. package/dist/rpc/runner-models.js +37 -13
  130. package/dist/rpc/runner-models.js.map +1 -1
  131. package/dist/rpc/slot.d.ts +3 -2
  132. package/dist/rpc/slot.d.ts.map +1 -1
  133. package/dist/rpc/slot.js.map +1 -1
  134. package/dist/rpc/terminal-input-record.d.ts +20 -0
  135. package/dist/rpc/terminal-input-record.d.ts.map +1 -0
  136. package/dist/rpc/terminal-input-record.js +42 -0
  137. package/dist/rpc/terminal-input-record.js.map +1 -0
  138. package/dist/rpc/terminal.d.ts +1 -1
  139. package/dist/rpc/terminal.d.ts.map +1 -1
  140. package/dist/runs/list-trim.d.ts +13 -0
  141. package/dist/runs/list-trim.d.ts.map +1 -0
  142. package/dist/runs/list-trim.js +71 -0
  143. package/dist/runs/list-trim.js.map +1 -0
  144. package/dist/runs/list-version.d.ts +4 -0
  145. package/dist/runs/list-version.d.ts.map +1 -0
  146. package/dist/runs/list-version.js +9 -0
  147. package/dist/runs/list-version.js.map +1 -0
  148. package/dist/runs/review-chain.d.ts.map +1 -1
  149. package/dist/runs/review-chain.js +2 -1
  150. package/dist/runs/review-chain.js.map +1 -1
  151. package/dist/runs/run-engine-label.d.ts +40 -0
  152. package/dist/runs/run-engine-label.d.ts.map +1 -0
  153. package/dist/runs/run-engine-label.js +27 -0
  154. package/dist/runs/run-engine-label.js.map +1 -0
  155. package/dist/runs/run-family-groups.d.ts.map +1 -1
  156. package/dist/runs/run-family-groups.js +5 -4
  157. package/dist/runs/run-family-groups.js.map +1 -1
  158. package/dist/runs/run-list-view.d.ts.map +1 -1
  159. package/dist/runs/run-list-view.js +2 -1
  160. package/dist/runs/run-list-view.js.map +1 -1
  161. package/dist/slots/tracking-branch.d.ts +7 -0
  162. package/dist/slots/tracking-branch.d.ts.map +1 -1
  163. package/dist/slots/tracking-branch.js.map +1 -1
  164. package/dist/text-order.d.ts +7 -0
  165. package/dist/text-order.d.ts.map +1 -0
  166. package/dist/text-order.js +7 -0
  167. package/dist/text-order.js.map +1 -0
  168. package/dist/version.d.ts +1 -1
  169. package/dist/version.js +1 -1
  170. package/package.json +1 -1
@@ -1,11 +1,13 @@
1
1
  import { Methods } from '../rpc/registry.js';
2
+ import { runListVersion } from '../runs/list-version.js';
2
3
  import { ResourceCollectorAdmission } from './collector-admission.js';
3
- import { resourceBudgetEvictions, resourceContentWithinLimits, verifyResourceContent, } from './content.js';
4
+ import { resourceBudgetEvictions, resourceContentWithinLimits, verifyResourceContent, verifyResourceContentInSlices, } from './content.js';
4
5
  import { isKnownResourceFamily, RESOURCE_PUBLISH_INTERVAL_MS, RESOURCE_REREAD_INTERVAL_MS, RESOURCE_RETRY_MAX_WAIT_MS, resourceKeyId, } from './keys.js';
5
6
  import { nativeHistoryStorageId, readNativeHistory, storedNativeHistoryMatches, } from './native-history.js';
6
7
  import { resourceQuery } from './registry.js';
8
+ import { createRunDetailSync, runDetailTarget } from './run-details.js';
7
9
  import { isRunListCollectionRead, RUN_LIST_COLLECTION_ID, RunListCollection, runListSummaryFlags, } from './run-list-versions.js';
8
- import { createMemoryResourceStore, normalizeResourceStoreScope, resourceStoreScopeId, sameResourceAuthority, } from './store.js';
10
+ import { createMemoryResourceStore, normalizeResourceStoreScope, readStoredQueryBatch, resourceStoreScopeId, sameResourceAuthority, } from './store.js';
9
11
  import { isUpstreamStoredQuery, upstreamRetentionEvictions } from './upstream-retention.js';
10
12
  import { workspaceRetentionEvictions, workspaceRetentionMembership, } from './workspace-retention.js';
11
13
  const deletionReasons = new Set([
@@ -21,6 +23,13 @@ const evictionCodes = new Set([
21
23
  'RUN_NOT_FOUND',
22
24
  'RESOURCE_NOT_FOUND',
23
25
  ]);
26
+ /**
27
+ * The gateway refused the read: the resource is gone or not this principal's. The
28
+ * cache evicts saved data on these; a network failure or timeout is not one.
29
+ */
30
+ export function readRefused(error) {
31
+ return Boolean(error && evictionCodes.has(error.code));
32
+ }
24
33
  function queryError(error) {
25
34
  const code = error && typeof error === 'object' ? Reflect.get(error, 'code') : undefined;
26
35
  return {
@@ -44,6 +53,7 @@ function normalizeConnection(connection) {
44
53
  };
45
54
  }
46
55
  const defaultContentBudgetBytes = 500 * 1024 * 1024;
56
+ const startupQueryBytes = 8 * 1024 * 1024;
47
57
  // Only the identity crosses the store seam, never a caller's other fields.
48
58
  const contentKey = ({ kind, id }) => ({ kind, id });
49
59
  function profileId(connection) {
@@ -56,6 +66,9 @@ export function createGatewayResourceCache(options) {
56
66
  const transport = options.transport;
57
67
  let store = options.store ?? createMemoryResourceStore();
58
68
  let metadata;
69
+ let startupStore;
70
+ let startupOwnerVersion = -1;
71
+ const startupQueries = new Map();
59
72
  let connection = normalizeConnection(transport.getConnection());
60
73
  let usable = false;
61
74
  let disposed = false;
@@ -91,9 +104,33 @@ export function createGatewayResourceCache(options) {
91
104
  maxBodyBytes: resourceQuery(Methods.RUN_LIST).maxBytes,
92
105
  });
93
106
  let runListLoad;
107
+ const fallbackDetailTargets = new Map();
108
+ const detailSync = createRunDetailSync({
109
+ connection: () => connection,
110
+ list: async () => (await readStoredContent((target) => target.listQueries())) ?? [],
111
+ stored: (runId) => readStoredContent((target) => target.getQuery(resourceQuery(Methods.RUN_GET, { runId }).id)),
112
+ touch: async (runId) => {
113
+ const target = store;
114
+ const ownerVersion = hydrationOwnerVersion;
115
+ await updateStore(target, () => target === store && !target.deleted && ownerVersion === hydrationOwnerVersion, () => target.touchQuery?.(resourceQuery(Methods.RUN_GET, { runId }).id, Date.now()) ??
116
+ Promise.resolve());
117
+ },
118
+ fetch: (runId, maxStoredBytes, signal) => retryQuery(resourceQuery(Methods.RUN_GET, { runId }), { origin: 'prefetch', cohort: 'run', maxStoredBytes }, signal),
119
+ onError: (error) => options.onStorageNotice?.(`Background run details could not sync: ${queryError(error).message}`),
120
+ });
121
+ function refreshDetailTargets() {
122
+ const versioned = runList.detailTargets();
123
+ detailSync.setTargets(store.persistent && usable
124
+ ? runList.held
125
+ ? versioned
126
+ : [...fallbackDetailTargets.values()]
127
+ : []);
128
+ }
94
129
  function forgetRunList() {
95
130
  runList.clear();
96
131
  runListLoad = undefined;
132
+ fallbackDetailTargets.clear();
133
+ detailSync.reset();
97
134
  }
98
135
  function updateCollectors() {
99
136
  collectors.update({ ...connection, foreground: usable && connection.foreground !== false }, usable
@@ -116,12 +153,29 @@ export function createGatewayResourceCache(options) {
116
153
  unsubscribeStore();
117
154
  store = next;
118
155
  storeVersion++;
156
+ startupStore = undefined;
157
+ startupQueries.clear();
158
+ detailSync.reset();
119
159
  unsubscribeStore = store.onDeleted(storeDeleted);
120
160
  }
161
+ /** Observers whose failure was already logged, so a repeating one is reported once. */
162
+ const failedObservers = new WeakSet();
163
+ /** One observer's failure is its own: the other observers and the cache's reads go on. */
164
+ function deliver(entry, listener) {
165
+ try {
166
+ listener(entry.state);
167
+ }
168
+ catch (error) {
169
+ if (failedObservers.has(listener))
170
+ return;
171
+ failedObservers.add(listener);
172
+ console.error(`Resource cache observer of ${entry.query.id} failed:`, error);
173
+ }
174
+ }
121
175
  function notify(entry) {
122
176
  entry.state = { ...entry.state, storageNotice };
123
177
  for (const listener of entry.listeners)
124
- listener(entry.state);
178
+ deliver(entry, listener);
125
179
  }
126
180
  function cancelScheduledRead(entry) {
127
181
  if (entry.readTimer !== undefined)
@@ -232,6 +286,46 @@ export function createGatewayResourceCache(options) {
232
286
  if (entry.state.status !== 'ready')
233
287
  hydrateEntry(entry);
234
288
  }
289
+ async function warmStoredQueries() {
290
+ if (!store.persistent ||
291
+ (startupStore === store && startupOwnerVersion === hydrationOwnerVersion))
292
+ return;
293
+ const readingStore = store;
294
+ const incarnation = storeVersion;
295
+ const ownerVersion = hydrationOwnerVersion;
296
+ const current = () => !disposed &&
297
+ store === readingStore &&
298
+ !readingStore.deleted &&
299
+ incarnation === storeVersion &&
300
+ ownerVersion === hydrationOwnerVersion;
301
+ try {
302
+ const batch = await readStoredQueryBatch(readingStore, startupQueryBytes);
303
+ if (!current())
304
+ return;
305
+ const collection = batch.find((saved) => saved.id === RUN_LIST_COLLECTION_ID);
306
+ const rows = collection && readingStore.getRows
307
+ ? await readingStore.getRows(RUN_LIST_COLLECTION_ID)
308
+ : undefined;
309
+ if (!current())
310
+ return;
311
+ startupQueries.clear();
312
+ let remaining = startupQueryBytes;
313
+ for (const saved of batch) {
314
+ if (saved.bytes > remaining)
315
+ continue;
316
+ remaining -= saved.bytes;
317
+ if (saved.id !== RUN_LIST_COLLECTION_ID)
318
+ startupQueries.set(saved.id, saved);
319
+ }
320
+ if (rows)
321
+ runList.hydrate(rows);
322
+ startupStore = readingStore;
323
+ startupOwnerVersion = ownerVersion;
324
+ }
325
+ catch (error) {
326
+ await recoverStore(error, readingStore);
327
+ }
328
+ }
235
329
  function schedule(entry) {
236
330
  if (entry.queued || disposed)
237
331
  return;
@@ -261,8 +355,13 @@ export function createGatewayResourceCache(options) {
261
355
  connection.id === next.id &&
262
356
  !scopeChanged &&
263
357
  !authorityChanged &&
264
- !revoke)
358
+ !revoke) {
359
+ if (connection.metered !== next.metered) {
360
+ connection = { ...connection, metered: next.metered };
361
+ refreshDetailTargets();
362
+ }
265
363
  return;
364
+ }
266
365
  const ticket = ++version;
267
366
  runList.abort();
268
367
  // Stored facts belong to an owner, not a socket attempt. Fence a revoked
@@ -273,6 +372,7 @@ export function createGatewayResourceCache(options) {
273
372
  }
274
373
  connection = next;
275
374
  usable = false;
375
+ refreshDetailTargets();
276
376
  updateCollectors();
277
377
  revisions.clear();
278
378
  for (const entry of entries.values()) {
@@ -361,9 +461,14 @@ export function createGatewayResourceCache(options) {
361
461
  await recoverStore(error, target);
362
462
  }
363
463
  }
464
+ if (disposed || ticket !== version)
465
+ return;
466
+ if (!revoke && canPersist(next))
467
+ await warmStoredQueries();
364
468
  if (disposed || ticket !== version)
365
469
  return;
366
470
  usable = next.connected && next.foreground !== false && !revoke;
471
+ refreshDetailTargets();
367
472
  updateCollectors();
368
473
  if (usable)
369
474
  for (const entry of entries.values())
@@ -409,7 +514,7 @@ export function createGatewayResourceCache(options) {
409
514
  if (change.revision <= (revisions.get(id) ?? -1))
410
515
  continue;
411
516
  revisions.set(id, change.revision);
412
- const affected = [...entries.values()].filter((entry) => entry.query.dependencies.some((key) => resourceKeyId(key) === id));
517
+ const affected = [...entries.values()].filter((entry) => entry.dependencyIds.has(id));
413
518
  // Coverage counts declared dependencies only, so a known key that no entry declares (another
414
519
  // slot, an unopened workspace) changes nothing held. Only a family this client cannot name might.
415
520
  if (!affected.length && isKnownResourceFamily(change.key?.family))
@@ -441,6 +546,7 @@ export function createGatewayResourceCache(options) {
441
546
  }
442
547
  const entry = {
443
548
  query,
549
+ dependencyIds: new Set(query.dependencies.map(resourceKeyId)),
444
550
  state: { status: 'loading' },
445
551
  listeners: new Set(),
446
552
  hydration: Promise.resolve(),
@@ -454,9 +560,40 @@ export function createGatewayResourceCache(options) {
454
560
  failures: 0,
455
561
  };
456
562
  entries.set(query.id, entry);
457
- hydrateEntry(entry);
563
+ const saved = isRunListView(query) ? heldRunListView(query) : takeStartupQuery(query);
564
+ if (applyStoredQuery(entry, saved)) {
565
+ entry.hydrated = true;
566
+ queueMicrotask(prune);
567
+ }
568
+ else
569
+ hydrateEntry(entry);
458
570
  return entry;
459
571
  }
572
+ function takeStartupQuery(query) {
573
+ if (startupStore !== store || startupOwnerVersion !== hydrationOwnerVersion)
574
+ return undefined;
575
+ const id = nativeHistoryStorageId(query);
576
+ const saved = startupQueries.get(id);
577
+ startupQueries.delete(id);
578
+ return saved;
579
+ }
580
+ function applyStoredQuery(entry, saved) {
581
+ const query = entry.query;
582
+ if (!saved ||
583
+ !query.persisted ||
584
+ saved.bytes > query.maxBytes ||
585
+ (query.method === Methods.NATIVE_SESSION_READ &&
586
+ !storedNativeHistoryMatches(query.params, saved.data)))
587
+ return false;
588
+ const sourceObservation = query.sourceObservation?.(saved.data);
589
+ entry.state = {
590
+ status: 'stale',
591
+ data: saved.data,
592
+ observedAt: saved.observedAt,
593
+ ...(sourceObservation ? { sourceObservation, error: sourceObservation.error } : {}),
594
+ };
595
+ return true;
596
+ }
460
597
  function hydrateEntry(entry) {
461
598
  const query = entry.query;
462
599
  const hydrationSerial = ++entry.hydrationSerial;
@@ -470,7 +607,8 @@ export function createGatewayResourceCache(options) {
470
607
  const readingStore = store;
471
608
  try {
472
609
  const saved = query.persisted
473
- ? ((isRunListView(query) ? await storedRunListView(query, readingStore) : undefined) ??
610
+ ? (takeStartupQuery(query) ??
611
+ (isRunListView(query) ? await storedRunListView(query, readingStore) : undefined) ??
474
612
  (await readingStore.getQuery(nativeHistoryStorageId(query))))
475
613
  : undefined;
476
614
  if (!disposed &&
@@ -480,17 +618,7 @@ export function createGatewayResourceCache(options) {
480
618
  ownerVersion === hydrationOwnerVersion &&
481
619
  entry.hydrationSerial === hydrationSerial &&
482
620
  entry.state.status !== 'ready' &&
483
- saved &&
484
- saved.bytes <= query.maxBytes &&
485
- (query.method !== Methods.NATIVE_SESSION_READ ||
486
- storedNativeHistoryMatches(query.params, saved.data))) {
487
- const sourceObservation = query.sourceObservation?.(saved.data);
488
- entry.state = {
489
- status: 'stale',
490
- data: saved.data,
491
- observedAt: saved.observedAt,
492
- ...(sourceObservation ? { sourceObservation, error: sourceObservation.error } : {}),
493
- };
621
+ applyStoredQuery(entry, saved)) {
494
622
  notify(entry);
495
623
  }
496
624
  }
@@ -521,6 +649,9 @@ export function createGatewayResourceCache(options) {
521
649
  };
522
650
  await runListLoad.promise;
523
651
  }
652
+ return heldRunListView(query);
653
+ }
654
+ function heldRunListView(query) {
524
655
  const data = runList.view(query.params);
525
656
  return data && runList.observedAt !== undefined
526
657
  ? { id: query.id, data, observedAt: runList.observedAt, bytes: 0 }
@@ -623,7 +754,11 @@ export function createGatewayResourceCache(options) {
623
754
  const shared = entry.pending.prefetch;
624
755
  entry.pending.prefetch =
625
756
  shared && prefetch
626
- ? { origin: 'prefetch', cohort: shared.cohort === 'gate' ? 'gate' : prefetch.cohort }
757
+ ? {
758
+ origin: 'prefetch',
759
+ cohort: shared.cohort === 'gate' ? 'gate' : prefetch.cohort,
760
+ maxStoredBytes: Math.min(shared.maxStoredBytes ?? Infinity, prefetch.maxStoredBytes ?? Infinity),
761
+ }
627
762
  : undefined;
628
763
  return entry.pending.promise;
629
764
  }
@@ -744,6 +879,7 @@ export function createGatewayResourceCache(options) {
744
879
  const retention = entry.query.retention;
745
880
  const saved = serialized !== undefined &&
746
881
  bytes <= entry.query.maxBytes &&
882
+ (entry.listeners.size > 0 || bytes <= (pending.prefetch?.maxStoredBytes ?? Infinity)) &&
747
883
  (!retention || bytes <= retention.maxBytes)
748
884
  ? {
749
885
  id: nativeHistoryStorageId(entry.query),
@@ -816,7 +952,9 @@ export function createGatewayResourceCache(options) {
816
952
  (retention.cohort === 'upstream'
817
953
  ? isUpstreamStoredQuery(value.id)
818
954
  : value.id.startsWith(`${entry.query.method}:`)))
819
- .sort((a, b) => a.observedAt - b.observedAt ||
955
+ .sort((a, b) => (entry.query.method === Methods.RUN_GET
956
+ ? (a.accessedAt ?? a.observedAt) - (b.accessedAt ?? b.observedAt)
957
+ : a.observedAt - b.observedAt) ||
820
958
  (entries.get(a.id)?.lastUsed ?? 0) - (entries.get(b.id)?.lastUsed ?? 0));
821
959
  if (!current())
822
960
  return;
@@ -836,6 +974,14 @@ export function createGatewayResourceCache(options) {
836
974
  await writingStore.putQuery(row);
837
975
  });
838
976
  }
977
+ if (store.persistent && entry.query.method === Methods.RUN_LIST) {
978
+ const listed = reply.data;
979
+ if (Array.isArray(listed.runs)) {
980
+ for (const run of listed.runs)
981
+ fallbackDetailTargets.set(run.id, runDetailTarget(run, runListVersion(run)));
982
+ }
983
+ refreshDetailTargets();
984
+ }
839
985
  }
840
986
  catch (error) {
841
987
  if (disposed ||
@@ -962,14 +1108,25 @@ export function createGatewayResourceCache(options) {
962
1108
  });
963
1109
  return written;
964
1110
  }
1111
+ function contentWriteToken() {
1112
+ return { connectionId: connection.id, ownerVersion: hydrationOwnerVersion, storeVersion };
1113
+ }
965
1114
  /** Stores verified bytes within the caps and the shared budget. */
966
- function storeContent(input) {
1115
+ async function storeContent(input) {
967
1116
  const key = contentKey(input.key);
968
1117
  const { data, origin, cohort } = input;
969
- if (!resourceContentWithinLimits(key, data) ||
970
- !verifyResourceContent(key, data, input.untrackedFiles))
971
- return Promise.resolve(false);
972
- return writeContent(input.token, async (target, current) => {
1118
+ // Prefetch verifies between frames, so the write still answers to the owner it was asked under.
1119
+ const token = input.token ?? contentWriteToken();
1120
+ if (!resourceContentWithinLimits(key, data))
1121
+ return false;
1122
+ // Prefetch is background work; content a person opened is on their critical path and
1123
+ // verifies at once, as before.
1124
+ const verified = origin === 'prefetch'
1125
+ ? await verifyResourceContentInSlices(key, data, input.untrackedFiles)
1126
+ : verifyResourceContent(key, data, input.untrackedFiles);
1127
+ if (!verified)
1128
+ return false;
1129
+ return writeContent(token, async (target, current) => {
973
1130
  const accessedAt = Date.now();
974
1131
  const content = await target.listContent();
975
1132
  if (content.some((row) => row.kind === key.kind && row.id === key.id)) {
@@ -1019,6 +1176,13 @@ export function createGatewayResourceCache(options) {
1019
1176
  return true;
1020
1177
  });
1021
1178
  }
1179
+ async function retryQuery(query, placement, signal) {
1180
+ if (disposed)
1181
+ throw new Error('Resource cache is disposed');
1182
+ const entry = entryFor(query);
1183
+ await fetchEntry(entry, false, placement, signal);
1184
+ return entry.state;
1185
+ }
1022
1186
  return {
1023
1187
  observedKey(match) {
1024
1188
  for (const entry of entries.values())
@@ -1071,7 +1235,7 @@ export function createGatewayResourceCache(options) {
1071
1235
  notify(entry);
1072
1236
  });
1073
1237
  }
1074
- receive(entry.state);
1238
+ deliver(entry, receive);
1075
1239
  if (entry.state.status !== 'ready' || entry.dirty)
1076
1240
  schedule(entry);
1077
1241
  return () => {
@@ -1091,13 +1255,21 @@ export function createGatewayResourceCache(options) {
1091
1255
  throw new Error('Resource cache is disposed');
1092
1256
  return entryFor(query).state;
1093
1257
  },
1094
- async retry(query, placement, signal) {
1258
+ async readStored(query, refresh = true) {
1095
1259
  if (disposed)
1096
1260
  throw new Error('Resource cache is disposed');
1261
+ const ownerVersion = hydrationOwnerVersion;
1262
+ await barrier;
1097
1263
  const entry = entryFor(query);
1098
- await fetchEntry(entry, false, placement, signal);
1264
+ await entry.hydration;
1265
+ if (disposed || ownerVersion !== hydrationOwnerVersion)
1266
+ throw new Error('The offline store owner changed');
1267
+ // fetchEntry records network refusals in the query state and notifies its readers.
1268
+ if (refresh && entry.state.data !== undefined)
1269
+ void fetchEntry(entry);
1099
1270
  return entry.state;
1100
1271
  },
1272
+ retry: retryQuery,
1101
1273
  async retryAfterPending(query) {
1102
1274
  if (disposed)
1103
1275
  throw new Error('Resource cache is disposed');
@@ -1112,20 +1284,22 @@ export function createGatewayResourceCache(options) {
1112
1284
  readContent: (key) => readContentBytes((target) => target.getContent(contentKey(key)), false),
1113
1285
  runListProjections: () => runList.projections(),
1114
1286
  openContent: (key) => readContentBytes((target) => target.getContent(contentKey(key)), true),
1287
+ async forgetContent(key) {
1288
+ const target = store;
1289
+ const ownerVersion = hydrationOwnerVersion;
1290
+ await updateStore(target, () => target === store && !target.deleted && ownerVersion === hydrationOwnerVersion, () => target.deleteContent(contentKey(key)));
1291
+ },
1115
1292
  readContentByAlias: (alias) => readContentBytes((target) => target.getContentByAlias(alias), false),
1116
1293
  openContentByAlias: (alias) => readContentBytes((target) => target.getContentByAlias(alias), true),
1117
1294
  storeContent,
1118
1295
  retainContent,
1119
- contentWriteToken: () => ({
1120
- connectionId: connection.id,
1121
- ownerVersion: hydrationOwnerVersion,
1122
- storeVersion,
1123
- }),
1296
+ contentWriteToken,
1124
1297
  async dispose(reason) {
1125
1298
  if (disposed)
1126
1299
  return;
1127
1300
  const withdrawing = collectors.close();
1128
1301
  disposed = true;
1302
+ detailSync.reset();
1129
1303
  version++;
1130
1304
  unsubscribeChanges();
1131
1305
  unsubscribeConnection();