@inline-chat/hermes-agent-adapter 0.0.16 → 0.0.17

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.
@@ -5568,7 +5568,449 @@ var require_websocket_server = __commonJS((exports, module) => {
5568
5568
 
5569
5569
  // src/sidecar/index.ts
5570
5570
  import http from "node:http";
5571
- import { once } from "node:events";
5571
+
5572
+ // src/sidecar/inbound-stream.ts
5573
+ class InboundStream {
5574
+ consumer = null;
5575
+ stopped = false;
5576
+ changed = new Set;
5577
+ attach(consumer) {
5578
+ if (this.stopped) {
5579
+ consumer.end();
5580
+ return;
5581
+ }
5582
+ const previous = this.consumer;
5583
+ this.consumer = consumer;
5584
+ const retired = () => {
5585
+ if (this.consumer === consumer) {
5586
+ this.consumer = null;
5587
+ this.wake();
5588
+ }
5589
+ };
5590
+ consumer.once("close", retired);
5591
+ consumer.on("error", retired);
5592
+ this.wake();
5593
+ previous?.end();
5594
+ }
5595
+ close() {
5596
+ this.stopped = true;
5597
+ const previous = this.consumer;
5598
+ this.consumer = null;
5599
+ this.wake();
5600
+ previous?.end();
5601
+ }
5602
+ async deliver(event) {
5603
+ const line = JSON.stringify(event) + `
5604
+ `;
5605
+ while (!this.stopped) {
5606
+ const owner = this.consumer;
5607
+ if (!owner) {
5608
+ await new Promise((resolve) => this.changed.add(resolve));
5609
+ continue;
5610
+ }
5611
+ let cleanup = () => {};
5612
+ const drained = new Promise((resolve) => {
5613
+ const finish = (ok) => {
5614
+ cleanup();
5615
+ resolve(ok);
5616
+ };
5617
+ const onDrain = () => finish(true);
5618
+ const onChange = () => finish(false);
5619
+ cleanup = () => {
5620
+ owner.off("drain", onDrain);
5621
+ owner.off("close", onChange);
5622
+ owner.off("error", onChange);
5623
+ this.changed.delete(onChange);
5624
+ };
5625
+ owner.once("drain", onDrain);
5626
+ owner.once("close", onChange);
5627
+ owner.once("error", onChange);
5628
+ this.changed.add(onChange);
5629
+ });
5630
+ try {
5631
+ if (owner.destroyed || owner.writableEnded) {
5632
+ if (this.consumer === owner)
5633
+ this.consumer = null;
5634
+ continue;
5635
+ }
5636
+ const accepted = owner.write(line);
5637
+ if (this.consumer !== owner || this.stopped)
5638
+ continue;
5639
+ if (accepted)
5640
+ return;
5641
+ if (await drained && this.consumer === owner && !this.stopped)
5642
+ return;
5643
+ } catch {
5644
+ if (this.consumer === owner)
5645
+ this.consumer = null;
5646
+ } finally {
5647
+ cleanup();
5648
+ }
5649
+ }
5650
+ throw new Error("Inbound stream closed before delivery completed");
5651
+ }
5652
+ wake() {
5653
+ for (const resolve of this.changed)
5654
+ resolve();
5655
+ this.changed.clear();
5656
+ }
5657
+ }
5658
+
5659
+ // src/sidecar/inbound-delivery.ts
5660
+ import { setTimeout as delay } from "node:timers/promises";
5661
+
5662
+ // src/sidecar/contract.ts
5663
+ var MAX_INLINE_ID = 9223372036854775807n;
5664
+ var MAX_UINT64 = 18446744073709551615n;
5665
+ var sensitiveUrlParams = new Set([
5666
+ "access_token",
5667
+ "auth",
5668
+ "authorization",
5669
+ "key",
5670
+ "token"
5671
+ ]);
5672
+ var botSettingsEventKinds = new Set([
5673
+ "bot.chatSettings.request",
5674
+ "bot.chatSettings.item.invoke"
5675
+ ]);
5676
+ function inboundEventNeedsSenderResolution(event) {
5677
+ return !botSettingsEventKinds.has(event.kind ?? "");
5678
+ }
5679
+
5680
+ class SidecarError extends Error {
5681
+ errorKind;
5682
+ constructor(message, errorKind) {
5683
+ super(message);
5684
+ this.errorKind = errorKind;
5685
+ }
5686
+ }
5687
+ function parseTarget(record) {
5688
+ const targetRecord = asOptionalRecord(record.target) ?? record;
5689
+ const chatId = readOptionalInlineId(targetRecord, "chatId");
5690
+ const userId = readOptionalInlineId(targetRecord, "userId");
5691
+ if (chatId && userId)
5692
+ throw new SidecarError("target cannot include both chatId and userId", "bad_format");
5693
+ if (chatId)
5694
+ return { chatId };
5695
+ if (userId)
5696
+ return { userId };
5697
+ throw new SidecarError("target requires chatId or userId", "bad_format");
5698
+ }
5699
+ function normalizeUploadKind(raw, filePath) {
5700
+ if (raw === "photo" || raw === "image")
5701
+ return "photo";
5702
+ if (raw === "video")
5703
+ return "video";
5704
+ if (raw === "voice")
5705
+ return "voice";
5706
+ if (raw === "document" || raw === "file")
5707
+ return "document";
5708
+ const lower = filePath.toLowerCase();
5709
+ if (/\.(png|jpg|jpeg|gif|webp|heic|heif)$/.test(lower))
5710
+ return "photo";
5711
+ if (/\.(mp4|mov|webm)$/.test(lower))
5712
+ return "video";
5713
+ return "document";
5714
+ }
5715
+ function normalizeError(error, redact = defaultErrorText) {
5716
+ if (error instanceof SidecarError) {
5717
+ return {
5718
+ status: statusForErrorKind(error.errorKind),
5719
+ errorKind: error.errorKind,
5720
+ message: redact(error)
5721
+ };
5722
+ }
5723
+ const message = redact(error);
5724
+ const lower = message.toLowerCase();
5725
+ if (lower.includes("rate") && lower.includes("limit")) {
5726
+ return { status: 429, errorKind: "rate_limited", message };
5727
+ }
5728
+ if (lower.includes("forbidden") || lower.includes("unauthorized")) {
5729
+ return { status: 403, errorKind: "forbidden", message };
5730
+ }
5731
+ if (lower.includes("not found") || lower.includes("missing")) {
5732
+ return { status: 404, errorKind: "not_found", message };
5733
+ }
5734
+ if (lower.includes("timeout") || lower.includes("network") || lower.includes("closed")) {
5735
+ return { status: 503, errorKind: "transient", message };
5736
+ }
5737
+ return { status: 500, errorKind: "unknown", message };
5738
+ }
5739
+ function statusForErrorKind(errorKind) {
5740
+ switch (errorKind) {
5741
+ case "bad_format":
5742
+ return 400;
5743
+ case "forbidden":
5744
+ return 403;
5745
+ case "not_found":
5746
+ return 404;
5747
+ case "too_long":
5748
+ return 413;
5749
+ case "rate_limited":
5750
+ return 429;
5751
+ case "transient":
5752
+ return 503;
5753
+ case "unknown":
5754
+ return 500;
5755
+ }
5756
+ }
5757
+ function normalizeInboundEvent(event, meId, sender, meUsername) {
5758
+ if (event.kind === "message.new" || event.kind === "message.edit") {
5759
+ const message = asOptionalRecord(event.message);
5760
+ return safeJson({
5761
+ kind: event.kind,
5762
+ chatId: event.chatId,
5763
+ seq: event.seq,
5764
+ date: event.date,
5765
+ meId,
5766
+ meUsername,
5767
+ ...sender ? { sender } : {},
5768
+ message: message ? normalizeMessage(message) : null
5769
+ });
5770
+ }
5771
+ if (event.kind === "message.action.invoke") {
5772
+ return safeJson({
5773
+ ...event,
5774
+ meId,
5775
+ meUsername,
5776
+ ...sender ? { sender } : {},
5777
+ dataBase64: event.data instanceof Uint8Array ? Buffer.from(event.data).toString("base64") : typeof event.data === "string" ? event.data : ""
5778
+ });
5779
+ }
5780
+ return safeJson({ ...event, meId, meUsername, ...sender ? { sender } : {} });
5781
+ }
5782
+ function normalizeMessage(message) {
5783
+ return {
5784
+ id: message.id,
5785
+ fromId: message.fromId,
5786
+ chatId: message.chatId,
5787
+ peerId: message.peerId,
5788
+ message: message.message ?? null,
5789
+ out: Boolean(message.out),
5790
+ date: message.date,
5791
+ mentioned: Boolean(message.mentioned),
5792
+ replyToMsgId: message.replyToMsgId ?? null,
5793
+ entities: message.entities ?? null,
5794
+ media: message.media ?? null,
5795
+ attachments: message.attachments ?? null,
5796
+ reactions: message.reactions ?? null,
5797
+ replies: message.replies ?? null,
5798
+ actions: message.actions ?? null,
5799
+ rev: message.rev ?? null,
5800
+ raw: message
5801
+ };
5802
+ }
5803
+ function redactText(value, secrets) {
5804
+ let text = value instanceof Error ? value.message : String(value);
5805
+ for (const secret of secrets) {
5806
+ const raw = typeof secret.value === "string" ? secret.value : "";
5807
+ if (!raw)
5808
+ continue;
5809
+ text = text.split(raw).join(secret.label);
5810
+ }
5811
+ return text;
5812
+ }
5813
+ function redactUrl(value) {
5814
+ let url;
5815
+ try {
5816
+ url = new URL(value);
5817
+ } catch {
5818
+ return value;
5819
+ }
5820
+ if (url.username)
5821
+ url.username = "redacted";
5822
+ if (url.password)
5823
+ url.password = "redacted";
5824
+ const keys = Array.from(url.searchParams.keys());
5825
+ for (const key of keys) {
5826
+ const normalized = key.toLowerCase();
5827
+ if (sensitiveUrlParams.has(normalized) || normalized.includes("token")) {
5828
+ url.searchParams.set(key, "redacted");
5829
+ }
5830
+ }
5831
+ return url.toString();
5832
+ }
5833
+ function safeJson(value) {
5834
+ if (value == null)
5835
+ return null;
5836
+ if (typeof value === "string" || typeof value === "boolean")
5837
+ return value;
5838
+ if (typeof value === "number")
5839
+ return Number.isFinite(value) ? value : null;
5840
+ if (typeof value === "bigint")
5841
+ return value.toString();
5842
+ if (value instanceof Uint8Array)
5843
+ return Buffer.from(value).toString("base64");
5844
+ if (Array.isArray(value))
5845
+ return value.map(safeJson);
5846
+ if (typeof value === "object") {
5847
+ const out = {};
5848
+ for (const [key, item] of Object.entries(value)) {
5849
+ if (item !== undefined)
5850
+ out[key] = safeJson(item);
5851
+ }
5852
+ return out;
5853
+ }
5854
+ return String(value);
5855
+ }
5856
+ function asRecord(value) {
5857
+ const record = asOptionalRecord(value);
5858
+ if (!record)
5859
+ throw new SidecarError("expected JSON object", "bad_format");
5860
+ return record;
5861
+ }
5862
+ function asOptionalRecord(value) {
5863
+ if (!value || typeof value !== "object" || Array.isArray(value))
5864
+ return null;
5865
+ return value;
5866
+ }
5867
+ function readRequiredString(record, key) {
5868
+ const value = readOptionalString(record, key);
5869
+ if (!value)
5870
+ throw new SidecarError(`missing ${key}`, "bad_format");
5871
+ return value;
5872
+ }
5873
+ function readOptionalString(record, key) {
5874
+ const value = record[key];
5875
+ if (typeof value === "string")
5876
+ return value.trim() || undefined;
5877
+ if (typeof value === "bigint" || typeof value === "number")
5878
+ return String(value);
5879
+ return;
5880
+ }
5881
+ function readOptionalBoolean(record, key) {
5882
+ const value = record[key];
5883
+ if (typeof value === "boolean")
5884
+ return value;
5885
+ if (typeof value === "string") {
5886
+ if (/^(1|true|yes|on)$/i.test(value))
5887
+ return true;
5888
+ if (/^(0|false|no|off)$/i.test(value))
5889
+ return false;
5890
+ }
5891
+ return;
5892
+ }
5893
+ function readOptionalNumber(record, key) {
5894
+ const value = record[key];
5895
+ if (typeof value === "number" && Number.isFinite(value))
5896
+ return value;
5897
+ if (typeof value === "string" && value.trim()) {
5898
+ const parsed = Number(value);
5899
+ if (Number.isFinite(parsed))
5900
+ return parsed;
5901
+ }
5902
+ return;
5903
+ }
5904
+ function readRequiredInlineId(record, key) {
5905
+ const value = readOptionalInlineId(record, key);
5906
+ if (value == null)
5907
+ throw new SidecarError(`missing ${key}`, "bad_format");
5908
+ return value;
5909
+ }
5910
+ function readOptionalInlineId(record, key) {
5911
+ const value = record[key];
5912
+ if (value == null || value === "")
5913
+ return;
5914
+ return parseInlineId(value, key);
5915
+ }
5916
+ function readInlineIdArray(record, key, maxItems, required = false) {
5917
+ const value = record[key];
5918
+ if (value == null) {
5919
+ if (required)
5920
+ throw new SidecarError(`missing ${key}`, "bad_format");
5921
+ return [];
5922
+ }
5923
+ if (!Array.isArray(value))
5924
+ throw new SidecarError(`${key} must be an array`, "bad_format");
5925
+ if (value.length === 0 && required)
5926
+ throw new SidecarError(`${key} must not be empty`, "bad_format");
5927
+ if (value.length > maxItems)
5928
+ throw new SidecarError(`${key} supports at most ${maxItems} items`, "bad_format");
5929
+ const ids = [];
5930
+ const seen = new Set;
5931
+ for (let index = 0;index < value.length; index += 1) {
5932
+ const id = parseInlineId(value[index], `${key}[${index}]`);
5933
+ const normalized = id.toString();
5934
+ if (seen.has(normalized))
5935
+ continue;
5936
+ seen.add(normalized);
5937
+ ids.push(id);
5938
+ }
5939
+ return ids;
5940
+ }
5941
+ function parseOptionalInt(value) {
5942
+ const raw = (value || "").trim();
5943
+ if (!raw || !/^\d+$/.test(raw))
5944
+ return;
5945
+ const parsed = Number(raw);
5946
+ return Number.isSafeInteger(parsed) ? parsed : undefined;
5947
+ }
5948
+ function parseInlineId(value, field) {
5949
+ try {
5950
+ if (typeof value === "number" && (!Number.isSafeInteger(value) || value <= 0)) {
5951
+ throw new Error("unsafe number");
5952
+ }
5953
+ const raw = typeof value === "string" ? value.trim() : value;
5954
+ if (typeof raw === "string" && !/^[1-9][0-9]*$/.test(raw))
5955
+ throw new Error("invalid digits");
5956
+ if (typeof raw !== "string" && typeof raw !== "bigint" && typeof raw !== "number") {
5957
+ throw new Error("invalid type");
5958
+ }
5959
+ const parsed = BigInt(raw);
5960
+ if (parsed <= 0n || parsed > MAX_INLINE_ID)
5961
+ throw new Error("out of range");
5962
+ return parsed;
5963
+ } catch {
5964
+ throw new SidecarError(`${field} must be a positive signed 64-bit integer`, "bad_format");
5965
+ }
5966
+ }
5967
+ function parseUnsigned64Id(value, field) {
5968
+ try {
5969
+ if (typeof value === "number" && (!Number.isSafeInteger(value) || value <= 0)) {
5970
+ throw new Error("unsafe number");
5971
+ }
5972
+ const raw = typeof value === "string" ? value.trim() : value;
5973
+ if (typeof raw === "string" && !/^[1-9][0-9]*$/.test(raw))
5974
+ throw new Error("invalid digits");
5975
+ if (typeof raw !== "string" && typeof raw !== "bigint" && typeof raw !== "number") {
5976
+ throw new Error("invalid type");
5977
+ }
5978
+ const parsed = BigInt(raw);
5979
+ if (parsed <= 0n || parsed > MAX_UINT64)
5980
+ throw new Error("out of range");
5981
+ return parsed;
5982
+ } catch {
5983
+ throw new SidecarError(`${field} must be a positive unsigned 64-bit integer`, "bad_format");
5984
+ }
5985
+ }
5986
+ function defaultErrorText(error) {
5987
+ return error instanceof Error ? error.message : String(error);
5988
+ }
5989
+
5990
+ // src/sidecar/inbound-delivery.ts
5991
+ function explicitlyMentionsSelf(event, meId) {
5992
+ if (!meId || event.kind !== "message.new" && event.kind !== "message.edit")
5993
+ return false;
5994
+ const message = event.message;
5995
+ return message?.entities?.entities?.some((e) => e.entity?.oneofKind === "mention" && e.entity.mention?.userId?.toString() === meId) ?? false;
5996
+ }
5997
+ async function deliverInboundEvent(event, owner) {
5998
+ const explicitMention = explicitlyMentionsSelf(event, owner.meId);
5999
+ while (!owner.signal.aborted) {
6000
+ const resolution = explicitMention ? { provenanceVerified: false } : inboundEventNeedsSenderResolution(event) ? await owner.resolveSender(event) : { provenanceVerified: true };
6001
+ owner.signal.throwIfAborted();
6002
+ if (!explicitMention && !resolution.provenanceVerified && (event.kind === "message.new" || event.kind === "message.edit")) {
6003
+ await delay(1000, undefined, { signal: owner.signal });
6004
+ continue;
6005
+ }
6006
+ const normalized = normalizeInboundEvent(event, owner.meId, resolution.profile, owner.meUsername);
6007
+ await owner.deliver(resolution.provenanceVerified ? normalized : { ...normalized, _inlineSenderProvenanceVerified: false });
6008
+ return;
6009
+ }
6010
+ owner.signal.throwIfAborted();
6011
+ }
6012
+
6013
+ // src/sidecar/index.ts
5572
6014
  import { timingSafeEqual } from "node:crypto";
5573
6015
  import { mkdir, readFile as readFile3, stat } from "node:fs/promises";
5574
6016
  import path from "node:path";
@@ -38683,7 +39125,7 @@ var reportProgress = (job) => {
38683
39125
  job.input.onProgress?.({ acceptedBytes: acceptedBytes(job), totalBytes: job.input.source.byteCount });
38684
39126
  } catch {}
38685
39127
  };
38686
- var delay = async (seconds, signal) => {
39128
+ var delay2 = async (seconds, signal) => {
38687
39129
  if (signal?.aborted)
38688
39130
  throw new NativeUploadError("canceled", "Upload was canceled");
38689
39131
  await new Promise((resolve, reject) => {
@@ -38966,7 +39408,7 @@ class NativeUploadClient {
38966
39408
  }
38967
39409
  }
38968
39410
  #resumeFinishAfter(job, seconds) {
38969
- delay(seconds, job.input.signal).then(() => {
39411
+ delay2(seconds, job.input.signal).then(() => {
38970
39412
  if (job.settled)
38971
39413
  return;
38972
39414
  job.active = 0;
@@ -39074,6 +39516,13 @@ var asInlineId = (value, fieldName = "id") => {
39074
39516
  };
39075
39517
 
39076
39518
  // node_modules/@inline-chat/realtime-sdk/dist/utils/async-channel.js
39519
+ class ChannelConsumerError extends Error {
39520
+ constructor(message) {
39521
+ super(message);
39522
+ this.name = "ChannelConsumerError";
39523
+ }
39524
+ }
39525
+
39077
39526
  class AsyncChannelOverflowError extends Error {
39078
39527
  capacity;
39079
39528
  constructor(capacity) {
@@ -39183,6 +39632,7 @@ class AcknowledgedAsyncChannel {
39183
39632
  closed = false;
39184
39633
  failure = null;
39185
39634
  iteratorClaimed = false;
39635
+ consumption = null;
39186
39636
  constructor(capacity, byteLimit) {
39187
39637
  this.capacity = capacity;
39188
39638
  this.byteLimit = byteLimit;
@@ -39196,7 +39646,7 @@ class AcknowledgedAsyncChannel {
39196
39646
  send(value) {
39197
39647
  if (this.closed)
39198
39648
  return Promise.resolve(false);
39199
- if (!this.waiter && this.queue.length >= this.capacity) {
39649
+ if (!this.waiter && this.queue.length + (this.consumption?.active.size ?? 0) >= this.capacity) {
39200
39650
  throw new AsyncChannelOverflowError(this.capacity);
39201
39651
  }
39202
39652
  const bytes = this.byteLimit?.byteLength(value) ?? 0;
@@ -39216,6 +39666,7 @@ class AcknowledgedAsyncChannel {
39216
39666
  waiter.resolve({ value, done: false });
39217
39667
  } else {
39218
39668
  this.queue.push(item);
39669
+ this.pumpConsumption();
39219
39670
  }
39220
39671
  });
39221
39672
  }
@@ -39230,6 +39681,16 @@ class AcknowledgedAsyncChannel {
39230
39681
  return;
39231
39682
  this.closed = true;
39232
39683
  this.failure = error;
39684
+ const consumption = this.consumption;
39685
+ this.consumption = null;
39686
+ if (consumption) {
39687
+ for (const item of consumption.active.keys())
39688
+ item.acknowledge(false);
39689
+ if (error)
39690
+ consumption.reject(error);
39691
+ else
39692
+ consumption.resolve();
39693
+ }
39233
39694
  this.active?.acknowledge(false);
39234
39695
  this.active = null;
39235
39696
  for (const item of this.queue)
@@ -39245,6 +39706,65 @@ class AcknowledgedAsyncChannel {
39245
39706
  else
39246
39707
  waiter.resolve({ value: undefined, done: true });
39247
39708
  }
39709
+ consume(handler, key, concurrency = 8) {
39710
+ if (this.iteratorClaimed)
39711
+ return Promise.reject(new ChannelConsumerError("AcknowledgedAsyncChannel supports one consumer"));
39712
+ if (!Number.isSafeInteger(concurrency) || concurrency < 1) {
39713
+ return Promise.reject(new ChannelConsumerError("Consumer concurrency must be a positive safe integer"));
39714
+ }
39715
+ if (this.closed)
39716
+ return this.failure ? Promise.reject(this.failure) : Promise.resolve();
39717
+ this.iteratorClaimed = true;
39718
+ return new Promise((resolve, reject) => {
39719
+ this.consumption = { handler, key, concurrency, active: new Map, resolve, reject };
39720
+ this.pumpConsumption();
39721
+ });
39722
+ }
39723
+ pumpConsumption() {
39724
+ const owner = this.consumption;
39725
+ if (!owner || this.closed)
39726
+ return;
39727
+ try {
39728
+ while (owner.active.size < owner.concurrency && this.queue.length > 0) {
39729
+ if ([...owner.active.values()].includes(null))
39730
+ return;
39731
+ const keys = new Set(owner.active.values());
39732
+ let index = -1;
39733
+ let key = null;
39734
+ for (let i = 0;i < this.queue.length; i++) {
39735
+ const candidate = this.queue[i];
39736
+ const candidateKey = owner.key(candidate.value);
39737
+ if (candidateKey === null) {
39738
+ if (i === 0 && owner.active.size === 0)
39739
+ index = i;
39740
+ break;
39741
+ }
39742
+ if (!keys.has(candidateKey)) {
39743
+ index = i;
39744
+ key = candidateKey;
39745
+ break;
39746
+ }
39747
+ }
39748
+ if (index < 0)
39749
+ return;
39750
+ const item = this.queue.splice(index, 1)[0];
39751
+ owner.active.set(item, key);
39752
+ Promise.resolve().then(() => owner.handler(item.value)).then(() => {
39753
+ if (this.consumption !== owner)
39754
+ return;
39755
+ owner.active.delete(item);
39756
+ this.bufferedBytes -= item.bytes;
39757
+ item.acknowledge(true);
39758
+ this.pumpConsumption();
39759
+ }, (error) => {
39760
+ if (this.consumption === owner)
39761
+ this.fail(error instanceof Error ? error : new Error("Inbound handler failed"));
39762
+ });
39763
+ }
39764
+ } catch (error) {
39765
+ this.fail(error instanceof Error ? error : new Error("Inbound routing failed"));
39766
+ }
39767
+ }
39248
39768
  [Symbol.asyncIterator]() {
39249
39769
  if (this.iteratorClaimed) {
39250
39770
  return {
@@ -39252,8 +39772,11 @@ class AcknowledgedAsyncChannel {
39252
39772
  };
39253
39773
  }
39254
39774
  this.iteratorClaimed = true;
39775
+ let returned = false;
39255
39776
  return {
39256
39777
  next: () => {
39778
+ if (returned)
39779
+ return Promise.resolve({ value: undefined, done: true });
39257
39780
  if (this.active) {
39258
39781
  this.bufferedBytes -= this.active.bytes;
39259
39782
  this.active.acknowledge(true);
@@ -39277,6 +39800,11 @@ class AcknowledgedAsyncChannel {
39277
39800
  });
39278
39801
  },
39279
39802
  return: () => {
39803
+ if (returned)
39804
+ return Promise.resolve({ value: undefined, done: true });
39805
+ returned = true;
39806
+ this.waiter?.resolve({ value: undefined, done: true });
39807
+ this.waiter = null;
39280
39808
  if (this.active) {
39281
39809
  this.bufferedBytes -= this.active.bytes;
39282
39810
  this.active.acknowledge(false);
@@ -39955,10 +40483,12 @@ var normalizeRpcTimeoutMs = (timeoutMs, fallback) => {
39955
40483
 
39956
40484
  class ProtocolClientError extends Error {
39957
40485
  code;
40486
+ rpcCode;
39958
40487
  constructor(code, details) {
39959
40488
  super(details?.message ?? code);
39960
40489
  this.code = code;
39961
40490
  this.name = `ProtocolClientError:${code}`;
40491
+ this.rpcCode = details?.code;
39962
40492
  }
39963
40493
  }
39964
40494
  var commitOutcomeUnknownError = (message) => new ProtocolClientError("commit-outcome-unknown", { message });
@@ -43583,15 +44113,15 @@ class InlineProtocolV3Connection {
43583
44113
  if (!authorization?.temporary || authorization.expiresAt === undefined || this.#closing)
43584
44114
  return;
43585
44115
  const boundary = authorization.expiresAt * 1000 - TEMPORARY_KEY_ROTATION_MILLISECONDS;
43586
- const delay2 = boundary - this.#clock.nowMilliseconds();
43587
- if (!Number.isFinite(delay2) || delay2 <= 0) {
44116
+ const delay3 = boundary - this.#clock.nowMilliseconds();
44117
+ if (!Number.isFinite(delay3) || delay3 <= 0) {
43588
44118
  this.#markRotationDue();
43589
44119
  return;
43590
44120
  }
43591
44121
  const timer = setTimeout(() => {
43592
44122
  this.#rotationTimer = undefined;
43593
44123
  this.#scheduleRotationTimer();
43594
- }, Math.min(delay2, 2147000000));
44124
+ }, Math.min(delay3, 2147000000));
43595
44125
  if (typeof timer !== "number" && typeof timer.unref === "function")
43596
44126
  timer.unref();
43597
44127
  this.#rotationTimer = timer;
@@ -44781,8 +45311,11 @@ class InlineSdkClient {
44781
45311
  peerResolutionRequestedByChatId = new Map;
44782
45312
  recoveryReconnectInFlight = null;
44783
45313
  degradedUpdateBuckets = new Map;
45314
+ discoveryRetry = null;
45315
+ recoveryRetries = new Map;
44784
45316
  liveCursorFences = new Set;
44785
45317
  liveAdmittedSeqByBucket = new Map;
45318
+ liveDeliveriesByBucket = new Map;
44786
45319
  discoveryRound = null;
44787
45320
  discoveryInFlight = null;
44788
45321
  discoveryCommitInFlight = null;
@@ -44875,12 +45408,14 @@ class InlineSdkClient {
44875
45408
  }
44876
45409
  }
44877
45410
  async close() {
44878
- if (!this.started)
44879
- return;
45411
+ const wasStarted = this.started;
44880
45412
  this.closed = true;
44881
45413
  this.started = false;
45414
+ this.cancelRecoveryRetries();
44882
45415
  this.rejectOpen(new Error("closed"));
44883
45416
  this.eventStream.close();
45417
+ if (!wasStarted)
45418
+ return;
44884
45419
  await settleWithin(this.protocol.stopTransport(), closeJoinTimeoutMs, () => this.log.warn?.("Timed out stopping SDK transport during close"));
44885
45420
  await settleWithin(Promise.allSettled([
44886
45421
  ...this.catchUpInFlightByChatId.values(),
@@ -44942,6 +45477,20 @@ class InlineSdkClient {
44942
45477
  events() {
44943
45478
  return this.eventStream;
44944
45479
  }
45480
+ consumeEvents(handler, options) {
45481
+ return this.eventStream.consume(handler, (event) => {
45482
+ const bucket = this.bucketForEvent(event);
45483
+ if (bucket?.kind === "user")
45484
+ return null;
45485
+ if (bucket)
45486
+ return this.updateBucketKey(bucket);
45487
+ return "chatId" in event && typeof event.chatId === "bigint" ? `chat:${event.chatId}` : null;
45488
+ }, options?.concurrency).catch(async (error) => {
45489
+ if (!(error instanceof ChannelConsumerError))
45490
+ await this.close();
45491
+ throw error;
45492
+ });
45493
+ }
44945
45494
  exportState() {
44946
45495
  return {
44947
45496
  version: 1,
@@ -45351,6 +45900,7 @@ class InlineSdkClient {
45351
45900
  this.log.error?.("SDK listener crashed", failure);
45352
45901
  this.started = false;
45353
45902
  this.closed = true;
45903
+ this.cancelRecoveryRetries();
45354
45904
  this.rejectOpen(failure);
45355
45905
  this.eventStream.fail(failure);
45356
45906
  this.protocol.stopTransport().catch((stopError) => {
@@ -45363,6 +45913,7 @@ class InlineSdkClient {
45363
45913
  return;
45364
45914
  this.authenticationError = error;
45365
45915
  this.started = false;
45916
+ this.cancelRecoveryRetries();
45366
45917
  this.rejectOpen(error);
45367
45918
  this.eventStream.close();
45368
45919
  try {
@@ -45372,6 +45923,7 @@ class InlineSdkClient {
45372
45923
  }
45373
45924
  }
45374
45925
  async onOpen() {
45926
+ this.cancelRecoveryRetries();
45375
45927
  this.requestCatchUpUser();
45376
45928
  for (const bucket of this.degradedUpdateBuckets.values()) {
45377
45929
  this.requestCatchUpForDegradedBucket(bucket);
@@ -45422,6 +45974,8 @@ class InlineSdkClient {
45422
45974
  return this.discoveryInFlight;
45423
45975
  if (this.discoveryCommitInFlight)
45424
45976
  return this.discoveryCommitInFlight;
45977
+ if (this.discoveryRetry?.timer)
45978
+ return;
45425
45979
  const round = {
45426
45980
  resultReceived: false,
45427
45981
  collectingHints: true,
@@ -45454,6 +46008,11 @@ class InlineSdkClient {
45454
46008
  this.log.warn?.("GET_UPDATES_STATE failed (continuing without date cursor)", error);
45455
46009
  if (this.discoveryRound === round)
45456
46010
  this.discoveryRound = null;
46011
+ if (error instanceof ProtocolClientError && (error.code === "timeout" || error.code === "not-connected" || error.code === "capacity-exceeded" || error.rpcCode === RpcError_Code.INTERNAL_ERROR || error.rpcCode === 500 || error.rpcCode === RpcError_Code.RATE_LIMIT || error.rpcCode === 429)) {
46012
+ this.scheduleDiscoveryRetry(error.rpcCode === RpcError_Code.RATE_LIMIT || error.rpcCode === 429 ? "rate_limited" : "discovery_failed");
46013
+ } else {
46014
+ this.log.error?.("Sync discovery unavailable", { reason: "request_rejected" });
46015
+ }
45457
46016
  }
45458
46017
  }
45459
46018
  tryCommitDiscoveryRound(round) {
@@ -45478,12 +46037,15 @@ class InlineSdkClient {
45478
46037
  return;
45479
46038
  if (saved) {
45480
46039
  this.discoveryRound = null;
46040
+ clearTimeout(this.discoveryRetry?.timer);
46041
+ this.discoveryRetry = null;
45481
46042
  return;
45482
46043
  }
45483
46044
  this.state.dateCursor = previousDateCursor;
45484
46045
  this.scheduleStateSave();
45485
46046
  this.discoveryRound = null;
45486
46047
  this.log.warn?.("Failed to persist discovery checkpoint; preserving previous date cursor");
46048
+ this.scheduleDiscoveryRetry("checkpoint_write_failed");
45487
46049
  }).finally(() => {
45488
46050
  if (this.discoveryCommitInFlight === commit)
45489
46051
  this.discoveryCommitInFlight = null;
@@ -45818,20 +46380,23 @@ class InlineSdkClient {
45818
46380
  try {
45819
46381
  acknowledged = this.eventStream.send(event);
45820
46382
  } catch (error) {
45821
- const bucket = this.bucketForEvent(event);
45822
- if (bucket) {
45823
- this.fenceLiveCursor(bucket);
45824
- this.markUpdateBucketDegraded(bucket);
45825
- this.requestCatchUpAfterEventOverflow(bucket, event);
46383
+ const bucket2 = this.bucketForEvent(event);
46384
+ if (bucket2) {
46385
+ this.fenceLiveCursor(bucket2);
46386
+ this.markUpdateBucketDegraded(bucket2);
46387
+ this.requestCatchUpAfterEventOverflow(bucket2, event);
45826
46388
  }
45827
46389
  this.log.warn?.("Inbound event buffer overflow; durable cursor remains unchanged and transport will recover", {
45828
- bucket: bucket ? this.updateBucketKey(bucket) : "none",
46390
+ bucket: bucket2 ? this.updateBucketKey(bucket2) : "none",
45829
46391
  error: extractErrorMessage(error)
45830
46392
  });
45831
46393
  this.requestRecoveryReconnect("inbound-event-buffer-overflow");
45832
46394
  return Promise.resolve(false);
45833
46395
  }
45834
- return acknowledged.then((applied) => {
46396
+ const bucket = source === "live" ? this.sequencedBucketForEvent(event) : undefined;
46397
+ const key = bucket ? this.updateBucketKey(bucket) : undefined;
46398
+ const deliveries = key ? this.liveDeliveriesByBucket.get(key) ?? new Set : undefined;
46399
+ const delivery = acknowledged.then((applied) => {
45835
46400
  if (applied) {
45836
46401
  onApplied?.();
45837
46402
  if (source === "live")
@@ -45839,8 +46404,16 @@ class InlineSdkClient {
45839
46404
  } else if (source === "live" && this.started) {
45840
46405
  this.recoverLiveEvent(event, "consumer-did-not-apply");
45841
46406
  }
46407
+ deliveries?.delete(delivery);
46408
+ if (key && deliveries?.size === 0)
46409
+ this.liveDeliveriesByBucket.delete(key);
45842
46410
  return applied;
45843
46411
  });
46412
+ if (key && deliveries) {
46413
+ deliveries.add(delivery);
46414
+ this.liveDeliveriesByBucket.set(key, deliveries);
46415
+ }
46416
+ return delivery;
45844
46417
  }
45845
46418
  bucketForEvent(event) {
45846
46419
  switch (event.kind) {
@@ -46018,20 +46591,25 @@ class InlineSdkClient {
46018
46591
  }
46019
46592
  requestCatchUpForDegradedBucket(bucket) {
46020
46593
  switch (bucket.kind) {
46021
- case "chat":
46022
- if (bucket.peer && this.isReliableChatPeer(bucket.peer)) {
46023
- this.requestCatchUpChat({ chatId: bucket.chatId, peer: bucket.peer });
46594
+ case "chat": {
46595
+ const request = this.catchUpRequestedByChatId.get(bucket.chatId) ?? this.peerResolutionRequestedByChatId.get(bucket.chatId);
46596
+ const updateSeq = request && !request.toLatest ? request.endSeq : undefined;
46597
+ const peer = bucket.peer ?? this.persistedChatPeer(bucket.chatId);
46598
+ if (peer && this.isReliableChatPeer(peer)) {
46599
+ this.requestCatchUpChat({ chatId: bucket.chatId, peer, updateSeq });
46024
46600
  } else {
46025
- const persistedPeer = this.persistedChatPeer(bucket.chatId);
46026
- if (persistedPeer)
46027
- this.requestCatchUpChat({ chatId: bucket.chatId, peer: persistedPeer });
46028
- else
46029
- this.resolvePersistedChatPeer(bucket.chatId);
46601
+ this.resolvePersistedChatPeer(bucket.chatId, updateSeq);
46030
46602
  }
46031
46603
  return;
46032
- case "space":
46033
- this.requestCatchUpSpace({ spaceId: bucket.spaceId });
46604
+ }
46605
+ case "space": {
46606
+ const request = this.catchUpRequestedBySpaceId.get(bucket.spaceId);
46607
+ this.requestCatchUpSpace({
46608
+ spaceId: bucket.spaceId,
46609
+ updateSeq: request && !request.toLatest ? request.endSeq : undefined
46610
+ });
46034
46611
  return;
46612
+ }
46035
46613
  case "user":
46036
46614
  this.requestCatchUpUser(true);
46037
46615
  return;
@@ -46216,7 +46794,7 @@ class InlineSdkClient {
46216
46794
  bumpChatSeq(chatId, seq, source) {
46217
46795
  if (source === "user" || source === "space" || source === "chat")
46218
46796
  return;
46219
- if (source === "live" && this.liveCursorFences.has(`chat:${chatId}`))
46797
+ if (source === "live" && this.liveCursorFences.has(`chat:${chatId}`) && seq !== (this.state.lastSeqByChatId?.[chatId.toString()] ?? 0) + 1)
46220
46798
  return;
46221
46799
  if (!Number.isFinite(seq))
46222
46800
  return;
@@ -46225,6 +46803,7 @@ class InlineSdkClient {
46225
46803
  const key = chatId.toString();
46226
46804
  const prev = this.state.lastSeqByChatId[key] ?? 0;
46227
46805
  if (seq > prev) {
46806
+ this.resetRecoveryBackoff({ kind: "chat", chatId });
46228
46807
  this.state.lastSeqByChatId[key] = seq;
46229
46808
  this.scheduleStateSave();
46230
46809
  }
@@ -46233,7 +46812,7 @@ class InlineSdkClient {
46233
46812
  bumpSpaceSeq(spaceId, seq, source) {
46234
46813
  if (source === "user" || source === "space" || source === "chat")
46235
46814
  return;
46236
- if (source === "live" && this.liveCursorFences.has(`space:${spaceId}`))
46815
+ if (source === "live" && this.liveCursorFences.has(`space:${spaceId}`) && seq !== (this.state.lastSeqBySpaceId?.[spaceId.toString()] ?? 0) + 1)
46237
46816
  return;
46238
46817
  if (!Number.isFinite(seq))
46239
46818
  return;
@@ -46242,6 +46821,7 @@ class InlineSdkClient {
46242
46821
  const key = spaceId.toString();
46243
46822
  const prev = this.state.lastSeqBySpaceId[key] ?? 0;
46244
46823
  if (seq > prev) {
46824
+ this.resetRecoveryBackoff({ kind: "space", spaceId });
46245
46825
  this.state.lastSeqBySpaceId[key] = seq;
46246
46826
  this.scheduleStateSave();
46247
46827
  }
@@ -46256,12 +46836,13 @@ class InlineSdkClient {
46256
46836
  bumpUserSeq(seq, source) {
46257
46837
  if (source === "user" || source === "space" || source === "chat")
46258
46838
  return;
46259
- if (source === "live" && this.liveCursorFences.has("user"))
46839
+ if (source === "live" && this.liveCursorFences.has("user") && seq !== (this.state.lastUserSeq ?? 0) + 1)
46260
46840
  return;
46261
46841
  if (!Number.isFinite(seq) || seq <= 0)
46262
46842
  return;
46263
46843
  const prev = this.state.lastUserSeq ?? 0;
46264
46844
  if (seq > prev) {
46845
+ this.resetRecoveryBackoff({ kind: "user" });
46265
46846
  this.state.lastUserSeq = seq;
46266
46847
  this.scheduleStateSave();
46267
46848
  }
@@ -46275,14 +46856,17 @@ class InlineSdkClient {
46275
46856
  if (this.userCatchUpInFlight) {
46276
46857
  return this.userCatchUpInFlight;
46277
46858
  }
46859
+ if (this.recoveryRetries.get("user")?.timer)
46860
+ return null;
46278
46861
  this.fenceLiveCursor({ kind: "user" });
46279
46862
  this.userCatchUpInFlight = this.doCatchUpUser(lastUserSeq ?? 0).catch((error) => {
46280
- this.markUpdateBucketDegraded({ kind: "user" });
46863
+ this.recordCatchUpFailure({ kind: "user" }, error);
46281
46864
  this.log.warn?.("GET_UPDATES user catch-up failed; bucket remains degraded", {
46282
46865
  error: extractErrorMessage(error)
46283
46866
  });
46284
46867
  }).finally(() => {
46285
46868
  this.userCatchUpInFlight = null;
46869
+ this.scheduleRecoveryRetry({ kind: "user" });
46286
46870
  });
46287
46871
  return this.userCatchUpInFlight;
46288
46872
  }
@@ -46335,7 +46919,7 @@ class InlineSdkClient {
46335
46919
  });
46336
46920
  }
46337
46921
  if (deliveredSeq <= cursor && !payload.final) {
46338
- this.markUpdateBucketDegraded({ kind: "user" });
46922
+ this.markUpdateBucketDegraded({ kind: "user" }, "non_progress");
46339
46923
  this.log.warn?.("GET_UPDATES user catch-up made no progress; bucket remains degraded", {
46340
46924
  cursor,
46341
46925
  deliveredSeq
@@ -46371,15 +46955,18 @@ class InlineSdkClient {
46371
46955
  const existing = this.catchUpInFlightByChatId.get(params.chatId);
46372
46956
  if (existing)
46373
46957
  return existing;
46958
+ if (this.recoveryRetries.get(`chat:${params.chatId}`)?.timer)
46959
+ return Promise.resolve();
46960
+ const initialDemand = this.catchUpRequestedByChatId.get(params.chatId);
46374
46961
  const task = this.drainCatchUpChat(params.chatId).catch((error) => {
46375
- this.catchUpRequestedByChatId.delete(params.chatId);
46376
- this.markUpdateBucketDegraded({ kind: "chat", chatId: params.chatId, peer: params.peer });
46962
+ this.recordCatchUpFailure({ kind: "chat", chatId: params.chatId, peer: params.peer }, error, this.catchUpRequestedByChatId.get(params.chatId) !== initialDemand);
46377
46963
  this.log.warn?.("GET_UPDATES chat catch-up failed; bucket remains degraded", {
46378
46964
  chatId: params.chatId.toString(),
46379
46965
  error: extractErrorMessage(error)
46380
46966
  });
46381
46967
  }).finally(() => {
46382
46968
  this.catchUpInFlightByChatId.delete(params.chatId);
46969
+ this.scheduleRecoveryRetry({ kind: "chat", chatId: params.chatId });
46383
46970
  });
46384
46971
  this.catchUpInFlightByChatId.set(params.chatId, task);
46385
46972
  return task;
@@ -46403,12 +46990,8 @@ class InlineSdkClient {
46403
46990
  if (stop) {
46404
46991
  if (this.catchUpRequestedByChatId.get(chatId) !== request)
46405
46992
  continue;
46406
- this.catchUpRequestedByChatId.delete(chatId);
46407
- return;
46408
- }
46409
- const latest = this.catchUpRequestedByChatId.get(chatId);
46410
- const syncedSeq = this.state.lastSeqByChatId?.[key] ?? 0;
46411
- if (!latest || latest.endSeq != null && latest.endSeq <= syncedSeq) {
46993
+ if (this.degradedUpdateBuckets.has(`chat:${chatId}`))
46994
+ return;
46412
46995
  this.catchUpRequestedByChatId.delete(chatId);
46413
46996
  return;
46414
46997
  }
@@ -46475,7 +47058,7 @@ class InlineSdkClient {
46475
47058
  });
46476
47059
  }
46477
47060
  if (deliveredSeq <= cursor && !payload.final) {
46478
- this.markUpdateBucketDegraded({ kind: "chat", chatId, peer });
47061
+ this.markUpdateBucketDegraded({ kind: "chat", chatId, peer }, "non_progress");
46479
47062
  this.log.warn?.("GET_UPDATES made no progress; bucket remains degraded", {
46480
47063
  chatId: chatId.toString(),
46481
47064
  cursor,
@@ -46493,7 +47076,7 @@ class InlineSdkClient {
46493
47076
  continue;
46494
47077
  }
46495
47078
  if (endSeq != null && deliveredSeq < endSeq) {
46496
- this.markUpdateBucketDegraded({ kind: "chat", chatId, peer });
47079
+ this.markUpdateBucketDegraded({ kind: "chat", chatId, peer }, "target_not_reached");
46497
47080
  this.log.warn?.("GET_UPDATES final chat page remained behind the requested target", {
46498
47081
  chatId: chatId.toString(),
46499
47082
  requestedEndSeq: endSeq,
@@ -46519,15 +47102,18 @@ class InlineSdkClient {
46519
47102
  const existing = this.catchUpInFlightBySpaceId.get(params.spaceId);
46520
47103
  if (existing)
46521
47104
  return existing;
47105
+ if (this.recoveryRetries.get(`space:${params.spaceId}`)?.timer)
47106
+ return Promise.resolve();
47107
+ const initialDemand = this.catchUpRequestedBySpaceId.get(params.spaceId);
46522
47108
  const task = this.drainCatchUpSpace(params.spaceId).catch((error) => {
46523
- this.catchUpRequestedBySpaceId.delete(params.spaceId);
46524
- this.markUpdateBucketDegraded({ kind: "space", spaceId: params.spaceId });
47109
+ this.recordCatchUpFailure({ kind: "space", spaceId: params.spaceId }, error, this.catchUpRequestedBySpaceId.get(params.spaceId) !== initialDemand);
46525
47110
  this.log.warn?.("GET_UPDATES space catch-up failed; bucket remains degraded", {
46526
47111
  spaceId: params.spaceId.toString(),
46527
47112
  error: extractErrorMessage(error)
46528
47113
  });
46529
47114
  }).finally(() => {
46530
47115
  this.catchUpInFlightBySpaceId.delete(params.spaceId);
47116
+ this.scheduleRecoveryRetry({ kind: "space", spaceId: params.spaceId });
46531
47117
  });
46532
47118
  this.catchUpInFlightBySpaceId.set(params.spaceId, task);
46533
47119
  return task;
@@ -46551,12 +47137,8 @@ class InlineSdkClient {
46551
47137
  if (stop) {
46552
47138
  if (this.catchUpRequestedBySpaceId.get(spaceId) !== request)
46553
47139
  continue;
46554
- this.catchUpRequestedBySpaceId.delete(spaceId);
46555
- return;
46556
- }
46557
- const latest = this.catchUpRequestedBySpaceId.get(spaceId);
46558
- const syncedSeq = this.state.lastSeqBySpaceId?.[key] ?? 0;
46559
- if (!latest || latest.endSeq != null && latest.endSeq <= syncedSeq) {
47140
+ if (this.degradedUpdateBuckets.has(`space:${spaceId}`))
47141
+ return;
46560
47142
  this.catchUpRequestedBySpaceId.delete(spaceId);
46561
47143
  return;
46562
47144
  }
@@ -46625,7 +47207,7 @@ class InlineSdkClient {
46625
47207
  });
46626
47208
  }
46627
47209
  if (deliveredSeq <= cursor && !payload.final) {
46628
- this.markUpdateBucketDegraded({ kind: "space", spaceId });
47210
+ this.markUpdateBucketDegraded({ kind: "space", spaceId }, "non_progress");
46629
47211
  this.log.warn?.("GET_UPDATES space made no progress; bucket remains degraded", {
46630
47212
  spaceId: spaceId.toString(),
46631
47213
  cursor,
@@ -46643,7 +47225,7 @@ class InlineSdkClient {
46643
47225
  continue;
46644
47226
  }
46645
47227
  if (endSeq != null && deliveredSeq < endSeq) {
46646
- this.markUpdateBucketDegraded({ kind: "space", spaceId });
47228
+ this.markUpdateBucketDegraded({ kind: "space", spaceId }, "target_not_reached");
46647
47229
  this.log.warn?.("GET_UPDATES final space page remained behind the requested target", {
46648
47230
  spaceId: spaceId.toString(),
46649
47231
  requestedEndSeq: endSeq,
@@ -46668,6 +47250,9 @@ class InlineSdkClient {
46668
47250
  const existing = this.peerResolutionInFlightByChatId.get(chatId);
46669
47251
  if (existing)
46670
47252
  return existing;
47253
+ if (this.recoveryRetries.get(`chat:${chatId}`)?.timer)
47254
+ return Promise.resolve();
47255
+ const initialDemand = this.peerResolutionRequestedByChatId.get(chatId);
46671
47256
  const task = this.getChat({ chatId }).then(async (chat) => {
46672
47257
  const peer = chat.peer;
46673
47258
  if (!peer || !this.isReliableChatPeer(peer)) {
@@ -46681,14 +47266,16 @@ class InlineSdkClient {
46681
47266
  ...requested && !requested.toLatest && requested.endSeq != null ? { updateSeq: requested.endSeq } : {}
46682
47267
  });
46683
47268
  }).catch((error) => {
46684
- this.markUpdateBucketDegraded({ kind: "chat", chatId });
47269
+ this.recordCatchUpFailure({ kind: "chat", chatId }, error, this.peerResolutionRequestedByChatId.get(chatId) !== initialDemand);
46685
47270
  this.log.warn?.("Unable to resolve legacy chat peer; bucket remains degraded", {
46686
47271
  chatId: chatId.toString(),
46687
47272
  error: extractErrorMessage(error)
46688
47273
  });
46689
47274
  }).finally(() => {
46690
47275
  this.peerResolutionInFlightByChatId.delete(chatId);
46691
- this.peerResolutionRequestedByChatId.delete(chatId);
47276
+ if (!this.degradedUpdateBuckets.has(`chat:${chatId}`))
47277
+ this.peerResolutionRequestedByChatId.delete(chatId);
47278
+ this.scheduleRecoveryRetry({ kind: "chat", chatId });
46692
47279
  });
46693
47280
  this.peerResolutionInFlightByChatId.set(chatId, task);
46694
47281
  return task;
@@ -46741,7 +47328,7 @@ class InlineSdkClient {
46741
47328
  }
46742
47329
  validateCatchUpPage(payload, startSeq, bucket) {
46743
47330
  if (payload.resultType !== GetUpdatesResult_ResultType.SLICE && payload.resultType !== GetUpdatesResult_ResultType.EMPTY) {
46744
- this.markUpdateBucketDegraded(bucket);
47331
+ this.markUpdateBucketDegraded(bucket, "invalid_page_type");
46745
47332
  this.log.warn?.("GET_UPDATES returned an invalid page result type; bucket remains degraded", {
46746
47333
  bucket: this.updateBucketKey(bucket),
46747
47334
  resultType: payload.resultType
@@ -46750,7 +47337,7 @@ class InlineSdkClient {
46750
47337
  }
46751
47338
  const deliveredSeq = Number(payload.seq);
46752
47339
  if (!Number.isSafeInteger(deliveredSeq) || deliveredSeq < startSeq) {
46753
- this.markUpdateBucketDegraded(bucket);
47340
+ this.markUpdateBucketDegraded(bucket, "invalid_page_cursor");
46754
47341
  this.log.warn?.("GET_UPDATES page moved backwards or returned an invalid seq; bucket remains degraded", {
46755
47342
  bucket: this.updateBucketKey(bucket),
46756
47343
  startSeq,
@@ -46762,7 +47349,7 @@ class InlineSdkClient {
46762
47349
  for (const update of payload.updates) {
46763
47350
  const seq = update.seq;
46764
47351
  if (!Number.isSafeInteger(seq) || seq == null || seq <= startSeq || seq > deliveredSeq || accounted.has(seq)) {
46765
- this.markUpdateBucketDegraded(bucket);
47352
+ this.markUpdateBucketDegraded(bucket, "invalid_update_sequence");
46766
47353
  this.log.warn?.("GET_UPDATES page included an invalid or duplicate update seq; bucket remains degraded", {
46767
47354
  bucket: this.updateBucketKey(bucket),
46768
47355
  seq
@@ -46775,7 +47362,7 @@ class InlineSdkClient {
46775
47362
  for (const skipped of payload.skippedSequences) {
46776
47363
  const seq = Number(skipped.seq);
46777
47364
  if (!Number.isSafeInteger(seq) || seq <= startSeq || seq > deliveredSeq || accounted.has(seq)) {
46778
- this.markUpdateBucketDegraded(bucket);
47365
+ this.markUpdateBucketDegraded(bucket, "invalid_skipped_sequence");
46779
47366
  this.log.warn?.("GET_UPDATES page included an invalid or duplicate skipped seq; bucket remains degraded", {
46780
47367
  bucket: this.updateBucketKey(bucket),
46781
47368
  seq
@@ -46791,7 +47378,7 @@ class InlineSdkClient {
46791
47378
  break;
46792
47379
  case SyncSkippedSequence_Reason.REASON_UNSPECIFIED:
46793
47380
  default:
46794
- this.markUpdateBucketDegraded(bucket);
47381
+ this.markUpdateBucketDegraded(bucket, "unknown_skip_reason");
46795
47382
  this.log.warn?.("GET_UPDATES page included an unsupported skipped-sequence reason; bucket remains degraded", {
46796
47383
  bucket: this.updateBucketKey(bucket),
46797
47384
  reason: skipped.reason
@@ -46800,7 +47387,7 @@ class InlineSdkClient {
46800
47387
  }
46801
47388
  }
46802
47389
  if (accounted.size !== deliveredSeq - startSeq) {
46803
- this.markUpdateBucketDegraded(bucket);
47390
+ this.markUpdateBucketDegraded(bucket, "unaccounted_gap");
46804
47391
  this.log.warn?.("GET_UPDATES page did not account for every advanced sequence; bucket remains degraded", {
46805
47392
  bucket: this.updateBucketKey(bucket),
46806
47393
  startSeq,
@@ -46882,7 +47469,7 @@ class InlineSdkClient {
46882
47469
  this.clearUpdateBucketDegraded(bucket);
46883
47470
  return true;
46884
47471
  } catch (error) {
46885
- this.markUpdateBucketDegraded(bucket);
47472
+ this.markUpdateBucketDegraded(bucket, "repair_failed");
46886
47473
  this.log.warn?.("Authoritative update repair failed; bucket remains degraded", {
46887
47474
  bucket: this.updateBucketKey(bucket),
46888
47475
  error: extractErrorMessage(error)
@@ -46891,7 +47478,17 @@ class InlineSdkClient {
46891
47478
  }
46892
47479
  }
46893
47480
  async acceptCatchUpUpdates(updates, source, bucket) {
47481
+ const liveDeliveries = this.liveDeliveriesByBucket.get(this.updateBucketKey(bucket));
47482
+ if (liveDeliveries && (await Promise.all(liveDeliveries)).some((applied) => !applied)) {
47483
+ this.markUpdateBucketDegraded(bucket, "host_unacknowledged");
47484
+ return false;
47485
+ }
47486
+ if (this.closed)
47487
+ return false;
47488
+ const appliedThrough = this.bucketCursor(bucket);
46894
47489
  for (const update of updates) {
47490
+ if (update.seq != null && update.seq > 0 && update.seq <= appliedThrough)
47491
+ continue;
46895
47492
  if (update.update.oneofKind === "chatSkipPts" && source === "chat") {
46896
47493
  continue;
46897
47494
  }
@@ -46902,7 +47499,7 @@ class InlineSdkClient {
46902
47499
  if (accepted === true)
46903
47500
  continue;
46904
47501
  if (accepted === false) {
46905
- this.markUpdateBucketDegraded(bucket);
47502
+ this.markUpdateBucketDegraded(bucket, "host_unacknowledged");
46906
47503
  this.log.warn?.("GET_UPDATES event was not acknowledged by the SDK host; cursor remains unchanged", {
46907
47504
  bucket: this.updateBucketKey(bucket),
46908
47505
  updateKind: update.update.oneofKind
@@ -46923,8 +47520,95 @@ class InlineSdkClient {
46923
47520
  }
46924
47521
  return true;
46925
47522
  }
46926
- markUpdateBucketDegraded(bucket) {
46927
- this.degradedUpdateBuckets.set(this.updateBucketKey(bucket), bucket);
47523
+ recordCatchUpFailure(bucket, error, hasNewerDemand = false) {
47524
+ const rpcCode = error instanceof ProtocolClientError ? error.rpcCode : undefined;
47525
+ if (!hasNewerDemand && bucket.kind !== "user" && (rpcCode === RpcError_Code.PEER_ID_INVALID || rpcCode === RpcError_Code.CHAT_ID_INVALID || rpcCode === RpcError_Code.SPACE_ID_INVALID)) {
47526
+ this.clearUpdateBucketDegraded(bucket);
47527
+ if (bucket.kind === "chat") {
47528
+ this.catchUpRequestedByChatId.delete(bucket.chatId);
47529
+ this.peerResolutionRequestedByChatId.delete(bucket.chatId);
47530
+ } else
47531
+ this.catchUpRequestedBySpaceId.delete(bucket.spaceId);
47532
+ const round = this.discoveryRound;
47533
+ if (round) {
47534
+ round.targets.delete(this.updateBucketKey(bucket));
47535
+ this.tryCommitDiscoveryRound(round);
47536
+ }
47537
+ this.log.error?.("Sync bucket unavailable", { bucketKind: bucket.kind, reason: "access_rejected" });
47538
+ return;
47539
+ }
47540
+ this.markUpdateBucketDegraded(bucket, rpcCode === RpcError_Code.RATE_LIMIT || rpcCode === 429 ? "rate_limited" : error instanceof ProtocolClientError ? error.code : "request_failed");
47541
+ }
47542
+ markUpdateBucketDegraded(bucket, failure = "incomplete") {
47543
+ const key = this.updateBucketKey(bucket);
47544
+ this.degradedUpdateBuckets.set(key, bucket);
47545
+ const retry = this.recoveryRetries.get(key) ?? { attempt: 0, failure, reported: new Set };
47546
+ retry.failure = failure;
47547
+ this.recoveryRetries.set(key, retry);
47548
+ queueMicrotask(() => this.scheduleRecoveryRetry(bucket));
47549
+ }
47550
+ resetRecoveryBackoff(bucket) {
47551
+ const retry = this.recoveryRetries.get(this.updateBucketKey(bucket));
47552
+ if (retry)
47553
+ retry.attempt = 0;
47554
+ }
47555
+ cancelRecoveryRetries() {
47556
+ clearTimeout(this.discoveryRetry?.timer);
47557
+ this.discoveryRetry = null;
47558
+ for (const retry of this.recoveryRetries.values())
47559
+ clearTimeout(retry.timer);
47560
+ this.recoveryRetries.clear();
47561
+ }
47562
+ scheduleDiscoveryRetry(reason) {
47563
+ if (!this.started || this.closed || this.protocol.getDiagnostics().state !== "open")
47564
+ return;
47565
+ const retry = this.discoveryRetry ?? { attempt: 0, reported: new Set };
47566
+ if (retry.timer)
47567
+ return;
47568
+ if (!retry.reported.has(reason)) {
47569
+ retry.reported.add(reason);
47570
+ this.log.error?.("Sync recovery scheduled", { bucketKind: "discovery", reason });
47571
+ }
47572
+ const delayMs = reason === "rate_limited" ? 60000 : Math.min(30000, 1000 * 2 ** Math.min(retry.attempt, 5));
47573
+ retry.attempt++;
47574
+ retry.timer = setTimeout(() => {
47575
+ retry.timer = undefined;
47576
+ if (this.started && !this.closed && this.protocol.getDiagnostics().state === "open") {
47577
+ this.initializeDateCursor();
47578
+ }
47579
+ }, delayMs);
47580
+ retry.timer.unref?.();
47581
+ this.discoveryRetry = retry;
47582
+ }
47583
+ scheduleRecoveryRetry(bucket) {
47584
+ const key = this.updateBucketKey(bucket);
47585
+ if (!this.started || this.closed || !this.degradedUpdateBuckets.has(key))
47586
+ return;
47587
+ if (bucket.kind === "chat" && (this.catchUpInFlightByChatId.has(bucket.chatId) || this.peerResolutionInFlightByChatId.has(bucket.chatId)))
47588
+ return;
47589
+ if (bucket.kind === "space" && this.catchUpInFlightBySpaceId.has(bucket.spaceId))
47590
+ return;
47591
+ if (bucket.kind === "user" && this.userCatchUpInFlight)
47592
+ return;
47593
+ const retry = this.recoveryRetries.get(key) ?? { attempt: 0, failure: "incomplete", reported: new Set };
47594
+ if (retry.timer || this.protocol.getDiagnostics().state !== "open")
47595
+ return;
47596
+ const delayMs = retry.failure === "rate_limited" ? 60000 : Math.min(5000, 1000 * 2 ** Math.min(retry.attempt, 3));
47597
+ if (!retry.reported.has(retry.failure)) {
47598
+ retry.reported.add(retry.failure);
47599
+ this.log.error?.("Sync recovery scheduled", { bucketKind: bucket.kind, reason: retry.failure });
47600
+ }
47601
+ retry.attempt++;
47602
+ retry.timer = setTimeout(() => {
47603
+ retry.timer = undefined;
47604
+ if (!this.started || this.closed || this.protocol.getDiagnostics().state !== "open")
47605
+ return;
47606
+ const pending = this.degradedUpdateBuckets.get(key);
47607
+ if (pending)
47608
+ this.requestCatchUpForDegradedBucket(pending);
47609
+ }, delayMs);
47610
+ retry.timer.unref?.();
47611
+ this.recoveryRetries.set(key, retry);
46928
47612
  }
46929
47613
  fenceLiveCursor(bucket) {
46930
47614
  this.liveCursorFences.add(this.updateBucketKey(bucket));
@@ -46932,6 +47616,8 @@ class InlineSdkClient {
46932
47616
  clearUpdateBucketDegraded(bucket) {
46933
47617
  const key = this.updateBucketKey(bucket);
46934
47618
  this.degradedUpdateBuckets.delete(key);
47619
+ clearTimeout(this.recoveryRetries.get(key)?.timer);
47620
+ this.recoveryRetries.delete(key);
46935
47621
  this.liveCursorFences.delete(key);
46936
47622
  this.liveAdmittedSeqByBucket.delete(key);
46937
47623
  }
@@ -47538,334 +48224,6 @@ var portableCoreV1Vector = {
47538
48224
  aesIvHex: "a32285aa1fc31b5b3a6c5e75843de0d48393f2350d23d7ccbbc3cb7b34ce7e6c",
47539
48225
  recordHex: "32d1586ea457dfc80b016bab73824ee1e75f00f0fa824908302fa5dab375c8029b169848525548f61add2955845b9810fe817fcc7581efd11aaac110560a2cc78ae6a20cc6216a0b86fa0d061a57f84bacbf84af84ec31b4"
47540
48226
  };
47541
- // src/sidecar/contract.ts
47542
- var MAX_INLINE_ID = 9223372036854775807n;
47543
- var MAX_UINT64 = 18446744073709551615n;
47544
- var sensitiveUrlParams = new Set([
47545
- "access_token",
47546
- "auth",
47547
- "authorization",
47548
- "key",
47549
- "token"
47550
- ]);
47551
- var botSettingsEventKinds = new Set([
47552
- "bot.chatSettings.request",
47553
- "bot.chatSettings.item.invoke"
47554
- ]);
47555
- function inboundEventNeedsSenderResolution(event) {
47556
- return !botSettingsEventKinds.has(event.kind ?? "");
47557
- }
47558
-
47559
- class SidecarError extends Error {
47560
- errorKind;
47561
- constructor(message, errorKind) {
47562
- super(message);
47563
- this.errorKind = errorKind;
47564
- }
47565
- }
47566
- function parseTarget(record2) {
47567
- const targetRecord = asOptionalRecord(record2.target) ?? record2;
47568
- const chatId = readOptionalInlineId(targetRecord, "chatId");
47569
- const userId = readOptionalInlineId(targetRecord, "userId");
47570
- if (chatId && userId)
47571
- throw new SidecarError("target cannot include both chatId and userId", "bad_format");
47572
- if (chatId)
47573
- return { chatId };
47574
- if (userId)
47575
- return { userId };
47576
- throw new SidecarError("target requires chatId or userId", "bad_format");
47577
- }
47578
- function normalizeUploadKind(raw, filePath) {
47579
- if (raw === "photo" || raw === "image")
47580
- return "photo";
47581
- if (raw === "video")
47582
- return "video";
47583
- if (raw === "voice")
47584
- return "voice";
47585
- if (raw === "document" || raw === "file")
47586
- return "document";
47587
- const lower = filePath.toLowerCase();
47588
- if (/\.(png|jpg|jpeg|gif|webp|heic|heif)$/.test(lower))
47589
- return "photo";
47590
- if (/\.(mp4|mov|webm)$/.test(lower))
47591
- return "video";
47592
- return "document";
47593
- }
47594
- function normalizeError(error, redact = defaultErrorText) {
47595
- if (error instanceof SidecarError) {
47596
- return {
47597
- status: statusForErrorKind(error.errorKind),
47598
- errorKind: error.errorKind,
47599
- message: redact(error)
47600
- };
47601
- }
47602
- const message = redact(error);
47603
- const lower = message.toLowerCase();
47604
- if (lower.includes("rate") && lower.includes("limit")) {
47605
- return { status: 429, errorKind: "rate_limited", message };
47606
- }
47607
- if (lower.includes("forbidden") || lower.includes("unauthorized")) {
47608
- return { status: 403, errorKind: "forbidden", message };
47609
- }
47610
- if (lower.includes("not found") || lower.includes("missing")) {
47611
- return { status: 404, errorKind: "not_found", message };
47612
- }
47613
- if (lower.includes("timeout") || lower.includes("network") || lower.includes("closed")) {
47614
- return { status: 503, errorKind: "transient", message };
47615
- }
47616
- return { status: 500, errorKind: "unknown", message };
47617
- }
47618
- function statusForErrorKind(errorKind) {
47619
- switch (errorKind) {
47620
- case "bad_format":
47621
- return 400;
47622
- case "forbidden":
47623
- return 403;
47624
- case "not_found":
47625
- return 404;
47626
- case "too_long":
47627
- return 413;
47628
- case "rate_limited":
47629
- return 429;
47630
- case "transient":
47631
- return 503;
47632
- case "unknown":
47633
- return 500;
47634
- }
47635
- }
47636
- function normalizeInboundEvent(event, meId, sender, meUsername) {
47637
- if (event.kind === "message.new" || event.kind === "message.edit") {
47638
- const message = asOptionalRecord(event.message);
47639
- return safeJson({
47640
- kind: event.kind,
47641
- chatId: event.chatId,
47642
- seq: event.seq,
47643
- date: event.date,
47644
- meId,
47645
- meUsername,
47646
- ...sender ? { sender } : {},
47647
- message: message ? normalizeMessage(message) : null
47648
- });
47649
- }
47650
- if (event.kind === "message.action.invoke") {
47651
- return safeJson({
47652
- ...event,
47653
- meId,
47654
- meUsername,
47655
- ...sender ? { sender } : {},
47656
- dataBase64: event.data instanceof Uint8Array ? Buffer.from(event.data).toString("base64") : typeof event.data === "string" ? event.data : ""
47657
- });
47658
- }
47659
- return safeJson({ ...event, meId, meUsername, ...sender ? { sender } : {} });
47660
- }
47661
- function normalizeMessage(message) {
47662
- return {
47663
- id: message.id,
47664
- fromId: message.fromId,
47665
- chatId: message.chatId,
47666
- peerId: message.peerId,
47667
- message: message.message ?? null,
47668
- out: Boolean(message.out),
47669
- date: message.date,
47670
- mentioned: Boolean(message.mentioned),
47671
- replyToMsgId: message.replyToMsgId ?? null,
47672
- entities: message.entities ?? null,
47673
- media: message.media ?? null,
47674
- attachments: message.attachments ?? null,
47675
- reactions: message.reactions ?? null,
47676
- replies: message.replies ?? null,
47677
- actions: message.actions ?? null,
47678
- rev: message.rev ?? null,
47679
- raw: message
47680
- };
47681
- }
47682
- function redactText(value, secrets) {
47683
- let text = value instanceof Error ? value.message : String(value);
47684
- for (const secret of secrets) {
47685
- const raw = typeof secret.value === "string" ? secret.value : "";
47686
- if (!raw)
47687
- continue;
47688
- text = text.split(raw).join(secret.label);
47689
- }
47690
- return text;
47691
- }
47692
- function redactUrl(value) {
47693
- let url;
47694
- try {
47695
- url = new URL(value);
47696
- } catch {
47697
- return value;
47698
- }
47699
- if (url.username)
47700
- url.username = "redacted";
47701
- if (url.password)
47702
- url.password = "redacted";
47703
- const keys = Array.from(url.searchParams.keys());
47704
- for (const key of keys) {
47705
- const normalized = key.toLowerCase();
47706
- if (sensitiveUrlParams.has(normalized) || normalized.includes("token")) {
47707
- url.searchParams.set(key, "redacted");
47708
- }
47709
- }
47710
- return url.toString();
47711
- }
47712
- function safeJson(value) {
47713
- if (value == null)
47714
- return null;
47715
- if (typeof value === "string" || typeof value === "boolean")
47716
- return value;
47717
- if (typeof value === "number")
47718
- return Number.isFinite(value) ? value : null;
47719
- if (typeof value === "bigint")
47720
- return value.toString();
47721
- if (value instanceof Uint8Array)
47722
- return Buffer.from(value).toString("base64");
47723
- if (Array.isArray(value))
47724
- return value.map(safeJson);
47725
- if (typeof value === "object") {
47726
- const out = {};
47727
- for (const [key, item] of Object.entries(value)) {
47728
- if (item !== undefined)
47729
- out[key] = safeJson(item);
47730
- }
47731
- return out;
47732
- }
47733
- return String(value);
47734
- }
47735
- function asRecord(value) {
47736
- const record2 = asOptionalRecord(value);
47737
- if (!record2)
47738
- throw new SidecarError("expected JSON object", "bad_format");
47739
- return record2;
47740
- }
47741
- function asOptionalRecord(value) {
47742
- if (!value || typeof value !== "object" || Array.isArray(value))
47743
- return null;
47744
- return value;
47745
- }
47746
- function readRequiredString(record2, key) {
47747
- const value = readOptionalString(record2, key);
47748
- if (!value)
47749
- throw new SidecarError(`missing ${key}`, "bad_format");
47750
- return value;
47751
- }
47752
- function readOptionalString(record2, key) {
47753
- const value = record2[key];
47754
- if (typeof value === "string")
47755
- return value.trim() || undefined;
47756
- if (typeof value === "bigint" || typeof value === "number")
47757
- return String(value);
47758
- return;
47759
- }
47760
- function readOptionalBoolean(record2, key) {
47761
- const value = record2[key];
47762
- if (typeof value === "boolean")
47763
- return value;
47764
- if (typeof value === "string") {
47765
- if (/^(1|true|yes|on)$/i.test(value))
47766
- return true;
47767
- if (/^(0|false|no|off)$/i.test(value))
47768
- return false;
47769
- }
47770
- return;
47771
- }
47772
- function readOptionalNumber(record2, key) {
47773
- const value = record2[key];
47774
- if (typeof value === "number" && Number.isFinite(value))
47775
- return value;
47776
- if (typeof value === "string" && value.trim()) {
47777
- const parsed = Number(value);
47778
- if (Number.isFinite(parsed))
47779
- return parsed;
47780
- }
47781
- return;
47782
- }
47783
- function readRequiredInlineId(record2, key) {
47784
- const value = readOptionalInlineId(record2, key);
47785
- if (value == null)
47786
- throw new SidecarError(`missing ${key}`, "bad_format");
47787
- return value;
47788
- }
47789
- function readOptionalInlineId(record2, key) {
47790
- const value = record2[key];
47791
- if (value == null || value === "")
47792
- return;
47793
- return parseInlineId(value, key);
47794
- }
47795
- function readInlineIdArray(record2, key, maxItems, required = false) {
47796
- const value = record2[key];
47797
- if (value == null) {
47798
- if (required)
47799
- throw new SidecarError(`missing ${key}`, "bad_format");
47800
- return [];
47801
- }
47802
- if (!Array.isArray(value))
47803
- throw new SidecarError(`${key} must be an array`, "bad_format");
47804
- if (value.length === 0 && required)
47805
- throw new SidecarError(`${key} must not be empty`, "bad_format");
47806
- if (value.length > maxItems)
47807
- throw new SidecarError(`${key} supports at most ${maxItems} items`, "bad_format");
47808
- const ids = [];
47809
- const seen = new Set;
47810
- for (let index = 0;index < value.length; index += 1) {
47811
- const id = parseInlineId(value[index], `${key}[${index}]`);
47812
- const normalized = id.toString();
47813
- if (seen.has(normalized))
47814
- continue;
47815
- seen.add(normalized);
47816
- ids.push(id);
47817
- }
47818
- return ids;
47819
- }
47820
- function parseOptionalInt(value) {
47821
- const raw = (value || "").trim();
47822
- if (!raw || !/^\d+$/.test(raw))
47823
- return;
47824
- const parsed = Number(raw);
47825
- return Number.isSafeInteger(parsed) ? parsed : undefined;
47826
- }
47827
- function parseInlineId(value, field) {
47828
- try {
47829
- if (typeof value === "number" && (!Number.isSafeInteger(value) || value <= 0)) {
47830
- throw new Error("unsafe number");
47831
- }
47832
- const raw = typeof value === "string" ? value.trim() : value;
47833
- if (typeof raw === "string" && !/^[1-9][0-9]*$/.test(raw))
47834
- throw new Error("invalid digits");
47835
- if (typeof raw !== "string" && typeof raw !== "bigint" && typeof raw !== "number") {
47836
- throw new Error("invalid type");
47837
- }
47838
- const parsed = BigInt(raw);
47839
- if (parsed <= 0n || parsed > MAX_INLINE_ID)
47840
- throw new Error("out of range");
47841
- return parsed;
47842
- } catch {
47843
- throw new SidecarError(`${field} must be a positive signed 64-bit integer`, "bad_format");
47844
- }
47845
- }
47846
- function parseUnsigned64Id(value, field) {
47847
- try {
47848
- if (typeof value === "number" && (!Number.isSafeInteger(value) || value <= 0)) {
47849
- throw new Error("unsafe number");
47850
- }
47851
- const raw = typeof value === "string" ? value.trim() : value;
47852
- if (typeof raw === "string" && !/^[1-9][0-9]*$/.test(raw))
47853
- throw new Error("invalid digits");
47854
- if (typeof raw !== "string" && typeof raw !== "bigint" && typeof raw !== "number") {
47855
- throw new Error("invalid type");
47856
- }
47857
- const parsed = BigInt(raw);
47858
- if (parsed <= 0n || parsed > MAX_UINT64)
47859
- throw new Error("out of range");
47860
- return parsed;
47861
- } catch {
47862
- throw new SidecarError(`${field} must be a positive unsigned 64-bit integer`, "bad_format");
47863
- }
47864
- }
47865
- function defaultErrorText(error) {
47866
- return error instanceof Error ? error.message : String(error);
47867
- }
47868
-
47869
48227
  // src/sidecar/user-directory.ts
47870
48228
  var DEFAULT_PROFILE_TTL_MS = 10 * 60000;
47871
48229
  var DEFAULT_MAX_PROFILES = 5000;
@@ -47873,6 +48231,7 @@ var DEFAULT_MAX_PROFILES = 5000;
47873
48231
  class InlineUserDirectory {
47874
48232
  client;
47875
48233
  profiles = new Map;
48234
+ lookupTimeouts = new Map;
47876
48235
  hydratedChats = new Map;
47877
48236
  chatFetches = new Map;
47878
48237
  directoryExpiresAt = 0;
@@ -47897,7 +48256,7 @@ class InlineUserDirectory {
47897
48256
  async resolveWithProvenance(params) {
47898
48257
  const userId = params.userId.toString();
47899
48258
  const cached2 = this.getFresh(userId);
47900
- if (hasDisplayIdentity(cached2) && cached2.bot != null) {
48259
+ if (cached2?.bot != null) {
47901
48260
  return { profile: cached2, provenanceVerified: true };
47902
48261
  }
47903
48262
  if (params.direct) {
@@ -47910,7 +48269,7 @@ class InlineUserDirectory {
47910
48269
  } else {
47911
48270
  const chatHydrated = await this.hydrateChat(params.chatId);
47912
48271
  const participant = this.getFresh(userId);
47913
- if (hasDisplayIdentity(participant)) {
48272
+ if (participant?.bot != null || hasDisplayIdentity(participant)) {
47914
48273
  return {
47915
48274
  profile: participant,
47916
48275
  provenanceVerified: chatHydrated || participant.bot != null
@@ -47979,7 +48338,7 @@ class InlineUserDirectory {
47979
48338
  if (existing)
47980
48339
  return existing;
47981
48340
  const fetch2 = (async () => {
47982
- const result = await this.client.invokeUncheckedRaw(Method.GET_CHAT_PARTICIPANTS, {
48341
+ const result = await this.invokeLookup(Method.GET_CHAT_PARTICIPANTS, {
47983
48342
  oneofKind: "getChatParticipants",
47984
48343
  getChatParticipants: { chatId }
47985
48344
  });
@@ -48000,13 +48359,24 @@ class InlineUserDirectory {
48000
48359
  this.chatFetches.set(key, fetch2);
48001
48360
  return await fetch2;
48002
48361
  }
48362
+ async invokeLookup(method, input) {
48363
+ const timeoutMs = this.lookupTimeouts.get(method) ?? 1500;
48364
+ try {
48365
+ return await this.client.invokeUncheckedRaw(method, input, { timeoutMs });
48366
+ } catch (error) {
48367
+ if (error instanceof ProtocolClientError && error.code === "timeout") {
48368
+ this.lookupTimeouts.set(method, Math.max(this.lookupTimeouts.get(method) ?? 0, Math.min(30000, timeoutMs * 2)));
48369
+ }
48370
+ throw error;
48371
+ }
48372
+ }
48003
48373
  async hydrateDirectory() {
48004
48374
  if (this.directoryExpiresAt > this.now())
48005
48375
  return true;
48006
48376
  if (this.directoryFetch)
48007
48377
  return this.directoryFetch;
48008
48378
  const fetch2 = (async () => {
48009
- const result = await this.client.invokeUncheckedRaw(Method.GET_CHATS, {
48379
+ const result = await this.invokeLookup(Method.GET_CHATS, {
48010
48380
  oneofKind: "getChats",
48011
48381
  getChats: {}
48012
48382
  });
@@ -48244,8 +48614,8 @@ var meUsername = null;
48244
48614
  var connectError = null;
48245
48615
  var connectAttempts = 0;
48246
48616
  var nextConnectRetryAt = null;
48247
- var consumer = null;
48248
- var consumerWaiters = [];
48617
+ var inboundStream = new InboundStream;
48618
+ var inboundAbort = new AbortController;
48249
48619
  var clientOptions = {
48250
48620
  token,
48251
48621
  baseUrl,
@@ -48321,11 +48691,15 @@ async function connectClientLoop() {
48321
48691
  }
48322
48692
  async function consumeEvents() {
48323
48693
  try {
48324
- for await (const event of client.events()) {
48325
- const senderResolution = inboundEventNeedsSenderResolution(event) ? await resolveInboundSender(event) : { provenanceVerified: true };
48326
- const normalized = normalizeInboundEvent(event, meId, senderResolution.profile, meUsername);
48327
- await deliver(senderResolution.provenanceVerified ? normalized : { ...asRecord(normalized), _inlineSenderProvenanceVerified: false });
48328
- }
48694
+ await client.consumeEvents(async (event) => {
48695
+ await deliverInboundEvent(event, {
48696
+ meId,
48697
+ meUsername,
48698
+ signal: inboundAbort.signal,
48699
+ resolveSender: resolveInboundSender,
48700
+ deliver
48701
+ });
48702
+ });
48329
48703
  } catch (error) {
48330
48704
  if (!stopping) {
48331
48705
  reportHermesPluginError("inbound.loop", error, { handled: false });
@@ -49166,6 +49540,10 @@ class MockInlineClient {
49166
49540
  calls: this.calls
49167
49541
  };
49168
49542
  }
49543
+ async consumeEvents(handler) {
49544
+ for await (const event of this.events())
49545
+ await handler(event);
49546
+ }
49169
49547
  events() {
49170
49548
  return {
49171
49549
  [Symbol.asyncIterator]: () => ({
@@ -49458,41 +49836,15 @@ server.listen(port, bind, () => {
49458
49836
  });
49459
49837
  setupShutdown();
49460
49838
  function attachConsumer(res) {
49461
- if (consumer) {
49462
- consumer.end();
49463
- consumer = null;
49464
- }
49465
49839
  res.writeHead(200, {
49466
49840
  "content-type": "application/x-ndjson; charset=utf-8",
49467
49841
  "cache-control": "no-cache",
49468
49842
  connection: "keep-alive"
49469
49843
  });
49470
- consumer = res;
49471
- const waiters = consumerWaiters;
49472
- consumerWaiters = [];
49473
- for (const resolve of waiters)
49474
- resolve();
49475
- res.on("close", () => {
49476
- if (consumer === res)
49477
- consumer = null;
49478
- });
49844
+ inboundStream.attach(res);
49479
49845
  }
49480
49846
  async function deliver(event) {
49481
- while (!stopping) {
49482
- if (!consumer) {
49483
- await new Promise((resolve) => consumerWaiters.push(resolve));
49484
- continue;
49485
- }
49486
- try {
49487
- const ok = consumer.write(JSON.stringify(event) + `
49488
- `);
49489
- if (!ok)
49490
- await once(consumer, "drain");
49491
- return;
49492
- } catch {
49493
- consumer = null;
49494
- }
49495
- }
49847
+ await inboundStream.deliver(event);
49496
49848
  }
49497
49849
  function inputPeerFromTarget(target) {
49498
49850
  if ("chatId" in target) {
@@ -49593,6 +49945,9 @@ function redactError(error) {
49593
49945
  }
49594
49946
  function log(level, args) {
49595
49947
  const message = args.map((arg) => typeof arg === "string" ? arg : JSON.stringify(safeJson(arg))).join(" ");
49948
+ if (level === "error" && (args[0] === "Sync recovery scheduled" || args[0] === "Sync bucket unavailable" || args[0] === "Sync discovery unavailable")) {
49949
+ reportHermesPluginError("sdk.sync_recovery", new Error(redactError(message)));
49950
+ }
49596
49951
  console.error(`inline-sidecar:${level}: ${redactError(message)}`);
49597
49952
  }
49598
49953
  function clampRetryMs(value, fallback) {
@@ -49643,8 +49998,8 @@ async function shutdown(code) {
49643
49998
  return;
49644
49999
  stopping = true;
49645
50000
  try {
49646
- consumer?.end();
49647
- consumer = null;
50001
+ inboundAbort.abort();
50002
+ inboundStream.close();
49648
50003
  server.close();
49649
50004
  await client.close();
49650
50005
  } catch (error) {