@canonmsg/agent-sdk 13.2.1 → 13.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{chunk-TJBG6NKF.js → chunk-B6AOBWNZ.js} +463 -275
- package/dist/chunks/{chunk-POBMFS2K.js → chunk-BQGXOAIW.js} +2 -2
- package/dist/chunks/{chunk-VIS5WXNH.js → chunk-CUPSYLBV.js} +2042 -2026
- package/dist/chunks/{chunk-BPSFHEAA.js → chunk-HFT75BNB.js} +1 -1
- package/dist/chunks/{chunk-2NVLVPSG.js → chunk-NIORQKUI.js} +1 -1
- package/dist/chunks/{chunk-HTLKCUQG.js → chunk-SS22I6HM.js} +291 -59
- package/dist/chunks/{mls-crypto-ZBEC26B5.js → mls-crypto-AW7TY744.js} +2 -2
- package/dist/cli.js +143 -26
- package/dist/index.js +6 -6
- package/dist/internal/agent-tools/communication-tool.d.ts +80 -0
- package/dist/internal/agent-tools/index.d.ts +1 -0
- package/dist/internal/backend-contracts/endpoint.d.ts +25 -0
- package/dist/internal/backend-contracts/mlsDevKeyPackages.d.ts +4 -0
- package/dist/internal/core/client.d.ts +0 -4
- package/dist/internal/core/endpoint-client.d.ts +8 -0
- package/dist/internal/core/index.d.ts +1 -1
- package/dist/internal/core/registration-profile.d.ts +4 -1
- package/dist/internal/core/types.d.ts +3 -1
- package/dist/internal/endpoint/engine.d.ts +32 -3
- package/dist/internal/endpoint/media.d.ts +2 -2
- package/dist/internal/endpoint/mls-dev-groups.d.ts +9 -1
- package/dist/media.js +4 -4
- package/dist/mls-wasm-host.js +3 -3
- package/dist/runner/mls.d.ts +12 -2
- package/dist/runner/runner.d.ts +5 -2
- package/package.json +1 -1
|
@@ -5,8 +5,9 @@ import {
|
|
|
5
5
|
createHttpEndpointTransport,
|
|
6
6
|
createNodeEndpointStore,
|
|
7
7
|
validateEndpointContentBoundary
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-HFT75BNB.js";
|
|
9
9
|
import {
|
|
10
|
+
AGENT_HOME_ELSEWHERE,
|
|
10
11
|
CANON_ENDPOINT_PROTOCOL,
|
|
11
12
|
CANON_VERB_WIRE_SCHEMA_VERSION,
|
|
12
13
|
ENDPOINT_ACTION_PATTERN,
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
MLS_DEV_DISCLOSED_PROCESSING_FIELDS,
|
|
16
17
|
MLS_DEV_GROUP_RECEIPT_CLOCK_SKEW,
|
|
17
18
|
MLS_DEV_INTERACTION_LIMITS,
|
|
19
|
+
MLS_DEV_KEY_PACKAGE_CLAIM_LEASE_MS,
|
|
18
20
|
MLS_DEV_REPLY_QUOTE_MAX_CHARACTERS,
|
|
19
21
|
MLS_DEV_TEXT_PROTOCOL_V3,
|
|
20
22
|
MLS_DEV_UNSUPPORTED_CONTENT_ACTIONS,
|
|
@@ -22,6 +24,7 @@ import {
|
|
|
22
24
|
RUNTIME_CARD_FIELD_TYPES,
|
|
23
25
|
RUNTIME_CARD_LINE_ITEM_COLUMN_TYPES,
|
|
24
26
|
__name,
|
|
27
|
+
agentHomeElsewhereMessage,
|
|
25
28
|
assertMlsDevInteractionPayloadBinding,
|
|
26
29
|
canonicalEndpointJson,
|
|
27
30
|
createMlsDevCredentialSigningInput,
|
|
@@ -36,6 +39,7 @@ import {
|
|
|
36
39
|
getCanonEnvironmentContract,
|
|
37
40
|
isCanonMediaOrigin,
|
|
38
41
|
isCanonUploadUrl,
|
|
42
|
+
isConnectorRelayInstallation,
|
|
39
43
|
isMlsDevInteractionAnswerAllowed,
|
|
40
44
|
isMlsDevTextProtocol,
|
|
41
45
|
isMlsEnvironmentId,
|
|
@@ -104,7 +108,7 @@ import {
|
|
|
104
108
|
readMembershipChangeMetadata,
|
|
105
109
|
resolveAllowedUploadMimeType,
|
|
106
110
|
sanitizeDownloadFileName
|
|
107
|
-
} from "./chunk-
|
|
111
|
+
} from "./chunk-CUPSYLBV.js";
|
|
108
112
|
|
|
109
113
|
// src/media.ts
|
|
110
114
|
import { createWriteStream as createWriteStream2 } from "node:fs";
|
|
@@ -1315,19 +1319,22 @@ async function recordMlsInteractionProjection(tx, event, timestamp) {
|
|
|
1315
1319
|
await tx.put(key2, { event: copyJson(event), state: "settled", reason: "mls_interaction", createdAt: timestamp, updatedAt: timestamp });
|
|
1316
1320
|
}
|
|
1317
1321
|
__name(recordMlsInteractionProjection, "recordMlsInteractionProjection");
|
|
1322
|
+
var WORK_FLOOR_KEY = "recovery/work-floor";
|
|
1323
|
+
var beforeWorkFloor = /* @__PURE__ */ __name((floor, acceptedAt) => !!floor && Date.parse(acceptedAt) < Date.parse(floor.cutoverAt), "beforeWorkFloor");
|
|
1318
1324
|
async function recordVerifiedMlsInboundEvent(tx, event, feedOrdinal, timestamp, own = false) {
|
|
1319
1325
|
if (!event.conversationId || !Number.isSafeInteger(feedOrdinal) || feedOrdinal < 1) throw new Error("Invalid verified MLS input");
|
|
1320
1326
|
const key2 = `inbound/${event.id}`, existing = await tx.get(key2);
|
|
1321
1327
|
if (existing && existing.reason !== "mls_plaintext_withheld") return false;
|
|
1328
|
+
const history = !own && beforeWorkFloor(await tx.get(WORK_FLOOR_KEY), timestamp);
|
|
1322
1329
|
const record6 = {
|
|
1323
1330
|
event: copyJson(event),
|
|
1324
|
-
state: own ? "settled" : "observed",
|
|
1325
|
-
...own ? { reason: "own_mls_message" } : { queueKey: `inbound-ready/${encodeURIComponent(event.conversationId)}/mls/${String(feedOrdinal).padStart(16, "0")}/${encodeURIComponent(event.id)}` },
|
|
1331
|
+
state: own || history ? "settled" : "observed",
|
|
1332
|
+
...own ? { reason: "own_mls_message" } : history ? { reason: "before_take_over" } : { queueKey: `inbound-ready/${encodeURIComponent(event.conversationId)}/mls/${String(feedOrdinal).padStart(16, "0")}/${encodeURIComponent(event.id)}` },
|
|
1326
1333
|
createdAt: timestamp,
|
|
1327
1334
|
updatedAt: timestamp
|
|
1328
1335
|
};
|
|
1329
1336
|
await tx.put(key2, record6);
|
|
1330
|
-
if (!own) await enqueueInbound(tx, record6);
|
|
1337
|
+
if (!own && !history) await enqueueInbound(tx, record6);
|
|
1331
1338
|
return true;
|
|
1332
1339
|
}
|
|
1333
1340
|
__name(recordVerifiedMlsInboundEvent, "recordVerifiedMlsInboundEvent");
|
|
@@ -2261,7 +2268,7 @@ function createEndpointMediaPipeline(options) {
|
|
|
2261
2268
|
const progress = /* @__PURE__ */ __name((phase) => resumeOptions?.onProgress?.({ phase, totalBytes, uploadedBytes: offsets.reduce((sum, offset) => sum + offset, 0) }), "progress");
|
|
2262
2269
|
const publicationOperationId = /* @__PURE__ */ __name(() => `message:${flow.conversationId}:${flow.messageId}${flow.publicationGeneration ? `:g${flow.publicationGeneration}` : ""}`, "publicationOperationId");
|
|
2263
2270
|
const publish = /* @__PURE__ */ __name(async (reconciling = false) => {
|
|
2264
|
-
if (!reconciling && flow.requiresPublicationApproval && !await publicationGuards.get(id2)?.check()) {
|
|
2271
|
+
if (!reconciling && flow.requiresPublicationApproval && !await publicationGuards.get(id2)?.check({ automaticResend: false })) {
|
|
2265
2272
|
throw new EndpointError("Attachment publication requires a current audience approval", { code: "MEDIA_PUBLICATION_APPROVAL_REQUIRED", retryable: true });
|
|
2266
2273
|
}
|
|
2267
2274
|
flow.status = "sending";
|
|
@@ -4969,6 +4976,12 @@ var appIntentKey = /* @__PURE__ */ __name((operationId) => `mls-dev-group/applic
|
|
|
4969
4976
|
var appOperationKey = /* @__PURE__ */ __name((deviceId, operationId) => `mls-dev-group/application-operation/${encodeURIComponent(deviceId)}/${encodeURIComponent(operationId)}`, "appOperationKey");
|
|
4970
4977
|
var acceptedReceiptKey = /* @__PURE__ */ __name((operationId) => `mls-dev-group/accepted-receipt/${encodeURIComponent(operationId)}`, "acceptedReceiptKey");
|
|
4971
4978
|
var appMessageKey = /* @__PURE__ */ __name((cid, messageId) => `mls-dev-group/application-id/${encodeURIComponent(cid)}/${encodeURIComponent(messageId)}`, "appMessageKey");
|
|
4979
|
+
var BEHIND_CODES = ["MLS_DEV_GROUP_HEAD_CHANGED", "MLS_DEV_GROUP_ADMISSION_CHANGED"];
|
|
4980
|
+
var MLS_DEV_RESEND_MAX = 3;
|
|
4981
|
+
var readersOf = /* @__PURE__ */ __name((roster) => [...new Set(roster.flatMap(({ device: { principal, installationId } }) => [
|
|
4982
|
+
`${principal.type}:${principal.id}`,
|
|
4983
|
+
...isConnectorRelayInstallation(installationId) ? [`relay:${installationId}`] : []
|
|
4984
|
+
]))].sort(), "readersOf");
|
|
4972
4985
|
var same6 = /* @__PURE__ */ __name((a, b) => canonicalEndpointJson(a) === canonicalEndpointJson(b), "same");
|
|
4973
4986
|
async function roomRemoval(tx, conversationId) {
|
|
4974
4987
|
const room = await tx.get(`conversation/${conversationId}`);
|
|
@@ -4996,6 +5009,10 @@ function fail6(code = "MLS_DEV_GROUP_STATE_INVALID", retryable = false) {
|
|
|
4996
5009
|
throw new EndpointError(code, { code, retryable });
|
|
4997
5010
|
}
|
|
4998
5011
|
__name(fail6, "fail");
|
|
5012
|
+
function admissionOf(operation) {
|
|
5013
|
+
return operation.request?.body.value?.preconditions?.admissionId;
|
|
5014
|
+
}
|
|
5015
|
+
__name(admissionOf, "admissionOf");
|
|
4999
5016
|
function id(value) {
|
|
5000
5017
|
if (typeof value !== "string" || !ENDPOINT_ID_PATTERN.test(value) || value === "." || value === "..") fail6("INVALID_MLS_DEV_GROUP_INPUT");
|
|
5001
5018
|
return value;
|
|
@@ -5739,9 +5756,10 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5739
5756
|
const room = await tx.get(`conversation/${conversationId}`);
|
|
5740
5757
|
const group = envelope((await readPreparationSnapshot(tx, scope4(conversationId))).protocolState, init.binding, conversationId);
|
|
5741
5758
|
const missed = await tx.get(missedEarlierKey(conversationId)) === true ? { missedEarlier: true } : {};
|
|
5759
|
+
const outside = !!room && !!room.mls && (!group || group.removed);
|
|
5760
|
+
const noted = { ...missed, ...outside ? { outside: true } : {} };
|
|
5742
5761
|
if (room?.mlsAdmissionHold !== true) {
|
|
5743
|
-
|
|
5744
|
-
return Object.freeze({ held: joining, waitingPrincipalIds: Object.freeze(joining ? [init.identity.principalId] : []), ...missed });
|
|
5762
|
+
return Object.freeze({ held: outside, waitingPrincipalIds: Object.freeze(outside ? [init.identity.principalId] : []), ...noted });
|
|
5745
5763
|
}
|
|
5746
5764
|
const accounts = mlsRoomAccounts(room, init.binding.environmentId);
|
|
5747
5765
|
const admitted = new Set((group && !group.removed ? group.roster : []).map((entry) => mlsAccountOf(accounts, entry.device.principal)));
|
|
@@ -5754,7 +5772,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5754
5772
|
joinedAtMs: Number(room.mlsMemberJoinedAtMs),
|
|
5755
5773
|
nowMs: context.clock()
|
|
5756
5774
|
});
|
|
5757
|
-
return Object.freeze({ held: waiting.length > 0, waitingPrincipalIds: Object.freeze(waiting), ...
|
|
5775
|
+
return Object.freeze({ held: waiting.length > 0, waitingPrincipalIds: Object.freeze(waiting), ...noted });
|
|
5758
5776
|
});
|
|
5759
5777
|
}
|
|
5760
5778
|
__name(admissionHold, "admissionHold");
|
|
@@ -5762,7 +5780,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5762
5780
|
if ((await admissionHold(conversationId)).held) fail6("MLS_DEV_GROUP_ADMISSION_PENDING", true);
|
|
5763
5781
|
}
|
|
5764
5782
|
__name(assertNotHeld, "assertNotHeld");
|
|
5765
|
-
async function sendText(input, operationId, guardId) {
|
|
5783
|
+
async function sendText(input, operationId, guardId, resend) {
|
|
5766
5784
|
const cid = input.conversationId, init = await initialized();
|
|
5767
5785
|
const authoredHash = await bridge.hash(canonicalEndpointJson(guardId ? { ...input, guardId } : input));
|
|
5768
5786
|
async function existing(tx) {
|
|
@@ -5774,10 +5792,11 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5774
5792
|
__name(existing, "existing");
|
|
5775
5793
|
const previous = await store.transaction(existing);
|
|
5776
5794
|
if (previous) return previous;
|
|
5777
|
-
if (!await caughtUp(cid)) await refresh(cid);
|
|
5795
|
+
if (resend || !await caughtUp(cid)) await refresh(cid);
|
|
5778
5796
|
await assertNotHeld(cid);
|
|
5779
5797
|
const messageId = input.messageId ?? id(context.ids());
|
|
5780
5798
|
let captured, replyTo;
|
|
5799
|
+
let replaced, written;
|
|
5781
5800
|
const operation = await prepareAndCommitContent({
|
|
5782
5801
|
store,
|
|
5783
5802
|
scope: scope4(cid),
|
|
@@ -5785,7 +5804,9 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5785
5804
|
existing: /* @__PURE__ */ __name(async (tx, state) => {
|
|
5786
5805
|
const previous2 = await existing(tx);
|
|
5787
5806
|
if (previous2) return previous2;
|
|
5788
|
-
|
|
5807
|
+
const mapped = await tx.get(appMessageKey(cid, messageId)), local = await tx.get(mlsDevTextMessageKey(cid, messageId));
|
|
5808
|
+
if (resend ? mapped !== resend.previous.operationId || local?.message.operationId !== resend.previous.operationId || local.feedOrdinal !== null || local.deleted : mapped !== void 0 || local !== void 0) fail6("MLS_DEV_APPLICATION_ID_CONFLICT");
|
|
5809
|
+
written = local?.createdAtMs;
|
|
5789
5810
|
const current = await capture(tx, state, init, cid), group = current.envelope;
|
|
5790
5811
|
if (!isMlsDevTextProtocol(current.protocol)) fail6("MLS_DEV_TEXT_PROTOCOL_UNSUPPORTED");
|
|
5791
5812
|
if ((input.replyTo || input.mentions || input.item) && current.protocol !== MLS_DEV_TEXT_PROTOCOL_V3) fail6("MLS_DEV_TEXT_ONLY");
|
|
@@ -5794,7 +5815,8 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5794
5815
|
if (!Array.isArray(members) || input.mentions.some((mention) => !members.includes(mention))) fail6("MLS_DEV_MENTION_NOT_MEMBER");
|
|
5795
5816
|
}
|
|
5796
5817
|
if (input.item && !canonItemReference(input.item, cid, init.binding.environmentId)) fail6("MLS_DEV_ITEM_INVALID");
|
|
5797
|
-
if (
|
|
5818
|
+
if (resend?.replyTo) replyTo = resend.replyTo;
|
|
5819
|
+
else if (input.replyTo) {
|
|
5798
5820
|
const quote = mlsDevReplyQuote(await tx.get(mlsDevTextMessageKey(cid, input.replyTo)), MLS_DEV_REPLY_QUOTE_MAX_CHARACTERS);
|
|
5799
5821
|
replyTo = { messageId: input.replyTo, quote: quote ?? fail6("MLS_DEV_REPLY_UNAVAILABLE") };
|
|
5800
5822
|
}
|
|
@@ -5802,6 +5824,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5802
5824
|
await assertRoomOpen(tx, cid, group.roster);
|
|
5803
5825
|
if (group.pending) fail6("MLS_DEV_GROUP_PENDING_CONTROL", true);
|
|
5804
5826
|
if (!same6(group.head, current.authority?.marker?.head)) fail6("MLS_DEV_GROUP_HEAD_CHANGED", true);
|
|
5827
|
+
if (resend?.readers && (readersOf(group.roster).some((reader) => !resend.readers.includes(reader)) || current.authority?.admissionId !== admissionOf(resend.previous))) fail6("MLS_DEV_RESEND_AUDIENCE_CHANGED");
|
|
5805
5828
|
captured = current;
|
|
5806
5829
|
return void 0;
|
|
5807
5830
|
}, "existing"),
|
|
@@ -5853,8 +5876,24 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5853
5876
|
commit: /* @__PURE__ */ __name(async (tx, prepared, state) => {
|
|
5854
5877
|
const current = await capture(tx, state, init, cid);
|
|
5855
5878
|
if (current.fingerprint !== prepared.fingerprint) fail6("MLS_DEV_GROUP_DEPENDENCY_CHANGED", true);
|
|
5856
|
-
const
|
|
5857
|
-
|
|
5879
|
+
const timestamp = new Date(context.clock()).toISOString();
|
|
5880
|
+
let queueOrder;
|
|
5881
|
+
if (resend) {
|
|
5882
|
+
const before = await tx.get(`operation/${resend.previous.operationId}`);
|
|
5883
|
+
const settled = before && resend.receipt ? await bridge.settle(tx, before, resend.receipt, false) : before;
|
|
5884
|
+
if (settled?.status !== "rejected" || settled.resentAs) fail6("MLS_DEV_RESEND_UNAVAILABLE");
|
|
5885
|
+
if (resend.receipt) queueOrder = settled.queueOrder;
|
|
5886
|
+
replaced = { ...settled, resentAs: operationId, updatedAt: timestamp };
|
|
5887
|
+
await tx.put(`operation/${settled.operationId}`, replaced);
|
|
5888
|
+
}
|
|
5889
|
+
await tx.put(appIntentKey(operationId), {
|
|
5890
|
+
version: 1,
|
|
5891
|
+
authoredHash,
|
|
5892
|
+
envelope: prepared.wire,
|
|
5893
|
+
envelopeHash: prepared.envelopeHash,
|
|
5894
|
+
readers: resend?.readers ?? readersOf(current.envelope.roster),
|
|
5895
|
+
resends: resend?.resends ?? 0
|
|
5896
|
+
});
|
|
5858
5897
|
await tx.put(appMessageKey(cid, messageId), operationId);
|
|
5859
5898
|
await putMlsDevTextMessage(tx, {
|
|
5860
5899
|
version: 1,
|
|
@@ -5862,7 +5901,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5862
5901
|
senderId: init.binding.principal.id,
|
|
5863
5902
|
senderType: principal.type,
|
|
5864
5903
|
status: "queued",
|
|
5865
|
-
createdAtMs,
|
|
5904
|
+
createdAtMs: written ?? context.clock(),
|
|
5866
5905
|
feedOrdinal: null
|
|
5867
5906
|
});
|
|
5868
5907
|
const media = prepared.text.kind === "item" ? { mediaUploadIds: [prepared.text.item.uploadId] } : {};
|
|
@@ -5875,7 +5914,9 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5875
5914
|
messageId,
|
|
5876
5915
|
fingerprint: prepared.envelopeHash,
|
|
5877
5916
|
ordering: "conversation",
|
|
5917
|
+
...queueOrder === void 0 ? {} : { queueOrder },
|
|
5878
5918
|
...guardId ? { guardId } : {},
|
|
5919
|
+
...resend?.receipt ? { automaticResend: true } : {},
|
|
5879
5920
|
sensitive: false,
|
|
5880
5921
|
status: "queued",
|
|
5881
5922
|
attempts: 0,
|
|
@@ -5893,10 +5934,45 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5893
5934
|
});
|
|
5894
5935
|
}, "commit")
|
|
5895
5936
|
});
|
|
5937
|
+
if (replaced) await bridge.afterReceipt(replaced);
|
|
5896
5938
|
bridge.submitted(operation);
|
|
5897
5939
|
return operation;
|
|
5898
5940
|
}
|
|
5899
5941
|
__name(sendText, "sendText");
|
|
5942
|
+
async function resendText(operation, receipt) {
|
|
5943
|
+
const cid = operation.conversationId, messageId = operation.messageId;
|
|
5944
|
+
if (operation.action !== "mls_application" || !cid || !messageId) return void 0;
|
|
5945
|
+
if (receipt && (receipt.status !== "rejected" || !BEHIND_CODES.includes(receipt.error?.code ?? ""))) return void 0;
|
|
5946
|
+
const { intent: intent2, local } = await store.transaction(async (tx) => ({
|
|
5947
|
+
intent: await tx.get(appIntentKey(operation.operationId)),
|
|
5948
|
+
local: await tx.get(mlsDevTextMessageKey(cid, messageId))
|
|
5949
|
+
}));
|
|
5950
|
+
const message = local?.message;
|
|
5951
|
+
if (!intent2 || !local || local.deleted || local.feedOrdinal !== null || message?.operationId !== operation.operationId || message.kind !== "text" && message.kind !== "item") return void 0;
|
|
5952
|
+
if (receipt && (!intent2.readers || (intent2.resends ?? 0) >= MLS_DEV_RESEND_MAX)) return void 0;
|
|
5953
|
+
const { replyTo, mentions, ...item } = message.kind === "item" ? message.item : { replyTo: message.replyTo, mentions: message.mentions };
|
|
5954
|
+
const replyAuthority = operation.request?.body.value.replyAuthority;
|
|
5955
|
+
const input = {
|
|
5956
|
+
conversationId: cid,
|
|
5957
|
+
messageId,
|
|
5958
|
+
...message.kind === "item" ? { item } : { text: message.text },
|
|
5959
|
+
...replyTo ? { replyTo: replyTo.messageId } : {},
|
|
5960
|
+
...mentions?.length ? { mentions: [...mentions] } : {},
|
|
5961
|
+
...replyAuthority ? { replyAuthority } : {}
|
|
5962
|
+
};
|
|
5963
|
+
const successor = id(await bridge.hash(canonicalEndpointJson(["canon.mls.dev-resend.v1", operation.operationId])));
|
|
5964
|
+
try {
|
|
5965
|
+
return await sendText(input, successor, operation.guardId, {
|
|
5966
|
+
previous: operation,
|
|
5967
|
+
...replyTo ? { replyTo } : {},
|
|
5968
|
+
...receipt ? { receipt, readers: intent2.readers, resends: (intent2.resends ?? 0) + 1 } : { resends: 0 }
|
|
5969
|
+
});
|
|
5970
|
+
} catch (error) {
|
|
5971
|
+
if (!receipt || !(error instanceof EndpointError) || error.retryable || ["ENDPOINT_CLOSED", "PREPARATION_INSTALLATION_FENCED"].includes(error.code)) throw error;
|
|
5972
|
+
return void 0;
|
|
5973
|
+
}
|
|
5974
|
+
}
|
|
5975
|
+
__name(resendText, "resendText");
|
|
5900
5976
|
async function holdText(input, operationId) {
|
|
5901
5977
|
const cid = input.conversationId;
|
|
5902
5978
|
if (input.item || input.replyAuthority || typeof input.text !== "string") return sendText(input, operationId);
|
|
@@ -6743,6 +6819,11 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6743
6819
|
const input = textInput(value), selectedId = id(operationId), selectedGuard = guardId === void 0 ? void 0 : id(guardId);
|
|
6744
6820
|
return run(() => serializeInboundPreparation(store, input.conversationId, () => sendText(input, selectedId, selectedGuard)));
|
|
6745
6821
|
},
|
|
6822
|
+
/** A person's Retry of a text or item Canon refused: encrypted again for the room as it is now (`resendText`). */
|
|
6823
|
+
resendText(operation) {
|
|
6824
|
+
const cid = id(operation.conversationId);
|
|
6825
|
+
return run(() => serializeInboundPreparation(store, cid, () => resendText(operation)));
|
|
6826
|
+
},
|
|
6746
6827
|
/** `sendText` for a person's composer: in a held room the text is kept, not refused (undefined), until `sendHeldTexts`. */
|
|
6747
6828
|
holdText(value, operationId) {
|
|
6748
6829
|
const input = textInput(value), selectedId = id(operationId);
|
|
@@ -6790,9 +6871,9 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6790
6871
|
return receipt;
|
|
6791
6872
|
});
|
|
6792
6873
|
},
|
|
6793
|
-
async settleReceipt(operation, receipt) {
|
|
6874
|
+
async settleReceipt(operation, receipt, options2) {
|
|
6794
6875
|
if (!["mls_group_control", "mls_application"].includes(operation.action)) return false;
|
|
6795
|
-
if (operation.action === "mls_application" && receipt.status !== "succeeded") return
|
|
6876
|
+
if (operation.action === "mls_application" && receipt.status !== "succeeded") return !options2?.reconcileOnly && receipt.status === "rejected" && BEHIND_CODES.includes(receipt.error?.code ?? "") && !!operation.conversationId && await run(() => serializeInboundPreparation(store, operation.conversationId, () => resendText(operation, receipt))) !== void 0;
|
|
6796
6877
|
if (receipt.status === "succeeded" && isMlsDevTextProtocol(await bridge.resolveProtocol?.(operation.conversationId ?? fail6()))) {
|
|
6797
6878
|
const cid = operation.conversationId;
|
|
6798
6879
|
const accepted2 = operation.action === "mls_application" ? parseMlsDevApplicationReceipt(receipt.result) : parseMlsDevGroupAcceptedControl(receipt.result);
|
|
@@ -6878,6 +6959,8 @@ function terminal(status) {
|
|
|
6878
6959
|
return status === "succeeded" || status === "rejected";
|
|
6879
6960
|
}
|
|
6880
6961
|
__name(terminal, "terminal");
|
|
6962
|
+
var TAKE_OVER_OPERATION = "initialize-endpoint:take-over:";
|
|
6963
|
+
var TAKE_OVER_DISCARDED = "A take-over is not sent again by itself; start the runner with --take-over again";
|
|
6881
6964
|
function safeError(error) {
|
|
6882
6965
|
return error instanceof EndpointError ? error : new EndpointError("Canon delivery is deferred until the connection recovers");
|
|
6883
6966
|
}
|
|
@@ -6969,6 +7052,19 @@ function createEndpoint(options) {
|
|
|
6969
7052
|
return identity;
|
|
6970
7053
|
});
|
|
6971
7054
|
void initialization.catch(() => void 0);
|
|
7055
|
+
const opened = initialization.then(() => store.transaction(async (tx) => {
|
|
7056
|
+
for (let after; ; ) {
|
|
7057
|
+
const page = await tx.scan("outbox/", { ...after ? { after } : {}, limit: 100 });
|
|
7058
|
+
for (const entry of page) {
|
|
7059
|
+
if (!entry.value.operationId.startsWith(TAKE_OVER_OPERATION)) continue;
|
|
7060
|
+
const operation = await tx.get(key2(entry.value.operationId));
|
|
7061
|
+
if (operation && !terminal(operation.status)) await discardOperation(tx, operation, TAKE_OVER_DISCARDED);
|
|
7062
|
+
}
|
|
7063
|
+
if (page.length < 100) return;
|
|
7064
|
+
after = page.at(-1).key;
|
|
7065
|
+
}
|
|
7066
|
+
}));
|
|
7067
|
+
void opened.catch(() => void 0);
|
|
6972
7068
|
let mlsPrincipal;
|
|
6973
7069
|
function readMlsPrincipalId() {
|
|
6974
7070
|
mlsPrincipal ??= (async () => {
|
|
@@ -7180,6 +7276,7 @@ function createEndpoint(options) {
|
|
|
7180
7276
|
const admissionTimers = /* @__PURE__ */ new Map();
|
|
7181
7277
|
const ADMISSION_IDLE_MS = 10 * 6e4, CLAIM_JOURNAL_KEEP_MS = 60 * 6e4;
|
|
7182
7278
|
const admissionUnfinished = /* @__PURE__ */ new Set();
|
|
7279
|
+
const announcementLooks = /* @__PURE__ */ new Map();
|
|
7183
7280
|
const claimAdds = /* @__PURE__ */ new Map(), CLAIM_ADDS_MAX = 3;
|
|
7184
7281
|
const admissionRetry = { initial: options.mlsAdmissionRetryMs?.initial ?? 3e4, maximum: options.mlsAdmissionRetryMs?.maximum ?? 12e4 };
|
|
7185
7282
|
function scheduleAdmission(conversationId, delayMs, discover = false) {
|
|
@@ -7206,13 +7303,24 @@ function createEndpoint(options) {
|
|
|
7206
7303
|
const inRoster = new Set(group.roster.map((entry) => mlsAccountOf(accounts, entry.device.principal)));
|
|
7207
7304
|
const invitee = local?.mlsInviteeId;
|
|
7208
7305
|
const missing = [...local?.memberIds ?? [], ...typeof invitee === "string" ? [invitee] : []].some((memberId) => !inRoster.has(memberId)) || admissionUnfinished.has(conversationId);
|
|
7209
|
-
if (!missing) admissionDelays.delete(conversationId);
|
|
7210
|
-
const delay = admissionDelays.get(conversationId) ?? admissionRetry.initial;
|
|
7211
7306
|
const announced = local?.mlsDeviceAnnouncedAtMs;
|
|
7212
|
-
const
|
|
7213
|
-
if (
|
|
7307
|
+
const fresh = announced !== announcementsSeen.get(conversationId);
|
|
7308
|
+
if ((announcementLooks.get(conversationId)?.roster ?? Infinity) < group.roster.length) announcementLooks.delete(conversationId);
|
|
7309
|
+
const look = announcementLooks.get(conversationId), retrying = missing || !!look;
|
|
7310
|
+
if (!retrying) admissionDelays.delete(conversationId);
|
|
7311
|
+
const delay = admissionDelays.get(conversationId) ?? admissionRetry.initial;
|
|
7312
|
+
let due = fresh ? -Infinity : (admissionRuns.get(conversationId) ?? -Infinity) + (retrying ? delay : ADMISSION_IDLE_MS);
|
|
7313
|
+
if (look && !missing) due = Math.min(due, look.untilMs);
|
|
7314
|
+
if (clock() < due) return retrying ? scheduleAdmission(conversationId, due - clock()) : void 0;
|
|
7214
7315
|
admitting.add(conversationId);
|
|
7316
|
+
const watched = announcementsSeen.has(conversationId);
|
|
7215
7317
|
announcementsSeen.set(conversationId, announced);
|
|
7318
|
+
if (fresh && typeof announced === "number" && (watched || clock() < announced + MLS_DEV_KEY_PACKAGE_CLAIM_LEASE_MS)) {
|
|
7319
|
+
announcementLooks.set(conversationId, {
|
|
7320
|
+
untilMs: clock() + MLS_DEV_KEY_PACKAGE_CLAIM_LEASE_MS + admissionRetry.initial,
|
|
7321
|
+
roster: group.roster.length
|
|
7322
|
+
});
|
|
7323
|
+
}
|
|
7216
7324
|
try {
|
|
7217
7325
|
admissionRuns.set(conversationId, clock());
|
|
7218
7326
|
await mlsDevKeyPackages.pruneClaims(clock() - CLAIM_JOURNAL_KEEP_MS);
|
|
@@ -7270,13 +7378,16 @@ function createEndpoint(options) {
|
|
|
7270
7378
|
admissionRuns.delete(conversationId);
|
|
7271
7379
|
admissionDelays.delete(conversationId);
|
|
7272
7380
|
admissionUnfinished.delete(conversationId);
|
|
7381
|
+
announcementLooks.delete(conversationId);
|
|
7273
7382
|
return scheduleAdmission(conversationId, admissionRetry.initial);
|
|
7274
7383
|
}
|
|
7275
7384
|
if (unfinished) admissionUnfinished.add(conversationId);
|
|
7276
7385
|
else admissionUnfinished.delete(conversationId);
|
|
7277
|
-
if (
|
|
7386
|
+
if ((announcementLooks.get(conversationId)?.untilMs ?? -Infinity) <= clock()) announcementLooks.delete(conversationId);
|
|
7387
|
+
const looking = !missing && !unfinished ? announcementLooks.get(conversationId) : void 0;
|
|
7388
|
+
if (missing || unfinished || looking) {
|
|
7278
7389
|
admissionDelays.set(conversationId, Math.min(delay * 2, admissionRetry.maximum));
|
|
7279
|
-
scheduleAdmission(conversationId, delay);
|
|
7390
|
+
scheduleAdmission(conversationId, looking ? Math.min(delay, Math.max(0, looking.untilMs - clock())) : delay);
|
|
7280
7391
|
}
|
|
7281
7392
|
} finally {
|
|
7282
7393
|
admitting.delete(conversationId);
|
|
@@ -7299,7 +7410,7 @@ function createEndpoint(options) {
|
|
|
7299
7410
|
}
|
|
7300
7411
|
__name(joinInvitedRoom, "joinInvitedRoom");
|
|
7301
7412
|
async function discoverInviteeRooms() {
|
|
7302
|
-
if (!mlsDevGroups || closed) return;
|
|
7413
|
+
if (!mlsDevGroups || closed || options.mlsDevDevice?.principal.type !== "human") return;
|
|
7303
7414
|
const { requests } = await endpoint.query(
|
|
7304
7415
|
"list_contact_requests",
|
|
7305
7416
|
{ direction: "inbound" }
|
|
@@ -7773,6 +7884,24 @@ function createEndpoint(options) {
|
|
|
7773
7884
|
else await enqueueInbound(tx, next);
|
|
7774
7885
|
}
|
|
7775
7886
|
__name(writeInboundState, "writeInboundState");
|
|
7887
|
+
async function discardOperation(tx, operation, message) {
|
|
7888
|
+
const status = operation.attempted ? "uncertain" : "rejected";
|
|
7889
|
+
const next = { ...operation, status, updatedAt: now(), receipt: {
|
|
7890
|
+
...deferredReceipt(operation),
|
|
7891
|
+
status,
|
|
7892
|
+
updatedAt: now(),
|
|
7893
|
+
error: { code: "OPERATION_DISCARDED", message, retryable: false }
|
|
7894
|
+
} };
|
|
7895
|
+
delete next.request;
|
|
7896
|
+
delete next.preparedContent;
|
|
7897
|
+
delete next.lease;
|
|
7898
|
+
delete next.nextAttemptAt;
|
|
7899
|
+
await tx.put(key2(operation.operationId), next);
|
|
7900
|
+
await settleMlsDevTextOperation(tx, next);
|
|
7901
|
+
await removeOperationOutbox(tx, operation);
|
|
7902
|
+
if (!operation.attempted) await settleOperationOrder(tx, operation);
|
|
7903
|
+
}
|
|
7904
|
+
__name(discardOperation, "discardOperation");
|
|
7776
7905
|
function deferredReceipt(operation, error) {
|
|
7777
7906
|
return operation.receipt ?? {
|
|
7778
7907
|
protocol: CANON_ENDPOINT_PROTOCOL,
|
|
@@ -7894,12 +8023,18 @@ function createEndpoint(options) {
|
|
|
7894
8023
|
if (mlsDevGroups && typeof made === "string" && ENDPOINT_ID_PATTERN.test(made)) void refreshMlsRoom(made, true).catch(() => void 0);
|
|
7895
8024
|
}
|
|
7896
8025
|
__name(afterReceipt, "afterReceipt");
|
|
7897
|
-
async function persistReceipt(operation, receipt) {
|
|
7898
|
-
if (await mlsDevGroups?.settleReceipt(operation, receipt)) return;
|
|
8026
|
+
async function persistReceipt(operation, receipt, reconcileOnly = false) {
|
|
8027
|
+
if (await mlsDevGroups?.settleReceipt(operation, receipt, { reconcileOnly })) return;
|
|
7899
8028
|
const updated = await store.transaction((tx) => commitReceipt(tx, operation, receipt));
|
|
7900
8029
|
await afterReceipt(updated);
|
|
7901
8030
|
}
|
|
7902
8031
|
__name(persistReceipt, "persistReceipt");
|
|
8032
|
+
async function answerOf(operationId, receipt, reconcileOnly = false) {
|
|
8033
|
+
if (receipt.action !== "mls_application" || receipt.status !== "rejected") return receipt;
|
|
8034
|
+
const resentAs = (await store.transaction((tx) => tx.get(key2(operationId))))?.resentAs;
|
|
8035
|
+
return resentAs ? endpoint.retry(resentAs, { reconcileOnly }) : receipt;
|
|
8036
|
+
}
|
|
8037
|
+
__name(answerOf, "answerOf");
|
|
7903
8038
|
async function dispatch(operationId, reconcileOnly = false) {
|
|
7904
8039
|
assertOpen();
|
|
7905
8040
|
await initialization;
|
|
@@ -7939,12 +8074,17 @@ function createEndpoint(options) {
|
|
|
7939
8074
|
await tx.put(key2(operationId), next);
|
|
7940
8075
|
return { operation: next, acquired: true };
|
|
7941
8076
|
});
|
|
8077
|
+
if (operation.resentAs) return endpoint.retry(operation.resentAs, { reconcileOnly });
|
|
7942
8078
|
if (terminal(operation.status) && operation.receipt) {
|
|
7943
8079
|
if ((operation.receipt.resultReference || operation.resultEvicted) && operation.status === "succeeded") {
|
|
7944
8080
|
const hydrated = await transport.getOperation(operationId, operation.installationId, controller.signal);
|
|
7945
8081
|
if (!hydrated) throw new EndpointError("The private operation result is no longer available", { status: 410, code: "RESULT_EXPIRED", retryable: false, operationId });
|
|
7946
8082
|
return hydrated;
|
|
7947
8083
|
}
|
|
8084
|
+
if (!reconcileOnly && operation.action === "mls_application" && operation.status === "rejected") {
|
|
8085
|
+
await mlsDevGroups?.settleReceipt(operation, operation.receipt);
|
|
8086
|
+
return answerOf(operationId, operation.receipt);
|
|
8087
|
+
}
|
|
7948
8088
|
return operation.receipt;
|
|
7949
8089
|
}
|
|
7950
8090
|
if (!acquired) {
|
|
@@ -7986,8 +8126,8 @@ function createEndpoint(options) {
|
|
|
7986
8126
|
});
|
|
7987
8127
|
return receipt2;
|
|
7988
8128
|
}
|
|
7989
|
-
await persistReceipt(operation, receipt2);
|
|
7990
|
-
return receipt2;
|
|
8129
|
+
await persistReceipt(operation, receipt2, reconcileOnly);
|
|
8130
|
+
return answerOf(operationId, receipt2, reconcileOnly);
|
|
7991
8131
|
}
|
|
7992
8132
|
if (operation.attempted) {
|
|
7993
8133
|
resolvingReceipt = true;
|
|
@@ -7995,7 +8135,7 @@ function createEndpoint(options) {
|
|
|
7995
8135
|
resolvingReceipt = false;
|
|
7996
8136
|
if (receipt2 && (receipt2.status !== "pending" || !operation.request && !sensitiveRequests.has(operationId))) {
|
|
7997
8137
|
await persistReceipt(operation, receipt2);
|
|
7998
|
-
return receipt2;
|
|
8138
|
+
return answerOf(operationId, receipt2, reconcileOnly);
|
|
7999
8139
|
}
|
|
8000
8140
|
}
|
|
8001
8141
|
if (clock() - Date.parse(operation.createdAt) > retryHorizonMs) {
|
|
@@ -8020,7 +8160,7 @@ function createEndpoint(options) {
|
|
|
8020
8160
|
checkingDeliveryGuard = true;
|
|
8021
8161
|
const guard = deliveryGuards.get(operation.guardId);
|
|
8022
8162
|
if (!guard) throw new EndpointError("This publication requires its runtime to recheck the current audience", { code: "DELIVERY_GUARD_REQUIRED", retryable: true });
|
|
8023
|
-
if (!await guard()) throw new EndpointError("Publication is withheld by the runtime policy", { code: "DELIVERY_GUARD_WITHHELD", retryable: true });
|
|
8163
|
+
if (!await guard({ automaticResend: operation.automaticResend === true })) throw new EndpointError("Publication is withheld by the runtime policy", { code: "DELIVERY_GUARD_WITHHELD", retryable: true });
|
|
8024
8164
|
assertOpen();
|
|
8025
8165
|
checkingDeliveryGuard = false;
|
|
8026
8166
|
}
|
|
@@ -8055,7 +8195,7 @@ function createEndpoint(options) {
|
|
|
8055
8195
|
});
|
|
8056
8196
|
const receipt = await transport.execute(request, controller.signal);
|
|
8057
8197
|
await persistReceipt(operation, receipt);
|
|
8058
|
-
return receipt;
|
|
8198
|
+
return answerOf(operationId, receipt, reconcileOnly);
|
|
8059
8199
|
} catch (error) {
|
|
8060
8200
|
const failure = safeError(error);
|
|
8061
8201
|
if (reconcileOnly && failure.retryable) {
|
|
@@ -8320,9 +8460,20 @@ function createEndpoint(options) {
|
|
|
8320
8460
|
running.set(operationId, work);
|
|
8321
8461
|
return work;
|
|
8322
8462
|
},
|
|
8323
|
-
async
|
|
8463
|
+
async resend(operationId) {
|
|
8324
8464
|
assertOpen();
|
|
8325
8465
|
await initialization;
|
|
8466
|
+
let operation = await endpoint.getOperation(operationId);
|
|
8467
|
+
while (operation?.resentAs) operation = await endpoint.getOperation(operation.resentAs);
|
|
8468
|
+
if (!operation) throw new EndpointError("Unknown endpoint operation", { status: 404, code: "OPERATION_NOT_FOUND", retryable: false, operationId });
|
|
8469
|
+
if (operation.status !== "rejected") return operation;
|
|
8470
|
+
const resent = operation.action === "mls_application" && mlsDevGroups ? await mlsDevGroups.resendText(operation) : void 0;
|
|
8471
|
+
if (!resent) throw new EndpointError("This message can no longer be sent again", { code: "OPERATION_NOT_RESENDABLE", retryable: false, operationId });
|
|
8472
|
+
return resent;
|
|
8473
|
+
},
|
|
8474
|
+
async drain(drainOptions = {}) {
|
|
8475
|
+
assertOpen();
|
|
8476
|
+
await opened;
|
|
8326
8477
|
await compactExpiredPayloads(store, clock());
|
|
8327
8478
|
await sendDeferredOpeners(void 0, true).catch(() => void 0);
|
|
8328
8479
|
let dispatched = 0;
|
|
@@ -8371,7 +8522,8 @@ function createEndpoint(options) {
|
|
|
8371
8522
|
await initialization;
|
|
8372
8523
|
return store.transaction((tx) => tx.get(key2(operationId)));
|
|
8373
8524
|
},
|
|
8374
|
-
waitForOperation(
|
|
8525
|
+
waitForOperation(waited, waitOptions = {}) {
|
|
8526
|
+
let operationId = waited;
|
|
8375
8527
|
return new Promise((resolve3, reject) => {
|
|
8376
8528
|
let finished = false;
|
|
8377
8529
|
let checking = false;
|
|
@@ -8397,7 +8549,11 @@ function createEndpoint(options) {
|
|
|
8397
8549
|
if (finished || checking) return;
|
|
8398
8550
|
checking = true;
|
|
8399
8551
|
try {
|
|
8400
|
-
|
|
8552
|
+
let operation = await endpoint.getOperation(operationId);
|
|
8553
|
+
while (operation?.resentAs) {
|
|
8554
|
+
operationId = operation.resentAs;
|
|
8555
|
+
operation = await endpoint.getOperation(operationId);
|
|
8556
|
+
}
|
|
8401
8557
|
if (!operation) return finish(void 0, new EndpointError("Unknown endpoint operation", { status: 404, code: "OPERATION_NOT_FOUND", retryable: false, operationId }));
|
|
8402
8558
|
if (operation.status === "uncertain") return finish(void 0, new EndpointError("Delivery outcome requires reconciliation", { code: "OPERATION_UNCERTAIN", retryable: false, operationId }));
|
|
8403
8559
|
if (terminal(operation.status) && operation.receipt) {
|
|
@@ -8660,6 +8816,57 @@ function createEndpoint(options) {
|
|
|
8660
8816
|
startRecovery: recovery.start,
|
|
8661
8817
|
configureRecoveryBaseline: recovery.configureBaseline,
|
|
8662
8818
|
getRecoveryBaseline: recovery.getBaseline,
|
|
8819
|
+
async startWorkAt(cutoverAt) {
|
|
8820
|
+
assertOpen();
|
|
8821
|
+
await initialization;
|
|
8822
|
+
if (typeof cutoverAt !== "string" || !Number.isFinite(Date.parse(cutoverAt))) {
|
|
8823
|
+
throw new EndpointError("Invalid work floor", { code: "INVALID_RECOVERY_BASELINE", retryable: false });
|
|
8824
|
+
}
|
|
8825
|
+
const sweep = await store.transaction(async (tx) => {
|
|
8826
|
+
const floor = await tx.get(WORK_FLOOR_KEY);
|
|
8827
|
+
const at = floor ? Date.parse(floor.cutoverAt) : -Infinity, next = Date.parse(cutoverAt);
|
|
8828
|
+
if (at > next || at === next && floor?.swept) return false;
|
|
8829
|
+
if (at < next) await tx.put(WORK_FLOOR_KEY, { cutoverAt });
|
|
8830
|
+
return true;
|
|
8831
|
+
});
|
|
8832
|
+
if (!sweep) return;
|
|
8833
|
+
for (let after; ; ) {
|
|
8834
|
+
const page = await store.transaction(async (tx) => {
|
|
8835
|
+
const floor = await tx.get(WORK_FLOOR_KEY);
|
|
8836
|
+
const answered = await completions.answered(tx);
|
|
8837
|
+
const entries2 = await tx.scan("inbound/", { ...after ? { after } : {}, limit: 100 });
|
|
8838
|
+
for (const { key: recordKey, value } of entries2) {
|
|
8839
|
+
if (value.state === "settled" || value.externalClaim || answered.has(value.event.id) || !beforeWorkFloor(floor, value.createdAt)) continue;
|
|
8840
|
+
const next = { ...value, state: "settled", reason: "before_take_over", updatedAt: now() };
|
|
8841
|
+
delete next.lease;
|
|
8842
|
+
delete next.handoffGeneration;
|
|
8843
|
+
delete next.handoffInstallationGeneration;
|
|
8844
|
+
await removeInboundReady(tx, value);
|
|
8845
|
+
await tx.put(recordKey, next);
|
|
8846
|
+
}
|
|
8847
|
+
return entries2;
|
|
8848
|
+
});
|
|
8849
|
+
if (page.length < 100) break;
|
|
8850
|
+
after = page.at(-1).key;
|
|
8851
|
+
}
|
|
8852
|
+
await store.transaction(async (tx) => {
|
|
8853
|
+
const floor = await tx.get(WORK_FLOOR_KEY);
|
|
8854
|
+
if (floor && Date.parse(floor.cutoverAt) === Date.parse(cutoverAt)) await tx.put(WORK_FLOOR_KEY, { cutoverAt: floor.cutoverAt, swept: true });
|
|
8855
|
+
});
|
|
8856
|
+
},
|
|
8857
|
+
async takeOverHome() {
|
|
8858
|
+
assertOpen();
|
|
8859
|
+
await opened;
|
|
8860
|
+
const operationId = `${TAKE_OVER_OPERATION}${ids()}`;
|
|
8861
|
+
const { cutoverAt } = await endpoint.executeResult({ action: "initialize_endpoint", body: {} }, { operationId }).catch(async (error) => {
|
|
8862
|
+
await store.transaction(async (tx) => {
|
|
8863
|
+
const current = await tx.get(key2(operationId));
|
|
8864
|
+
if (current && !terminal(current.status)) await discardOperation(tx, current, TAKE_OVER_DISCARDED);
|
|
8865
|
+
});
|
|
8866
|
+
throw error;
|
|
8867
|
+
});
|
|
8868
|
+
await endpoint.startWorkAt(cutoverAt);
|
|
8869
|
+
},
|
|
8663
8870
|
refreshRecovery: recovery.refresh,
|
|
8664
8871
|
planRecoveryMigration(baseline, conversationIds) {
|
|
8665
8872
|
assertOpen();
|
|
@@ -8768,23 +8975,11 @@ function createEndpoint(options) {
|
|
|
8768
8975
|
const batch = await store.transaction(async (tx) => {
|
|
8769
8976
|
const entries2 = await tx.scan("operation/", { after, limit: 100 });
|
|
8770
8977
|
for (const entry of entries2) {
|
|
8771
|
-
if (!terminal(entry.value.status))
|
|
8772
|
-
|
|
8773
|
-
|
|
8774
|
-
|
|
8775
|
-
status,
|
|
8776
|
-
updatedAt: now(),
|
|
8777
|
-
error: { code: "OPERATION_DISCARDED", message: "Local delivery was cancelled", retryable: false }
|
|
8778
|
-
} };
|
|
8779
|
-
delete next.request;
|
|
8780
|
-
delete next.preparedContent;
|
|
8781
|
-
delete next.lease;
|
|
8782
|
-
delete next.nextAttemptAt;
|
|
8783
|
-
await tx.put(entry.key, next);
|
|
8784
|
-
await settleMlsDevTextOperation(tx, next);
|
|
8978
|
+
if (!terminal(entry.value.status)) await discardOperation(tx, entry.value, "Local delivery was cancelled");
|
|
8979
|
+
else {
|
|
8980
|
+
await removeOperationOutbox(tx, entry.value);
|
|
8981
|
+
await settleOperationOrder(tx, entry.value);
|
|
8785
8982
|
}
|
|
8786
|
-
await removeOperationOutbox(tx, entry.value);
|
|
8787
|
-
if (!entry.value.attempted || terminal(entry.value.status)) await settleOperationOrder(tx, entry.value);
|
|
8788
8983
|
}
|
|
8789
8984
|
return entries2;
|
|
8790
8985
|
});
|
|
@@ -8894,7 +9089,7 @@ async function acquireAgentEndpoint(input) {
|
|
|
8894
9089
|
}
|
|
8895
9090
|
const requestedMls = input.options.mlsDev;
|
|
8896
9091
|
const mls = requestedMls ? await (async () => {
|
|
8897
|
-
const crypto2 = await import("./mls-crypto-
|
|
9092
|
+
const crypto2 = await import("./mls-crypto-AW7TY744.js");
|
|
8898
9093
|
const configuration = crypto2.parseMlsDevClientConfiguration(requestedMls.configuration);
|
|
8899
9094
|
if (configuration.environmentId !== environmentId || baseUrl !== CANON_RUNTIME_ENDPOINT_DEFAULTS[configuration.environmentId]?.apiBaseUrl || configuration.principalIds && !configuration.principalIds.includes(input.principalId) || !Number.isSafeInteger(input.ownershipVersion) || input.ownershipVersion < 0) throw new Error("MLS_DEV_AGENT_CONFIGURATION_BINDING_MISMATCH");
|
|
8900
9095
|
const custody = requestedMls.custody;
|
|
@@ -9079,6 +9274,17 @@ function isDiscoverAgentsResult(value) {
|
|
|
9079
9274
|
return isRecord(value) && Array.isArray(value.agents) && value.agents.every(isAgentDirectoryEntry) && (value.nextCursor === null || typeof value.nextCursor === "string");
|
|
9080
9275
|
}
|
|
9081
9276
|
__name(isDiscoverAgentsResult, "isDiscoverAgentsResult");
|
|
9277
|
+
function homeClaim(input) {
|
|
9278
|
+
const first = input.baseline ? void 0 : "initialize-endpoint";
|
|
9279
|
+
if (isConnectorRelayInstallation(input.installationId)) return first;
|
|
9280
|
+
if (input.home?.kind === "connector") return input.runner ? "refuse" : void 0;
|
|
9281
|
+
const holder = input.home?.installationId;
|
|
9282
|
+
if (holder === void 0 || holder === input.installationId) return first;
|
|
9283
|
+
if (!input.runner) return void 0;
|
|
9284
|
+
if (!input.baseline) return first;
|
|
9285
|
+
return input.takeOver ? "take-over" : "refuse";
|
|
9286
|
+
}
|
|
9287
|
+
__name(homeClaim, "homeClaim");
|
|
9082
9288
|
var CanonClient = class {
|
|
9083
9289
|
static {
|
|
9084
9290
|
__name(this, "CanonClient");
|
|
@@ -9110,12 +9316,29 @@ var CanonClient = class {
|
|
|
9110
9316
|
options: this.endpointOptions
|
|
9111
9317
|
});
|
|
9112
9318
|
try {
|
|
9113
|
-
if (context.endpointBootstrapEligible && this.endpointOptions.autoDrain !== false
|
|
9114
|
-
const baseline = await lease.endpoint.
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9319
|
+
if (context.endpointBootstrapEligible && this.endpointOptions.autoDrain !== false) {
|
|
9320
|
+
const baseline = await lease.endpoint.getRecoveryBaseline();
|
|
9321
|
+
const { installationId } = await lease.endpoint.ready();
|
|
9322
|
+
const home = context.home;
|
|
9323
|
+
const claim = homeClaim({
|
|
9324
|
+
installationId,
|
|
9325
|
+
home,
|
|
9326
|
+
baseline: !!baseline,
|
|
9327
|
+
runner: !!this.endpointOptions.claimHome,
|
|
9328
|
+
takeOver: !!this.endpointOptions.takeOver
|
|
9329
|
+
});
|
|
9330
|
+
if (claim === "refuse") {
|
|
9331
|
+
throw new EndpointError(agentHomeElsewhereMessage(context.displayName, home), { status: 409, code: AGENT_HOME_ELSEWHERE, retryable: false });
|
|
9332
|
+
}
|
|
9333
|
+
if (claim === "take-over") await lease.endpoint.takeOverHome();
|
|
9334
|
+
else if (claim) {
|
|
9335
|
+
await lease.endpoint.configureRecoveryBaseline(await lease.endpoint.executeResult(
|
|
9336
|
+
{ action: "initialize_endpoint", body: {} },
|
|
9337
|
+
{ operationId: claim }
|
|
9338
|
+
));
|
|
9339
|
+
} else if (home?.kind === "runner" && home.installationId === installationId && home.since) {
|
|
9340
|
+
await lease.endpoint.startWorkAt(new Date(home.since).toISOString());
|
|
9341
|
+
}
|
|
9119
9342
|
}
|
|
9120
9343
|
return lease;
|
|
9121
9344
|
} catch (error) {
|
|
@@ -10280,8 +10503,16 @@ async function ensureAgentProfile(options) {
|
|
|
10280
10503
|
await verifyCanonRuntimeConnection(connection);
|
|
10281
10504
|
return { status: "approved", apiKey: existing.apiKey, agentId: existing.agentId, agentName: existing.agentName };
|
|
10282
10505
|
}
|
|
10506
|
+
const requestedAgentId = input.requestedAgentId ?? (options.reconnect && !pending ? existing?.agentId : void 0);
|
|
10507
|
+
if (options.reconnect && !pending && !requestedAgentId) {
|
|
10508
|
+
throw new Error(`Canon profile "${profile}" has no saved agent to reconnect: give input.requestedAgentId, or register without reconnect`);
|
|
10509
|
+
}
|
|
10510
|
+
if (options.reconnect && !pending && existing && requestedAgentId !== existing.agentId) {
|
|
10511
|
+
throw new Error("Saved Canon profile belongs to another agent identity");
|
|
10512
|
+
}
|
|
10283
10513
|
return resumeRegistration({
|
|
10284
10514
|
...options,
|
|
10515
|
+
input: { ...input, ...requestedAgentId ? { requestedAgentId } : {} },
|
|
10285
10516
|
store: {
|
|
10286
10517
|
load: /* @__PURE__ */ __name(() => loadPendingRegistrations()[profile], "load"),
|
|
10287
10518
|
save: /* @__PURE__ */ __name((session) => {
|
|
@@ -11534,14 +11765,15 @@ export {
|
|
|
11534
11765
|
loadProfiles,
|
|
11535
11766
|
isProfileLocked,
|
|
11536
11767
|
acquireLock,
|
|
11768
|
+
loadPendingRegistrations,
|
|
11537
11769
|
clearPendingRegistration,
|
|
11538
11770
|
ensureAgentProfile,
|
|
11539
11771
|
initRTDBAuth,
|
|
11540
11772
|
renderCanonHostInboundContent,
|
|
11541
11773
|
createRuntimeStatePublisher,
|
|
11542
11774
|
isPendingCanonOperation,
|
|
11775
|
+
utf8ByteLength,
|
|
11543
11776
|
splitTextWithinBytes,
|
|
11544
|
-
readCliPackageVersion,
|
|
11545
11777
|
runCli,
|
|
11546
11778
|
BoundedFileReadError,
|
|
11547
11779
|
readBoundedRegularFileHandle,
|