memeloop 0.1.3 → 0.2.1

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 (80) hide show
  1. package/dist/browser.cjs +1 -1
  2. package/dist/browser.cjs.map +1 -1
  3. package/dist/browser.d.cts +15 -15
  4. package/dist/browser.d.ts +15 -15
  5. package/dist/browser.js +1 -1
  6. package/dist/{chunk-5JEV7632.js → chunk-5VYZV33O.js} +2 -1
  7. package/dist/{chunk-5JEV7632.js.map → chunk-5VYZV33O.js.map} +1 -1
  8. package/dist/chunk-E5R24U4I.js +1292 -0
  9. package/dist/chunk-E5R24U4I.js.map +1 -0
  10. package/dist/{chunk-MRKY4HGF.js → chunk-EPHRIRAL.js} +20 -11
  11. package/dist/chunk-EPHRIRAL.js.map +1 -0
  12. package/dist/{chunk-P6L3444O.js → chunk-KV6TC5UJ.js} +2 -2
  13. package/dist/chunk-MXRXYGWW.js +1 -0
  14. package/dist/{chunk-CCEFAOSH.js → chunk-WHXFUMDE.js} +2 -2
  15. package/dist/{chunk-CCEFAOSH.js.map → chunk-WHXFUMDE.js.map} +1 -1
  16. package/dist/conversation.d.cts +3 -3
  17. package/dist/conversation.d.ts +3 -3
  18. package/dist/device-network-portable.cjs +814 -144
  19. package/dist/device-network-portable.cjs.map +1 -1
  20. package/dist/device-network-portable.d.cts +24 -14
  21. package/dist/device-network-portable.d.ts +24 -14
  22. package/dist/device-network-portable.js +18 -2
  23. package/dist/device-network.cjs +833 -154
  24. package/dist/device-network.cjs.map +1 -1
  25. package/dist/device-network.d.cts +5 -5
  26. package/dist/device-network.d.ts +5 -5
  27. package/dist/device-network.js +19 -3
  28. package/dist/{fetchProvider-WWnWnQFP.d.ts → fetchProvider-BhqRqi5-.d.ts} +1 -1
  29. package/dist/{fetchProvider-toTHy5Nu.d.cts → fetchProvider-HJ_FkORN.d.cts} +1 -1
  30. package/dist/index.cjs +841 -157
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.d.cts +16 -16
  33. package/dist/index.d.ts +16 -16
  34. package/dist/index.js +26 -8
  35. package/dist/index.js.map +1 -1
  36. package/dist/{interface-IrKBFtL1.d.ts → interface-3C3eG8Hn.d.cts} +13 -1
  37. package/dist/{interface-IrKBFtL1.d.cts → interface-3C3eG8Hn.d.ts} +13 -1
  38. package/dist/llm-providers.d.cts +2 -2
  39. package/dist/llm-providers.d.ts +2 -2
  40. package/dist/loop-api.cjs +1 -0
  41. package/dist/loop-api.cjs.map +1 -1
  42. package/dist/loop-api.d.cts +7 -7
  43. package/dist/loop-api.d.ts +7 -7
  44. package/dist/loop-api.js +2 -2
  45. package/dist/mobile.d.cts +5 -5
  46. package/dist/mobile.d.ts +5 -5
  47. package/dist/{orchestration-portable-BHB_YrNN.d.ts → orchestration-portable-B4udCSVd.d.ts} +4 -4
  48. package/dist/{orchestration-portable-mvYBvGBb.d.cts → orchestration-portable-BWaCsCGK.d.cts} +4 -4
  49. package/dist/orchestration-portable.cjs +1 -1
  50. package/dist/orchestration-portable.cjs.map +1 -1
  51. package/dist/orchestration-portable.d.cts +5 -5
  52. package/dist/orchestration-portable.d.ts +5 -5
  53. package/dist/orchestration-portable.js +1 -1
  54. package/dist/pairingInvite-DENOiqa0.d.cts +271 -0
  55. package/dist/pairingInvite-DZy77i0p.d.ts +271 -0
  56. package/dist/{promptConcat-0WNFTg4l.d.ts → promptConcat-CUEIA_3T.d.ts} +3 -3
  57. package/dist/{promptConcat-D6wr5V-V.d.cts → promptConcat-VLe5SEN4.d.cts} +3 -3
  58. package/dist/{providerRegistry-CPsktuiX.d.cts → providerRegistry-BDGGs5gz.d.cts} +2 -2
  59. package/dist/{providerRegistry-Bi6pAvSj.d.ts → providerRegistry-CaFK_aUd.d.ts} +2 -2
  60. package/dist/{registry-D0kbxksn.d.cts → registry-DNSEVYug.d.cts} +3 -3
  61. package/dist/{registry-DDbvhlhs.d.ts → registry-yGnYHWhq.d.ts} +3 -3
  62. package/dist/{remoteClient-CBggnRy3.d.cts → remoteClient-DFIyCZEv.d.cts} +2 -2
  63. package/dist/{remoteClient-B_spK019.d.ts → remoteClient-DwBCoHpc.d.ts} +2 -2
  64. package/dist/{runtime-UONPTAYK.js → runtime-6EELHWVB.js} +2 -2
  65. package/dist/{scriptDeploymentPipeline-CXJcC82H.d.ts → scriptDeploymentPipeline-B1q-UhrO.d.ts} +1 -1
  66. package/dist/{scriptDeploymentPipeline-yG1ZhVVI.d.cts → scriptDeploymentPipeline-XJzQyYys.d.cts} +1 -1
  67. package/dist/{syncCloudDevices-Ds_Kcgrv.d.cts → syncCloudDevices-BFw_c1g5.d.cts} +6 -5
  68. package/dist/{syncCloudDevices-BEqwgrhD.d.ts → syncCloudDevices-C8fCUhnX.d.ts} +6 -5
  69. package/dist/{types-CdFVSntj.d.ts → types-C8gnhG9M.d.cts} +6 -4
  70. package/dist/{types-7pYti-k9.d.cts → types-CcFFIk8W.d.ts} +6 -4
  71. package/package.json +1 -1
  72. package/dist/chunk-426CJUZP.js +0 -630
  73. package/dist/chunk-426CJUZP.js.map +0 -1
  74. package/dist/chunk-MRKY4HGF.js.map +0 -1
  75. package/dist/chunk-VZKQX5PP.js +0 -1
  76. package/dist/pairingInvite-BXPWY3aM.d.cts +0 -149
  77. package/dist/pairingInvite-Cuz0wuq9.d.ts +0 -149
  78. /package/dist/{chunk-P6L3444O.js.map → chunk-KV6TC5UJ.js.map} +0 -0
  79. /package/dist/{chunk-VZKQX5PP.js.map → chunk-MXRXYGWW.js.map} +0 -0
  80. /package/dist/{runtime-UONPTAYK.js.map → runtime-6EELHWVB.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -21121,6 +21121,7 @@ function createMemeLoopRuntime(context) {
21121
21121
  lastMessageTimestamp: now,
21122
21122
  messageCount: context.runAgentToolLoop || !options.initialMessage ? 0 : 1,
21123
21123
  originNodeId: "local",
21124
+ originClock: 1,
21124
21125
  definitionId: options.definitionId,
21125
21126
  isUserInitiated: true
21126
21127
  };
@@ -22322,7 +22323,7 @@ var init_remoteClient = __esm({
22322
22323
  "src/orchestration/remoteClient.ts"() {
22323
22324
  "use strict";
22324
22325
  init_errors();
22325
- REMOTE_ORCHESTRATION_PROTOCOL = "memeloop.resource.v1";
22326
+ REMOTE_ORCHESTRATION_PROTOCOL = "memeloop.resource.v2";
22326
22327
  REMOTE_OPERATIONS = /* @__PURE__ */ new Set([
22327
22328
  "capabilities",
22328
22329
  "apply",
@@ -26462,11 +26463,13 @@ __export(src_exports, {
26462
26463
  DRIVER_MANIFEST_API_VERSION: () => DRIVER_MANIFEST_API_VERSION,
26463
26464
  DRIVER_MANIFEST_KIND: () => DRIVER_MANIFEST_KIND,
26464
26465
  DRIVER_REQUEST_API_VERSION: () => DRIVER_REQUEST_API_VERSION,
26466
+ DeviceCloudConnectionCoordinator: () => DeviceCloudConnectionCoordinator,
26465
26467
  FLEET_ROLLOUT_API_VERSION: () => FLEET_ROLLOUT_API_VERSION,
26466
26468
  FLEET_ROLLOUT_KIND: () => FLEET_ROLLOUT_KIND,
26467
26469
  HookRegistry: () => HookRegistry,
26468
26470
  IMChannelManager: () => IMChannelManager,
26469
26471
  IM_SESSION_TOOL_IDS: () => IM_SESSION_TOOL_IDS,
26472
+ JsonFrameError: () => JsonFrameError,
26470
26473
  LIBP2P_RPC_REQUEST_TYPE: () => LIBP2P_RPC_REQUEST_TYPE,
26471
26474
  LIBP2P_RPC_RESPONSE_TYPE: () => LIBP2P_RPC_RESPONSE_TYPE,
26472
26475
  LIBP2P_SYNC_REQUEST_TYPE: () => LIBP2P_SYNC_REQUEST_TYPE,
@@ -26484,6 +26487,7 @@ __export(src_exports, {
26484
26487
  MODEL_ENDPOINT_API_VERSION: () => MODEL_ENDPOINT_API_VERSION,
26485
26488
  MODEL_ENDPOINT_KIND: () => MODEL_ENDPOINT_KIND,
26486
26489
  MemoryDeviceNetworkService: () => MemoryDeviceNetworkService,
26490
+ MemoryDeviceSyncStateStore: () => MemoryDeviceSyncStateStore,
26487
26491
  NETWORK_ATTACHMENT_API_VERSION: () => NETWORK_ATTACHMENT_API_VERSION,
26488
26492
  NETWORK_ATTACHMENT_KIND: () => NETWORK_ATTACHMENT_KIND,
26489
26493
  NETWORK_CLASS_API_VERSION: () => NETWORK_CLASS_API_VERSION,
@@ -26582,6 +26586,7 @@ __export(src_exports, {
26582
26586
  canDriverSatisfyClass: () => canDriverSatisfyClass,
26583
26587
  cancelPendingApprovals: () => cancelPendingApprovals,
26584
26588
  canonicalControlStoreValue: () => canonicalControlStoreValue,
26589
+ canonicalDevicePairingInviteBytes: () => canonicalDevicePairingInviteBytes,
26585
26590
  canonicalDriverValue: () => canonicalDriverValue,
26586
26591
  canonicalWorkerBootstrapDescriptorBytes: () => canonicalWorkerBootstrapDescriptorBytes,
26587
26592
  canonicalWorkerProtocolRequestBytes: () => canonicalWorkerProtocolRequestBytes,
@@ -26591,6 +26596,7 @@ __export(src_exports, {
26591
26596
  clearHooks: () => clearHooks,
26592
26597
  clearPluginRegistrations: () => clearPluginRegistrations,
26593
26598
  compactMessages: () => compactMessages,
26599
+ computeMissingVersionRanges: () => computeMissingVersionRanges,
26594
26600
  consumeWorkloadCapabilityGrant: () => consumeWorkloadCapabilityGrant,
26595
26601
  containsSecrets: () => containsSecrets,
26596
26602
  controlStoreApplyMatches: () => controlStoreApplyMatches,
@@ -26678,6 +26684,7 @@ __export(src_exports, {
26678
26684
  createInProcessLoopRuntimeDriver: () => createInProcessLoopRuntimeDriver,
26679
26685
  createInProcessToolExecutionDriver: () => createInProcessToolExecutionDriver,
26680
26686
  createInstanceDeltaFromDefinition: () => createInstanceDeltaFromDefinition,
26687
+ createJsonFrameReader: () => createJsonFrameReader,
26681
26688
  createLoopRuntimeManagementConformanceSuite: () => createLoopRuntimeManagementConformanceSuite,
26682
26689
  createManagedArtifactDriverAdapter: () => createManagedArtifactDriverAdapter,
26683
26690
  createManagedCredentialBrokerAdapter: () => createManagedCredentialBrokerAdapter,
@@ -26759,6 +26766,8 @@ __export(src_exports, {
26759
26766
  detectMimeConfusion: () => detectMimeConfusion,
26760
26767
  digestNormalizedScript: () => digestNormalizedScript,
26761
26768
  encodeDevicePairingInvite: () => encodeDevicePairingInvite,
26769
+ encodeJsonFrame: () => encodeJsonFrame,
26770
+ encodeJsonFrames: () => encodeJsonFrames,
26762
26771
  enrollWorker: () => enrollWorker,
26763
26772
  evaluateApproval: () => evaluateApproval,
26764
26773
  evaluateToolAdmission: () => evaluateToolAdmission,
@@ -27163,48 +27172,85 @@ function decodeAttachmentBlobRpc(result) {
27163
27172
  };
27164
27173
  }
27165
27174
 
27175
+ // src/device-network/deviceSyncStateStore.ts
27176
+ var MemoryDeviceSyncStateStore = class {
27177
+ versionVector;
27178
+ constructor(initial = {}) {
27179
+ this.versionVector = { ...initial };
27180
+ }
27181
+ async loadVersionVector() {
27182
+ return { ...this.versionVector };
27183
+ }
27184
+ async saveVersionVector(versionVector) {
27185
+ this.versionVector = { ...versionVector };
27186
+ }
27187
+ };
27188
+
27166
27189
  // src/sync/chatSyncEngine.ts
27167
27190
  var ChatSyncEngine = class {
27168
27191
  nodeId;
27169
27192
  storage;
27170
27193
  getPeers;
27194
+ stateStore;
27171
27195
  versionVector = {};
27196
+ loadStatePromise;
27197
+ stateMutation = Promise.resolve();
27172
27198
  constructor(options) {
27173
27199
  this.nodeId = options.nodeId;
27174
27200
  this.storage = options.storage;
27175
27201
  this.getPeers = options.peers;
27176
- this.versionVector[this.nodeId] = 0;
27202
+ this.stateStore = options.stateStore ?? new MemoryDeviceSyncStateStore();
27177
27203
  }
27178
27204
  /**
27179
27205
  * 在本节点新产生一条消息时调用,增加本节点的 lamportClock。
27180
27206
  */
27181
27207
  bumpLocalVersion() {
27182
- this.versionVector[this.nodeId] = (this.versionVector[this.nodeId] ?? 0) + 1;
27208
+ let nextClock = 0;
27209
+ const mutation = this.stateMutation.then(async () => {
27210
+ await this.ensureStateLoaded();
27211
+ nextClock = (this.versionVector[this.nodeId] ?? 0) + 1;
27212
+ this.versionVector[this.nodeId] = nextClock;
27213
+ await this.stateStore.saveVersionVector(this.versionVector);
27214
+ });
27215
+ this.stateMutation = mutation.catch(() => void 0);
27216
+ return mutation.then(() => nextClock);
27183
27217
  }
27184
27218
  /**
27185
27219
  * 执行一次与所有 peer 的增量同步(metadata 级别),并尽力拉取缺失消息。
27186
27220
  */
27187
27221
  async syncOnce() {
27222
+ await this.ensureStateLoaded();
27223
+ await this.stateMutation;
27224
+ await this.reconcileVersionVectorFromStorage();
27188
27225
  const peers = this.getPeers();
27189
27226
  if (peers.length === 0) return;
27190
- const localVersion = { ...this.versionVector };
27191
27227
  const pulledConversations = /* @__PURE__ */ new Set();
27192
27228
  for (const peer of peers) {
27193
- const { remoteVersion, missingForRemote } = await peer.exchangeVersionVector(localVersion);
27194
- const missingForLocal = await peer.pullMissingMetadata(this.versionVector);
27195
- for (const meta of missingForLocal) {
27196
- await this.storage.upsertConversationMetadata(meta);
27197
- pulledConversations.add(meta.conversationId);
27198
- }
27229
+ const { remoteVersion } = await peer.exchangeVersionVector({ ...this.versionVector });
27230
+ let cursor;
27231
+ do {
27232
+ const page = await peer.pullMissingMetadata(this.versionVector, cursor);
27233
+ for (const meta of page.items) {
27234
+ await this.storage.upsertConversationMetadata(meta);
27235
+ pulledConversations.add(meta.conversationId);
27236
+ }
27237
+ cursor = page.nextCursor;
27238
+ } while (cursor !== void 0);
27199
27239
  for (const [nodeId, clock] of Object.entries(remoteVersion)) {
27200
27240
  const current = this.versionVector[nodeId] ?? 0;
27201
- if (clock > current) {
27202
- this.versionVector[nodeId] = clock;
27203
- }
27241
+ if (clock > current) this.versionVector[nodeId] = clock;
27204
27242
  }
27205
- if (missingForRemote.length > 0) {
27206
- this.bumpLocalVersion();
27243
+ await this.stateStore.saveVersionVector(this.versionVector);
27244
+ }
27245
+ const pageSize = 100;
27246
+ let offset = 0;
27247
+ for (; ; ) {
27248
+ const conversations = await this.storage.listConversations({ limit: pageSize, offset });
27249
+ for (const conversation of conversations) {
27250
+ pulledConversations.add(conversation.conversationId);
27207
27251
  }
27252
+ if (conversations.length < pageSize) break;
27253
+ offset += conversations.length;
27208
27254
  }
27209
27255
  for (const conversationId of pulledConversations) {
27210
27256
  await this.pullMessagesForConversationFromPeers(conversationId, peers);
@@ -27215,11 +27261,6 @@ var ChatSyncEngine = class {
27215
27261
  */
27216
27262
  async antiEntropyOnce() {
27217
27263
  await this.syncOnce();
27218
- const list = await this.storage.listConversations({ limit: 500 });
27219
- const peers = this.getPeers();
27220
- for (const meta of list) {
27221
- await this.pullMessagesForConversationFromPeers(meta.conversationId, peers);
27222
- }
27223
27264
  }
27224
27265
  async pullMessagesForConversationFromPeers(conversationId, peers) {
27225
27266
  const localMsgs = await this.storage.getMessages(conversationId, { mode: "full-content" });
@@ -27272,12 +27313,39 @@ var ChatSyncEngine = class {
27272
27313
  }
27273
27314
  }
27274
27315
  }
27275
- getVersionVector() {
27316
+ async getVersionVector() {
27317
+ await this.ensureStateLoaded();
27318
+ await this.stateMutation;
27276
27319
  return { ...this.versionVector };
27277
27320
  }
27278
27321
  getStorage() {
27279
27322
  return this.storage;
27280
27323
  }
27324
+ ensureStateLoaded() {
27325
+ this.loadStatePromise ??= (async () => {
27326
+ const stored = await this.stateStore.loadVersionVector();
27327
+ this.versionVector = { ...stored, [this.nodeId]: stored[this.nodeId] ?? 0 };
27328
+ })();
27329
+ return this.loadStatePromise;
27330
+ }
27331
+ async reconcileVersionVectorFromStorage() {
27332
+ const pageSize = 100;
27333
+ let offset = 0;
27334
+ let changed = false;
27335
+ for (; ; ) {
27336
+ const conversations = await this.storage.listConversations({ limit: pageSize, offset });
27337
+ for (const conversation of conversations) {
27338
+ const current = this.versionVector[conversation.originNodeId] ?? 0;
27339
+ if (conversation.originClock > current) {
27340
+ this.versionVector[conversation.originNodeId] = conversation.originClock;
27341
+ changed = true;
27342
+ }
27343
+ }
27344
+ if (conversations.length < pageSize) break;
27345
+ offset += conversations.length;
27346
+ }
27347
+ if (changed) await this.stateStore.saveVersionVector(this.versionVector);
27348
+ }
27281
27349
  };
27282
27350
 
27283
27351
  // src/sync/peerNodeAdapter.ts
@@ -27291,8 +27359,8 @@ var PeerNodeSyncAdapter = class {
27291
27359
  exchangeVersionVector(localVersion) {
27292
27360
  return this.transport.exchangeVersionVector(this.nodeId, localVersion);
27293
27361
  }
27294
- pullMissingMetadata(sinceVersion) {
27295
- return this.transport.pullMissingMetadata(this.nodeId, sinceVersion);
27362
+ pullMissingMetadata(sinceVersion, cursor) {
27363
+ return this.transport.pullMissingMetadata(this.nodeId, sinceVersion, cursor);
27296
27364
  }
27297
27365
  pullMissingMessages(conversationId, knownMessageIds) {
27298
27366
  const function_ = this.transport.pullMissingMessages?.bind(this.transport);
@@ -27385,6 +27453,7 @@ async function runStorageConformance(storage, options = {}) {
27385
27453
  lastMessageTimestamp: Date.now(),
27386
27454
  messageCount: 0,
27387
27455
  originNodeId: "local",
27456
+ originClock: 1,
27388
27457
  definitionId: "conformance-agent",
27389
27458
  isUserInitiated: true
27390
27459
  });
@@ -28696,6 +28765,7 @@ async function upsertConversationForRemoteTurn(input) {
28696
28765
  lastMessageTimestamp: now,
28697
28766
  messageCount: 0,
28698
28767
  originNodeId: input.localNodeId ?? "remote",
28768
+ originClock: 1,
28699
28769
  definitionId: input.definitionId,
28700
28770
  isUserInitiated: true
28701
28771
  });
@@ -28747,82 +28817,460 @@ function isChatMessage(value) {
28747
28817
  function isConversationMetaLike(value) {
28748
28818
  if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
28749
28819
  const record2 = value;
28750
- return typeof record2.conversationId === "string" && typeof record2.title === "string" && typeof record2.lastMessageTimestamp === "number" && typeof record2.messageCount === "number" && typeof record2.originNodeId === "string" && typeof record2.definitionId === "string" && typeof record2.isUserInitiated === "boolean";
28820
+ return typeof record2.conversationId === "string" && typeof record2.title === "string" && typeof record2.lastMessageTimestamp === "number" && typeof record2.messageCount === "number" && typeof record2.originNodeId === "string" && typeof record2.originClock === "number" && typeof record2.definitionId === "string" && typeof record2.isUserInitiated === "boolean";
28821
+ }
28822
+
28823
+ // src/device-network/deviceCloudConnectionCoordinator.ts
28824
+ var DEFAULT_HEARTBEAT_INTERVAL_MS = 6e4;
28825
+ var DEFAULT_INITIAL_BACKOFF_MS = 1e3;
28826
+ var DEFAULT_MAX_BACKOFF_MS = 6e4;
28827
+ var DEFAULT_JITTER_RATIO = 0.2;
28828
+ function emptyComponents() {
28829
+ return {
28830
+ authorizer: "not-run",
28831
+ registration: "not-run",
28832
+ relay: "not-run",
28833
+ heartbeat: "not-run",
28834
+ directory: "not-run"
28835
+ };
28751
28836
  }
28837
+ function assertNonNegativeFinite(value, name) {
28838
+ if (!Number.isFinite(value) || value < 0) {
28839
+ throw new TypeError(`${name} must be a finite non-negative number`);
28840
+ }
28841
+ }
28842
+ var DeviceCloudConnectionCoordinator = class {
28843
+ constructor(options) {
28844
+ this.options = options;
28845
+ this.configuration = options.configuration;
28846
+ this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
28847
+ this.initialBackoffMs = options.initialBackoffMs ?? DEFAULT_INITIAL_BACKOFF_MS;
28848
+ this.maxBackoffMs = options.maxBackoffMs ?? DEFAULT_MAX_BACKOFF_MS;
28849
+ this.jitterRatio = options.jitterRatio ?? DEFAULT_JITTER_RATIO;
28850
+ this.now = options.now ?? Date.now;
28851
+ this.random = options.random ?? Math.random;
28852
+ assertNonNegativeFinite(this.heartbeatIntervalMs, "heartbeatIntervalMs");
28853
+ assertNonNegativeFinite(this.initialBackoffMs, "initialBackoffMs");
28854
+ assertNonNegativeFinite(this.maxBackoffMs, "maxBackoffMs");
28855
+ assertNonNegativeFinite(this.jitterRatio, "jitterRatio");
28856
+ if (this.jitterRatio > 1) throw new TypeError("jitterRatio must not exceed 1");
28857
+ this.resetSnapshot();
28858
+ }
28859
+ configuration;
28860
+ generation = 0;
28861
+ controller = new AbortController();
28862
+ timer;
28863
+ inFlight;
28864
+ started = false;
28865
+ consecutiveFailures = 0;
28866
+ snapshotValue = {
28867
+ status: "not-configured",
28868
+ generation: 0,
28869
+ components: emptyComponents()
28870
+ };
28871
+ heartbeatIntervalMs;
28872
+ initialBackoffMs;
28873
+ maxBackoffMs;
28874
+ jitterRatio;
28875
+ now;
28876
+ random;
28877
+ get snapshot() {
28878
+ return {
28879
+ ...this.snapshotValue,
28880
+ components: { ...this.snapshotValue.components }
28881
+ };
28882
+ }
28883
+ async start() {
28884
+ if (this.started) return this.runNow();
28885
+ this.started = true;
28886
+ await this.runNow();
28887
+ }
28888
+ async stop() {
28889
+ this.started = false;
28890
+ this.clearTimer();
28891
+ const stoppedController = this.controller;
28892
+ stoppedController.abort(new Error("device cloud coordinator stopped"));
28893
+ await this.inFlight?.catch(() => void 0);
28894
+ if (this.controller === stoppedController) {
28895
+ this.controller = new AbortController();
28896
+ }
28897
+ }
28898
+ async setConfiguration(configuration) {
28899
+ this.configuration = configuration;
28900
+ this.generation += 1;
28901
+ this.consecutiveFailures = 0;
28902
+ this.clearTimer();
28903
+ this.controller.abort(new Error("device cloud configuration changed"));
28904
+ this.controller = new AbortController();
28905
+ this.inFlight = void 0;
28906
+ this.resetSnapshot();
28907
+ if (this.started && this.options.adapter.isConfigured(configuration)) await this.runNow();
28908
+ }
28909
+ runNow() {
28910
+ if (this.inFlight) return this.inFlight;
28911
+ if (!this.options.adapter.isConfigured(this.configuration)) {
28912
+ this.resetSnapshot();
28913
+ return Promise.resolve();
28914
+ }
28915
+ const generation = this.generation;
28916
+ const configuration = this.configuration;
28917
+ const signal = this.controller.signal;
28918
+ const run = this.maintain(configuration, generation, signal);
28919
+ const tracked = run.finally(() => {
28920
+ if (this.inFlight === tracked) this.inFlight = void 0;
28921
+ });
28922
+ this.inFlight = tracked;
28923
+ return tracked;
28924
+ }
28925
+ async maintain(configuration, generation, signal) {
28926
+ if (!this.isCurrent(generation, signal)) return;
28927
+ await this.updateSnapshot(generation, {
28928
+ status: "connecting",
28929
+ lastError: void 0,
28930
+ nextRetryAt: void 0
28931
+ });
28932
+ try {
28933
+ if (this.snapshotValue.components.authorizer !== "ready") {
28934
+ await this.runStep("authorizer", generation, signal, () => this.options.adapter.ensureAuthorizer(configuration, signal));
28935
+ }
28936
+ if (this.snapshotValue.components.registration !== "ready") {
28937
+ await this.runStep("registration", generation, signal, () => this.options.adapter.registerDevice(configuration, signal));
28938
+ }
28939
+ let relayError;
28940
+ try {
28941
+ await this.runStep("relay", generation, signal, () => this.options.adapter.ensureRelay(configuration, signal));
28942
+ } catch (error2) {
28943
+ relayError = error2;
28944
+ this.options.logWarning?.("Cloud relay maintenance failed", error2);
28945
+ }
28946
+ await this.runStep("heartbeat", generation, signal, () => this.options.adapter.heartbeat(configuration, signal));
28947
+ let directoryError;
28948
+ try {
28949
+ await this.runStep("directory", generation, signal, () => this.options.adapter.syncDirectory(configuration, signal));
28950
+ } catch (error2) {
28951
+ directoryError = error2;
28952
+ this.options.logWarning?.("Cloud directory synchronization failed", error2);
28953
+ }
28954
+ if (!this.isCurrent(generation, signal)) return;
28955
+ this.consecutiveFailures = 0;
28956
+ const relayRequired = this.options.adapter.relayRequiredForOnline(configuration);
28957
+ await this.updateSnapshot(generation, {
28958
+ status: directoryError || relayRequired && relayError ? "degraded" : "online",
28959
+ lastError: directoryError ?? relayError,
28960
+ nextRetryAt: this.now() + this.heartbeatIntervalMs
28961
+ });
28962
+ this.schedule(this.heartbeatIntervalMs, generation);
28963
+ } catch (error2) {
28964
+ if (!this.isCurrent(generation, signal)) return;
28965
+ this.consecutiveFailures += 1;
28966
+ if (this.snapshotValue.components.heartbeat === "failed") {
28967
+ this.snapshotValue.components.registration = "not-run";
28968
+ }
28969
+ const status = this.options.adapter.classifyError?.(error2) ?? "offline";
28970
+ const delay = this.backoffDelay();
28971
+ await this.updateSnapshot(generation, {
28972
+ status,
28973
+ lastError: error2,
28974
+ nextRetryAt: this.now() + delay
28975
+ });
28976
+ this.schedule(delay, generation);
28977
+ throw error2;
28978
+ }
28979
+ }
28980
+ async runStep(component, generation, signal, operation) {
28981
+ if (!this.isCurrent(generation, signal)) return;
28982
+ await this.setComponent(component, "pending", generation);
28983
+ try {
28984
+ const result = await operation();
28985
+ if (!this.isCurrent(generation, signal)) return;
28986
+ await result?.commit?.();
28987
+ if (!this.isCurrent(generation, signal)) return;
28988
+ await this.setComponent(component, "ready", generation);
28989
+ } catch (error2) {
28990
+ if (this.isCurrent(generation, signal)) await this.setComponent(component, "failed", generation);
28991
+ throw error2;
28992
+ }
28993
+ }
28994
+ backoffDelay() {
28995
+ const exponential = Math.min(
28996
+ this.maxBackoffMs,
28997
+ this.initialBackoffMs * 2 ** Math.max(0, this.consecutiveFailures - 1)
28998
+ );
28999
+ const jitter = exponential * this.jitterRatio * (this.random() * 2 - 1);
29000
+ return Math.max(0, Math.round(exponential + jitter));
29001
+ }
29002
+ schedule(delayMs, generation) {
29003
+ if (!this.started || generation !== this.generation) return;
29004
+ this.clearTimer();
29005
+ this.timer = setTimeout(() => {
29006
+ if (generation !== this.generation) return;
29007
+ void this.runNow().catch((error2) => {
29008
+ this.options.logWarning?.("Cloud maintenance failed", error2);
29009
+ });
29010
+ }, delayMs);
29011
+ }
29012
+ clearTimer() {
29013
+ if (this.timer) clearTimeout(this.timer);
29014
+ this.timer = void 0;
29015
+ }
29016
+ isCurrent(generation, signal) {
29017
+ return generation === this.generation && !signal.aborted;
29018
+ }
29019
+ async setComponent(component, status, generation) {
29020
+ if (generation !== this.generation) return;
29021
+ this.snapshotValue = {
29022
+ ...this.snapshotValue,
29023
+ components: { ...this.snapshotValue.components, [component]: status }
29024
+ };
29025
+ await this.emit();
29026
+ }
29027
+ async updateSnapshot(generation, update) {
29028
+ if (generation !== this.generation) return;
29029
+ this.snapshotValue = { ...this.snapshotValue, ...update };
29030
+ await this.emit();
29031
+ }
29032
+ resetSnapshot() {
29033
+ this.snapshotValue = {
29034
+ status: this.options.adapter.isConfigured(this.configuration) ? "offline" : "not-configured",
29035
+ generation: this.generation,
29036
+ components: emptyComponents()
29037
+ };
29038
+ void this.emit();
29039
+ }
29040
+ async emit() {
29041
+ await this.options.onStatus?.(this.snapshot);
29042
+ }
29043
+ };
28752
29044
 
28753
29045
  // src/device-network/deviceOrchestrationTransport.ts
28754
29046
  init_errors();
28755
- var DEVICE_ORCHESTRATION_PROTOCOL = "/memeloop/orchestration/1.0.0";
28756
- var DEFAULT_MAX_FRAME_BYTES = 1024 * 1024;
28757
- function orchestrationTransportError(code, message) {
28758
- return new OrchestrationError({
28759
- code,
28760
- message,
28761
- retryable: code === "UNAVAILABLE"
28762
- });
29047
+
29048
+ // src/device-network/jsonFrame.ts
29049
+ var JsonFrameError = class extends Error {
29050
+ code;
29051
+ constructor(code, message, options) {
29052
+ super(message, options);
29053
+ this.name = "JsonFrameError";
29054
+ this.code = code;
29055
+ }
29056
+ };
29057
+ var HEADER_BYTES = 4;
29058
+ var MAX_UINT32 = 4294967295;
29059
+ function assertPositiveInteger(value, name) {
29060
+ if (!Number.isSafeInteger(value) || value <= 0) {
29061
+ throw new TypeError(`${name} must be a positive safe integer`);
29062
+ }
28763
29063
  }
28764
- function assertFrameLimit(limit) {
28765
- if (!Number.isSafeInteger(limit) || limit < 256) {
28766
- throw new TypeError("maxFrameBytes must be a safe integer of at least 256");
29064
+ function validateMaxPayloadBytes(maxPayloadBytes) {
29065
+ assertPositiveInteger(maxPayloadBytes, "maxPayloadBytes");
29066
+ if (maxPayloadBytes > MAX_UINT32) {
29067
+ throw new TypeError("maxPayloadBytes must fit in an unsigned 32-bit frame length");
28767
29068
  }
28768
29069
  }
28769
- async function* encodeLines(values, maxFrameBytes) {
28770
- const encoder3 = new TextEncoder();
29070
+ function encodeJsonFrame(value, maxPayloadBytes = MAX_UINT32) {
29071
+ validateMaxPayloadBytes(maxPayloadBytes);
29072
+ let serialized;
29073
+ try {
29074
+ serialized = JSON.stringify(value);
29075
+ } catch (error2) {
29076
+ throw new JsonFrameError("INVALID_JSON", "JSON frame value cannot be serialized", {
29077
+ cause: error2
29078
+ });
29079
+ }
29080
+ if (serialized === void 0) {
29081
+ throw new JsonFrameError("INVALID_JSON", "JSON frame value cannot be serialized");
29082
+ }
29083
+ const payload = new TextEncoder().encode(serialized);
29084
+ if (payload.byteLength > maxPayloadBytes) {
29085
+ throw new JsonFrameError(
29086
+ "FRAME_TOO_LARGE",
29087
+ `JSON frame payload exceeds ${maxPayloadBytes} bytes`
29088
+ );
29089
+ }
29090
+ const frame = new Uint8Array(HEADER_BYTES + payload.byteLength);
29091
+ new DataView(frame.buffer, frame.byteOffset, HEADER_BYTES).setUint32(0, payload.byteLength, false);
29092
+ frame.set(payload, HEADER_BYTES);
29093
+ return frame;
29094
+ }
29095
+ async function* encodeJsonFrames(values, maxPayloadBytes = MAX_UINT32) {
28771
29096
  for await (const value of values) {
28772
- const frame = encoder3.encode(`${JSON.stringify(value)}
28773
- `);
28774
- if (frame.byteLength > maxFrameBytes) {
28775
- throw orchestrationTransportError(
28776
- "EXHAUSTED",
28777
- `device orchestration frame exceeds ${maxFrameBytes} bytes`
28778
- );
28779
- }
28780
- yield frame;
29097
+ yield encodeJsonFrame(value, maxPayloadBytes);
28781
29098
  }
28782
29099
  }
28783
- async function* decodeLines(source, maxFrameBytes) {
28784
- const decoder = new TextDecoder();
28785
- let buffered = "";
28786
- for await (const chunk of source) {
28787
- buffered += decoder.decode(chunk, { stream: true });
28788
- for (; ; ) {
28789
- const newline = buffered.indexOf("\n");
28790
- if (newline < 0) break;
28791
- const line = buffered.slice(0, newline);
28792
- buffered = buffered.slice(newline + 1);
28793
- if (!line) continue;
28794
- if (new TextEncoder().encode(line).byteLength > maxFrameBytes) {
28795
- throw orchestrationTransportError(
28796
- "EXHAUSTED",
28797
- `device orchestration frame exceeds ${maxFrameBytes} bytes`
29100
+ function concatBytes(left, right) {
29101
+ if (left.byteLength === 0) return right.slice();
29102
+ if (right.byteLength === 0) return left;
29103
+ const combined = new Uint8Array(left.byteLength + right.byteLength);
29104
+ combined.set(left);
29105
+ combined.set(right, left.byteLength);
29106
+ return combined;
29107
+ }
29108
+ function abortedError(signal) {
29109
+ return new JsonFrameError("ABORTED", "JSON frame reading was aborted", {
29110
+ cause: signal?.reason
29111
+ });
29112
+ }
29113
+ async function readNextChunk(iterator, idleDeadline, totalDeadline, signal) {
29114
+ if (signal?.aborted) throw abortedError(signal);
29115
+ const now = Date.now();
29116
+ const deadline = totalDeadline === void 0 ? idleDeadline : Math.min(idleDeadline, totalDeadline);
29117
+ if (deadline <= now) {
29118
+ throw new JsonFrameError(
29119
+ totalDeadline !== void 0 && totalDeadline <= idleDeadline ? "TOTAL_TIMEOUT" : "IDLE_TIMEOUT",
29120
+ totalDeadline !== void 0 && totalDeadline <= idleDeadline ? "JSON frame total timeout expired" : "JSON frame idle timeout expired"
29121
+ );
29122
+ }
29123
+ return new Promise((resolve, reject2) => {
29124
+ let settled = false;
29125
+ const finish = (callback) => {
29126
+ if (settled) return;
29127
+ settled = true;
29128
+ clearTimeout(timer);
29129
+ signal?.removeEventListener("abort", onAbort);
29130
+ callback();
29131
+ };
29132
+ const onAbort = () => {
29133
+ finish(() => {
29134
+ reject2(abortedError(signal));
29135
+ });
29136
+ };
29137
+ const timer = setTimeout(() => {
29138
+ const currentTime = Date.now();
29139
+ const totalExpired = totalDeadline !== void 0 && currentTime >= totalDeadline;
29140
+ finish(() => {
29141
+ reject2(
29142
+ new JsonFrameError(
29143
+ totalExpired ? "TOTAL_TIMEOUT" : "IDLE_TIMEOUT",
29144
+ totalExpired ? "JSON frame total timeout expired" : "JSON frame idle timeout expired"
29145
+ )
28798
29146
  );
29147
+ });
29148
+ }, Math.max(1, deadline - now));
29149
+ signal?.addEventListener("abort", onAbort, { once: true });
29150
+ void iterator.next().then(
29151
+ (result) => {
29152
+ finish(() => {
29153
+ resolve(result);
29154
+ });
29155
+ },
29156
+ (error2) => {
29157
+ finish(() => {
29158
+ reject2(
29159
+ error2 instanceof Error ? error2 : new Error("JSON frame source failed", {
29160
+ cause: error2
29161
+ })
29162
+ );
29163
+ });
28799
29164
  }
28800
- try {
28801
- yield JSON.parse(line);
28802
- } catch {
28803
- throw orchestrationTransportError(
28804
- "INVALID",
28805
- "device orchestration stream contains invalid JSON"
29165
+ );
29166
+ });
29167
+ }
29168
+ function createJsonFrameReader(source, options) {
29169
+ validateMaxPayloadBytes(options.maxPayloadBytes);
29170
+ assertPositiveInteger(options.idleTimeoutMs, "idleTimeoutMs");
29171
+ assertPositiveInteger(options.totalTimeoutMs, "totalTimeoutMs");
29172
+ return (async function* readFrames() {
29173
+ const iterator = source[Symbol.asyncIterator]();
29174
+ let buffered = new Uint8Array(0);
29175
+ let expectedPayloadBytes;
29176
+ let idleDeadline = Date.now() + options.idleTimeoutMs;
29177
+ let totalDeadline;
29178
+ let abortCalled = false;
29179
+ const abortOnce = async (error2) => {
29180
+ if (abortCalled) return;
29181
+ abortCalled = true;
29182
+ await Promise.resolve(options.abort?.(error2)).catch(() => void 0);
29183
+ };
29184
+ try {
29185
+ for (; ; ) {
29186
+ if (expectedPayloadBytes === void 0 && buffered.byteLength >= HEADER_BYTES) {
29187
+ expectedPayloadBytes = new DataView(
29188
+ buffered.buffer,
29189
+ buffered.byteOffset,
29190
+ HEADER_BYTES
29191
+ ).getUint32(0, false);
29192
+ buffered = buffered.slice(HEADER_BYTES);
29193
+ totalDeadline ??= Date.now() + options.totalTimeoutMs;
29194
+ if (expectedPayloadBytes > options.maxPayloadBytes) {
29195
+ throw new JsonFrameError(
29196
+ "FRAME_TOO_LARGE",
29197
+ `JSON frame payload exceeds ${options.maxPayloadBytes} bytes`
29198
+ );
29199
+ }
29200
+ }
29201
+ if (expectedPayloadBytes !== void 0 && buffered.byteLength >= expectedPayloadBytes) {
29202
+ const payload = buffered.slice(0, expectedPayloadBytes);
29203
+ buffered = buffered.slice(expectedPayloadBytes);
29204
+ expectedPayloadBytes = void 0;
29205
+ totalDeadline = buffered.byteLength > 0 ? Date.now() + options.totalTimeoutMs : void 0;
29206
+ let text;
29207
+ try {
29208
+ text = new TextDecoder("utf-8", { fatal: true }).decode(payload);
29209
+ } catch (error2) {
29210
+ throw new JsonFrameError("INVALID_UTF8", "JSON frame payload is not valid UTF-8", {
29211
+ cause: error2
29212
+ });
29213
+ }
29214
+ let value;
29215
+ try {
29216
+ value = JSON.parse(text);
29217
+ } catch (error2) {
29218
+ throw new JsonFrameError("INVALID_JSON", "JSON frame payload is not valid JSON", {
29219
+ cause: error2
29220
+ });
29221
+ }
29222
+ yield value;
29223
+ idleDeadline = Date.now() + options.idleTimeoutMs;
29224
+ continue;
29225
+ }
29226
+ const result = await readNextChunk(
29227
+ iterator,
29228
+ idleDeadline,
29229
+ totalDeadline,
29230
+ options.signal
28806
29231
  );
29232
+ if (result.done) {
29233
+ if (buffered.byteLength > 0 || expectedPayloadBytes !== void 0) {
29234
+ throw new JsonFrameError("TRUNCATED_FRAME", "JSON frame stream ended mid-frame");
29235
+ }
29236
+ return;
29237
+ }
29238
+ if (!(result.value instanceof Uint8Array)) {
29239
+ throw new TypeError("JSON frame source must yield Uint8Array chunks");
29240
+ }
29241
+ if (result.value.byteLength === 0) continue;
29242
+ const now = Date.now();
29243
+ idleDeadline = now + options.idleTimeoutMs;
29244
+ totalDeadline ??= now + options.totalTimeoutMs;
29245
+ buffered = concatBytes(buffered, result.value);
28807
29246
  }
29247
+ } catch (error2) {
29248
+ const frameError = error2 instanceof JsonFrameError ? error2 : new JsonFrameError("ABORTED", "JSON frame source failed", { cause: error2 });
29249
+ await abortOnce(frameError);
29250
+ throw frameError;
29251
+ } finally {
29252
+ void iterator.return?.().catch(() => void 0);
28808
29253
  }
28809
- if (new TextEncoder().encode(buffered).byteLength > maxFrameBytes) {
28810
- throw orchestrationTransportError(
28811
- "EXHAUSTED",
28812
- `device orchestration frame exceeds ${maxFrameBytes} bytes`
28813
- );
28814
- }
28815
- }
28816
- buffered += decoder.decode();
28817
- if (buffered.trim()) {
28818
- try {
28819
- yield JSON.parse(buffered);
28820
- } catch {
28821
- throw orchestrationTransportError(
28822
- "INVALID",
28823
- "device orchestration stream contains invalid trailing JSON"
28824
- );
28825
- }
29254
+ })();
29255
+ }
29256
+
29257
+ // src/device-network/deviceOrchestrationTransport.ts
29258
+ var DEVICE_ORCHESTRATION_PROTOCOL = "/memeloop/orchestration/2.0.0";
29259
+ var DEFAULT_MAX_FRAME_BYTES = 1024 * 1024;
29260
+ var REQUEST_IDLE_TIMEOUT_MS = 1e4;
29261
+ var REQUEST_TOTAL_TIMEOUT_MS = 3e4;
29262
+ var WATCH_IDLE_TIMEOUT_MS = 9e4;
29263
+ var WATCH_BOOKMARK_INTERVAL_MS = 3e4;
29264
+ function orchestrationTransportError(code, message) {
29265
+ return new OrchestrationError({
29266
+ code,
29267
+ message,
29268
+ retryable: code === "UNAVAILABLE"
29269
+ });
29270
+ }
29271
+ function assertFrameLimit(limit) {
29272
+ if (!Number.isSafeInteger(limit) || limit < 256) {
29273
+ throw new TypeError("maxFrameBytes must be a safe integer of at least 256");
28826
29274
  }
28827
29275
  }
28828
29276
  function throwIfAborted(signal) {
@@ -28843,11 +29291,41 @@ async function openAuthenticatedStream(options) {
28843
29291
  }
28844
29292
  async function writeRequest(stream, request, grant, maxFrameBytes) {
28845
29293
  const envelope = {
28846
- type: "memeloop-device-orchestration-request-v1",
29294
+ type: "memeloop-device-orchestration-request-v2",
28847
29295
  request,
28848
29296
  ...grant ? { grant } : {}
28849
29297
  };
28850
- await stream.sink(encodeLines([envelope], maxFrameBytes));
29298
+ await stream.sink(encodeJsonFrames([envelope], maxFrameBytes));
29299
+ }
29300
+ function abortStreamOnce(stream) {
29301
+ let aborted = false;
29302
+ return async (error2) => {
29303
+ if (aborted) return;
29304
+ aborted = true;
29305
+ await stream.abort(error2);
29306
+ };
29307
+ }
29308
+ function frameReader(stream, maxFrameBytes, idleTimeoutMs, signal, abort) {
29309
+ return createJsonFrameReader(stream.source, {
29310
+ maxPayloadBytes: maxFrameBytes,
29311
+ idleTimeoutMs,
29312
+ totalTimeoutMs: REQUEST_TOTAL_TIMEOUT_MS,
29313
+ signal,
29314
+ abort
29315
+ });
29316
+ }
29317
+ function mapFrameError(error2) {
29318
+ if (!(error2 instanceof JsonFrameError)) throw error2;
29319
+ if (error2.code === "ABORTED") {
29320
+ throw orchestrationTransportError("CANCELLED", error2.message);
29321
+ }
29322
+ if (error2.code === "FRAME_TOO_LARGE") {
29323
+ throw orchestrationTransportError("EXHAUSTED", error2.message);
29324
+ }
29325
+ if (error2.code === "IDLE_TIMEOUT" || error2.code === "TOTAL_TIMEOUT") {
29326
+ throw orchestrationTransportError("UNAVAILABLE", error2.message);
29327
+ }
29328
+ throw orchestrationTransportError("INVALID", error2.message);
28851
29329
  }
28852
29330
  function createDeviceOrchestrationTransport(options) {
28853
29331
  const maxFrameBytes = options.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES;
@@ -28856,8 +29334,9 @@ function createDeviceOrchestrationTransport(options) {
28856
29334
  async request(request, transportOptions = {}) {
28857
29335
  throwIfAborted(transportOptions.signal);
28858
29336
  const { stream, grant } = await openAuthenticatedStream(options);
29337
+ const abort = abortStreamOnce(stream);
28859
29338
  const closeOnAbort = () => {
28860
- void stream.close();
29339
+ void abort(orchestrationTransportError("CANCELLED", "device orchestration request was cancelled"));
28861
29340
  };
28862
29341
  transportOptions.signal?.addEventListener("abort", closeOnAbort, {
28863
29342
  once: true
@@ -28865,7 +29344,13 @@ function createDeviceOrchestrationTransport(options) {
28865
29344
  try {
28866
29345
  await writeRequest(stream, request, grant, maxFrameBytes);
28867
29346
  let response;
28868
- for await (const value of decodeLines(stream.source, maxFrameBytes)) {
29347
+ for await (const value of frameReader(
29348
+ stream,
29349
+ maxFrameBytes,
29350
+ REQUEST_IDLE_TIMEOUT_MS,
29351
+ transportOptions.signal,
29352
+ abort
29353
+ )) {
28869
29354
  if (response) {
28870
29355
  throw orchestrationTransportError(
28871
29356
  "INVALID",
@@ -28882,6 +29367,9 @@ function createDeviceOrchestrationTransport(options) {
28882
29367
  );
28883
29368
  }
28884
29369
  return response;
29370
+ } catch (error2) {
29371
+ if (error2 instanceof JsonFrameError) await abort(error2);
29372
+ mapFrameError(error2);
28885
29373
  } finally {
28886
29374
  transportOptions.signal?.removeEventListener("abort", closeOnAbort);
28887
29375
  await stream.close().catch(() => void 0);
@@ -28890,19 +29378,29 @@ function createDeviceOrchestrationTransport(options) {
28890
29378
  async *watch(request, transportOptions = {}) {
28891
29379
  throwIfAborted(transportOptions.signal);
28892
29380
  const { stream, grant } = await openAuthenticatedStream(options);
29381
+ const abort = abortStreamOnce(stream);
28893
29382
  const closeOnAbort = () => {
28894
- void stream.close();
29383
+ void abort(orchestrationTransportError("CANCELLED", "device orchestration watch was cancelled"));
28895
29384
  };
28896
29385
  transportOptions.signal?.addEventListener("abort", closeOnAbort, {
28897
29386
  once: true
28898
29387
  });
28899
29388
  try {
28900
29389
  await writeRequest(stream, request, grant, maxFrameBytes);
28901
- for await (const value of decodeLines(stream.source, maxFrameBytes)) {
29390
+ for await (const value of frameReader(
29391
+ stream,
29392
+ maxFrameBytes,
29393
+ WATCH_IDLE_TIMEOUT_MS,
29394
+ transportOptions.signal,
29395
+ abort
29396
+ )) {
28902
29397
  throwIfAborted(transportOptions.signal);
28903
29398
  yield value;
28904
29399
  }
28905
29400
  throwIfAborted(transportOptions.signal);
29401
+ } catch (error2) {
29402
+ if (error2 instanceof JsonFrameError) await abort(error2);
29403
+ mapFrameError(error2);
28906
29404
  } finally {
28907
29405
  transportOptions.signal?.removeEventListener("abort", closeOnAbort);
28908
29406
  await stream.close().catch(() => void 0);
@@ -28911,15 +29409,22 @@ function createDeviceOrchestrationTransport(options) {
28911
29409
  };
28912
29410
  }
28913
29411
  async function readSingleRequest(stream, maxFrameBytes) {
29412
+ const abort = abortStreamOnce(stream);
28914
29413
  let envelope;
28915
- for await (const value of decodeLines(stream.source, maxFrameBytes)) {
29414
+ for await (const value of frameReader(
29415
+ stream,
29416
+ maxFrameBytes,
29417
+ REQUEST_IDLE_TIMEOUT_MS,
29418
+ void 0,
29419
+ abort
29420
+ )) {
28916
29421
  if (envelope) {
28917
29422
  throw orchestrationTransportError(
28918
29423
  "INVALID",
28919
29424
  "device orchestration stream accepts exactly one request"
28920
29425
  );
28921
29426
  }
28922
- if (value === null || typeof value !== "object" || Array.isArray(value) || value.type !== "memeloop-device-orchestration-request-v1" || value.request === null || typeof value.request !== "object") {
29427
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value.type !== "memeloop-device-orchestration-request-v2" || value.request === null || typeof value.request !== "object") {
28923
29428
  throw orchestrationTransportError(
28924
29429
  "INVALID",
28925
29430
  "device orchestration request envelope is invalid"
@@ -28932,6 +29437,94 @@ async function readSingleRequest(stream, maxFrameBytes) {
28932
29437
  }
28933
29438
  return envelope;
28934
29439
  }
29440
+ async function* withWatchBookmarks(source, request, signal) {
29441
+ const iterator = source[Symbol.asyncIterator]();
29442
+ let activity;
29443
+ let activityWaiter;
29444
+ let bookmarkTimer;
29445
+ let readInFlight = false;
29446
+ let lastResourceVersion = watchResourceVersion(request.payload.options) ?? "0";
29447
+ const clearBookmarkTimer = () => {
29448
+ if (bookmarkTimer !== void 0) clearTimeout(bookmarkTimer);
29449
+ bookmarkTimer = void 0;
29450
+ };
29451
+ const publishActivity = (value) => {
29452
+ if (activityWaiter) {
29453
+ const resolve = activityWaiter;
29454
+ activityWaiter = void 0;
29455
+ clearBookmarkTimer();
29456
+ resolve(value);
29457
+ return;
29458
+ }
29459
+ activity = value;
29460
+ };
29461
+ const readNext = () => {
29462
+ if (readInFlight) return;
29463
+ readInFlight = true;
29464
+ void iterator.next().then(
29465
+ (value) => {
29466
+ readInFlight = false;
29467
+ publishActivity({ kind: "value", value });
29468
+ },
29469
+ (error2) => {
29470
+ readInFlight = false;
29471
+ publishActivity({ kind: "error", error: error2 });
29472
+ }
29473
+ );
29474
+ };
29475
+ const waitForActivity = () => {
29476
+ if (activity) {
29477
+ const value = activity;
29478
+ activity = void 0;
29479
+ return Promise.resolve(value);
29480
+ }
29481
+ if (signal.aborted) return Promise.resolve({ kind: "aborted" });
29482
+ return new Promise((resolve) => {
29483
+ activityWaiter = resolve;
29484
+ bookmarkTimer = setTimeout(() => {
29485
+ bookmarkTimer = void 0;
29486
+ activityWaiter = void 0;
29487
+ resolve({ kind: "bookmark" });
29488
+ }, WATCH_BOOKMARK_INTERVAL_MS);
29489
+ });
29490
+ };
29491
+ const onAbort = () => {
29492
+ publishActivity({ kind: "aborted" });
29493
+ };
29494
+ signal.addEventListener("abort", onAbort, { once: true });
29495
+ readNext();
29496
+ try {
29497
+ for (; ; ) {
29498
+ const result = await waitForActivity();
29499
+ if (result.kind === "aborted") return;
29500
+ if (result.kind === "bookmark") {
29501
+ yield {
29502
+ protocol: request.protocol,
29503
+ requestId: request.requestId,
29504
+ ok: true,
29505
+ result: { type: "BOOKMARK", resourceVersion: lastResourceVersion }
29506
+ };
29507
+ continue;
29508
+ }
29509
+ if (result.kind === "error") throw result.error;
29510
+ if (result.value.done) return;
29511
+ const response = result.value.value;
29512
+ lastResourceVersion = response.ok ? watchResourceVersion(response.result) ?? lastResourceVersion : lastResourceVersion;
29513
+ yield response;
29514
+ readNext();
29515
+ }
29516
+ } finally {
29517
+ clearBookmarkTimer();
29518
+ activityWaiter = void 0;
29519
+ signal.removeEventListener("abort", onAbort);
29520
+ void iterator.return?.();
29521
+ }
29522
+ }
29523
+ function watchResourceVersion(value) {
29524
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
29525
+ const resourceVersion = value.resourceVersion;
29526
+ return typeof resourceVersion === "string" && resourceVersion.length > 0 ? resourceVersion : void 0;
29527
+ }
28935
29528
  function createDeviceOrchestrationStreamHandler(options) {
28936
29529
  const maxFrameBytes = options.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES;
28937
29530
  assertFrameLimit(maxFrameBytes);
@@ -28946,10 +29539,13 @@ function createDeviceOrchestrationStreamHandler(options) {
28946
29539
  const handler = await options.resolveHandler(remotePeerId);
28947
29540
  if (request.operation === "watch") {
28948
29541
  await stream.sink(
28949
- encodeLines(handler.watch(request, { signal: abort.signal }), maxFrameBytes)
29542
+ encodeJsonFrames(
29543
+ withWatchBookmarks(handler.watch(request, { signal: abort.signal }), request, abort.signal),
29544
+ maxFrameBytes
29545
+ )
28950
29546
  );
28951
29547
  } else {
28952
- await stream.sink(encodeLines([await handler.request(request)], maxFrameBytes));
29548
+ await stream.sink(encodeJsonFrames([await handler.request(request)], maxFrameBytes));
28953
29549
  }
28954
29550
  } finally {
28955
29551
  abort.abort();
@@ -28959,8 +29555,8 @@ function createDeviceOrchestrationStreamHandler(options) {
28959
29555
  }
28960
29556
 
28961
29557
  // src/device-network/libp2pSyncProtocol.ts
28962
- var LIBP2P_SYNC_REQUEST_TYPE = "memeloop-sync-request-v1";
28963
- var LIBP2P_SYNC_RESPONSE_TYPE = "memeloop-sync-response-v1";
29558
+ var LIBP2P_SYNC_REQUEST_TYPE = "memeloop-sync-request-v2";
29559
+ var LIBP2P_SYNC_RESPONSE_TYPE = "memeloop-sync-response-v2";
28964
29560
  var syncMethods = /* @__PURE__ */ new Set([
28965
29561
  "exchangeVersionVector",
28966
29562
  "pullMissingMetadata",
@@ -29020,8 +29616,11 @@ var Libp2pDeviceSyncTransport = class {
29020
29616
  exchangeVersionVector(peerId, localVersion) {
29021
29617
  return this.request(peerId, "exchangeVersionVector", { localVersion });
29022
29618
  }
29023
- pullMissingMetadata(peerId, sinceVersion) {
29024
- return this.request(peerId, "pullMissingMetadata", { sinceVersion });
29619
+ pullMissingMetadata(peerId, sinceVersion, cursor) {
29620
+ return this.request(peerId, "pullMissingMetadata", {
29621
+ sinceVersion,
29622
+ cursor
29623
+ });
29025
29624
  }
29026
29625
  pullMissingMessages(peerId, conversationId, knownMessageIds) {
29027
29626
  return this.request(peerId, "pullMissingMessages", { conversationId, knownMessageIds });
@@ -29032,7 +29631,7 @@ var Libp2pDeviceSyncTransport = class {
29032
29631
  }
29033
29632
  async request(peerId, method, parameters) {
29034
29633
  const grant = await this.grantProvider?.(peerId);
29035
- const stream = await this.deviceNetwork.openStream(peerId, "/memeloop/sync/1.0.0", grant);
29634
+ const stream = await this.deviceNetwork.openStream(peerId, "/memeloop/sync/2.0.0", grant);
29036
29635
  const request = {
29037
29636
  type: LIBP2P_SYNC_REQUEST_TYPE,
29038
29637
  id: crypto.randomUUID(),
@@ -29040,31 +29639,34 @@ var Libp2pDeviceSyncTransport = class {
29040
29639
  params: parameters,
29041
29640
  grant
29042
29641
  };
29043
- await writeJsonToStream(stream, request);
29044
- const response = await readJsonFromStream(stream);
29045
- await stream.close();
29046
- if (!isLibp2pSyncResponse(response)) throw new Error("invalid_sync_response");
29047
- if (response.id !== request.id) throw new Error("sync_response_id_mismatch");
29048
- if (!response.ok) throw new Error(response.error);
29049
- return response.result;
29642
+ try {
29643
+ await stream.sink(encodeJsonFrames([request], 16 * 1024 * 1024));
29644
+ let response;
29645
+ for await (const value of createJsonFrameReader(stream.source, {
29646
+ maxPayloadBytes: 16 * 1024 * 1024,
29647
+ idleTimeoutMs: 15e3,
29648
+ totalTimeoutMs: 12e4,
29649
+ abort: async (error2) => stream.abort(error2)
29650
+ })) {
29651
+ if (response !== void 0) throw new Error("sync_response_multiple");
29652
+ response = value;
29653
+ }
29654
+ if (!isLibp2pSyncResponse(response)) throw new Error("invalid_sync_response");
29655
+ if (response.id !== request.id) throw new Error("sync_response_id_mismatch");
29656
+ if (!response.ok) throw new Error(response.error);
29657
+ return response.result;
29658
+ } catch (error2) {
29659
+ if (error2 instanceof JsonFrameError) await stream.abort(error2);
29660
+ throw error2;
29661
+ } finally {
29662
+ await stream.close().catch(() => void 0);
29663
+ }
29050
29664
  }
29051
29665
  };
29052
- async function writeJsonToStream(stream, message) {
29053
- const payload = new TextEncoder().encode(JSON.stringify(message));
29054
- await stream.sink((async function* () {
29055
- yield payload;
29056
- })());
29057
- }
29058
- async function readJsonFromStream(stream) {
29059
- const reader = stream.source[Symbol.asyncIterator]();
29060
- const result = await reader.next();
29061
- if (result.done || !result.value) throw new Error("sync_response_missing");
29062
- return JSON.parse(new TextDecoder().decode(result.value));
29063
- }
29064
29666
 
29065
29667
  // src/device-network/libp2pRpcProtocol.ts
29066
- var LIBP2P_RPC_REQUEST_TYPE = "memeloop-rpc-request-v1";
29067
- var LIBP2P_RPC_RESPONSE_TYPE = "memeloop-rpc-response-v1";
29668
+ var LIBP2P_RPC_REQUEST_TYPE = "memeloop-rpc-request-v2";
29669
+ var LIBP2P_RPC_RESPONSE_TYPE = "memeloop-rpc-response-v2";
29068
29670
  function isLibp2pRpcRequest(value) {
29069
29671
  if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
29070
29672
  const record2 = value;
@@ -29079,7 +29681,7 @@ function isLibp2pRpcResponse(value) {
29079
29681
  }
29080
29682
 
29081
29683
  // src/device-network/localTrustDeviceAuthorizer.ts
29082
- var PAIRING_PROTOCOL = "/memeloop/pairing/1.0.0";
29684
+ var PAIRING_PROTOCOL = "/memeloop/pairing/2.0.0";
29083
29685
  var LocalTrustDeviceAuthorizer = class {
29084
29686
  constructor(options = {}) {
29085
29687
  this.options = options;
@@ -29241,6 +29843,8 @@ var MemoryDeviceNetworkService = class {
29241
29843
  async sink() {
29242
29844
  },
29243
29845
  async close() {
29846
+ },
29847
+ abort() {
29244
29848
  }
29245
29849
  };
29246
29850
  }
@@ -29273,7 +29877,7 @@ var MemoryDeviceNetworkService = class {
29273
29877
  };
29274
29878
 
29275
29879
  // src/device-network/pairingInvite.ts
29276
- var DEVICE_PAIRING_INVITE_PROTOCOL = "memeloop-device-pairing-v1";
29880
+ var DEVICE_PAIRING_INVITE_PROTOCOL = "memeloop-device-pairing-v2";
29277
29881
  var DEVICE_PAIRING_INVITE_TTL_MS = 5 * 6e4;
29278
29882
  var MAX_INVITE_LENGTH = 16 * 1024;
29279
29883
  var MAX_ADDRESSES = 8;
@@ -29284,37 +29888,82 @@ function requireText(value, name, maxLength) {
29284
29888
  }
29285
29889
  return trimmed;
29286
29890
  }
29287
- function validateAddresses(addresses) {
29891
+ function finalPeerId(address) {
29892
+ const parts = address.split("/");
29893
+ for (let index = parts.length - 2; index >= 0; index -= 1) {
29894
+ if (parts[index] === "p2p") return parts[index + 1];
29895
+ }
29896
+ return void 0;
29897
+ }
29898
+ function validateAddresses(addresses, peerId) {
29288
29899
  const unique = [...new Set(addresses.map((address) => address.trim()))].filter(Boolean);
29289
29900
  if (unique.length === 0 || unique.length > MAX_ADDRESSES || unique.some(
29290
- (address) => address.length > 2048 || !address.startsWith("/") || !address.includes("/ws") && !address.includes("/wss")
29901
+ (address) => address.length > 2048 || !address.startsWith("/") || !address.includes("/ws") && !address.includes("/wss") || finalPeerId(address) !== peerId
29291
29902
  )) {
29292
- throw new Error("device pairing invite requires WebSocket multiaddrs");
29903
+ throw new Error("device pairing invite requires PeerId-bound WebSocket multiaddrs");
29293
29904
  }
29294
29905
  return unique;
29295
29906
  }
29296
- function createDevicePairingInvite(device, options = {}) {
29907
+ function validateLifetime(createdAt, expiresAt, now) {
29908
+ if (!Number.isSafeInteger(createdAt) || !Number.isSafeInteger(expiresAt) || expiresAt <= createdAt || expiresAt - createdAt > DEVICE_PAIRING_INVITE_TTL_MS) {
29909
+ throw new Error("invalid device pairing invite lifetime");
29910
+ }
29911
+ if (expiresAt <= now) throw new Error("device pairing invite has expired");
29912
+ }
29913
+ function unsignedPayload(invite) {
29914
+ const peerId = requireText(invite.peerId, "peerId", 256);
29915
+ return {
29916
+ protocol: DEVICE_PAIRING_INVITE_PROTOCOL,
29917
+ peerId,
29918
+ publicKeyMultibase: requireText(invite.publicKeyMultibase, "publicKeyMultibase", 4096),
29919
+ deviceName: requireText(invite.deviceName, "deviceName", 256),
29920
+ multiaddrs: [...validateAddresses(invite.multiaddrs, peerId)].sort(),
29921
+ createdAt: invite.createdAt,
29922
+ expiresAt: invite.expiresAt
29923
+ };
29924
+ }
29925
+ function canonicalDevicePairingInviteBytes(invite) {
29926
+ const payload = unsignedPayload(invite);
29927
+ const canonical = [
29928
+ DEVICE_PAIRING_INVITE_PROTOCOL,
29929
+ `peerId=${payload.peerId}`,
29930
+ `publicKey=${payload.publicKeyMultibase}`,
29931
+ `deviceName=${payload.deviceName}`,
29932
+ `multiaddrs=${payload.multiaddrs.join(",")}`,
29933
+ `createdAt=${payload.createdAt}`,
29934
+ `expiresAt=${payload.expiresAt}`
29935
+ ].join("\n");
29936
+ return new TextEncoder().encode(canonical);
29937
+ }
29938
+ async function createDevicePairingInvite(device, options) {
29297
29939
  const createdAt = options.now ?? Date.now();
29298
29940
  const ttlMs = options.ttlMs ?? DEVICE_PAIRING_INVITE_TTL_MS;
29299
- if (!Number.isSafeInteger(createdAt) || !Number.isSafeInteger(ttlMs) || ttlMs <= 0 || ttlMs > DEVICE_PAIRING_INVITE_TTL_MS) {
29941
+ if (!Number.isSafeInteger(createdAt) || !Number.isSafeInteger(ttlMs) || ttlMs <= 0) {
29300
29942
  throw new Error("invalid device pairing invite lifetime");
29301
29943
  }
29302
- return {
29944
+ const payload = unsignedPayload({
29303
29945
  protocol: DEVICE_PAIRING_INVITE_PROTOCOL,
29304
- peerId: requireText(device.peerId, "peerId", 256),
29305
- deviceName: requireText(device.displayName, "deviceName", 256),
29306
- multiaddrs: validateAddresses(device.multiaddrs ?? []),
29946
+ peerId: device.peerId,
29947
+ publicKeyMultibase: device.publicKeyMultibase,
29948
+ deviceName: device.displayName,
29949
+ multiaddrs: device.multiaddrs ?? [],
29307
29950
  createdAt,
29308
29951
  expiresAt: createdAt + ttlMs
29309
- };
29952
+ });
29953
+ validateLifetime(payload.createdAt, payload.expiresAt, createdAt - 1);
29954
+ const signature = requireText(
29955
+ await options.sign(canonicalDevicePairingInviteBytes(payload)),
29956
+ "signature",
29957
+ 4096
29958
+ );
29959
+ return { ...payload, signature };
29310
29960
  }
29311
29961
  function encodeDevicePairingInvite(invite) {
29312
- const validated = parseDevicePairingInvite(JSON.stringify(invite), {
29313
- now: invite.createdAt
29314
- });
29315
- return JSON.stringify(validated);
29962
+ const payload = unsignedPayload(invite);
29963
+ const signature = requireText(invite.signature, "signature", 4096);
29964
+ return JSON.stringify({ ...payload, signature });
29316
29965
  }
29317
- function parseDevicePairingInvite(serialized, options = {}) {
29966
+ async function parseDevicePairingInvite(serialized, options) {
29318
29967
  if (!serialized || serialized.length > MAX_INVITE_LENGTH) {
29319
29968
  throw new Error("invalid device pairing invite length");
29320
29969
  }
@@ -29328,24 +29977,34 @@ function parseDevicePairingInvite(serialized, options = {}) {
29328
29977
  throw new Error("invalid device pairing invite");
29329
29978
  }
29330
29979
  const record2 = parsed;
29331
- if (record2.protocol !== DEVICE_PAIRING_INVITE_PROTOCOL || typeof record2.peerId !== "string" || typeof record2.deviceName !== "string" || !Array.isArray(record2.multiaddrs) || record2.multiaddrs.some((address) => typeof address !== "string") || typeof record2.createdAt !== "number" || typeof record2.expiresAt !== "number" || !Number.isSafeInteger(record2.createdAt) || !Number.isSafeInteger(record2.expiresAt) || record2.expiresAt <= record2.createdAt || record2.expiresAt - record2.createdAt > DEVICE_PAIRING_INVITE_TTL_MS) {
29980
+ if (record2.protocol !== DEVICE_PAIRING_INVITE_PROTOCOL || typeof record2.peerId !== "string" || typeof record2.publicKeyMultibase !== "string" || typeof record2.deviceName !== "string" || !Array.isArray(record2.multiaddrs) || record2.multiaddrs.some((address) => typeof address !== "string") || typeof record2.createdAt !== "number" || typeof record2.expiresAt !== "number" || typeof record2.signature !== "string") {
29332
29981
  throw new Error("invalid device pairing invite");
29333
29982
  }
29334
- if (record2.expiresAt <= (options.now ?? Date.now())) {
29335
- throw new Error("device pairing invite has expired");
29336
- }
29337
- return {
29983
+ validateLifetime(record2.createdAt, record2.expiresAt, options.now ?? Date.now());
29984
+ const payload = unsignedPayload({
29338
29985
  protocol: DEVICE_PAIRING_INVITE_PROTOCOL,
29339
- peerId: requireText(record2.peerId, "peerId", 256),
29340
- deviceName: requireText(record2.deviceName, "deviceName", 256),
29341
- multiaddrs: validateAddresses(record2.multiaddrs),
29986
+ peerId: record2.peerId,
29987
+ publicKeyMultibase: record2.publicKeyMultibase,
29988
+ deviceName: record2.deviceName,
29989
+ multiaddrs: record2.multiaddrs,
29342
29990
  createdAt: record2.createdAt,
29343
29991
  expiresAt: record2.expiresAt
29992
+ });
29993
+ const invite = {
29994
+ ...payload,
29995
+ signature: requireText(record2.signature, "signature", 4096)
29344
29996
  };
29997
+ if (!await options.verifyIdentity({
29998
+ invite,
29999
+ payload: canonicalDevicePairingInviteBytes(payload)
30000
+ })) {
30001
+ throw new Error("device pairing invite identity verification failed");
30002
+ }
30003
+ return invite;
29345
30004
  }
29346
30005
 
29347
30006
  // src/device-network/peerDriverTransport.ts
29348
- var PEER_DRIVER_PROTOCOL_VERSION = "memeloop-peer-driver/v1";
30007
+ var PEER_DRIVER_PROTOCOL_VERSION = "memeloop-peer-driver/v2";
29349
30008
  var IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]*$/;
29350
30009
  var MAX_ASSIGNMENT_ID_LENGTH = 256;
29351
30010
  var MAX_OPERATION_LENGTH = 128;
@@ -29484,11 +30143,21 @@ function createPeerDriverRpcHandler(handlers) {
29484
30143
  // src/device-network/syncCloudDevices.ts
29485
30144
  async function syncCloudDevices(input) {
29486
30145
  const cloudDevices = await input.cloudClient.listDevices();
29487
- if (cloudDevices.length === 0) return [];
29488
30146
  const existingRecords = await input.trustStore.loadTrustedDevices();
29489
30147
  const existingByPeerId = new Map(existingRecords.map((r) => [r.peerId, r]));
29490
- for (const cloudDevice of cloudDevices) {
30148
+ const excludedPeerIds = new Set(input.excludePeerIds ?? []);
30149
+ const activeCloudDevices = cloudDevices.filter(
30150
+ (device) => device.revokedAt === void 0 && !excludedPeerIds.has(device.peerId)
30151
+ );
30152
+ const visibleCloudPeerIds = new Set(activeCloudDevices.map((device) => device.peerId));
30153
+ for (const existing of existingRecords) {
30154
+ if (existing.trustMode === "cloud-account" && !visibleCloudPeerIds.has(existing.peerId)) {
30155
+ await input.trustStore.removeTrustedDevice(existing.peerId);
30156
+ }
30157
+ }
30158
+ for (const cloudDevice of activeCloudDevices) {
29491
30159
  const existing = existingByPeerId.get(cloudDevice.peerId);
30160
+ if (existing?.trustMode === "local-pairing") continue;
29492
30161
  const record2 = {
29493
30162
  peerId: cloudDevice.peerId,
29494
30163
  publicKeyMultibase: cloudDevice.publicKeyMultibase,
@@ -29500,17 +30169,14 @@ async function syncCloudDevices(input) {
29500
30169
  lastSeen: cloudDevice.lastSeen,
29501
30170
  revokedAt: cloudDevice.revokedAt
29502
30171
  };
29503
- if (existing) {
29504
- existing.lastSeen = cloudDevice.lastSeen;
29505
- existing.revokedAt = cloudDevice.revokedAt;
29506
- if (existing.publicKeyMultibase !== record2.publicKeyMultibase || existing.deviceName !== record2.deviceName || existing.platform !== record2.platform) {
29507
- await input.trustStore.saveTrustedDevice(record2);
29508
- }
29509
- } else {
30172
+ if (!existing || !sameTrustedDevice(existing, record2)) {
29510
30173
  await input.trustStore.saveTrustedDevice(record2);
29511
30174
  }
29512
30175
  }
29513
- return cloudDevices;
30176
+ return activeCloudDevices;
30177
+ }
30178
+ function sameTrustedDevice(left, right) {
30179
+ return left.peerId === right.peerId && left.publicKeyMultibase === right.publicKeyMultibase && left.deviceName === right.deviceName && left.platform === right.platform && left.trustMode === right.trustMode && left.accountId === right.accountId && left.createdAt === right.createdAt && left.lastSeen === right.lastSeen && left.revokedAt === right.revokedAt;
29514
30180
  }
29515
30181
 
29516
30182
  // src/network/terminalNotifications.ts
@@ -29847,7 +30513,17 @@ async function imSwitchConversation(rt, conversationId) {
29847
30513
  function isConversationMeta(value) {
29848
30514
  if (value === null || typeof value !== "object") return false;
29849
30515
  const o = value;
29850
- return typeof o.conversationId === "string" && typeof o.title === "string" && typeof o.originNodeId === "string" && typeof o.definitionId === "string" && typeof o.messageCount === "number" && typeof o.lastMessageTimestamp === "number" && typeof o.isUserInitiated === "boolean";
30516
+ return typeof o.conversationId === "string" && typeof o.title === "string" && typeof o.originNodeId === "string" && typeof o.originClock === "number" && typeof o.definitionId === "string" && typeof o.messageCount === "number" && typeof o.lastMessageTimestamp === "number" && typeof o.isUserInitiated === "boolean";
30517
+ }
30518
+ function computeMissingVersionRanges(currentVersion, availableVersion) {
30519
+ const ranges = [];
30520
+ for (const [originNodeId, availableClock] of Object.entries(availableVersion)) {
30521
+ const currentClock = currentVersion[originNodeId] ?? 0;
30522
+ if (availableClock > currentClock) {
30523
+ ranges.push({ originNodeId, fromExclusive: currentClock, toInclusive: availableClock });
30524
+ }
30525
+ }
30526
+ return ranges.sort((left, right) => left.originNodeId.localeCompare(right.originNodeId));
29851
30527
  }
29852
30528
 
29853
30529
  // src/index.ts
@@ -31011,11 +31687,13 @@ function getToolDefinition(toolId) {
31011
31687
  DRIVER_MANIFEST_API_VERSION,
31012
31688
  DRIVER_MANIFEST_KIND,
31013
31689
  DRIVER_REQUEST_API_VERSION,
31690
+ DeviceCloudConnectionCoordinator,
31014
31691
  FLEET_ROLLOUT_API_VERSION,
31015
31692
  FLEET_ROLLOUT_KIND,
31016
31693
  HookRegistry,
31017
31694
  IMChannelManager,
31018
31695
  IM_SESSION_TOOL_IDS,
31696
+ JsonFrameError,
31019
31697
  LIBP2P_RPC_REQUEST_TYPE,
31020
31698
  LIBP2P_RPC_RESPONSE_TYPE,
31021
31699
  LIBP2P_SYNC_REQUEST_TYPE,
@@ -31033,6 +31711,7 @@ function getToolDefinition(toolId) {
31033
31711
  MODEL_ENDPOINT_API_VERSION,
31034
31712
  MODEL_ENDPOINT_KIND,
31035
31713
  MemoryDeviceNetworkService,
31714
+ MemoryDeviceSyncStateStore,
31036
31715
  NETWORK_ATTACHMENT_API_VERSION,
31037
31716
  NETWORK_ATTACHMENT_KIND,
31038
31717
  NETWORK_CLASS_API_VERSION,
@@ -31131,6 +31810,7 @@ function getToolDefinition(toolId) {
31131
31810
  canDriverSatisfyClass,
31132
31811
  cancelPendingApprovals,
31133
31812
  canonicalControlStoreValue,
31813
+ canonicalDevicePairingInviteBytes,
31134
31814
  canonicalDriverValue,
31135
31815
  canonicalWorkerBootstrapDescriptorBytes,
31136
31816
  canonicalWorkerProtocolRequestBytes,
@@ -31140,6 +31820,7 @@ function getToolDefinition(toolId) {
31140
31820
  clearHooks,
31141
31821
  clearPluginRegistrations,
31142
31822
  compactMessages,
31823
+ computeMissingVersionRanges,
31143
31824
  consumeWorkloadCapabilityGrant,
31144
31825
  containsSecrets,
31145
31826
  controlStoreApplyMatches,
@@ -31227,6 +31908,7 @@ function getToolDefinition(toolId) {
31227
31908
  createInProcessLoopRuntimeDriver,
31228
31909
  createInProcessToolExecutionDriver,
31229
31910
  createInstanceDeltaFromDefinition,
31911
+ createJsonFrameReader,
31230
31912
  createLoopRuntimeManagementConformanceSuite,
31231
31913
  createManagedArtifactDriverAdapter,
31232
31914
  createManagedCredentialBrokerAdapter,
@@ -31308,6 +31990,8 @@ function getToolDefinition(toolId) {
31308
31990
  detectMimeConfusion,
31309
31991
  digestNormalizedScript,
31310
31992
  encodeDevicePairingInvite,
31993
+ encodeJsonFrame,
31994
+ encodeJsonFrames,
31311
31995
  enrollWorker,
31312
31996
  evaluateApproval,
31313
31997
  evaluateToolAdmission,