memeloop 0.1.2 → 0.2.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 (79) hide show
  1. package/dist/browser.d.cts +14 -14
  2. package/dist/browser.d.ts +14 -14
  3. package/dist/{chunk-MAJWHAKR.js → chunk-5VYZV33O.js} +3 -2
  4. package/dist/{chunk-MAJWHAKR.js.map → chunk-5VYZV33O.js.map} +1 -1
  5. package/dist/chunk-ECNKBH5A.js +1235 -0
  6. package/dist/chunk-ECNKBH5A.js.map +1 -0
  7. package/dist/{chunk-MRKY4HGF.js → chunk-EPHRIRAL.js} +20 -11
  8. package/dist/chunk-EPHRIRAL.js.map +1 -0
  9. package/dist/{chunk-KFS2EJT6.js → chunk-K5KZJNDP.js} +2 -2
  10. package/dist/{chunk-2C6BJZI3.js → chunk-KV6TC5UJ.js} +3 -3
  11. package/dist/{chunk-T7FSXWFR.js → chunk-LGUGYRR4.js} +4 -4
  12. package/dist/{chunk-T7FSXWFR.js.map → chunk-LGUGYRR4.js.map} +1 -1
  13. package/dist/chunk-MXRXYGWW.js +1 -0
  14. package/dist/conversation.d.cts +3 -3
  15. package/dist/conversation.d.ts +3 -3
  16. package/dist/device-network-portable.cjs +757 -144
  17. package/dist/device-network-portable.cjs.map +1 -1
  18. package/dist/device-network-portable.d.cts +24 -14
  19. package/dist/device-network-portable.d.ts +24 -14
  20. package/dist/device-network-portable.js +18 -2
  21. package/dist/device-network.cjs +776 -154
  22. package/dist/device-network.cjs.map +1 -1
  23. package/dist/device-network.d.cts +5 -5
  24. package/dist/device-network.d.ts +5 -5
  25. package/dist/device-network.js +19 -3
  26. package/dist/{fetchProvider-WWnWnQFP.d.ts → fetchProvider-BhqRqi5-.d.ts} +1 -1
  27. package/dist/{fetchProvider-toTHy5Nu.d.cts → fetchProvider-HJ_FkORN.d.cts} +1 -1
  28. package/dist/index.cjs +785 -158
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +16 -16
  31. package/dist/index.d.ts +16 -16
  32. package/dist/index.js +26 -8
  33. package/dist/index.js.map +1 -1
  34. package/dist/{interface-IrKBFtL1.d.ts → interface-3C3eG8Hn.d.cts} +13 -1
  35. package/dist/{interface-IrKBFtL1.d.cts → interface-3C3eG8Hn.d.ts} +13 -1
  36. package/dist/llm-providers.d.cts +2 -2
  37. package/dist/llm-providers.d.ts +2 -2
  38. package/dist/loop-api.cjs +4 -3
  39. package/dist/loop-api.cjs.map +1 -1
  40. package/dist/loop-api.d.cts +7 -7
  41. package/dist/loop-api.d.ts +7 -7
  42. package/dist/loop-api.js +4 -4
  43. package/dist/mobile.cjs +3 -3
  44. package/dist/mobile.cjs.map +1 -1
  45. package/dist/mobile.d.cts +5 -5
  46. package/dist/mobile.d.ts +5 -5
  47. package/dist/mobile.js +2 -2
  48. package/dist/{orchestration-portable-BHB_YrNN.d.ts → orchestration-portable-BbkwLbC9.d.ts} +4 -4
  49. package/dist/{orchestration-portable-mvYBvGBb.d.cts → orchestration-portable-DNKzAtNq.d.cts} +4 -4
  50. package/dist/orchestration-portable.d.cts +5 -5
  51. package/dist/orchestration-portable.d.ts +5 -5
  52. package/dist/pairingInvite-DDx_-IAp.d.cts +271 -0
  53. package/dist/pairingInvite-Dkz8CLsb.d.ts +271 -0
  54. package/dist/{promptConcat-0WNFTg4l.d.ts → promptConcat-CUEIA_3T.d.ts} +3 -3
  55. package/dist/{promptConcat-D6wr5V-V.d.cts → promptConcat-VLe5SEN4.d.cts} +3 -3
  56. package/dist/{providerRegistry-CPsktuiX.d.cts → providerRegistry-BDGGs5gz.d.cts} +2 -2
  57. package/dist/{providerRegistry-Bi6pAvSj.d.ts → providerRegistry-CaFK_aUd.d.ts} +2 -2
  58. package/dist/{registry-D0kbxksn.d.cts → registry-DNSEVYug.d.cts} +3 -3
  59. package/dist/{registry-DDbvhlhs.d.ts → registry-yGnYHWhq.d.ts} +3 -3
  60. package/dist/{remoteClient-CBggnRy3.d.cts → remoteClient-BKmQEQY9.d.cts} +1 -1
  61. package/dist/{remoteClient-B_spK019.d.ts → remoteClient-D5hco6cS.d.ts} +1 -1
  62. package/dist/{runtime-PGWR6FIV.js → runtime-6EELHWVB.js} +3 -3
  63. package/dist/{scriptDeploymentPipeline-CXJcC82H.d.ts → scriptDeploymentPipeline-B1q-UhrO.d.ts} +1 -1
  64. package/dist/{scriptDeploymentPipeline-yG1ZhVVI.d.cts → scriptDeploymentPipeline-XJzQyYys.d.cts} +1 -1
  65. package/dist/{syncCloudDevices-Ds_Kcgrv.d.cts → syncCloudDevices-BFw_c1g5.d.cts} +6 -5
  66. package/dist/{syncCloudDevices-BEqwgrhD.d.ts → syncCloudDevices-C8fCUhnX.d.ts} +6 -5
  67. package/dist/{types-CdFVSntj.d.ts → types-C8gnhG9M.d.cts} +6 -4
  68. package/dist/{types-7pYti-k9.d.cts → types-CcFFIk8W.d.ts} +6 -4
  69. package/package.json +1 -1
  70. package/dist/chunk-426CJUZP.js +0 -630
  71. package/dist/chunk-426CJUZP.js.map +0 -1
  72. package/dist/chunk-MRKY4HGF.js.map +0 -1
  73. package/dist/chunk-VZKQX5PP.js +0 -1
  74. package/dist/pairingInvite-BXPWY3aM.d.cts +0 -149
  75. package/dist/pairingInvite-Cuz0wuq9.d.ts +0 -149
  76. /package/dist/{chunk-KFS2EJT6.js.map → chunk-K5KZJNDP.js.map} +0 -0
  77. /package/dist/{chunk-2C6BJZI3.js.map → chunk-KV6TC5UJ.js.map} +0 -0
  78. /package/dist/{chunk-VZKQX5PP.js.map → chunk-MXRXYGWW.js.map} +0 -0
  79. /package/dist/{runtime-PGWR6FIV.js.map → runtime-6EELHWVB.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -20607,7 +20607,7 @@ var init_builtinProfileSources = __esm({
20607
20607
  ' "name": "\u901A\u7528\u52A9\u624B",',
20608
20608
  ' "description": "\u53EF\u9760\u7684\u901A\u7528\u667A\u80FD\u4F53\uFF0C\u7528\u4E8E\u5BF9\u8BDD\u3001Wiki \u77E5\u8BC6\u5DE5\u4F5C\u3001\u76EE\u6807\u63A8\u8FDB\u548C\u65E5\u5E38\u8BA1\u7B97\u673A\u64CD\u4F5C\u3002",',
20609
20609
  ' "loopId": "agent-tool-loop",',
20610
- ` "systemPrompt": "You are MemeLoop, a reliable general-purpose agent for conversation, knowledge work, goal completion, and everyday computer tasks.\\n\\nOperating contract:\\n- Treat the user request as the active goal. For a task with three or more meaningful steps, use the planning tool actually listed in the tool instructions (for example manage-todo or todoWrite) to create a short plan, keep exactly one item in progress, and update it as work advances. Continue until the goal is achieved or genuinely blocked.\\n- Use tools for actions and for facts that must be read from the user's environment. Never claim that an action succeeded without a successful tool result. Never invent search results, file contents, counts, or completion evidence.\\n- Use the exact tool name and parameter schema shown in the tool list. When calling a tool, output ONLY one tool call in this exact XML format:\\n<tool_use name=\\"TOOL_NAME\\">{\\"param\\":\\"value\\"}</tool_use>\\n The JSON must be valid. Never wrap the call in markdown or add text before or after it. After the tool result, reassess the goal and continue.\\n- If a tool fails, read the error and change the arguments or approach. Do not repeat an identical failed call more than once, and do not retry the same approach more than twice.\\n- For Wiki work, use an injected available workspace name or ID; do not guess one. Search before relying on stored knowledge. For wiki-search, exact-title filter syntax is [title[Exact Title]], tag syntax is [tag[Tag]], and semantic search uses searchType \\"vector\\" with query. A filter search uses searchType \\"filter\\" with filter. After a write, verify important content with a valid exact-title search. If verification fails, report that honestly.\\n- Save durable facts, decisions, plans, and user-requested memories to Wiki when useful. Do not store credentials or other sensitive data unless the user explicitly asks.\\n- For UI or computer actions, inspect the current state before acting, prefer reversible changes, verify the resulting state, and ask before destructive actions or consequential external communication.\\n- Do not ask for confirmation before an explicitly requested reversible action when the target is already known. Ask a concise question only when missing information materially changes the result. Otherwise make safe, explicit assumptions and proceed.\\n- In the final response, lead with the outcome, cite concrete verification, and state any remaining limitation briefly.",`,
20610
+ ` "systemPrompt": "You are MemeLoop, a reliable general-purpose agent for conversation, knowledge work, goal completion, and everyday computer tasks.\\n\\nOperating contract:\\n- Treat the user request as the active goal. For a task with three or more meaningful steps, use an enabled planning tool listed in the injected tool instructions to create a short plan, keep exactly one item in progress, and update it as work advances. Continue until the goal is achieved or genuinely blocked.\\n- Use tools for actions and for facts that must be read from the user's environment. Never claim that an action succeeded without a successful tool result. Never invent search results, file contents, counts, or completion evidence.\\n- Use the exact tool name and parameter schema shown in the tool list. When calling a tool, output ONLY one tool call in this exact XML format:\\n<tool_use name=\\"TOOL_NAME\\">{\\"param\\":\\"value\\"}</tool_use>\\n The JSON must be valid. Never wrap the call in markdown or add text before or after it. After the tool result, reassess the goal and continue.\\n- If a tool fails, read the error and change the arguments or approach. Do not repeat an identical failed call more than once, and do not retry the same approach more than twice.\\n- For Wiki work, use an injected available workspace name or ID; do not guess one. Search before relying on stored knowledge. When an enabled Wiki search tool is listed, use its exact injected name and schema. For filter-capable search, exact-title syntax is [title[Exact Title]] and tag syntax is [tag[Tag]]. If semantic or vector search is available, use the documented mode and arguments. After a write, verify important content with an exact-title search. If verification fails, report that honestly.\\n- Save durable facts, decisions, plans, and user-requested memories to Wiki when useful. Do not store credentials or other sensitive data unless the user explicitly asks.\\n- For UI or computer actions, inspect the current state before acting, prefer reversible changes, verify the resulting state, and ask before destructive actions or consequential external communication.\\n- Do not ask for confirmation before an explicitly requested reversible action when the target is already known. Ask a concise question only when missing information materially changes the result. Otherwise make safe, explicit assumptions and proceed.\\n- In the final response, lead with the outcome, cite concrete verification, and state any remaining limitation briefly.",`,
20611
20611
  ' "tools": [',
20612
20612
  ' "workspacesList",',
20613
20613
  ' "wikiSearch",',
@@ -20694,7 +20694,7 @@ var init_builtinProfileSources = __esm({
20694
20694
  " {",
20695
20695
  ' "id": "builtin-system",',
20696
20696
  ' "role": "system",',
20697
- ` "text": "You are MemeLoop, a reliable general-purpose agent for conversation, knowledge work, goal completion, and everyday computer tasks.\\n\\nOperating contract:\\n- Treat the user request as the active goal. For a task with three or more meaningful steps, use the planning tool actually listed in the tool instructions (for example manage-todo or todoWrite) to create a short plan, keep exactly one item in progress, and update it as work advances. Continue until the goal is achieved or genuinely blocked.\\n- Use tools for actions and for facts that must be read from the user's environment. Never claim that an action succeeded without a successful tool result. Never invent search results, file contents, counts, or completion evidence.\\n- Use the exact tool name and parameter schema shown in the tool list. When calling a tool, output ONLY one tool call in this exact XML format:\\n<tool_use name=\\"TOOL_NAME\\">{\\"param\\":\\"value\\"}</tool_use>\\n The JSON must be valid. Never wrap the call in markdown or add text before or after it. After the tool result, reassess the goal and continue.\\n- If a tool fails, read the error and change the arguments or approach. Do not repeat an identical failed call more than once, and do not retry the same approach more than twice.\\n- For Wiki work, use an injected available workspace name or ID; do not guess one. Search before relying on stored knowledge. For wiki-search, exact-title filter syntax is [title[Exact Title]], tag syntax is [tag[Tag]], and semantic search uses searchType \\"vector\\" with query. A filter search uses searchType \\"filter\\" with filter. After a write, verify important content with a valid exact-title search. If verification fails, report that honestly.\\n- Save durable facts, decisions, plans, and user-requested memories to Wiki when useful. Do not store credentials or other sensitive data unless the user explicitly asks.\\n- For UI or computer actions, inspect the current state before acting, prefer reversible changes, verify the resulting state, and ask before destructive actions or consequential external communication.\\n- Do not ask for confirmation before an explicitly requested reversible action when the target is already known. Ask a concise question only when missing information materially changes the result. Otherwise make safe, explicit assumptions and proceed.\\n- In the final response, lead with the outcome, cite concrete verification, and state any remaining limitation briefly."`,
20697
+ ` "text": "You are MemeLoop, a reliable general-purpose agent for conversation, knowledge work, goal completion, and everyday computer tasks.\\n\\nOperating contract:\\n- Treat the user request as the active goal. For a task with three or more meaningful steps, use an enabled planning tool listed in the injected tool instructions to create a short plan, keep exactly one item in progress, and update it as work advances. Continue until the goal is achieved or genuinely blocked.\\n- Use tools for actions and for facts that must be read from the user's environment. Never claim that an action succeeded without a successful tool result. Never invent search results, file contents, counts, or completion evidence.\\n- Use the exact tool name and parameter schema shown in the tool list. When calling a tool, output ONLY one tool call in this exact XML format:\\n<tool_use name=\\"TOOL_NAME\\">{\\"param\\":\\"value\\"}</tool_use>\\n The JSON must be valid. Never wrap the call in markdown or add text before or after it. After the tool result, reassess the goal and continue.\\n- If a tool fails, read the error and change the arguments or approach. Do not repeat an identical failed call more than once, and do not retry the same approach more than twice.\\n- For Wiki work, use an injected available workspace name or ID; do not guess one. Search before relying on stored knowledge. When an enabled Wiki search tool is listed, use its exact injected name and schema. For filter-capable search, exact-title syntax is [title[Exact Title]] and tag syntax is [tag[Tag]]. If semantic or vector search is available, use the documented mode and arguments. After a write, verify important content with an exact-title search. If verification fails, report that honestly.\\n- Save durable facts, decisions, plans, and user-requested memories to Wiki when useful. Do not store credentials or other sensitive data unless the user explicitly asks.\\n- For UI or computer actions, inspect the current state before acting, prefer reversible changes, verify the resulting state, and ask before destructive actions or consequential external communication.\\n- Do not ask for confirmation before an explicitly requested reversible action when the target is already known. Ask a concise question only when missing information materially changes the result. Otherwise make safe, explicit assumptions and proceed.\\n- In the final response, lead with the outcome, cite concrete verification, and state any remaining limitation briefly."`,
20698
20698
  " }",
20699
20699
  " ],",
20700
20700
  ' "plugins": [{ "toolId": "fullReplacement" }],',
@@ -20706,7 +20706,7 @@ var init_builtinProfileSources = __esm({
20706
20706
  ' "temperature": 0.5,',
20707
20707
  ' "maxTokens": 4096',
20708
20708
  " },",
20709
- ' "version": "1.1.0"',
20709
+ ' "version": "1.1.1"',
20710
20710
  "}",
20711
20711
  ""
20712
20712
  ].join("\n"),
@@ -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
  };
@@ -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,456 @@ 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";
28751
28821
  }
28752
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
+ };
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
+ this.controller.abort(new Error("device cloud coordinator stopped"));
28892
+ await this.inFlight?.catch(() => void 0);
28893
+ }
28894
+ async setConfiguration(configuration) {
28895
+ this.configuration = configuration;
28896
+ this.generation += 1;
28897
+ this.consecutiveFailures = 0;
28898
+ this.clearTimer();
28899
+ this.controller.abort(new Error("device cloud configuration changed"));
28900
+ this.controller = new AbortController();
28901
+ this.inFlight = void 0;
28902
+ this.resetSnapshot();
28903
+ if (this.started && this.options.adapter.isConfigured(configuration)) await this.runNow();
28904
+ }
28905
+ runNow() {
28906
+ if (this.inFlight) return this.inFlight;
28907
+ if (!this.options.adapter.isConfigured(this.configuration)) {
28908
+ this.resetSnapshot();
28909
+ return Promise.resolve();
28910
+ }
28911
+ const generation = this.generation;
28912
+ const configuration = this.configuration;
28913
+ const signal = this.controller.signal;
28914
+ const run = this.maintain(configuration, generation, signal);
28915
+ const tracked = run.finally(() => {
28916
+ if (this.inFlight === tracked) this.inFlight = void 0;
28917
+ });
28918
+ this.inFlight = tracked;
28919
+ return tracked;
28920
+ }
28921
+ async maintain(configuration, generation, signal) {
28922
+ if (!this.isCurrent(generation, signal)) return;
28923
+ await this.updateSnapshot(generation, {
28924
+ status: "connecting",
28925
+ lastError: void 0,
28926
+ nextRetryAt: void 0
28927
+ });
28928
+ try {
28929
+ if (this.snapshotValue.components.authorizer !== "ready") {
28930
+ await this.runStep("authorizer", generation, signal, () => this.options.adapter.ensureAuthorizer(configuration, signal));
28931
+ }
28932
+ if (this.snapshotValue.components.registration !== "ready") {
28933
+ await this.runStep("registration", generation, signal, () => this.options.adapter.registerDevice(configuration, signal));
28934
+ }
28935
+ let relayError;
28936
+ try {
28937
+ await this.runStep("relay", generation, signal, () => this.options.adapter.ensureRelay(configuration, signal));
28938
+ } catch (error2) {
28939
+ relayError = error2;
28940
+ this.options.logWarning?.("Cloud relay maintenance failed", error2);
28941
+ }
28942
+ await this.runStep("heartbeat", generation, signal, () => this.options.adapter.heartbeat(configuration, signal));
28943
+ let directoryError;
28944
+ try {
28945
+ await this.runStep("directory", generation, signal, () => this.options.adapter.syncDirectory(configuration, signal));
28946
+ } catch (error2) {
28947
+ directoryError = error2;
28948
+ this.options.logWarning?.("Cloud directory synchronization failed", error2);
28949
+ }
28950
+ if (!this.isCurrent(generation, signal)) return;
28951
+ this.consecutiveFailures = 0;
28952
+ const relayRequired = this.options.adapter.relayRequiredForOnline(configuration);
28953
+ await this.updateSnapshot(generation, {
28954
+ status: directoryError || relayRequired && relayError ? "degraded" : "online",
28955
+ lastError: directoryError ?? relayError,
28956
+ nextRetryAt: this.now() + this.heartbeatIntervalMs
28957
+ });
28958
+ this.schedule(this.heartbeatIntervalMs, generation);
28959
+ } catch (error2) {
28960
+ if (!this.isCurrent(generation, signal)) return;
28961
+ this.consecutiveFailures += 1;
28962
+ if (this.snapshotValue.components.heartbeat === "failed") {
28963
+ this.snapshotValue.components.registration = "not-run";
28964
+ }
28965
+ const status = this.options.adapter.classifyError?.(error2) ?? "offline";
28966
+ const delay = this.backoffDelay();
28967
+ await this.updateSnapshot(generation, {
28968
+ status,
28969
+ lastError: error2,
28970
+ nextRetryAt: this.now() + delay
28971
+ });
28972
+ this.schedule(delay, generation);
28973
+ throw error2;
28974
+ }
28975
+ }
28976
+ async runStep(component, generation, signal, operation) {
28977
+ if (!this.isCurrent(generation, signal)) return;
28978
+ await this.setComponent(component, "pending", generation);
28979
+ try {
28980
+ const result = await operation();
28981
+ if (!this.isCurrent(generation, signal)) return;
28982
+ await result?.commit?.();
28983
+ if (!this.isCurrent(generation, signal)) return;
28984
+ await this.setComponent(component, "ready", generation);
28985
+ } catch (error2) {
28986
+ if (this.isCurrent(generation, signal)) await this.setComponent(component, "failed", generation);
28987
+ throw error2;
28988
+ }
28989
+ }
28990
+ backoffDelay() {
28991
+ const exponential = Math.min(
28992
+ this.maxBackoffMs,
28993
+ this.initialBackoffMs * 2 ** Math.max(0, this.consecutiveFailures - 1)
28994
+ );
28995
+ const jitter = exponential * this.jitterRatio * (this.random() * 2 - 1);
28996
+ return Math.max(0, Math.round(exponential + jitter));
28997
+ }
28998
+ schedule(delayMs, generation) {
28999
+ if (!this.started || generation !== this.generation) return;
29000
+ this.clearTimer();
29001
+ this.timer = setTimeout(() => {
29002
+ if (generation !== this.generation) return;
29003
+ void this.runNow().catch((error2) => {
29004
+ this.options.logWarning?.("Cloud maintenance failed", error2);
29005
+ });
29006
+ }, delayMs);
29007
+ }
29008
+ clearTimer() {
29009
+ if (this.timer) clearTimeout(this.timer);
29010
+ this.timer = void 0;
29011
+ }
29012
+ isCurrent(generation, signal) {
29013
+ return generation === this.generation && !signal.aborted;
29014
+ }
29015
+ async setComponent(component, status, generation) {
29016
+ if (generation !== this.generation) return;
29017
+ this.snapshotValue = {
29018
+ ...this.snapshotValue,
29019
+ components: { ...this.snapshotValue.components, [component]: status }
29020
+ };
29021
+ await this.emit();
29022
+ }
29023
+ async updateSnapshot(generation, update) {
29024
+ if (generation !== this.generation) return;
29025
+ this.snapshotValue = { ...this.snapshotValue, ...update };
29026
+ await this.emit();
29027
+ }
29028
+ resetSnapshot() {
29029
+ this.snapshotValue = {
29030
+ status: this.options.adapter.isConfigured(this.configuration) ? "offline" : "not-configured",
29031
+ generation: this.generation,
29032
+ components: emptyComponents()
29033
+ };
29034
+ void this.emit();
29035
+ }
29036
+ async emit() {
29037
+ await this.options.onStatus?.(this.snapshot);
29038
+ }
29039
+ };
29040
+
28753
29041
  // src/device-network/deviceOrchestrationTransport.ts
28754
29042
  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
- });
29043
+
29044
+ // src/device-network/jsonFrame.ts
29045
+ var JsonFrameError = class extends Error {
29046
+ code;
29047
+ constructor(code, message, options) {
29048
+ super(message, options);
29049
+ this.name = "JsonFrameError";
29050
+ this.code = code;
29051
+ }
29052
+ };
29053
+ var HEADER_BYTES = 4;
29054
+ var MAX_UINT32 = 4294967295;
29055
+ function assertPositiveInteger(value, name) {
29056
+ if (!Number.isSafeInteger(value) || value <= 0) {
29057
+ throw new TypeError(`${name} must be a positive safe integer`);
29058
+ }
28763
29059
  }
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");
29060
+ function validateMaxPayloadBytes(maxPayloadBytes) {
29061
+ assertPositiveInteger(maxPayloadBytes, "maxPayloadBytes");
29062
+ if (maxPayloadBytes > MAX_UINT32) {
29063
+ throw new TypeError("maxPayloadBytes must fit in an unsigned 32-bit frame length");
28767
29064
  }
28768
29065
  }
28769
- async function* encodeLines(values, maxFrameBytes) {
28770
- const encoder3 = new TextEncoder();
29066
+ function encodeJsonFrame(value, maxPayloadBytes = MAX_UINT32) {
29067
+ validateMaxPayloadBytes(maxPayloadBytes);
29068
+ let serialized;
29069
+ try {
29070
+ serialized = JSON.stringify(value);
29071
+ } catch (error2) {
29072
+ throw new JsonFrameError("INVALID_JSON", "JSON frame value cannot be serialized", {
29073
+ cause: error2
29074
+ });
29075
+ }
29076
+ if (serialized === void 0) {
29077
+ throw new JsonFrameError("INVALID_JSON", "JSON frame value cannot be serialized");
29078
+ }
29079
+ const payload = new TextEncoder().encode(serialized);
29080
+ if (payload.byteLength > maxPayloadBytes) {
29081
+ throw new JsonFrameError(
29082
+ "FRAME_TOO_LARGE",
29083
+ `JSON frame payload exceeds ${maxPayloadBytes} bytes`
29084
+ );
29085
+ }
29086
+ const frame = new Uint8Array(HEADER_BYTES + payload.byteLength);
29087
+ new DataView(frame.buffer, frame.byteOffset, HEADER_BYTES).setUint32(0, payload.byteLength, false);
29088
+ frame.set(payload, HEADER_BYTES);
29089
+ return frame;
29090
+ }
29091
+ async function* encodeJsonFrames(values, maxPayloadBytes = MAX_UINT32) {
28771
29092
  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;
29093
+ yield encodeJsonFrame(value, maxPayloadBytes);
28781
29094
  }
28782
29095
  }
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`
29096
+ function concatBytes(left, right) {
29097
+ if (left.byteLength === 0) return right.slice();
29098
+ if (right.byteLength === 0) return left;
29099
+ const combined = new Uint8Array(left.byteLength + right.byteLength);
29100
+ combined.set(left);
29101
+ combined.set(right, left.byteLength);
29102
+ return combined;
29103
+ }
29104
+ function abortedError(signal) {
29105
+ return new JsonFrameError("ABORTED", "JSON frame reading was aborted", {
29106
+ cause: signal?.reason
29107
+ });
29108
+ }
29109
+ async function readNextChunk(iterator, idleDeadline, totalDeadline, signal) {
29110
+ if (signal?.aborted) throw abortedError(signal);
29111
+ const now = Date.now();
29112
+ const deadline = totalDeadline === void 0 ? idleDeadline : Math.min(idleDeadline, totalDeadline);
29113
+ if (deadline <= now) {
29114
+ throw new JsonFrameError(
29115
+ totalDeadline !== void 0 && totalDeadline <= idleDeadline ? "TOTAL_TIMEOUT" : "IDLE_TIMEOUT",
29116
+ totalDeadline !== void 0 && totalDeadline <= idleDeadline ? "JSON frame total timeout expired" : "JSON frame idle timeout expired"
29117
+ );
29118
+ }
29119
+ return new Promise((resolve, reject2) => {
29120
+ let settled = false;
29121
+ const finish = (callback) => {
29122
+ if (settled) return;
29123
+ settled = true;
29124
+ clearTimeout(timer);
29125
+ signal?.removeEventListener("abort", onAbort);
29126
+ callback();
29127
+ };
29128
+ const onAbort = () => {
29129
+ finish(() => {
29130
+ reject2(abortedError(signal));
29131
+ });
29132
+ };
29133
+ const timer = setTimeout(() => {
29134
+ const currentTime = Date.now();
29135
+ const totalExpired = totalDeadline !== void 0 && currentTime >= totalDeadline;
29136
+ finish(() => {
29137
+ reject2(
29138
+ new JsonFrameError(
29139
+ totalExpired ? "TOTAL_TIMEOUT" : "IDLE_TIMEOUT",
29140
+ totalExpired ? "JSON frame total timeout expired" : "JSON frame idle timeout expired"
29141
+ )
28798
29142
  );
29143
+ });
29144
+ }, Math.max(1, deadline - now));
29145
+ signal?.addEventListener("abort", onAbort, { once: true });
29146
+ void iterator.next().then(
29147
+ (result) => {
29148
+ finish(() => {
29149
+ resolve(result);
29150
+ });
29151
+ },
29152
+ (error2) => {
29153
+ finish(() => {
29154
+ reject2(
29155
+ error2 instanceof Error ? error2 : new Error("JSON frame source failed", {
29156
+ cause: error2
29157
+ })
29158
+ );
29159
+ });
28799
29160
  }
28800
- try {
28801
- yield JSON.parse(line);
28802
- } catch {
28803
- throw orchestrationTransportError(
28804
- "INVALID",
28805
- "device orchestration stream contains invalid JSON"
29161
+ );
29162
+ });
29163
+ }
29164
+ function createJsonFrameReader(source, options) {
29165
+ validateMaxPayloadBytes(options.maxPayloadBytes);
29166
+ assertPositiveInteger(options.idleTimeoutMs, "idleTimeoutMs");
29167
+ assertPositiveInteger(options.totalTimeoutMs, "totalTimeoutMs");
29168
+ return (async function* readFrames() {
29169
+ const iterator = source[Symbol.asyncIterator]();
29170
+ let buffered = new Uint8Array(0);
29171
+ let expectedPayloadBytes;
29172
+ let idleDeadline = Date.now() + options.idleTimeoutMs;
29173
+ let totalDeadline;
29174
+ let abortCalled = false;
29175
+ const abortOnce = async (error2) => {
29176
+ if (abortCalled) return;
29177
+ abortCalled = true;
29178
+ await Promise.resolve(options.abort?.(error2)).catch(() => void 0);
29179
+ };
29180
+ try {
29181
+ for (; ; ) {
29182
+ if (expectedPayloadBytes === void 0 && buffered.byteLength >= HEADER_BYTES) {
29183
+ expectedPayloadBytes = new DataView(
29184
+ buffered.buffer,
29185
+ buffered.byteOffset,
29186
+ HEADER_BYTES
29187
+ ).getUint32(0, false);
29188
+ buffered = buffered.slice(HEADER_BYTES);
29189
+ totalDeadline ??= Date.now() + options.totalTimeoutMs;
29190
+ if (expectedPayloadBytes > options.maxPayloadBytes) {
29191
+ throw new JsonFrameError(
29192
+ "FRAME_TOO_LARGE",
29193
+ `JSON frame payload exceeds ${options.maxPayloadBytes} bytes`
29194
+ );
29195
+ }
29196
+ }
29197
+ if (expectedPayloadBytes !== void 0 && buffered.byteLength >= expectedPayloadBytes) {
29198
+ const payload = buffered.slice(0, expectedPayloadBytes);
29199
+ buffered = buffered.slice(expectedPayloadBytes);
29200
+ expectedPayloadBytes = void 0;
29201
+ totalDeadline = buffered.byteLength > 0 ? Date.now() + options.totalTimeoutMs : void 0;
29202
+ let text;
29203
+ try {
29204
+ text = new TextDecoder("utf-8", { fatal: true }).decode(payload);
29205
+ } catch (error2) {
29206
+ throw new JsonFrameError("INVALID_UTF8", "JSON frame payload is not valid UTF-8", {
29207
+ cause: error2
29208
+ });
29209
+ }
29210
+ let value;
29211
+ try {
29212
+ value = JSON.parse(text);
29213
+ } catch (error2) {
29214
+ throw new JsonFrameError("INVALID_JSON", "JSON frame payload is not valid JSON", {
29215
+ cause: error2
29216
+ });
29217
+ }
29218
+ yield value;
29219
+ idleDeadline = Date.now() + options.idleTimeoutMs;
29220
+ continue;
29221
+ }
29222
+ const result = await readNextChunk(
29223
+ iterator,
29224
+ idleDeadline,
29225
+ totalDeadline,
29226
+ options.signal
28806
29227
  );
29228
+ if (result.done) {
29229
+ if (buffered.byteLength > 0 || expectedPayloadBytes !== void 0) {
29230
+ throw new JsonFrameError("TRUNCATED_FRAME", "JSON frame stream ended mid-frame");
29231
+ }
29232
+ return;
29233
+ }
29234
+ if (!(result.value instanceof Uint8Array)) {
29235
+ throw new TypeError("JSON frame source must yield Uint8Array chunks");
29236
+ }
29237
+ if (result.value.byteLength === 0) continue;
29238
+ const now = Date.now();
29239
+ idleDeadline = now + options.idleTimeoutMs;
29240
+ totalDeadline ??= now + options.totalTimeoutMs;
29241
+ buffered = concatBytes(buffered, result.value);
28807
29242
  }
29243
+ } catch (error2) {
29244
+ const frameError = error2 instanceof JsonFrameError ? error2 : new JsonFrameError("ABORTED", "JSON frame source failed", { cause: error2 });
29245
+ await abortOnce(frameError);
29246
+ throw frameError;
29247
+ } finally {
29248
+ void iterator.return?.().catch(() => void 0);
28808
29249
  }
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
- }
29250
+ })();
29251
+ }
29252
+
29253
+ // src/device-network/deviceOrchestrationTransport.ts
29254
+ var DEVICE_ORCHESTRATION_PROTOCOL = "/memeloop/orchestration/2.0.0";
29255
+ var DEFAULT_MAX_FRAME_BYTES = 1024 * 1024;
29256
+ var REQUEST_IDLE_TIMEOUT_MS = 1e4;
29257
+ var REQUEST_TOTAL_TIMEOUT_MS = 3e4;
29258
+ var WATCH_IDLE_TIMEOUT_MS = 9e4;
29259
+ var WATCH_BOOKMARK_INTERVAL_MS = 3e4;
29260
+ function orchestrationTransportError(code, message) {
29261
+ return new OrchestrationError({
29262
+ code,
29263
+ message,
29264
+ retryable: code === "UNAVAILABLE"
29265
+ });
29266
+ }
29267
+ function assertFrameLimit(limit) {
29268
+ if (!Number.isSafeInteger(limit) || limit < 256) {
29269
+ throw new TypeError("maxFrameBytes must be a safe integer of at least 256");
28826
29270
  }
28827
29271
  }
28828
29272
  function throwIfAborted(signal) {
@@ -28843,11 +29287,41 @@ async function openAuthenticatedStream(options) {
28843
29287
  }
28844
29288
  async function writeRequest(stream, request, grant, maxFrameBytes) {
28845
29289
  const envelope = {
28846
- type: "memeloop-device-orchestration-request-v1",
29290
+ type: "memeloop-device-orchestration-request-v2",
28847
29291
  request,
28848
29292
  ...grant ? { grant } : {}
28849
29293
  };
28850
- await stream.sink(encodeLines([envelope], maxFrameBytes));
29294
+ await stream.sink(encodeJsonFrames([envelope], maxFrameBytes));
29295
+ }
29296
+ function abortStreamOnce(stream) {
29297
+ let aborted = false;
29298
+ return async (error2) => {
29299
+ if (aborted) return;
29300
+ aborted = true;
29301
+ await stream.abort(error2);
29302
+ };
29303
+ }
29304
+ function frameReader(stream, maxFrameBytes, idleTimeoutMs, signal, abort) {
29305
+ return createJsonFrameReader(stream.source, {
29306
+ maxPayloadBytes: maxFrameBytes,
29307
+ idleTimeoutMs,
29308
+ totalTimeoutMs: REQUEST_TOTAL_TIMEOUT_MS,
29309
+ signal,
29310
+ abort
29311
+ });
29312
+ }
29313
+ function mapFrameError(error2) {
29314
+ if (!(error2 instanceof JsonFrameError)) throw error2;
29315
+ if (error2.code === "ABORTED") {
29316
+ throw orchestrationTransportError("CANCELLED", error2.message);
29317
+ }
29318
+ if (error2.code === "FRAME_TOO_LARGE") {
29319
+ throw orchestrationTransportError("EXHAUSTED", error2.message);
29320
+ }
29321
+ if (error2.code === "IDLE_TIMEOUT" || error2.code === "TOTAL_TIMEOUT") {
29322
+ throw orchestrationTransportError("UNAVAILABLE", error2.message);
29323
+ }
29324
+ throw orchestrationTransportError("INVALID", error2.message);
28851
29325
  }
28852
29326
  function createDeviceOrchestrationTransport(options) {
28853
29327
  const maxFrameBytes = options.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES;
@@ -28856,8 +29330,9 @@ function createDeviceOrchestrationTransport(options) {
28856
29330
  async request(request, transportOptions = {}) {
28857
29331
  throwIfAborted(transportOptions.signal);
28858
29332
  const { stream, grant } = await openAuthenticatedStream(options);
29333
+ const abort = abortStreamOnce(stream);
28859
29334
  const closeOnAbort = () => {
28860
- void stream.close();
29335
+ void abort(orchestrationTransportError("CANCELLED", "device orchestration request was cancelled"));
28861
29336
  };
28862
29337
  transportOptions.signal?.addEventListener("abort", closeOnAbort, {
28863
29338
  once: true
@@ -28865,7 +29340,13 @@ function createDeviceOrchestrationTransport(options) {
28865
29340
  try {
28866
29341
  await writeRequest(stream, request, grant, maxFrameBytes);
28867
29342
  let response;
28868
- for await (const value of decodeLines(stream.source, maxFrameBytes)) {
29343
+ for await (const value of frameReader(
29344
+ stream,
29345
+ maxFrameBytes,
29346
+ REQUEST_IDLE_TIMEOUT_MS,
29347
+ transportOptions.signal,
29348
+ abort
29349
+ )) {
28869
29350
  if (response) {
28870
29351
  throw orchestrationTransportError(
28871
29352
  "INVALID",
@@ -28882,6 +29363,9 @@ function createDeviceOrchestrationTransport(options) {
28882
29363
  );
28883
29364
  }
28884
29365
  return response;
29366
+ } catch (error2) {
29367
+ if (error2 instanceof JsonFrameError) await abort(error2);
29368
+ mapFrameError(error2);
28885
29369
  } finally {
28886
29370
  transportOptions.signal?.removeEventListener("abort", closeOnAbort);
28887
29371
  await stream.close().catch(() => void 0);
@@ -28890,19 +29374,29 @@ function createDeviceOrchestrationTransport(options) {
28890
29374
  async *watch(request, transportOptions = {}) {
28891
29375
  throwIfAborted(transportOptions.signal);
28892
29376
  const { stream, grant } = await openAuthenticatedStream(options);
29377
+ const abort = abortStreamOnce(stream);
28893
29378
  const closeOnAbort = () => {
28894
- void stream.close();
29379
+ void abort(orchestrationTransportError("CANCELLED", "device orchestration watch was cancelled"));
28895
29380
  };
28896
29381
  transportOptions.signal?.addEventListener("abort", closeOnAbort, {
28897
29382
  once: true
28898
29383
  });
28899
29384
  try {
28900
29385
  await writeRequest(stream, request, grant, maxFrameBytes);
28901
- for await (const value of decodeLines(stream.source, maxFrameBytes)) {
29386
+ for await (const value of frameReader(
29387
+ stream,
29388
+ maxFrameBytes,
29389
+ WATCH_IDLE_TIMEOUT_MS,
29390
+ transportOptions.signal,
29391
+ abort
29392
+ )) {
28902
29393
  throwIfAborted(transportOptions.signal);
28903
29394
  yield value;
28904
29395
  }
28905
29396
  throwIfAborted(transportOptions.signal);
29397
+ } catch (error2) {
29398
+ if (error2 instanceof JsonFrameError) await abort(error2);
29399
+ mapFrameError(error2);
28906
29400
  } finally {
28907
29401
  transportOptions.signal?.removeEventListener("abort", closeOnAbort);
28908
29402
  await stream.close().catch(() => void 0);
@@ -28911,15 +29405,22 @@ function createDeviceOrchestrationTransport(options) {
28911
29405
  };
28912
29406
  }
28913
29407
  async function readSingleRequest(stream, maxFrameBytes) {
29408
+ const abort = abortStreamOnce(stream);
28914
29409
  let envelope;
28915
- for await (const value of decodeLines(stream.source, maxFrameBytes)) {
29410
+ for await (const value of frameReader(
29411
+ stream,
29412
+ maxFrameBytes,
29413
+ REQUEST_IDLE_TIMEOUT_MS,
29414
+ void 0,
29415
+ abort
29416
+ )) {
28916
29417
  if (envelope) {
28917
29418
  throw orchestrationTransportError(
28918
29419
  "INVALID",
28919
29420
  "device orchestration stream accepts exactly one request"
28920
29421
  );
28921
29422
  }
28922
- if (value === null || typeof value !== "object" || Array.isArray(value) || value.type !== "memeloop-device-orchestration-request-v1" || value.request === null || typeof value.request !== "object") {
29423
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value.type !== "memeloop-device-orchestration-request-v2" || value.request === null || typeof value.request !== "object") {
28923
29424
  throw orchestrationTransportError(
28924
29425
  "INVALID",
28925
29426
  "device orchestration request envelope is invalid"
@@ -28932,6 +29433,41 @@ async function readSingleRequest(stream, maxFrameBytes) {
28932
29433
  }
28933
29434
  return envelope;
28934
29435
  }
29436
+ async function* withWatchBookmarks(source, request, signal) {
29437
+ const iterator = source[Symbol.asyncIterator]();
29438
+ let pending2 = iterator.next();
29439
+ try {
29440
+ for (; ; ) {
29441
+ const result = await Promise.race([
29442
+ pending2.then((value) => ({ kind: "value", value })),
29443
+ new Promise((resolve) => {
29444
+ const timer = setTimeout(() => {
29445
+ resolve({ kind: "bookmark" });
29446
+ }, WATCH_BOOKMARK_INTERVAL_MS);
29447
+ signal.addEventListener("abort", () => {
29448
+ clearTimeout(timer);
29449
+ resolve({ kind: "bookmark" });
29450
+ }, { once: true });
29451
+ })
29452
+ ]);
29453
+ if (signal.aborted) return;
29454
+ if (result.kind === "bookmark") {
29455
+ yield {
29456
+ protocol: request.protocol,
29457
+ requestId: request.requestId,
29458
+ ok: true,
29459
+ result: { type: "BOOKMARK", resourceVersion: String(Date.now()) }
29460
+ };
29461
+ continue;
29462
+ }
29463
+ if (result.value.done) return;
29464
+ yield result.value.value;
29465
+ pending2 = iterator.next();
29466
+ }
29467
+ } finally {
29468
+ void iterator.return?.();
29469
+ }
29470
+ }
28935
29471
  function createDeviceOrchestrationStreamHandler(options) {
28936
29472
  const maxFrameBytes = options.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES;
28937
29473
  assertFrameLimit(maxFrameBytes);
@@ -28946,10 +29482,13 @@ function createDeviceOrchestrationStreamHandler(options) {
28946
29482
  const handler = await options.resolveHandler(remotePeerId);
28947
29483
  if (request.operation === "watch") {
28948
29484
  await stream.sink(
28949
- encodeLines(handler.watch(request, { signal: abort.signal }), maxFrameBytes)
29485
+ encodeJsonFrames(
29486
+ withWatchBookmarks(handler.watch(request, { signal: abort.signal }), request, abort.signal),
29487
+ maxFrameBytes
29488
+ )
28950
29489
  );
28951
29490
  } else {
28952
- await stream.sink(encodeLines([await handler.request(request)], maxFrameBytes));
29491
+ await stream.sink(encodeJsonFrames([await handler.request(request)], maxFrameBytes));
28953
29492
  }
28954
29493
  } finally {
28955
29494
  abort.abort();
@@ -28959,8 +29498,8 @@ function createDeviceOrchestrationStreamHandler(options) {
28959
29498
  }
28960
29499
 
28961
29500
  // 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";
29501
+ var LIBP2P_SYNC_REQUEST_TYPE = "memeloop-sync-request-v2";
29502
+ var LIBP2P_SYNC_RESPONSE_TYPE = "memeloop-sync-response-v2";
28964
29503
  var syncMethods = /* @__PURE__ */ new Set([
28965
29504
  "exchangeVersionVector",
28966
29505
  "pullMissingMetadata",
@@ -29020,8 +29559,11 @@ var Libp2pDeviceSyncTransport = class {
29020
29559
  exchangeVersionVector(peerId, localVersion) {
29021
29560
  return this.request(peerId, "exchangeVersionVector", { localVersion });
29022
29561
  }
29023
- pullMissingMetadata(peerId, sinceVersion) {
29024
- return this.request(peerId, "pullMissingMetadata", { sinceVersion });
29562
+ pullMissingMetadata(peerId, sinceVersion, cursor) {
29563
+ return this.request(peerId, "pullMissingMetadata", {
29564
+ sinceVersion,
29565
+ cursor
29566
+ });
29025
29567
  }
29026
29568
  pullMissingMessages(peerId, conversationId, knownMessageIds) {
29027
29569
  return this.request(peerId, "pullMissingMessages", { conversationId, knownMessageIds });
@@ -29032,7 +29574,7 @@ var Libp2pDeviceSyncTransport = class {
29032
29574
  }
29033
29575
  async request(peerId, method, parameters) {
29034
29576
  const grant = await this.grantProvider?.(peerId);
29035
- const stream = await this.deviceNetwork.openStream(peerId, "/memeloop/sync/1.0.0", grant);
29577
+ const stream = await this.deviceNetwork.openStream(peerId, "/memeloop/sync/2.0.0", grant);
29036
29578
  const request = {
29037
29579
  type: LIBP2P_SYNC_REQUEST_TYPE,
29038
29580
  id: crypto.randomUUID(),
@@ -29040,31 +29582,34 @@ var Libp2pDeviceSyncTransport = class {
29040
29582
  params: parameters,
29041
29583
  grant
29042
29584
  };
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;
29585
+ try {
29586
+ await stream.sink(encodeJsonFrames([request], 16 * 1024 * 1024));
29587
+ let response;
29588
+ for await (const value of createJsonFrameReader(stream.source, {
29589
+ maxPayloadBytes: 16 * 1024 * 1024,
29590
+ idleTimeoutMs: 15e3,
29591
+ totalTimeoutMs: 12e4,
29592
+ abort: async (error2) => stream.abort(error2)
29593
+ })) {
29594
+ if (response !== void 0) throw new Error("sync_response_multiple");
29595
+ response = value;
29596
+ }
29597
+ if (!isLibp2pSyncResponse(response)) throw new Error("invalid_sync_response");
29598
+ if (response.id !== request.id) throw new Error("sync_response_id_mismatch");
29599
+ if (!response.ok) throw new Error(response.error);
29600
+ return response.result;
29601
+ } catch (error2) {
29602
+ if (error2 instanceof JsonFrameError) await stream.abort(error2);
29603
+ throw error2;
29604
+ } finally {
29605
+ await stream.close().catch(() => void 0);
29606
+ }
29050
29607
  }
29051
29608
  };
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
29609
 
29065
29610
  // 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";
29611
+ var LIBP2P_RPC_REQUEST_TYPE = "memeloop-rpc-request-v2";
29612
+ var LIBP2P_RPC_RESPONSE_TYPE = "memeloop-rpc-response-v2";
29068
29613
  function isLibp2pRpcRequest(value) {
29069
29614
  if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
29070
29615
  const record2 = value;
@@ -29079,7 +29624,7 @@ function isLibp2pRpcResponse(value) {
29079
29624
  }
29080
29625
 
29081
29626
  // src/device-network/localTrustDeviceAuthorizer.ts
29082
- var PAIRING_PROTOCOL = "/memeloop/pairing/1.0.0";
29627
+ var PAIRING_PROTOCOL = "/memeloop/pairing/2.0.0";
29083
29628
  var LocalTrustDeviceAuthorizer = class {
29084
29629
  constructor(options = {}) {
29085
29630
  this.options = options;
@@ -29241,6 +29786,8 @@ var MemoryDeviceNetworkService = class {
29241
29786
  async sink() {
29242
29787
  },
29243
29788
  async close() {
29789
+ },
29790
+ abort() {
29244
29791
  }
29245
29792
  };
29246
29793
  }
@@ -29273,7 +29820,7 @@ var MemoryDeviceNetworkService = class {
29273
29820
  };
29274
29821
 
29275
29822
  // src/device-network/pairingInvite.ts
29276
- var DEVICE_PAIRING_INVITE_PROTOCOL = "memeloop-device-pairing-v1";
29823
+ var DEVICE_PAIRING_INVITE_PROTOCOL = "memeloop-device-pairing-v2";
29277
29824
  var DEVICE_PAIRING_INVITE_TTL_MS = 5 * 6e4;
29278
29825
  var MAX_INVITE_LENGTH = 16 * 1024;
29279
29826
  var MAX_ADDRESSES = 8;
@@ -29284,37 +29831,82 @@ function requireText(value, name, maxLength) {
29284
29831
  }
29285
29832
  return trimmed;
29286
29833
  }
29287
- function validateAddresses(addresses) {
29834
+ function finalPeerId(address) {
29835
+ const parts = address.split("/");
29836
+ for (let index = parts.length - 2; index >= 0; index -= 1) {
29837
+ if (parts[index] === "p2p") return parts[index + 1];
29838
+ }
29839
+ return void 0;
29840
+ }
29841
+ function validateAddresses(addresses, peerId) {
29288
29842
  const unique = [...new Set(addresses.map((address) => address.trim()))].filter(Boolean);
29289
29843
  if (unique.length === 0 || unique.length > MAX_ADDRESSES || unique.some(
29290
- (address) => address.length > 2048 || !address.startsWith("/") || !address.includes("/ws") && !address.includes("/wss")
29844
+ (address) => address.length > 2048 || !address.startsWith("/") || !address.includes("/ws") && !address.includes("/wss") || finalPeerId(address) !== peerId
29291
29845
  )) {
29292
- throw new Error("device pairing invite requires WebSocket multiaddrs");
29846
+ throw new Error("device pairing invite requires PeerId-bound WebSocket multiaddrs");
29293
29847
  }
29294
29848
  return unique;
29295
29849
  }
29296
- function createDevicePairingInvite(device, options = {}) {
29850
+ function validateLifetime(createdAt, expiresAt, now) {
29851
+ if (!Number.isSafeInteger(createdAt) || !Number.isSafeInteger(expiresAt) || expiresAt <= createdAt || expiresAt - createdAt > DEVICE_PAIRING_INVITE_TTL_MS) {
29852
+ throw new Error("invalid device pairing invite lifetime");
29853
+ }
29854
+ if (expiresAt <= now) throw new Error("device pairing invite has expired");
29855
+ }
29856
+ function unsignedPayload(invite) {
29857
+ const peerId = requireText(invite.peerId, "peerId", 256);
29858
+ return {
29859
+ protocol: DEVICE_PAIRING_INVITE_PROTOCOL,
29860
+ peerId,
29861
+ publicKeyMultibase: requireText(invite.publicKeyMultibase, "publicKeyMultibase", 4096),
29862
+ deviceName: requireText(invite.deviceName, "deviceName", 256),
29863
+ multiaddrs: [...validateAddresses(invite.multiaddrs, peerId)].sort(),
29864
+ createdAt: invite.createdAt,
29865
+ expiresAt: invite.expiresAt
29866
+ };
29867
+ }
29868
+ function canonicalDevicePairingInviteBytes(invite) {
29869
+ const payload = unsignedPayload(invite);
29870
+ const canonical = [
29871
+ DEVICE_PAIRING_INVITE_PROTOCOL,
29872
+ `peerId=${payload.peerId}`,
29873
+ `publicKey=${payload.publicKeyMultibase}`,
29874
+ `deviceName=${payload.deviceName}`,
29875
+ `multiaddrs=${payload.multiaddrs.join(",")}`,
29876
+ `createdAt=${payload.createdAt}`,
29877
+ `expiresAt=${payload.expiresAt}`
29878
+ ].join("\n");
29879
+ return new TextEncoder().encode(canonical);
29880
+ }
29881
+ async function createDevicePairingInvite(device, options) {
29297
29882
  const createdAt = options.now ?? Date.now();
29298
29883
  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) {
29884
+ if (!Number.isSafeInteger(createdAt) || !Number.isSafeInteger(ttlMs) || ttlMs <= 0) {
29300
29885
  throw new Error("invalid device pairing invite lifetime");
29301
29886
  }
29302
- return {
29887
+ const payload = unsignedPayload({
29303
29888
  protocol: DEVICE_PAIRING_INVITE_PROTOCOL,
29304
- peerId: requireText(device.peerId, "peerId", 256),
29305
- deviceName: requireText(device.displayName, "deviceName", 256),
29306
- multiaddrs: validateAddresses(device.multiaddrs ?? []),
29889
+ peerId: device.peerId,
29890
+ publicKeyMultibase: device.publicKeyMultibase,
29891
+ deviceName: device.displayName,
29892
+ multiaddrs: device.multiaddrs ?? [],
29307
29893
  createdAt,
29308
29894
  expiresAt: createdAt + ttlMs
29309
- };
29895
+ });
29896
+ validateLifetime(payload.createdAt, payload.expiresAt, createdAt - 1);
29897
+ const signature = requireText(
29898
+ await options.sign(canonicalDevicePairingInviteBytes(payload)),
29899
+ "signature",
29900
+ 4096
29901
+ );
29902
+ return { ...payload, signature };
29310
29903
  }
29311
29904
  function encodeDevicePairingInvite(invite) {
29312
- const validated = parseDevicePairingInvite(JSON.stringify(invite), {
29313
- now: invite.createdAt
29314
- });
29315
- return JSON.stringify(validated);
29905
+ const payload = unsignedPayload(invite);
29906
+ const signature = requireText(invite.signature, "signature", 4096);
29907
+ return JSON.stringify({ ...payload, signature });
29316
29908
  }
29317
- function parseDevicePairingInvite(serialized, options = {}) {
29909
+ async function parseDevicePairingInvite(serialized, options) {
29318
29910
  if (!serialized || serialized.length > MAX_INVITE_LENGTH) {
29319
29911
  throw new Error("invalid device pairing invite length");
29320
29912
  }
@@ -29328,20 +29920,30 @@ function parseDevicePairingInvite(serialized, options = {}) {
29328
29920
  throw new Error("invalid device pairing invite");
29329
29921
  }
29330
29922
  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) {
29923
+ 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
29924
  throw new Error("invalid device pairing invite");
29333
29925
  }
29334
- if (record2.expiresAt <= (options.now ?? Date.now())) {
29335
- throw new Error("device pairing invite has expired");
29336
- }
29337
- return {
29926
+ validateLifetime(record2.createdAt, record2.expiresAt, options.now ?? Date.now());
29927
+ const payload = unsignedPayload({
29338
29928
  protocol: DEVICE_PAIRING_INVITE_PROTOCOL,
29339
- peerId: requireText(record2.peerId, "peerId", 256),
29340
- deviceName: requireText(record2.deviceName, "deviceName", 256),
29341
- multiaddrs: validateAddresses(record2.multiaddrs),
29929
+ peerId: record2.peerId,
29930
+ publicKeyMultibase: record2.publicKeyMultibase,
29931
+ deviceName: record2.deviceName,
29932
+ multiaddrs: record2.multiaddrs,
29342
29933
  createdAt: record2.createdAt,
29343
29934
  expiresAt: record2.expiresAt
29935
+ });
29936
+ const invite = {
29937
+ ...payload,
29938
+ signature: requireText(record2.signature, "signature", 4096)
29344
29939
  };
29940
+ if (!await options.verifyIdentity({
29941
+ invite,
29942
+ payload: canonicalDevicePairingInviteBytes(payload)
29943
+ })) {
29944
+ throw new Error("device pairing invite identity verification failed");
29945
+ }
29946
+ return invite;
29345
29947
  }
29346
29948
 
29347
29949
  // src/device-network/peerDriverTransport.ts
@@ -29484,11 +30086,21 @@ function createPeerDriverRpcHandler(handlers) {
29484
30086
  // src/device-network/syncCloudDevices.ts
29485
30087
  async function syncCloudDevices(input) {
29486
30088
  const cloudDevices = await input.cloudClient.listDevices();
29487
- if (cloudDevices.length === 0) return [];
29488
30089
  const existingRecords = await input.trustStore.loadTrustedDevices();
29489
30090
  const existingByPeerId = new Map(existingRecords.map((r) => [r.peerId, r]));
29490
- for (const cloudDevice of cloudDevices) {
30091
+ const excludedPeerIds = new Set(input.excludePeerIds ?? []);
30092
+ const activeCloudDevices = cloudDevices.filter(
30093
+ (device) => device.revokedAt === void 0 && !excludedPeerIds.has(device.peerId)
30094
+ );
30095
+ const visibleCloudPeerIds = new Set(activeCloudDevices.map((device) => device.peerId));
30096
+ for (const existing of existingRecords) {
30097
+ if (existing.trustMode === "cloud-account" && !visibleCloudPeerIds.has(existing.peerId)) {
30098
+ await input.trustStore.removeTrustedDevice(existing.peerId);
30099
+ }
30100
+ }
30101
+ for (const cloudDevice of activeCloudDevices) {
29491
30102
  const existing = existingByPeerId.get(cloudDevice.peerId);
30103
+ if (existing?.trustMode === "local-pairing") continue;
29492
30104
  const record2 = {
29493
30105
  peerId: cloudDevice.peerId,
29494
30106
  publicKeyMultibase: cloudDevice.publicKeyMultibase,
@@ -29500,17 +30112,14 @@ async function syncCloudDevices(input) {
29500
30112
  lastSeen: cloudDevice.lastSeen,
29501
30113
  revokedAt: cloudDevice.revokedAt
29502
30114
  };
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 {
30115
+ if (!existing || !sameTrustedDevice(existing, record2)) {
29510
30116
  await input.trustStore.saveTrustedDevice(record2);
29511
30117
  }
29512
30118
  }
29513
- return cloudDevices;
30119
+ return activeCloudDevices;
30120
+ }
30121
+ function sameTrustedDevice(left, right) {
30122
+ 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
30123
  }
29515
30124
 
29516
30125
  // src/network/terminalNotifications.ts
@@ -29847,7 +30456,17 @@ async function imSwitchConversation(rt, conversationId) {
29847
30456
  function isConversationMeta(value) {
29848
30457
  if (value === null || typeof value !== "object") return false;
29849
30458
  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";
30459
+ 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";
30460
+ }
30461
+ function computeMissingVersionRanges(currentVersion, availableVersion) {
30462
+ const ranges = [];
30463
+ for (const [originNodeId, availableClock] of Object.entries(availableVersion)) {
30464
+ const currentClock = currentVersion[originNodeId] ?? 0;
30465
+ if (availableClock > currentClock) {
30466
+ ranges.push({ originNodeId, fromExclusive: currentClock, toInclusive: availableClock });
30467
+ }
30468
+ }
30469
+ return ranges.sort((left, right) => left.originNodeId.localeCompare(right.originNodeId));
29851
30470
  }
29852
30471
 
29853
30472
  // src/index.ts
@@ -31011,11 +31630,13 @@ function getToolDefinition(toolId) {
31011
31630
  DRIVER_MANIFEST_API_VERSION,
31012
31631
  DRIVER_MANIFEST_KIND,
31013
31632
  DRIVER_REQUEST_API_VERSION,
31633
+ DeviceCloudConnectionCoordinator,
31014
31634
  FLEET_ROLLOUT_API_VERSION,
31015
31635
  FLEET_ROLLOUT_KIND,
31016
31636
  HookRegistry,
31017
31637
  IMChannelManager,
31018
31638
  IM_SESSION_TOOL_IDS,
31639
+ JsonFrameError,
31019
31640
  LIBP2P_RPC_REQUEST_TYPE,
31020
31641
  LIBP2P_RPC_RESPONSE_TYPE,
31021
31642
  LIBP2P_SYNC_REQUEST_TYPE,
@@ -31033,6 +31654,7 @@ function getToolDefinition(toolId) {
31033
31654
  MODEL_ENDPOINT_API_VERSION,
31034
31655
  MODEL_ENDPOINT_KIND,
31035
31656
  MemoryDeviceNetworkService,
31657
+ MemoryDeviceSyncStateStore,
31036
31658
  NETWORK_ATTACHMENT_API_VERSION,
31037
31659
  NETWORK_ATTACHMENT_KIND,
31038
31660
  NETWORK_CLASS_API_VERSION,
@@ -31131,6 +31753,7 @@ function getToolDefinition(toolId) {
31131
31753
  canDriverSatisfyClass,
31132
31754
  cancelPendingApprovals,
31133
31755
  canonicalControlStoreValue,
31756
+ canonicalDevicePairingInviteBytes,
31134
31757
  canonicalDriverValue,
31135
31758
  canonicalWorkerBootstrapDescriptorBytes,
31136
31759
  canonicalWorkerProtocolRequestBytes,
@@ -31140,6 +31763,7 @@ function getToolDefinition(toolId) {
31140
31763
  clearHooks,
31141
31764
  clearPluginRegistrations,
31142
31765
  compactMessages,
31766
+ computeMissingVersionRanges,
31143
31767
  consumeWorkloadCapabilityGrant,
31144
31768
  containsSecrets,
31145
31769
  controlStoreApplyMatches,
@@ -31227,6 +31851,7 @@ function getToolDefinition(toolId) {
31227
31851
  createInProcessLoopRuntimeDriver,
31228
31852
  createInProcessToolExecutionDriver,
31229
31853
  createInstanceDeltaFromDefinition,
31854
+ createJsonFrameReader,
31230
31855
  createLoopRuntimeManagementConformanceSuite,
31231
31856
  createManagedArtifactDriverAdapter,
31232
31857
  createManagedCredentialBrokerAdapter,
@@ -31308,6 +31933,8 @@ function getToolDefinition(toolId) {
31308
31933
  detectMimeConfusion,
31309
31934
  digestNormalizedScript,
31310
31935
  encodeDevicePairingInvite,
31936
+ encodeJsonFrame,
31937
+ encodeJsonFrames,
31311
31938
  enrollWorker,
31312
31939
  evaluateApproval,
31313
31940
  evaluateToolAdmission,