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.
- package/dist/browser.d.cts +14 -14
- package/dist/browser.d.ts +14 -14
- package/dist/{chunk-MAJWHAKR.js → chunk-5VYZV33O.js} +3 -2
- package/dist/{chunk-MAJWHAKR.js.map → chunk-5VYZV33O.js.map} +1 -1
- package/dist/chunk-ECNKBH5A.js +1235 -0
- package/dist/chunk-ECNKBH5A.js.map +1 -0
- package/dist/{chunk-MRKY4HGF.js → chunk-EPHRIRAL.js} +20 -11
- package/dist/chunk-EPHRIRAL.js.map +1 -0
- package/dist/{chunk-KFS2EJT6.js → chunk-K5KZJNDP.js} +2 -2
- package/dist/{chunk-2C6BJZI3.js → chunk-KV6TC5UJ.js} +3 -3
- package/dist/{chunk-T7FSXWFR.js → chunk-LGUGYRR4.js} +4 -4
- package/dist/{chunk-T7FSXWFR.js.map → chunk-LGUGYRR4.js.map} +1 -1
- package/dist/chunk-MXRXYGWW.js +1 -0
- package/dist/conversation.d.cts +3 -3
- package/dist/conversation.d.ts +3 -3
- package/dist/device-network-portable.cjs +757 -144
- package/dist/device-network-portable.cjs.map +1 -1
- package/dist/device-network-portable.d.cts +24 -14
- package/dist/device-network-portable.d.ts +24 -14
- package/dist/device-network-portable.js +18 -2
- package/dist/device-network.cjs +776 -154
- package/dist/device-network.cjs.map +1 -1
- package/dist/device-network.d.cts +5 -5
- package/dist/device-network.d.ts +5 -5
- package/dist/device-network.js +19 -3
- package/dist/{fetchProvider-WWnWnQFP.d.ts → fetchProvider-BhqRqi5-.d.ts} +1 -1
- package/dist/{fetchProvider-toTHy5Nu.d.cts → fetchProvider-HJ_FkORN.d.cts} +1 -1
- package/dist/index.cjs +785 -158
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -16
- package/dist/index.d.ts +16 -16
- package/dist/index.js +26 -8
- package/dist/index.js.map +1 -1
- package/dist/{interface-IrKBFtL1.d.ts → interface-3C3eG8Hn.d.cts} +13 -1
- package/dist/{interface-IrKBFtL1.d.cts → interface-3C3eG8Hn.d.ts} +13 -1
- package/dist/llm-providers.d.cts +2 -2
- package/dist/llm-providers.d.ts +2 -2
- package/dist/loop-api.cjs +4 -3
- package/dist/loop-api.cjs.map +1 -1
- package/dist/loop-api.d.cts +7 -7
- package/dist/loop-api.d.ts +7 -7
- package/dist/loop-api.js +4 -4
- package/dist/mobile.cjs +3 -3
- package/dist/mobile.cjs.map +1 -1
- package/dist/mobile.d.cts +5 -5
- package/dist/mobile.d.ts +5 -5
- package/dist/mobile.js +2 -2
- package/dist/{orchestration-portable-BHB_YrNN.d.ts → orchestration-portable-BbkwLbC9.d.ts} +4 -4
- package/dist/{orchestration-portable-mvYBvGBb.d.cts → orchestration-portable-DNKzAtNq.d.cts} +4 -4
- package/dist/orchestration-portable.d.cts +5 -5
- package/dist/orchestration-portable.d.ts +5 -5
- package/dist/pairingInvite-DDx_-IAp.d.cts +271 -0
- package/dist/pairingInvite-Dkz8CLsb.d.ts +271 -0
- package/dist/{promptConcat-0WNFTg4l.d.ts → promptConcat-CUEIA_3T.d.ts} +3 -3
- package/dist/{promptConcat-D6wr5V-V.d.cts → promptConcat-VLe5SEN4.d.cts} +3 -3
- package/dist/{providerRegistry-CPsktuiX.d.cts → providerRegistry-BDGGs5gz.d.cts} +2 -2
- package/dist/{providerRegistry-Bi6pAvSj.d.ts → providerRegistry-CaFK_aUd.d.ts} +2 -2
- package/dist/{registry-D0kbxksn.d.cts → registry-DNSEVYug.d.cts} +3 -3
- package/dist/{registry-DDbvhlhs.d.ts → registry-yGnYHWhq.d.ts} +3 -3
- package/dist/{remoteClient-CBggnRy3.d.cts → remoteClient-BKmQEQY9.d.cts} +1 -1
- package/dist/{remoteClient-B_spK019.d.ts → remoteClient-D5hco6cS.d.ts} +1 -1
- package/dist/{runtime-PGWR6FIV.js → runtime-6EELHWVB.js} +3 -3
- package/dist/{scriptDeploymentPipeline-CXJcC82H.d.ts → scriptDeploymentPipeline-B1q-UhrO.d.ts} +1 -1
- package/dist/{scriptDeploymentPipeline-yG1ZhVVI.d.cts → scriptDeploymentPipeline-XJzQyYys.d.cts} +1 -1
- package/dist/{syncCloudDevices-Ds_Kcgrv.d.cts → syncCloudDevices-BFw_c1g5.d.cts} +6 -5
- package/dist/{syncCloudDevices-BEqwgrhD.d.ts → syncCloudDevices-C8fCUhnX.d.ts} +6 -5
- package/dist/{types-CdFVSntj.d.ts → types-C8gnhG9M.d.cts} +6 -4
- package/dist/{types-7pYti-k9.d.cts → types-CcFFIk8W.d.ts} +6 -4
- package/package.json +1 -1
- package/dist/chunk-426CJUZP.js +0 -630
- package/dist/chunk-426CJUZP.js.map +0 -1
- package/dist/chunk-MRKY4HGF.js.map +0 -1
- package/dist/chunk-VZKQX5PP.js +0 -1
- package/dist/pairingInvite-BXPWY3aM.d.cts +0 -149
- package/dist/pairingInvite-Cuz0wuq9.d.ts +0 -149
- /package/dist/{chunk-KFS2EJT6.js.map → chunk-K5KZJNDP.js.map} +0 -0
- /package/dist/{chunk-2C6BJZI3.js.map → chunk-KV6TC5UJ.js.map} +0 -0
- /package/dist/{chunk-VZKQX5PP.js.map → chunk-MXRXYGWW.js.map} +0 -0
- /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
|
|
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
|
|
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.
|
|
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.
|
|
27202
|
+
this.stateStore = options.stateStore ?? new MemoryDeviceSyncStateStore();
|
|
27177
27203
|
}
|
|
27178
27204
|
/**
|
|
27179
27205
|
* 在本节点新产生一条消息时调用,增加本节点的 lamportClock。
|
|
27180
27206
|
*/
|
|
27181
27207
|
bumpLocalVersion() {
|
|
27182
|
-
|
|
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
|
|
27194
|
-
|
|
27195
|
-
|
|
27196
|
-
await this.
|
|
27197
|
-
|
|
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
|
-
|
|
27206
|
-
|
|
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
|
-
|
|
28756
|
-
|
|
28757
|
-
|
|
28758
|
-
|
|
28759
|
-
|
|
28760
|
-
message,
|
|
28761
|
-
|
|
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
|
|
28765
|
-
|
|
28766
|
-
|
|
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
|
-
|
|
28770
|
-
|
|
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
|
-
|
|
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
|
-
|
|
28784
|
-
|
|
28785
|
-
|
|
28786
|
-
|
|
28787
|
-
|
|
28788
|
-
|
|
28789
|
-
|
|
28790
|
-
|
|
28791
|
-
|
|
28792
|
-
|
|
28793
|
-
|
|
28794
|
-
|
|
28795
|
-
|
|
28796
|
-
|
|
28797
|
-
|
|
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
|
-
|
|
28801
|
-
|
|
28802
|
-
|
|
28803
|
-
|
|
28804
|
-
|
|
28805
|
-
|
|
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
|
-
|
|
28810
|
-
|
|
28811
|
-
|
|
28812
|
-
|
|
28813
|
-
|
|
28814
|
-
|
|
28815
|
-
|
|
28816
|
-
|
|
28817
|
-
|
|
28818
|
-
|
|
28819
|
-
|
|
28820
|
-
|
|
28821
|
-
|
|
28822
|
-
|
|
28823
|
-
|
|
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-
|
|
29290
|
+
type: "memeloop-device-orchestration-request-v2",
|
|
28847
29291
|
request,
|
|
28848
29292
|
...grant ? { grant } : {}
|
|
28849
29293
|
};
|
|
28850
|
-
await stream.sink(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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
|
-
|
|
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(
|
|
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-
|
|
28963
|
-
var LIBP2P_SYNC_RESPONSE_TYPE = "memeloop-sync-response-
|
|
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", {
|
|
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/
|
|
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
|
-
|
|
29044
|
-
|
|
29045
|
-
|
|
29046
|
-
|
|
29047
|
-
|
|
29048
|
-
|
|
29049
|
-
|
|
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-
|
|
29067
|
-
var LIBP2P_RPC_RESPONSE_TYPE = "memeloop-rpc-response-
|
|
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/
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
29884
|
+
if (!Number.isSafeInteger(createdAt) || !Number.isSafeInteger(ttlMs) || ttlMs <= 0) {
|
|
29300
29885
|
throw new Error("invalid device pairing invite lifetime");
|
|
29301
29886
|
}
|
|
29302
|
-
|
|
29887
|
+
const payload = unsignedPayload({
|
|
29303
29888
|
protocol: DEVICE_PAIRING_INVITE_PROTOCOL,
|
|
29304
|
-
peerId:
|
|
29305
|
-
|
|
29306
|
-
|
|
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
|
|
29313
|
-
|
|
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" ||
|
|
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
|
-
|
|
29335
|
-
|
|
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:
|
|
29340
|
-
|
|
29341
|
-
|
|
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
|
-
|
|
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
|
|
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,
|