@gestaltrun/dsh-im-runtime 0.1.0 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.i18n.yaml +35 -20
  2. package/README.md +142 -10
  3. package/README.zh.md +141 -9
  4. package/lib/index.js +140 -2
  5. package/lib/inline-image-DuX6vSEI.js +20 -0
  6. package/lib/read-receipts.js +413 -0
  7. package/lib/read-state-CnUZKCuY.js +1868 -0
  8. package/lib/runtime-Bm6iSEpv.js +10939 -0
  9. package/lib/tools.js +1 -1
  10. package/lib/types/account-placement.d.ts +22 -0
  11. package/lib/types/admission-annotations.d.ts +40 -0
  12. package/lib/types/agent-coordinator.d.ts +47 -2
  13. package/lib/types/agent-media.d.ts +46 -0
  14. package/lib/types/agent-prompt.d.ts +8 -2
  15. package/lib/types/checkpoint-recovery.d.ts +32 -0
  16. package/lib/types/checkpoint.d.ts +127 -0
  17. package/lib/types/consumer-chat-links.d.ts +59 -0
  18. package/lib/types/delivery-schema.d.ts +126 -2
  19. package/lib/types/delivery-sqlite.d.ts +206 -6
  20. package/lib/types/delivery-types.d.ts +97 -1
  21. package/lib/types/delivery.d.ts +29 -6
  22. package/lib/types/errors.d.ts +1 -1
  23. package/lib/types/evaluation-accounts.d.ts +86 -0
  24. package/lib/types/index.d.ts +34 -2
  25. package/lib/types/inline-image.d.ts +7 -0
  26. package/lib/types/input-drain.d.ts +73 -0
  27. package/lib/types/knowledge-image.d.ts +64 -0
  28. package/lib/types/media-lifetime.d.ts +22 -0
  29. package/lib/types/media.d.ts +32 -0
  30. package/lib/types/message-preview.d.ts +4 -0
  31. package/lib/types/message-upload.d.ts +55 -0
  32. package/lib/types/nas-host-lease.d.ts +33 -0
  33. package/lib/types/placement-slice.d.ts +30 -0
  34. package/lib/types/policies.d.ts +10 -0
  35. package/lib/types/preset-model.d.ts +50 -0
  36. package/lib/types/read-context.d.ts +27 -0
  37. package/lib/types/read-receipts.d.ts +10 -0
  38. package/lib/types/read-records.d.ts +30 -0
  39. package/lib/types/read-state.d.ts +18 -0
  40. package/lib/types/read-types.d.ts +28 -0
  41. package/lib/types/restart-recovery.d.ts +38 -0
  42. package/lib/types/runtime.d.ts +318 -10
  43. package/lib/types/schema.d.ts +13 -2
  44. package/lib/types/script-clock.d.ts +14 -0
  45. package/lib/types/sdk.d.ts +108 -0
  46. package/lib/types/service-types.d.ts +161 -4
  47. package/lib/types/session-events.d.ts +9 -9
  48. package/lib/types/simulation-batch.d.ts +40 -0
  49. package/lib/types/simulation-controller.d.ts +16 -3
  50. package/lib/types/simulation-operator.d.ts +19 -0
  51. package/lib/types/sqlite-connection.d.ts +27 -0
  52. package/lib/types/time.d.ts +13 -0
  53. package/lib/types/transport.d.ts +44 -4
  54. package/lib/types/transports.d.ts +7 -0
  55. package/lib/types/types.d.ts +22 -5
  56. package/lib/types/typing.d.ts +33 -0
  57. package/lib/types/upload-types.d.ts +121 -0
  58. package/lib/types.js +2 -1
  59. package/locale/en.json +1 -1
  60. package/locale/zh.json +1 -1
  61. package/package.json +67 -41
  62. package/lib/runtime-C0-S-_CC.js +0 -6758
@@ -0,0 +1,1868 @@
1
+ import { brandString } from "@deepseek-ai/dsh-brand";
2
+ import { SessionId } from "@deepseek-ai/dsh-session";
3
+ import { WorkspaceId } from "@deepseek-ai/dsh-workspace";
4
+ import { z } from "zod";
5
+ import { createHash, randomUUID } from "node:crypto";
6
+ import { basename } from "node:path";
7
+ //#region src/errors.ts
8
+ /** Structured IM runtime failure for requests that cannot produce a durable receipt. */
9
+ var ImRuntimeError = class extends Error {
10
+ code;
11
+ /** @param code - stable failure code. @param message - diagnostic detail. */
12
+ constructor(code, message) {
13
+ super(message);
14
+ this.code = code;
15
+ this.name = "ImRuntimeError";
16
+ }
17
+ };
18
+ //#endregion
19
+ //#region src/delivery-schema.ts
20
+ const accountId = z.string().min(1).transform((value) => brandString(value));
21
+ const operationId = z.string().min(1).transform((value) => brandString(value));
22
+ const messageId = z.string().min(1).transform((value) => brandString(value));
23
+ const outboundRequestId = z.string().min(1).transform((value) => brandString(value));
24
+ const outboundAttemptId = z.string().min(1).transform((value) => brandString(value));
25
+ const scopeId = z.string().min(1).transform((value) => brandString(value));
26
+ const providerCursorId = z.string().min(1).transform((value) => brandString(value));
27
+ const admissionId = z.string().min(1).transform((value) => brandString(value));
28
+ const agentTaskId = z.string().min(1).transform((value) => brandString(value));
29
+ const timestamp$1 = z.iso.datetime();
30
+ /** Captured platform identities; legacy observations omit the whole value. */
31
+ const imUploadIdentitySchema = z.object({
32
+ senderAccountId: z.string().min(1).nullable(),
33
+ senderDisplayName: z.string().min(1).nullable(),
34
+ senderDomain: z.string().min(1).nullable(),
35
+ assignedAccountId: z.string().min(1).nullable()
36
+ });
37
+ const realScope = z.object({
38
+ kind: z.literal("real"),
39
+ platform: z.enum(["dingtalk", "wangwang"]),
40
+ accountId,
41
+ conversationKind: z.enum(["direct", "group"]),
42
+ conversationId: z.string().min(1)
43
+ });
44
+ const simulationScope = z.object({
45
+ kind: z.literal("simulation"),
46
+ instanceId: z.string().min(1).transform((value) => brandString(value)),
47
+ platform: z.enum(["dingtalk", "wangwang"]),
48
+ accountId,
49
+ conversationKind: z.enum(["direct", "group"]),
50
+ conversationId: z.string().min(1)
51
+ });
52
+ const imDeliveryScopeSchema = z.discriminatedUnion("kind", [realScope, simulationScope]);
53
+ const humanDshSender = z.object({
54
+ kind: z.literal("human-dsh"),
55
+ outboundRequestId,
56
+ providerActorId: z.string().min(1).optional()
57
+ });
58
+ const sender = z.discriminatedUnion("kind", [
59
+ z.object({
60
+ kind: z.literal("external"),
61
+ senderId: z.string().min(1),
62
+ senderDisplayName: z.string().optional(),
63
+ userId: z.string().min(1).optional(),
64
+ openDingTalkId: z.string().min(1).optional()
65
+ }),
66
+ z.object({
67
+ kind: z.literal("human-native"),
68
+ accountId,
69
+ providerActorId: z.string().min(1)
70
+ }),
71
+ humanDshSender,
72
+ z.object({
73
+ kind: z.literal("ai"),
74
+ outboundRequestId
75
+ }),
76
+ z.object({
77
+ kind: z.literal("unknown"),
78
+ reason: z.enum([
79
+ "insufficient-evidence",
80
+ "unmatched-self",
81
+ "unmatched-echo",
82
+ "provider-unknown"
83
+ ]),
84
+ observedSenderId: z.string().optional()
85
+ })
86
+ ]);
87
+ const quote = z.object({
88
+ text: z.string(),
89
+ senderDisplayName: z.string().min(1).optional(),
90
+ externalMessageId: z.string().min(1).optional()
91
+ });
92
+ const forbiddenDetailKeys = /* @__PURE__ */ new Set([
93
+ "accesskey",
94
+ "accesskeyid",
95
+ "accesskeysecret",
96
+ "authorization",
97
+ "connectionconfig",
98
+ "cookie",
99
+ "credential",
100
+ "credentials",
101
+ "header",
102
+ "headers",
103
+ "password",
104
+ "secret",
105
+ "token",
106
+ "transportconfig"
107
+ ]);
108
+ function unsafeDetailPath(value, path = []) {
109
+ if (value === null || typeof value !== "object") return void 0;
110
+ if (Array.isArray(value)) {
111
+ for (const [index, entry] of value.entries()) {
112
+ const unsafe = unsafeDetailPath(entry, [...path, String(index)]);
113
+ if (unsafe !== void 0) return unsafe;
114
+ }
115
+ return;
116
+ }
117
+ for (const [key, entry] of Object.entries(value)) {
118
+ const next = [...path, key];
119
+ if (forbiddenDetailKeys.has(key.replaceAll("-", "").replaceAll("_", "").toLowerCase())) return next;
120
+ const unsafe = unsafeDetailPath(entry, next);
121
+ if (unsafe !== void 0) return unsafe;
122
+ }
123
+ }
124
+ const displaySafeJson = z.json().superRefine((value, context) => {
125
+ const path = unsafeDetailPath(value);
126
+ if (path !== void 0) context.addIssue({
127
+ code: "custom",
128
+ message: "unsupported-message details contain private transport configuration",
129
+ path: [...path]
130
+ });
131
+ });
132
+ const content = z.discriminatedUnion("format", [
133
+ z.object({
134
+ text: z.string(),
135
+ format: z.literal("media"),
136
+ media: z.object({
137
+ kind: z.enum(["image", "file"]),
138
+ name: z.string().min(1),
139
+ mime: z.string(),
140
+ bytes: z.number().int().nonnegative(),
141
+ sha256: z.string().regex(/^[a-f0-9]{64}$/u),
142
+ sourceFile: z.object({
143
+ attachmentId: z.string().min(1).transform((value) => brandString(value)),
144
+ name: z.string().min(1),
145
+ bytes: z.number().int().nonnegative()
146
+ }).optional()
147
+ }),
148
+ quote: quote.optional()
149
+ }),
150
+ z.object({
151
+ text: z.string(),
152
+ format: z.enum(["text", "markdown"]),
153
+ quote: quote.optional()
154
+ }),
155
+ z.object({
156
+ text: z.string(),
157
+ format: z.literal("image"),
158
+ quote: quote.optional()
159
+ }),
160
+ z.object({
161
+ text: z.string(),
162
+ format: z.literal("unsupported"),
163
+ messageType: z.string().min(1),
164
+ details: displaySafeJson,
165
+ quote: quote.optional()
166
+ })
167
+ ]);
168
+ /** Parser for provider and JSONL inbound records. */
169
+ const imInboundMessageInputSchema = z.object({
170
+ uploadIdentity: imUploadIdentitySchema.optional(),
171
+ externalMessageId: z.string().min(1),
172
+ sender,
173
+ content,
174
+ occurredAt: timestamp$1,
175
+ mentionedConfiguredAccount: z.boolean().optional()
176
+ });
177
+ const inboundMessage = z.object({
178
+ uploadIdentity: imUploadIdentitySchema.optional(),
179
+ messageId,
180
+ scopeId,
181
+ externalMessageId: z.string().min(1),
182
+ sender,
183
+ content,
184
+ origin: z.enum(["live", "jsonl-import"]),
185
+ stage: z.enum(["received", "submitted"]),
186
+ sequenceNumber: z.number().int().positive(),
187
+ occurredAt: timestamp$1,
188
+ mentionedConfiguredAccount: z.boolean().optional(),
189
+ receivedAt: timestamp$1,
190
+ submission: z.object({
191
+ sessionId: z.string().min(1).transform(SessionId),
192
+ submittedAt: timestamp$1
193
+ }).optional()
194
+ });
195
+ const cursor = z.object({
196
+ scopeId,
197
+ platformCursor: z.string().nullable(),
198
+ lastReceivedSequenceNumber: z.number().int().nonnegative(),
199
+ lastSubmittedSequenceNumber: z.number().int().nonnegative(),
200
+ pendingCount: z.number().int().nonnegative(),
201
+ lastSuppressedSequenceNumber: z.number().int().nonnegative().optional(),
202
+ lastSyncedAt: timestamp$1.optional(),
203
+ updatedAt: timestamp$1
204
+ });
205
+ const conversationPresentation = z.object({
206
+ displayName: z.string().min(1).optional(),
207
+ memberCount: z.number().int().positive().optional(),
208
+ assignedActorId: z.string().min(1).optional()
209
+ });
210
+ const pageResult = z.object({
211
+ kind: z.literal("page"),
212
+ operationId,
213
+ status: z.enum(["applied", "conflict"]),
214
+ acceptedCount: z.number().int().nonnegative(),
215
+ evidenceMergedCount: z.number().int().nonnegative().optional(),
216
+ duplicateCount: z.number().int().nonnegative(),
217
+ messages: z.array(inboundMessage),
218
+ cursor
219
+ });
220
+ const importResult = z.object({
221
+ kind: z.literal("jsonl-import"),
222
+ operationId,
223
+ status: z.literal("applied"),
224
+ fileName: z.string().min(1),
225
+ messageCount: z.number().int().nonnegative(),
226
+ importedCount: z.number().int().nonnegative(),
227
+ duplicateCount: z.number().int().nonnegative()
228
+ });
229
+ const routeBinding = z.object({
230
+ handlingEpoch: z.number().int().nonnegative().optional(),
231
+ routeId: z.string().min(1).transform((value) => brandString(value)),
232
+ routeRevision: z.string().min(1).transform((value) => brandString(value)),
233
+ accountRevision: z.string().min(1).transform((value) => brandString(value)),
234
+ workspaceId: z.string().min(1).transform(WorkspaceId)
235
+ });
236
+ const receipt = z.object({
237
+ providerReceiptId: z.string().optional(),
238
+ providerStatus: z.string().optional(),
239
+ errorCode: z.string().optional(),
240
+ observedAt: timestamp$1
241
+ });
242
+ const outbound = z.object({
243
+ requestId: outboundRequestId,
244
+ scopeId,
245
+ intent: z.enum(["ai", "human-manual"]),
246
+ content,
247
+ media: z.object({
248
+ state: z.enum(["uploading", "prepared"]),
249
+ imageUrl: z.url().optional()
250
+ }).optional(),
251
+ status: z.enum([
252
+ "pending",
253
+ "dispatching",
254
+ "pre-send-failed",
255
+ "sent",
256
+ "result-unknown",
257
+ "confirmed-failed"
258
+ ]),
259
+ sequenceNumber: z.number().int().positive(),
260
+ handlingEpoch: z.number().int().nonnegative().optional(),
261
+ routeBinding: routeBinding.optional(),
262
+ sender: humanDshSender.optional(),
263
+ manualBinding: z.object({
264
+ sessionId: z.string().min(1).transform(SessionId),
265
+ taskId: agentTaskId
266
+ }).optional(),
267
+ retryOfRequestId: outboundRequestId.optional(),
268
+ preSendFailureReason: z.enum([
269
+ "account-not-found",
270
+ "platform-mismatch",
271
+ "account-paused",
272
+ "route-disabled",
273
+ "route-unmatched",
274
+ "route-changed",
275
+ "cancelled"
276
+ ]).optional(),
277
+ attempt: z.object({
278
+ attemptId: outboundAttemptId,
279
+ startedAt: timestamp$1
280
+ }).optional(),
281
+ receipt: receipt.optional(),
282
+ externalMessageId: z.string().optional(),
283
+ replyToExternalMessageId: z.string().optional(),
284
+ createdAt: timestamp$1,
285
+ updatedAt: timestamp$1
286
+ });
287
+ /** Parser shared by the sqlite transfer journal and the external queue file. */
288
+ const uploadTarget = z.object({
289
+ serverOrigin: z.url(),
290
+ tenantId: z.string().min(1).transform((value) => brandString(value)),
291
+ shopAccountId: z.string().min(1),
292
+ traffic: z.enum([
293
+ "formal",
294
+ "test",
295
+ "simulation"
296
+ ]),
297
+ testRunId: z.string().nullable()
298
+ });
299
+ /** Server V2 control-result fields shared by the journal and queue parsers. */
300
+ const imUploadConversationEventSchema = z.object({
301
+ eventId: z.string().min(1),
302
+ conversationKey: z.string().min(1),
303
+ accountId: z.string().min(1),
304
+ kind: z.enum([
305
+ "ai_to_human",
306
+ "hosting_disabled",
307
+ "hosting_resumed"
308
+ ]),
309
+ occurredAt: timestamp$1,
310
+ controlRevision: z.string().regex(/^(0|[1-9][0-9]{0,18})$/u),
311
+ relatedMessageIds: z.array(z.string().min(1)).max(200),
312
+ sourceOperationId: z.string().min(1),
313
+ sourceKind: z.literal("dsh-control-result"),
314
+ payloadHash: z.string().regex(/^[0-9a-f]{64}$/u)
315
+ });
316
+ const imUploadObservationSchema = z.object({
317
+ id: z.string().min(1).transform((value) => brandString(value)),
318
+ target: uploadTarget,
319
+ scope: imDeliveryScopeSchema,
320
+ observedAt: timestamp$1,
321
+ identity: imUploadIdentitySchema.optional(),
322
+ message: z.discriminatedUnion("kind", [
323
+ z.object({
324
+ kind: z.literal("inbound"),
325
+ value: inboundMessage
326
+ }),
327
+ z.object({
328
+ kind: z.literal("outbound"),
329
+ value: outbound
330
+ }),
331
+ z.object({
332
+ kind: z.literal("conversation-event"),
333
+ value: imUploadConversationEventSchema
334
+ })
335
+ ])
336
+ });
337
+ const imDeliveryAggregateSchema = z.object({
338
+ uploadIdentities: z.record(z.string(), imUploadIdentitySchema).optional(),
339
+ scope: imDeliveryScopeSchema,
340
+ scopeId,
341
+ uploadTargets: z.record(z.string(), uploadTarget).optional(),
342
+ uploadPending: z.array(imUploadObservationSchema).optional(),
343
+ uploadObservedAt: timestamp$1.optional(),
344
+ nextMessageSequenceNumber: z.number().int().positive(),
345
+ nextOutboundSequenceNumber: z.number().int().positive(),
346
+ cursor,
347
+ presentation: conversationPresentation.optional(),
348
+ directRecipient: z.object({
349
+ providerActorId: z.string().min(1),
350
+ userId: z.string().min(1).optional(),
351
+ openDingTalkId: z.string().min(1).optional()
352
+ }).optional(),
353
+ messages: z.record(z.string(), inboundMessage),
354
+ messageIdsByExternalId: z.record(z.string(), messageId),
355
+ operations: z.record(z.string(), z.object({
356
+ fingerprint: z.string(),
357
+ result: z.union([pageResult, importResult])
358
+ })),
359
+ outbounds: z.record(z.string(), outbound)
360
+ });
361
+ const providerCursorOwner = z.object({
362
+ platform: z.enum(["dingtalk", "wangwang"]),
363
+ accountId,
364
+ streamId: z.string().min(1)
365
+ });
366
+ const providerCursorView = z.object({
367
+ id: providerCursorId,
368
+ owner: providerCursorOwner,
369
+ cursor: z.string().nullable(),
370
+ updatedAt: timestamp$1
371
+ });
372
+ const providerCursorResult = z.object({
373
+ operationId,
374
+ status: z.enum([
375
+ "applied",
376
+ "unchanged",
377
+ "conflict",
378
+ "rejected"
379
+ ]),
380
+ cursor: providerCursorView,
381
+ code: z.literal("IM_PROVIDER_PAGE_NOT_DURABLE").optional(),
382
+ message: z.string().optional()
383
+ });
384
+ /** Parser for one provider-owned feed cursor plus its CAS receipts. */
385
+ const imProviderCursorAggregateSchema = z.object({
386
+ id: providerCursorId,
387
+ owner: providerCursorOwner,
388
+ cursor: z.string().nullable(),
389
+ updatedAt: timestamp$1,
390
+ operations: z.record(z.string(), z.object({
391
+ fingerprint: z.string(),
392
+ result: providerCursorResult
393
+ }))
394
+ });
395
+ const triggerReason = z.enum([
396
+ "direct",
397
+ "mention",
398
+ "every-n",
399
+ "fixed-interval"
400
+ ]);
401
+ /** Frozen analysis parse path, shared by JSON records and compatibility fixtures. */
402
+ const imAdmissionAnnotationSchema = z.discriminatedUnion("status", [z.object({
403
+ id: z.string().min(1).transform((value) => brandString(value)),
404
+ annotatorId: z.string().min(1).transform((value) => brandString(value)),
405
+ version: z.string().min(1),
406
+ status: z.literal("success"),
407
+ text: z.string().min(1)
408
+ }), z.object({
409
+ id: z.string().min(1).transform((value) => brandString(value)),
410
+ annotatorId: z.string().min(1).transform((value) => brandString(value)),
411
+ version: z.string().min(1),
412
+ status: z.enum(["timeout", "failed"])
413
+ })]);
414
+ /** Parser for the frozen batch retained until its exact Session append commits. */
415
+ const imPendingAdmissionSchema = z.object({
416
+ annotations: z.array(imAdmissionAnnotationSchema).optional(),
417
+ handlingEpoch: z.number().int().nonnegative().optional(),
418
+ admissionId,
419
+ messageIds: z.array(messageId).min(1),
420
+ triggerReasons: z.array(triggerReason).min(1),
421
+ createdAt: timestamp$1
422
+ });
423
+ /** Parser for one route-bound Agent task generation. */
424
+ const imExecutionAggregateSchema = z.object({
425
+ taskId: agentTaskId,
426
+ generation: z.number().int().positive(),
427
+ scope: imDeliveryScopeSchema,
428
+ scopeId,
429
+ sessionId: z.string().min(1).transform(SessionId),
430
+ routeId: z.string().min(1).transform((value) => brandString(value)),
431
+ routeRevision: z.string().min(1).transform((value) => brandString(value)),
432
+ accountRevision: z.string().min(1).transform((value) => brandString(value)),
433
+ workspaceId: z.string().min(1).transform(WorkspaceId),
434
+ agentPreset: z.string().min(1),
435
+ controller: z.string().min(1).optional(),
436
+ groupTrigger: z.object({
437
+ mention: z.boolean().optional(),
438
+ everyN: z.number().int().positive().optional(),
439
+ fixedIntervalSeconds: z.number().int().positive().optional()
440
+ }).optional(),
441
+ directRecipient: z.object({
442
+ providerActorId: z.string().min(1),
443
+ userId: z.string().min(1).optional(),
444
+ openDingTalkId: z.string().min(1).optional()
445
+ }).optional(),
446
+ createdAt: timestamp$1,
447
+ updatedAt: timestamp$1,
448
+ pendingAdmission: imPendingAdmissionSchema.optional()
449
+ });
450
+ //#endregion
451
+ //#region src/delivery.ts
452
+ /** Durable receive, history, Session reconciliation, and outbox state. */
453
+ const settled = async (promise) => {
454
+ try {
455
+ await promise;
456
+ } catch {}
457
+ };
458
+ function timestamp() {
459
+ return (/* @__PURE__ */ new Date()).toISOString();
460
+ }
461
+ /**
462
+ * Encode every scope component in an ordered JSON tuple before hashing.
463
+ * @param scope - complete real or simulation conversation identity.
464
+ * @returns stable scope identifier shared by history, Session sources, and outbox rows.
465
+ */
466
+ function encodeImScopeId(scope) {
467
+ const tuple = scope.kind === "real" ? [
468
+ scope.kind,
469
+ scope.platform,
470
+ scope.accountId,
471
+ scope.conversationKind,
472
+ scope.conversationId
473
+ ] : [
474
+ scope.kind,
475
+ scope.instanceId,
476
+ scope.platform,
477
+ scope.accountId,
478
+ scope.conversationKind,
479
+ scope.conversationId
480
+ ];
481
+ return brandString(`im-scope:${createHash("sha256").update(JSON.stringify(tuple)).digest("base64url")}`);
482
+ }
483
+ /**
484
+ * Encode the platform account and provider feed name that jointly own a cursor.
485
+ * @param owner - provider cursor owner.
486
+ * @returns stable provider cursor identifier.
487
+ */
488
+ function encodeImProviderCursorId(owner) {
489
+ return brandString(`im-provider-cursor:${createHash("sha256").update(JSON.stringify([
490
+ owner.platform,
491
+ owner.accountId,
492
+ owner.streamId
493
+ ])).digest("base64url")}`);
494
+ }
495
+ function messageIdentity(scopeId, externalMessageId) {
496
+ return brandString(`im-message:${createHash("sha256").update(JSON.stringify([scopeId, externalMessageId])).digest("base64url")}`);
497
+ }
498
+ function existingInboundId(messages, byExternalId, input) {
499
+ const byId = byExternalId[input.externalMessageId];
500
+ if (byId !== void 0) return byId;
501
+ if (input.sender.kind !== "human-dsh") return void 0;
502
+ const requestId = input.sender.outboundRequestId;
503
+ return Object.values(messages).find((message) => message.sender.kind === "human-dsh" && message.sender.outboundRequestId === requestId)?.messageId;
504
+ }
505
+ function attemptIdentity() {
506
+ return brandString(`im-attempt:${randomUUID()}`);
507
+ }
508
+ /** @param outbound - intent that now owns this platform message id. @returns its durable sender, or absence for an intent that has no echo attribution. */
509
+ function echoSender(outbound) {
510
+ if (outbound.intent === "ai") return {
511
+ kind: "ai",
512
+ outboundRequestId: outbound.requestId
513
+ };
514
+ if (outbound.intent !== "human-manual") return void 0;
515
+ return outbound.sender?.kind === "human-dsh" ? outbound.sender : {
516
+ kind: "human-dsh",
517
+ outboundRequestId: outbound.requestId
518
+ };
519
+ }
520
+ /**
521
+ * Reattribute live history that already carries this exact platform id.
522
+ * Rows with any other id, imported history, and buyer evidence stay as stored.
523
+ * @param messages - current history map.
524
+ * @param outbound - settled outbox row.
525
+ * @param externalMessageId - platform message id from the receipt.
526
+ * @returns the next map and the ids whose sender changed.
527
+ */
528
+ function claimExactEcho(messages, outbound, externalMessageId) {
529
+ const sender = echoSender(outbound);
530
+ if (sender === void 0) return {
531
+ messages,
532
+ revised: []
533
+ };
534
+ let next;
535
+ const revised = [];
536
+ for (const [id, message] of Object.entries(messages)) {
537
+ if (message.origin !== "live" || message.externalMessageId !== externalMessageId) continue;
538
+ if (message.sender.kind !== "human-native" && message.sender.kind !== "unknown") continue;
539
+ if (JSON.stringify(message.sender) === JSON.stringify(sender)) continue;
540
+ const updated = {
541
+ ...message,
542
+ sender
543
+ };
544
+ next ??= { ...messages };
545
+ next[id] = updated;
546
+ revised.push(updated.messageId);
547
+ }
548
+ return {
549
+ messages: next ?? messages,
550
+ revised
551
+ };
552
+ }
553
+ function assertLimit(limit) {
554
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e3) throw new ImRuntimeError("IM_DELIVERY_SCOPE_INVALID", "IM delivery query limit must be an integer from 1 through 1000");
555
+ }
556
+ function sameScope(left, right) {
557
+ return encodeImScopeId(left) === encodeImScopeId(right);
558
+ }
559
+ function initialAggregate(scope) {
560
+ const scopeId = encodeImScopeId(scope);
561
+ return {
562
+ scope,
563
+ scopeId,
564
+ nextMessageSequenceNumber: 1,
565
+ nextOutboundSequenceNumber: 1,
566
+ cursor: {
567
+ scopeId,
568
+ platformCursor: null,
569
+ lastReceivedSequenceNumber: 0,
570
+ lastSubmittedSequenceNumber: 0,
571
+ pendingCount: 0,
572
+ updatedAt: timestamp()
573
+ },
574
+ messages: {},
575
+ messageIdsByExternalId: {},
576
+ operations: {},
577
+ outbounds: {}
578
+ };
579
+ }
580
+ function sortedMessages(aggregate) {
581
+ return Object.values(aggregate.messages).sort((left, right) => left.sequenceNumber - right.sequenceNumber);
582
+ }
583
+ function sortedOutbounds(aggregate) {
584
+ return Object.values(aggregate.outbounds).sort((left, right) => left.sequenceNumber - right.sequenceNumber);
585
+ }
586
+ /** @param messages - one document's messages in sequence order. @param request - validated page bounds and origin filter. @returns the ascending page that ends before the bound. */
587
+ function historyPage(messages, request) {
588
+ const acceptedOrigins = request.origins === void 0 ? void 0 : new Set(request.origins);
589
+ const before = request.beforeSequenceNumber ?? Number.POSITIVE_INFINITY;
590
+ const candidates = messages.filter((message) => message.sequenceNumber < before && (acceptedOrigins?.has(message.origin) ?? true));
591
+ const offset = Math.max(0, candidates.length - request.limit);
592
+ const items = candidates.slice(offset);
593
+ return {
594
+ items,
595
+ hasMore: offset > 0,
596
+ ...offset > 0 && items[0] !== void 0 ? { nextBeforeSequenceNumber: items[0].sequenceNumber } : {}
597
+ };
598
+ }
599
+ /** @param outbounds - one document's outbounds in sequence order. @param request - validated page bounds. @returns the ascending page that ends before the bound. */
600
+ function outboundPage(outbounds, request) {
601
+ const before = request.beforeSequenceNumber ?? Number.POSITIVE_INFINITY;
602
+ const candidates = outbounds.filter((outbound) => outbound.sequenceNumber < before);
603
+ const offset = Math.max(0, candidates.length - request.limit);
604
+ const items = candidates.slice(offset);
605
+ return {
606
+ items,
607
+ hasMore: offset > 0,
608
+ ...offset > 0 && items[0] !== void 0 ? { nextBeforeSequenceNumber: items[0].sequenceNumber } : {}
609
+ };
610
+ }
611
+ /**
612
+ * @param aggregate - stored or initial conversation document.
613
+ * @returns records over the document with the same paging as `ImDeliveryStore.queryHistory` and `queryOutbound`.
614
+ */
615
+ function conversationRecords(aggregate) {
616
+ let messages;
617
+ let outbounds;
618
+ return {
619
+ scope: aggregate.scope,
620
+ cursor: aggregate.cursor,
621
+ queryHistory: (request) => {
622
+ assertLimit(request.limit);
623
+ messages ??= sortedMessages(aggregate);
624
+ return historyPage(messages, request);
625
+ },
626
+ queryOutbound: (request) => {
627
+ assertLimit(request.limit);
628
+ outbounds ??= sortedOutbounds(aggregate);
629
+ return outboundPage(outbounds, request);
630
+ }
631
+ };
632
+ }
633
+ function suppressedThrough(aggregate) {
634
+ return aggregate.cursor.lastSuppressedSequenceNumber ?? 0;
635
+ }
636
+ function pendingCount(aggregate) {
637
+ const suppressed = suppressedThrough(aggregate);
638
+ return Object.values(aggregate.messages).filter((message) => message.origin === "live" && message.stage === "received" && message.sequenceNumber > suppressed).length;
639
+ }
640
+ function submittedWatermark(aggregate) {
641
+ let watermark = 0;
642
+ for (const message of sortedMessages(aggregate)) {
643
+ if (message.origin !== "live") continue;
644
+ if (message.stage !== "submitted") break;
645
+ watermark = message.sequenceNumber;
646
+ }
647
+ return watermark;
648
+ }
649
+ function pageFingerprint(request, includePeer = true) {
650
+ return JSON.stringify([
651
+ "page",
652
+ request.scope,
653
+ request.observedCursor,
654
+ request.nextCursor,
655
+ request.presentation ?? null,
656
+ request.messages,
657
+ ...!includePeer || request.directRecipient === void 0 ? [] : [request.directRecipient]
658
+ ]);
659
+ }
660
+ function importFingerprint(request, fileName, messages) {
661
+ return JSON.stringify([
662
+ "jsonl-import",
663
+ request.scope,
664
+ fileName,
665
+ messages
666
+ ]);
667
+ }
668
+ function assertOperation(existing, fingerprint, operationId) {
669
+ if (existing !== void 0 && existing.fingerprint !== fingerprint) throw new ImRuntimeError("IM_DELIVERY_OPERATION_REUSED", `IM delivery operation '${operationId}' was already used for a different request`);
670
+ }
671
+ function assertOutbound(existing, request) {
672
+ if (existing === void 0) return;
673
+ if (JSON.stringify([
674
+ existing.intent,
675
+ existing.content,
676
+ existing.sender ?? null,
677
+ existing.manualBinding ?? null,
678
+ existing.retryOfRequestId ?? null,
679
+ existing.replyToExternalMessageId ?? null
680
+ ]) !== JSON.stringify([
681
+ request.intent,
682
+ request.content,
683
+ request.sender ?? null,
684
+ request.manualBinding ?? null,
685
+ request.retryOfRequestId ?? null,
686
+ request.replyToExternalMessageId ?? null
687
+ ])) throw new ImRuntimeError("IM_OUTBOUND_REQUEST_REUSED", `IM outbound request '${request.requestId}' was already used for a different intent`);
688
+ }
689
+ function preparationUnknown(outbound, changedAt) {
690
+ return {
691
+ ...outbound,
692
+ status: "result-unknown",
693
+ receipt: {
694
+ providerStatus: "media-preparation-unknown",
695
+ errorCode: "IM_MEDIA_PREPARATION_UNKNOWN",
696
+ observedAt: changedAt
697
+ },
698
+ updatedAt: changedAt
699
+ };
700
+ }
701
+ /** One-record mutations for complete conversation scopes. */
702
+ var ImDeliveryStore = class {
703
+ records;
704
+ host;
705
+ tails = /* @__PURE__ */ new Map();
706
+ cursorTails = /* @__PURE__ */ new Map();
707
+ cursors;
708
+ /** @param records - sqlite delivery records. @param host - account policy and post-commit publisher. */
709
+ constructor(records, host) {
710
+ this.records = records;
711
+ this.host = host;
712
+ this.cursors = records.projectScopes((value) => value.cursor);
713
+ }
714
+ /** Recover interrupted sends and unfinished file preparations before publishing the runtime, independently of provider availability. */
715
+ async recoverInterruptedAttempts() {
716
+ const unfinishedFiles = new Set(this.records.unfinishedFilePreparationIds());
717
+ const scopeIds = new Set(this.records.dispatchingScopeIds());
718
+ for (const requestId of unfinishedFiles) {
719
+ const owner = this.records.outboundOwner(requestId);
720
+ if (owner !== void 0) scopeIds.add(encodeImScopeId(owner.scope));
721
+ }
722
+ for (const scopeId of scopeIds) await this.enqueue(scopeId, async () => {
723
+ const current = this.require(scopeId);
724
+ const changedAt = timestamp();
725
+ let changed = false;
726
+ const outbounds = Object.fromEntries(Object.entries(current.outbounds).map(([id, outbound]) => {
727
+ if (unfinishedFiles.has(outbound.requestId) && outbound.status !== "sent") {
728
+ if (outbound.status === "result-unknown" && outbound.receipt?.errorCode === "IM_MEDIA_PREPARATION_UNKNOWN") return [id, outbound];
729
+ changed = true;
730
+ return [id, preparationUnknown(outbound, changedAt)];
731
+ }
732
+ if (outbound.status !== "dispatching") return [id, outbound];
733
+ changed = true;
734
+ return [id, {
735
+ ...outbound,
736
+ status: "result-unknown",
737
+ receipt: outbound.receipt ?? {
738
+ providerStatus: "interrupted",
739
+ observedAt: changedAt
740
+ },
741
+ updatedAt: changedAt
742
+ }];
743
+ }));
744
+ if (changed) await this.persistMutation(current, {
745
+ ...current,
746
+ outbounds
747
+ });
748
+ });
749
+ }
750
+ /** @param request - Validated page and scope. @param receiptTime - Instance script time; absent uses the ingestion clock. @returns The durable page receipt. */
751
+ async ingestInboundPage(request, receiptTime) {
752
+ if (request.scope.kind === "real") this.assertRealScope(request.scope);
753
+ if (request.scope.conversationKind === "direct" && request.presentation?.memberCount !== void 0) throw new ImRuntimeError("IM_DELIVERY_SCOPE_INVALID", "direct-conversation presentation cannot carry a member count");
754
+ const scopeId = encodeImScopeId(request.scope);
755
+ const fingerprint = pageFingerprint(request);
756
+ return this.mutate(request.scope, (current) => {
757
+ const existing = current.operations[request.operationId];
758
+ assertOperation(existing, existing !== void 0 && existing.fingerprint === pageFingerprint(request, false) ? existing.fingerprint : fingerprint, request.operationId);
759
+ if (existing !== void 0) {
760
+ if (existing.result.kind !== "page") throw new ImRuntimeError("IM_DELIVERY_OPERATION_REUSED", `IM delivery operation '${request.operationId}' is not a page operation`);
761
+ return {
762
+ aggregate: current,
763
+ value: existing.result,
764
+ changed: false
765
+ };
766
+ }
767
+ if (current.cursor.platformCursor !== request.observedCursor) {
768
+ const result = {
769
+ kind: "page",
770
+ operationId: request.operationId,
771
+ status: "conflict",
772
+ acceptedCount: 0,
773
+ evidenceMergedCount: 0,
774
+ duplicateCount: 0,
775
+ messages: [],
776
+ cursor: current.cursor
777
+ };
778
+ return {
779
+ aggregate: {
780
+ ...current,
781
+ operations: {
782
+ ...current.operations,
783
+ [request.operationId]: {
784
+ fingerprint,
785
+ result
786
+ }
787
+ }
788
+ },
789
+ value: result,
790
+ changed: true
791
+ };
792
+ }
793
+ let nextSequence = current.nextMessageSequenceNumber;
794
+ const messages = { ...current.messages };
795
+ const byExternalId = { ...current.messageIdsByExternalId };
796
+ const accepted = [];
797
+ const evidenceMerged = [];
798
+ let duplicateCount = 0;
799
+ const ordered = [...request.messages].sort((left, right) => left.occurredAt.localeCompare(right.occurredAt) || (request.scope.kind === "simulation" ? 0 : left.externalMessageId.localeCompare(right.externalMessageId)));
800
+ const receivedAt = receiptTime ?? timestamp();
801
+ for (const input of ordered) {
802
+ const existingId = existingInboundId(messages, byExternalId, input);
803
+ if (existingId !== void 0) {
804
+ duplicateCount++;
805
+ const existingMessage = messages[existingId];
806
+ if (existingMessage !== void 0 && existingMessage.mentionedConfiguredAccount !== true && input.mentionedConfiguredAccount === true) {
807
+ const enriched = {
808
+ ...existingMessage,
809
+ mentionedConfiguredAccount: true
810
+ };
811
+ messages[existingId] = enriched;
812
+ evidenceMerged.push(enriched);
813
+ }
814
+ if (existingMessage !== void 0 && input.sender.kind !== "unknown" && JSON.stringify(existingMessage.sender) !== JSON.stringify(input.sender)) {
815
+ const revised = {
816
+ ...messages[existingId],
817
+ sender: input.sender
818
+ };
819
+ messages[existingId] = revised;
820
+ evidenceMerged.push(revised);
821
+ }
822
+ if (existingMessage !== void 0 && existingMessage.externalMessageId !== input.externalMessageId) {
823
+ delete byExternalId[existingMessage.externalMessageId];
824
+ messages[existingId] = {
825
+ ...messages[existingId],
826
+ externalMessageId: input.externalMessageId
827
+ };
828
+ byExternalId[input.externalMessageId] = existingId;
829
+ }
830
+ continue;
831
+ }
832
+ const id = messageIdentity(scopeId, input.externalMessageId);
833
+ const message = {
834
+ ...input,
835
+ messageId: id,
836
+ scopeId,
837
+ origin: "live",
838
+ stage: "received",
839
+ sequenceNumber: nextSequence++,
840
+ receivedAt: request.scope.kind === "simulation" ? input.occurredAt : receivedAt
841
+ };
842
+ messages[id] = message;
843
+ byExternalId[input.externalMessageId] = id;
844
+ accepted.push(message);
845
+ }
846
+ const mergedMessages = [...new Set(evidenceMerged.map((message) => message.messageId))].map((id) => messages[id]);
847
+ const nextMessages = {
848
+ ...current,
849
+ nextMessageSequenceNumber: nextSequence,
850
+ messages
851
+ };
852
+ const cursor = {
853
+ ...current.cursor,
854
+ platformCursor: request.nextCursor,
855
+ lastReceivedSequenceNumber: accepted.at(-1)?.sequenceNumber ?? current.cursor.lastReceivedSequenceNumber,
856
+ pendingCount: pendingCount(nextMessages),
857
+ lastSyncedAt: receivedAt,
858
+ updatedAt: receivedAt
859
+ };
860
+ const result = {
861
+ kind: "page",
862
+ operationId: request.operationId,
863
+ status: "applied",
864
+ acceptedCount: accepted.length,
865
+ evidenceMergedCount: mergedMessages.length,
866
+ duplicateCount,
867
+ messages: [...accepted, ...mergedMessages],
868
+ cursor
869
+ };
870
+ return {
871
+ aggregate: {
872
+ ...current,
873
+ nextMessageSequenceNumber: nextSequence,
874
+ cursor,
875
+ messages,
876
+ messageIdsByExternalId: byExternalId,
877
+ ...request.presentation === void 0 ? {} : { presentation: {
878
+ ...current.presentation,
879
+ ...request.presentation
880
+ } },
881
+ ...request.directRecipient === void 0 ? {} : { directRecipient: request.directRecipient },
882
+ operations: {
883
+ ...current.operations,
884
+ [request.operationId]: {
885
+ fingerprint,
886
+ result
887
+ }
888
+ }
889
+ },
890
+ value: result,
891
+ changed: true
892
+ };
893
+ }).then((result) => {
894
+ if (result.changed) this.host.publish({
895
+ scope: request.scope,
896
+ scopeId,
897
+ kind: "inbound",
898
+ messageIds: result.value.messages.map((message) => message.messageId)
899
+ });
900
+ return result.value;
901
+ });
902
+ }
903
+ queryInboundOperation(scope, operationId) {
904
+ const result = this.lookup(scope)?.operations[operationId]?.result;
905
+ return result === void 0 ? { state: "not-found" } : {
906
+ state: "known",
907
+ result
908
+ };
909
+ }
910
+ getConversationCursor(scope) {
911
+ return this.cursors.get(encodeImScopeId(scope)) ?? initialAggregate(scope).cursor;
912
+ }
913
+ /**
914
+ * @param project - pure function of one conversation's records.
915
+ * @returns values derived at each document parse or write; a conversation without a document projects its initial records on every read.
916
+ */
917
+ projectConversations(project) {
918
+ const projection = this.records.projectScopes((value) => project(conversationRecords(value)));
919
+ return {
920
+ get: (scope) => projection.get(encodeImScopeId(scope)) ?? project(conversationRecords(initialAggregate(scope))),
921
+ dispose: () => {
922
+ projection.dispose();
923
+ }
924
+ };
925
+ }
926
+ /** @param scope - durable conversation. @returns last admitted presentation, including the assigned service account when known. */
927
+ getConversationPresentation(scope) {
928
+ return this.lookup(scope)?.presentation;
929
+ }
930
+ getProviderCursor(owner) {
931
+ const id = encodeImProviderCursorId(owner);
932
+ const aggregate = this.records.getCursor(id);
933
+ if (aggregate !== void 0) return {
934
+ id,
935
+ owner: aggregate.owner,
936
+ cursor: aggregate.cursor,
937
+ updatedAt: aggregate.updatedAt
938
+ };
939
+ return {
940
+ id,
941
+ owner,
942
+ cursor: null,
943
+ updatedAt: timestamp()
944
+ };
945
+ }
946
+ async commitProviderCursor(request) {
947
+ const account = this.host.inspectAccount(request.owner.accountId);
948
+ if (account === void 0) throw new ImRuntimeError("IM_ACCOUNT_NOT_FOUND", `IM account '${request.owner.accountId}' is unknown`);
949
+ if (account.platform !== request.owner.platform || request.owner.streamId.trim() === "") throw new ImRuntimeError("IM_DELIVERY_SCOPE_INVALID", "provider cursor owner must use the configured account platform and a non-empty streamId");
950
+ const id = encodeImProviderCursorId(request.owner);
951
+ const fingerprint = JSON.stringify([
952
+ "provider-cursor",
953
+ request.owner,
954
+ request.observedCursor,
955
+ request.nextCursor,
956
+ request.pages.map((page) => [page.scope, page.operationId])
957
+ ]);
958
+ return this.enqueueCursor(id, async () => {
959
+ const current = this.records.getCursor(id) ?? {
960
+ id,
961
+ owner: request.owner,
962
+ cursor: null,
963
+ updatedAt: timestamp(),
964
+ operations: {}
965
+ };
966
+ const existing = current.operations[request.operationId];
967
+ assertOperation(existing, fingerprint, request.operationId);
968
+ if (existing !== void 0) return existing.result;
969
+ let status;
970
+ let code;
971
+ let message;
972
+ if (current.cursor !== request.observedCursor) status = "conflict";
973
+ else {
974
+ const missing = request.pages.find((page) => {
975
+ const receipt = this.lookup(page.scope)?.operations[page.operationId]?.result;
976
+ return receipt?.kind !== "page" || receipt.status !== "applied";
977
+ });
978
+ if (missing !== void 0) {
979
+ status = "rejected";
980
+ code = "IM_PROVIDER_PAGE_NOT_DURABLE";
981
+ message = `conversation page '${missing.operationId}' is not durably applied`;
982
+ } else status = current.cursor === request.nextCursor ? "unchanged" : "applied";
983
+ }
984
+ const updatedAt = timestamp();
985
+ const cursor = {
986
+ id,
987
+ owner: current.owner,
988
+ cursor: status === "applied" ? request.nextCursor : current.cursor,
989
+ updatedAt: status === "applied" ? updatedAt : current.updatedAt
990
+ };
991
+ const result = {
992
+ operationId: request.operationId,
993
+ status,
994
+ cursor,
995
+ ...code === void 0 ? {} : { code },
996
+ ...message === void 0 ? {} : { message }
997
+ };
998
+ await this.records.putCursor(id, {
999
+ id,
1000
+ owner: current.owner,
1001
+ cursor: cursor.cursor,
1002
+ updatedAt: cursor.updatedAt,
1003
+ operations: {
1004
+ ...current.operations,
1005
+ [request.operationId]: {
1006
+ fingerprint,
1007
+ result
1008
+ }
1009
+ }
1010
+ });
1011
+ if (status === "applied") this.host.onProviderCursorApplied?.(request.owner);
1012
+ return result;
1013
+ });
1014
+ }
1015
+ queryProviderCursorOperation(owner, operationId) {
1016
+ const result = this.records.getCursor(encodeImProviderCursorId(owner))?.operations[operationId]?.result;
1017
+ return result === void 0 ? { state: "not-found" } : {
1018
+ state: "known",
1019
+ result
1020
+ };
1021
+ }
1022
+ queryHistory(request) {
1023
+ assertLimit(request.limit);
1024
+ const aggregate = this.lookup(request.scope);
1025
+ if (aggregate === void 0) return {
1026
+ items: [],
1027
+ hasMore: false
1028
+ };
1029
+ return historyPage(sortedMessages(aggregate), request);
1030
+ }
1031
+ pendingInbound(request) {
1032
+ assertLimit(request.limit);
1033
+ if (!this.records.hasPendingInbound(encodeImScopeId(request.scope))) return [];
1034
+ const aggregate = this.lookup(request.scope);
1035
+ if (aggregate === void 0) return [];
1036
+ const suppressed = suppressedThrough(aggregate);
1037
+ return sortedMessages(aggregate).filter((message) => message.origin === "live" && message.stage === "received" && message.sequenceNumber > suppressed).slice(0, request.limit);
1038
+ }
1039
+ /**
1040
+ * Consume the current received live batch without an Agent turn.
1041
+ * A later allow only admits newer sequence numbers.
1042
+ * @param scope - conversation whose policy just refused admission.
1043
+ */
1044
+ suppressUnadmitted(scope) {
1045
+ const scopeId = encodeImScopeId(scope);
1046
+ return this.enqueue(scopeId, async () => {
1047
+ if (!this.records.hasPendingInbound(scopeId)) return;
1048
+ await this.applyMutation(scope, (current) => {
1049
+ let through = suppressedThrough(current);
1050
+ for (const message of Object.values(current.messages)) if (message.origin === "live" && message.stage === "received" && message.sequenceNumber > through) through = message.sequenceNumber;
1051
+ if (through === suppressedThrough(current)) return {
1052
+ aggregate: current,
1053
+ value: void 0,
1054
+ changed: false
1055
+ };
1056
+ const cursor = {
1057
+ ...current.cursor,
1058
+ lastSuppressedSequenceNumber: through,
1059
+ pendingCount: pendingCount({
1060
+ ...current,
1061
+ cursor: {
1062
+ ...current.cursor,
1063
+ lastSuppressedSequenceNumber: through
1064
+ }
1065
+ }),
1066
+ updatedAt: timestamp()
1067
+ };
1068
+ return {
1069
+ aggregate: {
1070
+ ...current,
1071
+ cursor
1072
+ },
1073
+ value: void 0,
1074
+ changed: true
1075
+ };
1076
+ });
1077
+ });
1078
+ }
1079
+ messageSource(scope, messageId) {
1080
+ const message = this.lookup(scope)?.messages[messageId];
1081
+ if (message === void 0) throw new ImRuntimeError("IM_MESSAGE_NOT_FOUND", `IM message '${messageId}' is unknown for scope '${encodeImScopeId(scope)}'`);
1082
+ return {
1083
+ kind: "im",
1084
+ scopeId: message.scopeId,
1085
+ messageId: message.messageId,
1086
+ sequenceNumber: message.sequenceNumber
1087
+ };
1088
+ }
1089
+ /** @param source - durable Session message source. @returns matched scope, or absence for stale or foreign evidence. */
1090
+ matchMessageSource(source) {
1091
+ const aggregate = this.records.getScope(source.scopeId);
1092
+ const message = aggregate?.messages[source.messageId];
1093
+ if (aggregate === void 0 || message === void 0 || message.origin !== "live" || message.sequenceNumber !== source.sequenceNumber) return void 0;
1094
+ return {
1095
+ scope: aggregate.scope,
1096
+ messageId: message.messageId
1097
+ };
1098
+ }
1099
+ /** @param scope - complete scope. @param messageId - inbound identity. @returns stored row. */
1100
+ getInbound(scope, messageId) {
1101
+ const message = this.lookup(scope)?.messages[messageId];
1102
+ if (message === void 0) throw new ImRuntimeError("IM_MESSAGE_NOT_FOUND", `IM message '${messageId}' is unknown for scope '${encodeImScopeId(scope)}'`);
1103
+ return message;
1104
+ }
1105
+ markSubmitted(request) {
1106
+ const scopeId = encodeImScopeId(request.scope);
1107
+ return this.mutate(request.scope, (current) => {
1108
+ const submittedAt = request.submittedAt ?? timestamp();
1109
+ const messages = { ...current.messages };
1110
+ const changed = [];
1111
+ for (const id of new Set(request.messageIds)) {
1112
+ const message = messages[id];
1113
+ if (message === void 0 || message.origin !== "live") throw new ImRuntimeError("IM_MESSAGE_NOT_FOUND", `live IM message '${id}' is unknown for scope '${scopeId}'`);
1114
+ if (message.submission !== void 0 && message.submission.sessionId !== request.sessionId) throw new ImRuntimeError("IM_MESSAGE_ALREADY_SUBMITTED", `IM message '${id}' is already submitted to Session '${message.submission.sessionId}'`);
1115
+ if (message.stage === "submitted") continue;
1116
+ const submitted = {
1117
+ ...message,
1118
+ stage: "submitted",
1119
+ submission: {
1120
+ sessionId: request.sessionId,
1121
+ submittedAt
1122
+ }
1123
+ };
1124
+ messages[id] = submitted;
1125
+ changed.push(submitted);
1126
+ }
1127
+ if (changed.length === 0) return {
1128
+ aggregate: current,
1129
+ value: {
1130
+ messages: [],
1131
+ cursor: current.cursor
1132
+ },
1133
+ changed: false
1134
+ };
1135
+ const next = {
1136
+ ...current,
1137
+ messages
1138
+ };
1139
+ const cursor = {
1140
+ ...current.cursor,
1141
+ lastSubmittedSequenceNumber: submittedWatermark(next),
1142
+ pendingCount: pendingCount(next),
1143
+ updatedAt: submittedAt
1144
+ };
1145
+ return {
1146
+ aggregate: {
1147
+ ...next,
1148
+ cursor
1149
+ },
1150
+ value: {
1151
+ messages: changed,
1152
+ cursor
1153
+ },
1154
+ changed: true
1155
+ };
1156
+ }).then((result) => {
1157
+ if (result.changed) this.host.publish({
1158
+ scope: request.scope,
1159
+ scopeId,
1160
+ kind: "submitted",
1161
+ messageIds: result.value.messages.map((message) => message.messageId)
1162
+ });
1163
+ return result.value;
1164
+ });
1165
+ }
1166
+ async importJsonlHistory(request) {
1167
+ const fileName = basename(request.fileName.trim().replaceAll("\\", "/"));
1168
+ if (fileName === "" || fileName === "." || fileName === "..") throw new ImRuntimeError("IM_JSONL_INVALID", "IM history import fileName must name a file");
1169
+ const parsed = [];
1170
+ for (const [index, line] of request.jsonl.split(/\r?\n/u).entries()) {
1171
+ if (line.trim() === "") continue;
1172
+ let value;
1173
+ try {
1174
+ value = JSON.parse(line);
1175
+ } catch (error) {
1176
+ throw new ImRuntimeError("IM_JSONL_INVALID", `IM JSONL line ${index + 1} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
1177
+ }
1178
+ const admitted = imInboundMessageInputSchema.safeParse(value);
1179
+ if (!admitted.success) throw new ImRuntimeError("IM_JSONL_INVALID", `IM JSONL line ${index + 1} is invalid: ${admitted.error.message}`);
1180
+ parsed.push(admitted.data);
1181
+ }
1182
+ const scopeId = encodeImScopeId(request.scope);
1183
+ const fingerprint = importFingerprint(request, fileName, parsed);
1184
+ return this.mutate(request.scope, (current) => {
1185
+ const existing = current.operations[request.operationId];
1186
+ assertOperation(existing, fingerprint, request.operationId);
1187
+ if (existing !== void 0) {
1188
+ if (existing.result.kind !== "jsonl-import") throw new ImRuntimeError("IM_DELIVERY_OPERATION_REUSED", `IM delivery operation '${request.operationId}' is not an import operation`);
1189
+ return {
1190
+ aggregate: current,
1191
+ value: existing.result,
1192
+ changed: false
1193
+ };
1194
+ }
1195
+ let nextSequence = current.nextMessageSequenceNumber;
1196
+ const messages = { ...current.messages };
1197
+ const byExternalId = { ...current.messageIdsByExternalId };
1198
+ let importedCount = 0;
1199
+ let duplicateCount = 0;
1200
+ const receivedAt = timestamp();
1201
+ for (const input of parsed) {
1202
+ if (byExternalId[input.externalMessageId] !== void 0) {
1203
+ duplicateCount++;
1204
+ continue;
1205
+ }
1206
+ const id = messageIdentity(scopeId, input.externalMessageId);
1207
+ messages[id] = {
1208
+ ...input,
1209
+ messageId: id,
1210
+ scopeId,
1211
+ origin: "jsonl-import",
1212
+ stage: "submitted",
1213
+ sequenceNumber: nextSequence++,
1214
+ receivedAt
1215
+ };
1216
+ byExternalId[input.externalMessageId] = id;
1217
+ importedCount++;
1218
+ }
1219
+ const result = {
1220
+ kind: "jsonl-import",
1221
+ operationId: request.operationId,
1222
+ status: "applied",
1223
+ fileName,
1224
+ messageCount: parsed.length,
1225
+ importedCount,
1226
+ duplicateCount
1227
+ };
1228
+ return {
1229
+ aggregate: {
1230
+ ...current,
1231
+ nextMessageSequenceNumber: nextSequence,
1232
+ messages,
1233
+ messageIdsByExternalId: byExternalId,
1234
+ operations: {
1235
+ ...current.operations,
1236
+ [request.operationId]: {
1237
+ fingerprint,
1238
+ result
1239
+ }
1240
+ }
1241
+ },
1242
+ value: result,
1243
+ changed: true
1244
+ };
1245
+ }).then((result) => {
1246
+ if (result.changed) this.host.publish({
1247
+ scope: request.scope,
1248
+ scopeId,
1249
+ kind: "inbound"
1250
+ });
1251
+ return result.value;
1252
+ });
1253
+ }
1254
+ /** @param request - Scoped send intent. @param frozenBinding - Route observed by the Agent task. @param clock - Time sampled when the intent is stored. @returns Durable outbox intent. */
1255
+ registerOutbound(request, frozenBinding, clock = Date.now) {
1256
+ const scopeId = encodeImScopeId(request.scope);
1257
+ return this.mutate(request.scope, (current) => {
1258
+ const existing = current.outbounds[request.requestId];
1259
+ assertOutbound(existing, request);
1260
+ if (existing !== void 0) return {
1261
+ aggregate: current,
1262
+ value: existing,
1263
+ changed: false
1264
+ };
1265
+ if (request.sender !== void 0 && (request.intent !== "human-manual" || request.sender.outboundRequestId !== request.requestId)) throw new ImRuntimeError("IM_MANUAL_MESSAGE_INVALID", "manual sender evidence must identify its own human-manual request");
1266
+ if (request.manualBinding !== void 0 && (request.intent !== "human-manual" || request.sender === void 0)) throw new ImRuntimeError("IM_MANUAL_MESSAGE_INVALID", "manual Session binding requires persisted human sender evidence");
1267
+ if (request.retryOfRequestId !== void 0) {
1268
+ const prior = current.outbounds[request.retryOfRequestId];
1269
+ if (request.intent !== "human-manual" || request.retryOfRequestId === request.requestId || prior?.intent !== "human-manual" || !(prior.content.format === "media" ? ["confirmed-failed", "pre-send-failed"].includes(prior.status) : prior.status === "result-unknown") || prior.content.format === "media" && this.records.hasUnfinishedFilePreparation(prior.requestId) || JSON.stringify(prior.manualBinding) !== JSON.stringify(request.manualBinding)) throw new ImRuntimeError("IM_MANUAL_RETRY_INVALID", "manual retry must name a distinct eligible human message in the same scope");
1270
+ }
1271
+ const policy = this.registrationPolicy(request, frozenBinding);
1272
+ const createdAt = new Date(clock()).toISOString();
1273
+ const outbound = {
1274
+ requestId: request.requestId,
1275
+ scopeId,
1276
+ intent: request.intent,
1277
+ content: request.content,
1278
+ status: policy.reason === void 0 ? "pending" : "pre-send-failed",
1279
+ sequenceNumber: current.nextOutboundSequenceNumber,
1280
+ ...request.intent === "ai" ? { handlingEpoch: frozenBinding?.handlingEpoch ?? this.records.getHandlingControl(scopeId)?.handlingEpoch ?? 0 } : {},
1281
+ ...policy.binding === void 0 ? {} : { routeBinding: policy.binding },
1282
+ ...request.sender === void 0 ? {} : { sender: request.sender },
1283
+ ...request.manualBinding === void 0 ? {} : { manualBinding: request.manualBinding },
1284
+ ...request.retryOfRequestId === void 0 ? {} : { retryOfRequestId: request.retryOfRequestId },
1285
+ ...policy.reason === void 0 ? {} : { preSendFailureReason: policy.reason },
1286
+ ...request.replyToExternalMessageId === void 0 ? {} : { replyToExternalMessageId: request.replyToExternalMessageId },
1287
+ createdAt,
1288
+ updatedAt: createdAt
1289
+ };
1290
+ return {
1291
+ aggregate: {
1292
+ ...current,
1293
+ nextOutboundSequenceNumber: current.nextOutboundSequenceNumber + 1,
1294
+ outbounds: {
1295
+ ...current.outbounds,
1296
+ [request.requestId]: outbound
1297
+ }
1298
+ },
1299
+ value: outbound,
1300
+ changed: true
1301
+ };
1302
+ }).then((result) => {
1303
+ if (result.changed) this.host.publish({
1304
+ scope: request.scope,
1305
+ scopeId,
1306
+ kind: "outbound",
1307
+ requestId: request.requestId
1308
+ });
1309
+ return result.value;
1310
+ });
1311
+ }
1312
+ /** @param request - owning real outbox intent. @param media - durable upload display facts. @returns the updated row after its preparation commit. */
1313
+ recordMediaPreparation(request, media) {
1314
+ const scopeId = encodeImScopeId(request.scope);
1315
+ return this.mutate(request.scope, (current) => {
1316
+ const outbound = current.outbounds[request.requestId];
1317
+ if (outbound === void 0 || outbound.status !== "pending" || outbound.content.format !== "media") throw new ImRuntimeError("IM_OUTBOUND_STATE_INVALID", "media preparation requires a pending media intent");
1318
+ const updated = {
1319
+ ...outbound,
1320
+ media,
1321
+ updatedAt: timestamp()
1322
+ };
1323
+ return {
1324
+ aggregate: {
1325
+ ...current,
1326
+ outbounds: {
1327
+ ...current.outbounds,
1328
+ [request.requestId]: updated
1329
+ }
1330
+ },
1331
+ value: updated,
1332
+ changed: true
1333
+ };
1334
+ }).then((result) => {
1335
+ this.host.publish({
1336
+ scope: request.scope,
1337
+ scopeId,
1338
+ kind: "outbound",
1339
+ requestId: request.requestId
1340
+ });
1341
+ return result.value;
1342
+ });
1343
+ }
1344
+ beginOutboundAttempt(request) {
1345
+ const scopeId = encodeImScopeId(request.scope);
1346
+ return this.mutate(request.scope, (current) => {
1347
+ const outbound = current.outbounds[request.requestId];
1348
+ if (outbound === void 0) throw new ImRuntimeError("IM_OUTBOUND_NOT_FOUND", `IM outbound request '${request.requestId}' is unknown`);
1349
+ if (outbound.status !== "sent" && this.records.hasUnfinishedFilePreparation(request.requestId) && (outbound.status !== "result-unknown" || outbound.receipt?.errorCode !== "IM_MEDIA_PREPARATION_UNKNOWN")) {
1350
+ const updated = preparationUnknown(outbound, timestamp());
1351
+ return {
1352
+ aggregate: {
1353
+ ...current,
1354
+ outbounds: {
1355
+ ...current.outbounds,
1356
+ [request.requestId]: updated
1357
+ }
1358
+ },
1359
+ value: {
1360
+ state: "result-unknown",
1361
+ outbound: updated
1362
+ },
1363
+ changed: true
1364
+ };
1365
+ }
1366
+ if (outbound.status === "result-unknown") return {
1367
+ aggregate: current,
1368
+ value: {
1369
+ state: "result-unknown",
1370
+ outbound
1371
+ },
1372
+ changed: false
1373
+ };
1374
+ if (outbound.status === "dispatching") {
1375
+ const updated = {
1376
+ ...outbound,
1377
+ status: "result-unknown",
1378
+ receipt: outbound.receipt ?? {
1379
+ providerStatus: "repeated-begin",
1380
+ observedAt: timestamp()
1381
+ },
1382
+ updatedAt: timestamp()
1383
+ };
1384
+ return {
1385
+ aggregate: {
1386
+ ...current,
1387
+ outbounds: {
1388
+ ...current.outbounds,
1389
+ [request.requestId]: updated
1390
+ }
1391
+ },
1392
+ value: {
1393
+ state: "result-unknown",
1394
+ outbound: updated
1395
+ },
1396
+ changed: true
1397
+ };
1398
+ }
1399
+ if (outbound.status !== "pending") return {
1400
+ aggregate: current,
1401
+ value: {
1402
+ state: "blocked",
1403
+ outbound
1404
+ },
1405
+ changed: false
1406
+ };
1407
+ const reason = this.dispatchFailure(request.scope, outbound);
1408
+ if (reason !== void 0) {
1409
+ const updated = {
1410
+ ...outbound,
1411
+ status: "pre-send-failed",
1412
+ preSendFailureReason: reason,
1413
+ updatedAt: timestamp()
1414
+ };
1415
+ return {
1416
+ aggregate: {
1417
+ ...current,
1418
+ outbounds: {
1419
+ ...current.outbounds,
1420
+ [request.requestId]: updated
1421
+ }
1422
+ },
1423
+ value: {
1424
+ state: "blocked",
1425
+ outbound: updated
1426
+ },
1427
+ changed: true
1428
+ };
1429
+ }
1430
+ const attempt = {
1431
+ attemptId: attemptIdentity(),
1432
+ startedAt: timestamp()
1433
+ };
1434
+ const updated = {
1435
+ ...outbound,
1436
+ status: "dispatching",
1437
+ attempt,
1438
+ updatedAt: attempt.startedAt
1439
+ };
1440
+ return {
1441
+ aggregate: {
1442
+ ...current,
1443
+ outbounds: {
1444
+ ...current.outbounds,
1445
+ [request.requestId]: updated
1446
+ }
1447
+ },
1448
+ value: {
1449
+ state: "ready",
1450
+ attemptId: attempt.attemptId,
1451
+ outbound: updated
1452
+ },
1453
+ changed: true
1454
+ };
1455
+ }).then((result) => {
1456
+ if (result.changed) this.host.publish({
1457
+ scope: request.scope,
1458
+ scopeId,
1459
+ kind: "outbound",
1460
+ requestId: request.requestId
1461
+ });
1462
+ return result.value;
1463
+ });
1464
+ }
1465
+ /** @param request - Owning attempt. @returns Whether its frozen route and handling epoch still authorize an external submission. */
1466
+ authorizeOutboundSubmission(request) {
1467
+ const outbound = this.lookup(request.scope)?.outbounds[request.requestId];
1468
+ return outbound?.status === "dispatching" && outbound.attempt?.attemptId === request.attemptId && this.dispatchFailure(request.scope, outbound) === void 0;
1469
+ }
1470
+ settleOutboundAttempt(request) {
1471
+ const scopeId = encodeImScopeId(request.scope);
1472
+ return this.mutate(request.scope, (current) => {
1473
+ const outbound = current.outbounds[request.requestId];
1474
+ if (outbound === void 0) throw new ImRuntimeError("IM_OUTBOUND_NOT_FOUND", `IM outbound request '${request.requestId}' is unknown`);
1475
+ if (outbound.attempt?.attemptId !== request.attemptId) throw new ImRuntimeError("IM_OUTBOUND_ATTEMPT_MISMATCH", `IM outbound request '${request.requestId}' does not own attempt '${request.attemptId}'`);
1476
+ const unchanged = {
1477
+ outbound,
1478
+ revised: []
1479
+ };
1480
+ if (outbound.status === request.status && outbound.externalMessageId === request.externalMessageId && JSON.stringify(outbound.receipt) === JSON.stringify(request.receipt)) return {
1481
+ aggregate: current,
1482
+ value: unchanged,
1483
+ changed: false
1484
+ };
1485
+ if (outbound.status === "sent") return {
1486
+ aggregate: current,
1487
+ value: unchanged,
1488
+ changed: false
1489
+ };
1490
+ if (outbound.status !== "dispatching" && outbound.status !== "result-unknown") throw new ImRuntimeError("IM_OUTBOUND_STATE_INVALID", `IM outbound request '${request.requestId}' cannot settle from '${outbound.status}'`);
1491
+ const updated = {
1492
+ ...outbound,
1493
+ status: request.status,
1494
+ ...request.receipt === void 0 ? {} : { receipt: request.receipt },
1495
+ ...request.externalMessageId === void 0 ? {} : { externalMessageId: request.externalMessageId },
1496
+ updatedAt: timestamp()
1497
+ };
1498
+ const claimed = request.status === "sent" && request.externalMessageId !== void 0 ? claimExactEcho(current.messages, updated, request.externalMessageId) : {
1499
+ messages: current.messages,
1500
+ revised: []
1501
+ };
1502
+ return {
1503
+ aggregate: {
1504
+ ...current,
1505
+ messages: claimed.messages,
1506
+ outbounds: {
1507
+ ...current.outbounds,
1508
+ [request.requestId]: updated
1509
+ }
1510
+ },
1511
+ value: {
1512
+ outbound: updated,
1513
+ revised: claimed.revised
1514
+ },
1515
+ changed: true
1516
+ };
1517
+ }).then((result) => {
1518
+ if (result.changed) {
1519
+ this.host.publish({
1520
+ scope: request.scope,
1521
+ scopeId,
1522
+ kind: "outbound",
1523
+ requestId: request.requestId
1524
+ });
1525
+ if (result.value.revised.length > 0) this.host.publish({
1526
+ scope: request.scope,
1527
+ scopeId,
1528
+ kind: "inbound",
1529
+ messageIds: result.value.revised
1530
+ });
1531
+ }
1532
+ return result.value.outbound;
1533
+ });
1534
+ }
1535
+ /** @param request - Local simulation intent. @param clock - Time sampled when the send settles. @returns Durable local receipt. */
1536
+ settleSimulationOutbound(request, clock = Date.now) {
1537
+ const scopeId = encodeImScopeId(request.scope);
1538
+ return this.mutate(request.scope, (current) => {
1539
+ const outbound = current.outbounds[request.requestId];
1540
+ if (outbound === void 0) throw new ImRuntimeError("IM_OUTBOUND_NOT_FOUND", `IM outbound request '${request.requestId}' is unknown`);
1541
+ if (outbound.status === "sent") return {
1542
+ aggregate: current,
1543
+ value: outbound,
1544
+ changed: false
1545
+ };
1546
+ if (outbound.intent === "ai" && this.handlingRevoked(request.scope, outbound)) {
1547
+ const updated = {
1548
+ ...outbound,
1549
+ status: "pre-send-failed",
1550
+ preSendFailureReason: "cancelled",
1551
+ updatedAt: timestamp()
1552
+ };
1553
+ return {
1554
+ aggregate: {
1555
+ ...current,
1556
+ outbounds: {
1557
+ ...current.outbounds,
1558
+ [request.requestId]: updated
1559
+ }
1560
+ },
1561
+ value: updated,
1562
+ changed: true
1563
+ };
1564
+ }
1565
+ if (outbound.status !== "pending") throw new ImRuntimeError("IM_OUTBOUND_STATE_INVALID", `simulation outbound request '${request.requestId}' cannot settle from '${outbound.status}'`);
1566
+ const sentAt = new Date(clock()).toISOString();
1567
+ const updated = {
1568
+ ...outbound,
1569
+ status: "sent",
1570
+ receipt: {
1571
+ providerStatus: "simulation",
1572
+ observedAt: sentAt
1573
+ },
1574
+ updatedAt: sentAt
1575
+ };
1576
+ return {
1577
+ aggregate: {
1578
+ ...current,
1579
+ outbounds: {
1580
+ ...current.outbounds,
1581
+ [request.requestId]: updated
1582
+ }
1583
+ },
1584
+ value: updated,
1585
+ changed: true
1586
+ };
1587
+ }).then((result) => {
1588
+ if (result.changed) this.host.publish({
1589
+ scope: request.scope,
1590
+ scopeId,
1591
+ kind: "outbound",
1592
+ requestId: request.requestId
1593
+ });
1594
+ return result.value;
1595
+ });
1596
+ }
1597
+ getOutbound(request) {
1598
+ return this.lookup(request.scope)?.outbounds[request.requestId];
1599
+ }
1600
+ queryOutbound(request) {
1601
+ assertLimit(request.limit);
1602
+ const aggregate = this.lookup(request.scope);
1603
+ if (aggregate === void 0) return {
1604
+ items: [],
1605
+ hasMore: false
1606
+ };
1607
+ return outboundPage(sortedOutbounds(aggregate), request);
1608
+ }
1609
+ cancelPendingAi(request) {
1610
+ const scopeId = encodeImScopeId(request.scope);
1611
+ return this.mutate(request.scope, (current) => {
1612
+ const updatedAt = timestamp();
1613
+ const cancelled = [];
1614
+ const outbounds = Object.fromEntries(Object.entries(current.outbounds).map(([id, outbound]) => {
1615
+ if (outbound.intent !== "ai" || outbound.status !== "pending") return [id, outbound];
1616
+ const updated = {
1617
+ ...outbound,
1618
+ status: "pre-send-failed",
1619
+ preSendFailureReason: request.reason,
1620
+ updatedAt
1621
+ };
1622
+ cancelled.push(updated);
1623
+ return [id, updated];
1624
+ }));
1625
+ return {
1626
+ aggregate: cancelled.length === 0 ? current : {
1627
+ ...current,
1628
+ outbounds
1629
+ },
1630
+ value: cancelled,
1631
+ changed: cancelled.length > 0
1632
+ };
1633
+ }).then((result) => {
1634
+ if (result.changed) this.host.publish({
1635
+ scope: request.scope,
1636
+ scopeId,
1637
+ kind: "outbound"
1638
+ });
1639
+ return result.value;
1640
+ });
1641
+ }
1642
+ findSentOutbound(scope, externalMessageId) {
1643
+ return Object.values(this.lookup(scope)?.outbounds ?? {}).find((outbound) => outbound.status === "sent" && (outbound.externalMessageId === externalMessageId || outbound.receipt?.providerReceiptId === externalMessageId));
1644
+ }
1645
+ findUnknownOutbound(scope, text) {
1646
+ const needle = text.replace(/\s+/gu, " ").trim();
1647
+ if (needle === "") return void 0;
1648
+ return Object.values(this.lookup(scope)?.outbounds ?? {}).filter((outbound) => outbound.status === "result-unknown" && outbound.attempt !== void 0 && outbound.content.text.replace(/\s+/gu, " ").trim() === needle).sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.requestId.localeCompare(right.requestId)).at(-1);
1649
+ }
1650
+ registrationPolicy(request, frozenBinding) {
1651
+ const account = this.host.inspectAccount(request.scope.accountId, request.scope);
1652
+ if (account === void 0) return { reason: "account-not-found" };
1653
+ if (account.platform !== request.scope.platform) return { reason: "platform-mismatch" };
1654
+ if (request.intent === "human-manual") return {};
1655
+ const control = this.records.getHandlingControl(encodeImScopeId(request.scope));
1656
+ if (this.host.automaticHandlingBlocked(encodeImScopeId(request.scope)) || frozenBinding !== void 0 && (frozenBinding.handlingEpoch ?? 0) !== (control?.handlingEpoch ?? 0)) return { reason: "cancelled" };
1657
+ if (request.scope.kind === "simulation") return {};
1658
+ if (account.paused) return { reason: "account-paused" };
1659
+ const resolution = this.host.resolveRoute(request.scope);
1660
+ if (frozenBinding !== void 0) return resolution?.state === "matched" && account.revision === frozenBinding.accountRevision && resolution.route.id === frozenBinding.routeId && resolution.route.revision === frozenBinding.routeRevision && resolution.route.workspaceId === frozenBinding.workspaceId ? { binding: frozenBinding } : {
1661
+ binding: frozenBinding,
1662
+ reason: "route-changed"
1663
+ };
1664
+ if (resolution?.state === "matched") return { binding: {
1665
+ routeId: resolution.route.id,
1666
+ routeRevision: resolution.route.revision,
1667
+ workspaceId: resolution.route.workspaceId,
1668
+ accountRevision: account.revision
1669
+ } };
1670
+ if (resolution?.state === "disabled") return { reason: "route-disabled" };
1671
+ return { reason: "route-unmatched" };
1672
+ }
1673
+ handlingRevoked(scope, outbound) {
1674
+ const control = this.records.getHandlingControl(encodeImScopeId(scope));
1675
+ return this.host.automaticHandlingBlocked(encodeImScopeId(scope)) || (control?.handlingEpoch ?? 0) !== (outbound.handlingEpoch ?? 0);
1676
+ }
1677
+ dispatchFailure(scope, outbound) {
1678
+ const account = this.host.inspectAccount(scope.accountId);
1679
+ if (account === void 0) return "account-not-found";
1680
+ if (account.platform !== scope.platform) return "platform-mismatch";
1681
+ if (outbound.intent === "human-manual") return void 0;
1682
+ if (this.handlingRevoked(scope, outbound)) return "cancelled";
1683
+ if (account.paused) return "account-paused";
1684
+ const resolution = this.host.resolveRoute(scope);
1685
+ if (resolution?.state !== "matched" || outbound.routeBinding === void 0) return "route-changed";
1686
+ const binding = outbound.routeBinding;
1687
+ return account.revision === binding.accountRevision && resolution.route.id === binding.routeId && resolution.route.revision === binding.routeRevision && resolution.route.workspaceId === binding.workspaceId ? void 0 : "route-changed";
1688
+ }
1689
+ assertRealScope(scope) {
1690
+ if (scope.conversationId.trim() === "") throw new ImRuntimeError("IM_DELIVERY_SCOPE_INVALID", "IM conversationId must be non-empty");
1691
+ const account = this.host.inspectAccount(scope.accountId);
1692
+ if (account === void 0) throw new ImRuntimeError("IM_ACCOUNT_NOT_FOUND", `IM account '${scope.accountId}' is unknown`);
1693
+ if (account.platform !== scope.platform) throw new ImRuntimeError("IM_DELIVERY_SCOPE_INVALID", "IM delivery scope platform does not match its account");
1694
+ }
1695
+ lookup(scope) {
1696
+ const aggregate = this.records.getScope(encodeImScopeId(scope));
1697
+ if (aggregate !== void 0 && !sameScope(aggregate.scope, scope)) throw new Error("IM scope hash collision");
1698
+ return aggregate;
1699
+ }
1700
+ require(scopeId) {
1701
+ const aggregate = this.records.getScope(scopeId);
1702
+ if (aggregate === void 0) throw new Error(`IM delivery scope '${scopeId}' disappeared during a serialized mutation`);
1703
+ return aggregate;
1704
+ }
1705
+ mutate(scope, fn) {
1706
+ return this.enqueue(encodeImScopeId(scope), () => this.applyMutation(scope, fn));
1707
+ }
1708
+ /** Apply one transform to the stored or initial aggregate; the caller already holds the scope's queue. */
1709
+ async applyMutation(scope, fn) {
1710
+ const current = this.lookup(scope) ?? initialAggregate(scope);
1711
+ const result = fn(current);
1712
+ if (result.changed) await this.persistMutation(current, result.aggregate);
1713
+ return result;
1714
+ }
1715
+ /** @param queue - external durable queue. @returns completion after every journal transfer is synced and removed from SQLite. */
1716
+ async transferUploads(queue) {
1717
+ for (const scopeId of this.records.uploadScopeIds()) await this.enqueue(scopeId, async () => {
1718
+ const current = this.require(scopeId);
1719
+ const pending = current.uploadPending ?? [];
1720
+ if (pending.length === 0) return;
1721
+ queue.append(pending);
1722
+ await this.records.putScope(scopeId, {
1723
+ ...current,
1724
+ uploadPending: []
1725
+ });
1726
+ });
1727
+ }
1728
+ async persistMutation(current, next) {
1729
+ const target = this.host.uploadTarget?.(next.scope);
1730
+ const observations = [];
1731
+ const destinations = { ...current.uploadTargets };
1732
+ const identities = { ...current.uploadIdentities };
1733
+ const unknownIdentity = {
1734
+ senderAccountId: null,
1735
+ senderDisplayName: null,
1736
+ senderDomain: null,
1737
+ assignedAccountId: null
1738
+ };
1739
+ let observedAt = new Date(Math.max(Date.now(), Date.parse(current.uploadObservedAt ?? "1970-01-01T00:00:00.000Z") + 1)).toISOString();
1740
+ const record = (message, destination, key) => {
1741
+ destinations[key] = destination;
1742
+ let identity = identities[key];
1743
+ if (message.kind === "inbound") {
1744
+ const value = message.value;
1745
+ const sender = value.sender;
1746
+ const previous = current.messages[value.messageId];
1747
+ const unchangedSender = previous !== void 0 && JSON.stringify(previous.sender) === JSON.stringify(sender);
1748
+ const own = sender.kind === "ai" || sender.kind === "human-dsh" ? identities[`outbound:${sender.outboundRequestId}`] : void 0;
1749
+ identity = previous !== void 0 && identity === void 0 && previous.uploadIdentity === void 0 ? unknownIdentity : own === void 0 ? value.uploadIdentity ?? (unchangedSender ? identity : void 0) ?? {
1750
+ senderAccountId: sender.kind === "external" ? sender.senderId : sender.kind === "human-native" || sender.kind === "human-dsh" ? sender.providerActorId ?? null : null,
1751
+ senderDisplayName: sender.kind === "external" ? sender.senderDisplayName ?? null : null,
1752
+ senderDomain: null,
1753
+ assignedAccountId: next.presentation?.assignedActorId ?? null
1754
+ } : {
1755
+ ...own,
1756
+ assignedAccountId: value.uploadIdentity?.assignedAccountId ?? own.assignedAccountId
1757
+ };
1758
+ } else if (message.kind === "outbound" && identity === void 0) {
1759
+ const actor = this.host.uploadIdentity?.(next.scope);
1760
+ identity = current.outbounds[message.value.requestId] !== void 0 ? unknownIdentity : {
1761
+ senderAccountId: message.value.sender?.providerActorId ?? actor?.accountId ?? null,
1762
+ senderDisplayName: actor?.displayName ?? null,
1763
+ senderDomain: actor?.domain ?? null,
1764
+ assignedAccountId: next.presentation?.assignedActorId ?? null
1765
+ };
1766
+ }
1767
+ if (identity !== void 0) identities[key] = identity;
1768
+ observations.push({
1769
+ id: brandString(randomUUID()),
1770
+ target: destination,
1771
+ scope: next.scope,
1772
+ observedAt,
1773
+ message,
1774
+ ...identity === void 0 ? {} : { identity }
1775
+ });
1776
+ observedAt = new Date(Date.parse(observedAt) + 1).toISOString();
1777
+ };
1778
+ for (const [id, value] of Object.entries(next.messages)) {
1779
+ const key = `inbound:${id}`;
1780
+ const previous = current.messages[id];
1781
+ const ownEcho = value.sender.kind === "ai" || value.sender.kind === "human-dsh";
1782
+ const senderRevised = previous !== void 0 && JSON.stringify(previous.sender) !== JSON.stringify(value.sender);
1783
+ const destination = destinations[key] ?? (previous === void 0 ? target : void 0);
1784
+ if (destination !== void 0 && value.origin === "live" && JSON.stringify(value) !== JSON.stringify(previous) && (!ownEcho || senderRevised)) record({
1785
+ kind: "inbound",
1786
+ value
1787
+ }, destination, key);
1788
+ }
1789
+ for (const [id, value] of Object.entries(next.outbounds)) {
1790
+ const key = `outbound:${id}`;
1791
+ const destination = destinations[key] ?? (current.outbounds[id] === void 0 ? target : void 0);
1792
+ if (destination !== void 0 && JSON.stringify(value) !== JSON.stringify(current.outbounds[id])) record({
1793
+ kind: "outbound",
1794
+ value
1795
+ }, destination, key);
1796
+ }
1797
+ await this.records.putScope(next.scopeId, observations.length === 0 ? next : {
1798
+ ...next,
1799
+ uploadPending: [...current.uploadPending ?? [], ...observations],
1800
+ uploadObservedAt: observedAt,
1801
+ uploadTargets: destinations,
1802
+ uploadIdentities: identities
1803
+ });
1804
+ if (observations.length > 0) this.host.onUploadReady?.();
1805
+ }
1806
+ enqueue(scopeId, job) {
1807
+ const result = (this.tails.get(scopeId) ?? Promise.resolve()).then(job);
1808
+ const tail = settled(result);
1809
+ this.tails.set(scopeId, tail);
1810
+ tail.finally(() => {
1811
+ if (this.tails.get(scopeId) === tail) this.tails.delete(scopeId);
1812
+ });
1813
+ return result;
1814
+ }
1815
+ enqueueCursor(id, job) {
1816
+ const result = (this.cursorTails.get(id) ?? Promise.resolve()).then(job);
1817
+ const tail = settled(result);
1818
+ this.cursorTails.set(id, tail);
1819
+ tail.finally(() => {
1820
+ if (this.cursorTails.get(id) === tail) this.cursorTails.delete(id);
1821
+ });
1822
+ return result;
1823
+ }
1824
+ };
1825
+ //#endregion
1826
+ //#region src/read-state.ts
1827
+ /** Strict interpretation of untrusted single-chat buyer receipt evidence. */
1828
+ /** @param buyer - verified direct recipient. @returns its SDK user id when present, otherwise the platform actor id. */
1829
+ function buyerUid(buyer) {
1830
+ return buyer.userId ?? buyer.providerActorId;
1831
+ }
1832
+ /** @param sender - SDK sender string or object; absent receipt senders remain absent. @returns the supplied sender UID, or absence. */
1833
+ function senderUid(sender) {
1834
+ return typeof sender === "string" ? sender : sender?.uid;
1835
+ }
1836
+ /** @param uid - SDK identity. @param buyer - verified OpenAPI buyer UID. @returns whether domain-qualified or unqualified SDK identity names this buyer. */
1837
+ function sameBuyer(uid, buyer) {
1838
+ return uid === buyer || uid === `${buyer}@cntaobao`;
1839
+ }
1840
+ /** @param data - raw 40103 counts. @param singleton - independently verified receiver set consists solely of the target buyer. @returns conservative buyer state. */
1841
+ function buyerReadState(data, singleton) {
1842
+ if (!singleton || data.receiverCount !== 1) return "unknown";
1843
+ return data.unreadCount === 0 ? "read" : data.unreadCount === 1 ? "unread" : "unknown";
1844
+ }
1845
+ const rowsSchema = z.array(z.object({
1846
+ uid: z.string(),
1847
+ status: z.number().int().optional()
1848
+ }));
1849
+ /** @param data - ListMessageReadStatus response body. @param buyer - independently verified buyer UID. @returns matched status and whether the response establishes a singleton receiver set. */
1850
+ function buyerReadQuery(data, buyer) {
1851
+ const parsed = rowsSchema.safeParse(data);
1852
+ if (!parsed.success) return {
1853
+ state: "unknown",
1854
+ singleton: false
1855
+ };
1856
+ const matches = parsed.data.filter((row) => sameBuyer(row.uid, buyer));
1857
+ if (matches.length !== 1) return {
1858
+ state: "unknown",
1859
+ singleton: false
1860
+ };
1861
+ const status = matches[0].status;
1862
+ return {
1863
+ state: status === 2 ? "read" : status === 0 ? "unread" : "unknown",
1864
+ singleton: parsed.data.length === 1
1865
+ };
1866
+ }
1867
+ //#endregion
1868
+ export { senderUid as a, encodeImScopeId as c, imExecutionAggregateSchema as d, imProviderCursorAggregateSchema as f, sameBuyer as i, imDeliveryAggregateSchema as l, ImRuntimeError as m, buyerReadState as n, ImDeliveryStore as o, imUploadObservationSchema as p, buyerUid as r, encodeImProviderCursorId as s, buyerReadQuery as t, imDeliveryScopeSchema as u };