@inline-openclaw/inline 0.0.28 → 0.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5837,7 +5837,8 @@ class ConnectionInit$Type extends import_runtime4.MessageType {
5837
5837
  { no: 1, name: "token", kind: "scalar", T: 9 },
5838
5838
  { no: 2, name: "build_number", kind: "scalar", opt: true, T: 5 },
5839
5839
  { no: 3, name: "layer", kind: "scalar", opt: true, T: 13 },
5840
- { no: 4, name: "client_version", kind: "scalar", opt: true, T: 9 }
5840
+ { no: 4, name: "client_version", kind: "scalar", opt: true, T: 9 },
5841
+ { no: 5, name: "os_version", kind: "scalar", opt: true, T: 9 }
5841
5842
  ]);
5842
5843
  }
5843
5844
  create(value) {
@@ -5864,6 +5865,9 @@ class ConnectionInit$Type extends import_runtime4.MessageType {
5864
5865
  case 4:
5865
5866
  message.clientVersion = reader.string();
5866
5867
  break;
5868
+ case 5:
5869
+ message.osVersion = reader.string();
5870
+ break;
5867
5871
  default:
5868
5872
  let u = options.readUnknownField;
5869
5873
  if (u === "throw")
@@ -5884,6 +5888,8 @@ class ConnectionInit$Type extends import_runtime4.MessageType {
5884
5888
  writer.tag(3, import_runtime.WireType.Varint).uint32(message.layer);
5885
5889
  if (message.clientVersion !== undefined)
5886
5890
  writer.tag(4, import_runtime.WireType.LengthDelimited).string(message.clientVersion);
5891
+ if (message.osVersion !== undefined)
5892
+ writer.tag(5, import_runtime.WireType.LengthDelimited).string(message.osVersion);
5887
5893
  let u = options.writeUnknownFields;
5888
5894
  if (u !== false)
5889
5895
  (u == true ? import_runtime2.UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
@@ -18289,6 +18295,9 @@ class PingPongService {
18289
18295
  sleepTimer = null;
18290
18296
  sleepResolver = null;
18291
18297
  pings = new Map;
18298
+ lastPingAt = null;
18299
+ lastPongAt = null;
18300
+ lastTimeoutAt = null;
18292
18301
  constructor(options) {
18293
18302
  this.log = options?.logger ?? {};
18294
18303
  const cryptoAny = globalThis.crypto;
@@ -18317,13 +18326,16 @@ class PingPongService {
18317
18326
  return;
18318
18327
  const nonce = this.randomNonce();
18319
18328
  await client.sendPing(nonce);
18320
- this.pings.set(nonce, Date.now());
18329
+ const now = Date.now();
18330
+ this.lastPingAt = now;
18331
+ this.pings.set(nonce, now);
18321
18332
  }
18322
18333
  pong(nonce) {
18323
18334
  const pingDate = this.pings.get(nonce);
18324
18335
  if (!pingDate)
18325
18336
  return;
18326
18337
  this.pings.delete(nonce);
18338
+ this.lastPongAt = Date.now();
18327
18339
  }
18328
18340
  async loop() {
18329
18341
  while (this.running) {
@@ -18344,11 +18356,16 @@ class PingPongService {
18344
18356
  if (client.state !== "open")
18345
18357
  return;
18346
18358
  const now = Date.now();
18347
- const hasTimedOutPing = [...this.pings.values()].some((timestamp) => now - timestamp > 30000);
18348
- if (!hasTimedOutPing)
18359
+ const oldestPendingPingAt = [...this.pings.values()].reduce((oldest, timestamp) => oldest == null || timestamp < oldest ? timestamp : oldest, null);
18360
+ if (oldestPendingPingAt == null)
18349
18361
  return;
18350
- this.log.warn?.("Ping timeout, reconnecting");
18351
- await client.reconnect();
18362
+ const oldestPendingAgeMs = now - oldestPendingPingAt;
18363
+ if (oldestPendingAgeMs <= 30000)
18364
+ return;
18365
+ this.lastTimeoutAt = now;
18366
+ this.log.warn?.(`Ping timeout, reconnecting (pending=${this.pings.size}, oldestPendingAgeMs=${oldestPendingAgeMs}${this.lastPongAt != null ? `, lastPongAgeMs=${now - this.lastPongAt}` : ""})`);
18367
+ this.reset();
18368
+ await client.reconnect({ cause: "ping-timeout" });
18352
18369
  }
18353
18370
  async sleep(ms) {
18354
18371
  if (ms <= 0)
@@ -18382,6 +18399,19 @@ class PingPongService {
18382
18399
  }
18383
18400
  return BigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER));
18384
18401
  }
18402
+ getDiagnostics() {
18403
+ const now = Date.now();
18404
+ const oldestPendingPingAt = [...this.pings.values()].reduce((oldest, timestamp) => oldest == null || timestamp < oldest ? timestamp : oldest, null);
18405
+ return {
18406
+ running: this.running,
18407
+ pendingCount: this.pings.size,
18408
+ lastPingAt: this.lastPingAt,
18409
+ lastPongAt: this.lastPongAt,
18410
+ lastTimeoutAt: this.lastTimeoutAt,
18411
+ oldestPendingPingAt,
18412
+ oldestPendingPingAgeMs: oldestPendingPingAt != null ? now - oldestPendingPingAt : null
18413
+ };
18414
+ }
18385
18415
  }
18386
18416
  var isCrypto = (value) => typeof value === "object" && value !== null && ("getRandomValues" in value) && typeof value.getRandomValues === "function";
18387
18417
 
@@ -18411,6 +18441,11 @@ class ProtocolClient {
18411
18441
  reconnectionTimer = null;
18412
18442
  authenticationTimeout = null;
18413
18443
  listenersStarted = false;
18444
+ lastConnectingAt = null;
18445
+ lastOpenAt = null;
18446
+ lastTransportMessageAt = null;
18447
+ lastFailureAt = null;
18448
+ lastFailureReason = null;
18414
18449
  constructor(options) {
18415
18450
  this.transport = options.transport;
18416
18451
  this.log = options.logger ?? {};
@@ -18438,7 +18473,10 @@ class ProtocolClient {
18438
18473
  }
18439
18474
  }
18440
18475
  async reconnect(options) {
18441
- await this.transport.reconnect({ skipDelay: options?.skipDelay });
18476
+ await this.transport.reconnect({
18477
+ skipDelay: options?.skipDelay,
18478
+ cause: options?.cause ?? this.lastFailureReason ?? "protocol-reconnect"
18479
+ });
18442
18480
  }
18443
18481
  async sendRpc(method, input = emptyRpcInput) {
18444
18482
  this.ensureOpenForRpc();
@@ -18499,6 +18537,7 @@ class ProtocolClient {
18499
18537
  });
18500
18538
  }
18501
18539
  async handleTransportMessage(message) {
18540
+ this.lastTransportMessageAt = Date.now();
18502
18541
  switch (message.body.oneofKind) {
18503
18542
  case "connectionOpen":
18504
18543
  await this.connectionOpen();
@@ -18531,7 +18570,7 @@ class ProtocolClient {
18531
18570
  this.pingPong.pong(message.body.pong.nonce);
18532
18571
  break;
18533
18572
  case "connectionError":
18534
- this.handleClientFailure();
18573
+ this.handleClientFailure(describeConnectionError(message.body.connectionError));
18535
18574
  break;
18536
18575
  default:
18537
18576
  break;
@@ -18553,11 +18592,12 @@ class ProtocolClient {
18553
18592
  this.startAuthenticationTimeout();
18554
18593
  } catch (error) {
18555
18594
  this.log.error?.("Failed to authenticate", error);
18556
- this.handleClientFailure();
18595
+ this.handleClientFailure(`authenticate failed: ${summarizeError(error)}`);
18557
18596
  }
18558
18597
  }
18559
18598
  async connectionOpen() {
18560
18599
  this.state = "open";
18600
+ this.lastOpenAt = Date.now();
18561
18601
  await this.events.send({ type: "open" });
18562
18602
  this.stopAuthenticationTimeout();
18563
18603
  if (this.reconnectionTimer) {
@@ -18569,7 +18609,9 @@ class ProtocolClient {
18569
18609
  this.resendPendingRpcRequests();
18570
18610
  }
18571
18611
  async connecting() {
18612
+ this.pingPong.stop();
18572
18613
  this.state = "connecting";
18614
+ this.lastConnectingAt = Date.now();
18573
18615
  await this.events.send({ type: "connecting" });
18574
18616
  }
18575
18617
  async reset() {
@@ -18583,7 +18625,7 @@ class ProtocolClient {
18583
18625
  this.authenticationTimeout = setTimeout(() => {
18584
18626
  if (this.state === "open")
18585
18627
  return;
18586
- this.handleClientFailure();
18628
+ this.handleClientFailure("authentication timeout after 10000ms");
18587
18629
  }, 1e4);
18588
18630
  }
18589
18631
  stopAuthenticationTimeout() {
@@ -18592,19 +18634,23 @@ class ProtocolClient {
18592
18634
  clearTimeout(this.authenticationTimeout);
18593
18635
  this.authenticationTimeout = null;
18594
18636
  }
18595
- handleClientFailure() {
18637
+ handleClientFailure(reason = "connection failure") {
18596
18638
  this.pingPong.stop();
18597
18639
  this.stopAuthenticationTimeout();
18598
18640
  this.state = "connecting";
18641
+ this.lastFailureAt = Date.now();
18642
+ this.lastFailureReason = reason;
18599
18643
  if (this.reconnectionTimer) {
18600
18644
  clearTimeout(this.reconnectionTimer);
18601
18645
  }
18602
18646
  this.connectionAttemptNo = this.connectionAttemptNo + 1 >>> 0;
18647
+ const delayMs = Math.round(this.getReconnectionDelay() * 1000);
18648
+ this.log.warn?.(`Protocol reconnect scheduled (attempt=${this.connectionAttemptNo}, delayMs=${delayMs}, reason=${reason})`);
18603
18649
  this.reconnectionTimer = setTimeout(() => {
18604
18650
  if (this.state === "open")
18605
18651
  return;
18606
18652
  this.reconnect({ skipDelay: true });
18607
- }, this.getReconnectionDelay() * 1000);
18653
+ }, delayMs);
18608
18654
  }
18609
18655
  getReconnectionDelay() {
18610
18656
  const attemptNo = this.connectionAttemptNo;
@@ -18690,11 +18736,25 @@ class ProtocolClient {
18690
18736
  pending.sending = true;
18691
18737
  this.transport.send(pending.message).catch((error) => {
18692
18738
  this.log.warn?.("Failed to send RPC request; waiting for reconnect", error);
18693
- this.handleClientFailure();
18739
+ this.handleClientFailure(`rpc send failed: ${summarizeError(error)}`);
18694
18740
  }).finally(() => {
18695
18741
  pending.sending = false;
18696
18742
  });
18697
18743
  }
18744
+ getDiagnostics() {
18745
+ return {
18746
+ state: this.state,
18747
+ connectionAttemptNo: this.connectionAttemptNo,
18748
+ pendingRpcCount: this.pendingRpcRequests.size,
18749
+ lastConnectingAt: this.lastConnectingAt,
18750
+ lastOpenAt: this.lastOpenAt,
18751
+ lastTransportMessageAt: this.lastTransportMessageAt,
18752
+ lastFailureAt: this.lastFailureAt,
18753
+ lastFailureReason: this.lastFailureReason,
18754
+ ping: this.pingPong.getDiagnostics(),
18755
+ transport: typeof this.transport.getDiagnostics === "function" ? this.transport.getDiagnostics() : null
18756
+ };
18757
+ }
18698
18758
  }
18699
18759
  var normalizeRpcTimeoutMs = (timeoutMs, fallback) => {
18700
18760
  const resolved = timeoutMs === undefined ? fallback : timeoutMs;
@@ -18715,6 +18775,18 @@ class ProtocolClientError extends Error {
18715
18775
  this.name = `ProtocolClientError:${code}`;
18716
18776
  }
18717
18777
  }
18778
+ function summarizeError(error) {
18779
+ if (error instanceof Error) {
18780
+ return `${error.name}: ${error.message}`;
18781
+ }
18782
+ return String(error);
18783
+ }
18784
+ function describeConnectionError(error) {
18785
+ const value = typeof error === "object" && error !== null ? error : null;
18786
+ const code = typeof value?.code === "number" ? value.code : null;
18787
+ const message = typeof value?.message === "string" && value.message.trim() ? value.message.trim() : "unknown";
18788
+ return `server connection error${code != null ? ` (code=${code})` : ""}: ${message}`;
18789
+ }
18718
18790
 
18719
18791
  // ../../node_modules/.bun/ws@8.20.0/node_modules/ws/wrapper.mjs
18720
18792
  var import_stream = __toESM(require_stream(), 1);
@@ -18746,6 +18818,18 @@ class WebSocketTransport {
18746
18818
  connectionAttemptNo = 0;
18747
18819
  socket = null;
18748
18820
  reconnectionTimer = null;
18821
+ lastConnectStartedAt = null;
18822
+ lastConnectedAt = null;
18823
+ lastDisconnectedAt = null;
18824
+ lastMessageAt = null;
18825
+ lastCloseCode = null;
18826
+ lastCloseReason = null;
18827
+ lastErrorAt = null;
18828
+ lastErrorMessage = null;
18829
+ lastReconnectScheduledAt = null;
18830
+ lastReconnectDelayMs = null;
18831
+ lastReconnectCause = null;
18832
+ reconnectCount = 0;
18749
18833
  constructor(options) {
18750
18834
  this.url = options.url;
18751
18835
  this.log = options.logger ?? {};
@@ -18777,7 +18861,13 @@ class WebSocketTransport {
18777
18861
  this.cleanUpPreviousConnection();
18778
18862
  this.connectionAttemptNo = this.connectionAttemptNo + 1 >>> 0;
18779
18863
  const delaySeconds = this.getReconnectionDelaySeconds(this.connectionAttemptNo);
18780
- this.log.debug?.("reconnect scheduled", { attempt: this.connectionAttemptNo, delaySeconds });
18864
+ const delayMs = Math.round(delaySeconds * 1000);
18865
+ const cause = options?.cause?.trim() || "unspecified";
18866
+ this.lastReconnectScheduledAt = Date.now();
18867
+ this.lastReconnectDelayMs = delayMs;
18868
+ this.lastReconnectCause = cause;
18869
+ this.reconnectCount += 1;
18870
+ this.log.warn?.(`WebSocket reconnect scheduled (attempt=${this.connectionAttemptNo}, delayMs=${delayMs}, cause=${cause})`);
18781
18871
  this.reconnectionTimer = setTimeout(() => {
18782
18872
  this.reconnectionTimer = null;
18783
18873
  if (this.state === "idle" || this.state === "connected")
@@ -18821,6 +18911,7 @@ class WebSocketTransport {
18821
18911
  async openConnection() {
18822
18912
  if (this.state === "idle")
18823
18913
  return;
18914
+ this.lastConnectStartedAt = Date.now();
18824
18915
  const socket = new import_websocket.default(this.url);
18825
18916
  this.socket = socket;
18826
18917
  socket.on("open", () => {
@@ -18843,6 +18934,8 @@ class WebSocketTransport {
18843
18934
  return;
18844
18935
  this.connectionAttemptNo = 0;
18845
18936
  this.state = "connected";
18937
+ this.lastConnectedAt = Date.now();
18938
+ this.lastDisconnectedAt = null;
18846
18939
  await this.events.send({ type: "connected" });
18847
18940
  }
18848
18941
  async handleMessage(socket, data) {
@@ -18851,6 +18944,7 @@ class WebSocketTransport {
18851
18944
  try {
18852
18945
  const payload = this.coerceBinary(data);
18853
18946
  const message = ServerProtocolMessage.fromBinary(payload);
18947
+ this.lastMessageAt = Date.now();
18854
18948
  await this.events.send({ type: "message", message });
18855
18949
  } catch (error) {
18856
18950
  this.log.error?.("Failed to decode message", error);
@@ -18870,16 +18964,24 @@ class WebSocketTransport {
18870
18964
  return;
18871
18965
  if (this.state === "idle")
18872
18966
  return;
18873
- this.log.warn?.("WebSocket closed", { code, reason: reason.toString("utf8") });
18874
- await this.reconnect();
18967
+ const reasonText = stringifyCloseReason(reason);
18968
+ this.lastDisconnectedAt = Date.now();
18969
+ this.lastCloseCode = code;
18970
+ this.lastCloseReason = reasonText;
18971
+ const connectedForMs = this.lastConnectedAt != null ? Math.max(0, this.lastDisconnectedAt - this.lastConnectedAt) : null;
18972
+ this.log.warn?.(`WebSocket closed (code=${code}, reason=${reasonText || "none"}${connectedForMs != null ? `, connectedForMs=${connectedForMs}` : ""})`);
18973
+ await this.reconnect({ cause: `socket-close:${code}${reasonText ? `:${reasonText}` : ""}` });
18875
18974
  }
18876
18975
  async handleError(socket, error) {
18877
18976
  if (this.socket !== socket)
18878
18977
  return;
18879
18978
  if (this.state === "idle")
18880
18979
  return;
18881
- this.log.error?.("WebSocket error", error);
18882
- await this.reconnect();
18980
+ const summary = summarizeError2(error);
18981
+ this.lastErrorAt = Date.now();
18982
+ this.lastErrorMessage = summary;
18983
+ this.log.error?.(`WebSocket error: ${summary}`);
18984
+ await this.reconnect({ cause: `socket-error:${summary}` });
18883
18985
  }
18884
18986
  async setIdle() {
18885
18987
  if (this.state === "idle")
@@ -18893,6 +18995,46 @@ class WebSocketTransport {
18893
18995
  this.state = "connecting";
18894
18996
  await this.events.send({ type: "connecting" });
18895
18997
  }
18998
+ getDiagnostics() {
18999
+ return {
19000
+ kind: "websocket",
19001
+ url: redactUrlForDiagnostics(this.url),
19002
+ state: this.state,
19003
+ connectionAttemptNo: this.connectionAttemptNo,
19004
+ reconnectCount: this.reconnectCount,
19005
+ lastConnectStartedAt: this.lastConnectStartedAt,
19006
+ lastConnectedAt: this.lastConnectedAt,
19007
+ lastDisconnectedAt: this.lastDisconnectedAt,
19008
+ lastMessageAt: this.lastMessageAt,
19009
+ lastCloseCode: this.lastCloseCode,
19010
+ lastCloseReason: this.lastCloseReason,
19011
+ lastErrorAt: this.lastErrorAt,
19012
+ lastErrorMessage: this.lastErrorMessage,
19013
+ lastReconnectScheduledAt: this.lastReconnectScheduledAt,
19014
+ lastReconnectDelayMs: this.lastReconnectDelayMs,
19015
+ lastReconnectCause: this.lastReconnectCause,
19016
+ socketReadyState: this.socket?.readyState ?? null
19017
+ };
19018
+ }
19019
+ }
19020
+ function stringifyCloseReason(reason) {
19021
+ const text = reason.toString("utf8").trim();
19022
+ return text;
19023
+ }
19024
+ function summarizeError2(error) {
19025
+ if (error instanceof Error) {
19026
+ const code = typeof error.code === "string" ? error.code : null;
19027
+ return code ? `${error.name}: ${error.message} (code=${code})` : `${error.name}: ${error.message}`;
19028
+ }
19029
+ return String(error);
19030
+ }
19031
+ function redactUrlForDiagnostics(raw) {
19032
+ try {
19033
+ const url = new URL(raw);
19034
+ return `${url.protocol}//${url.host}${url.pathname}`;
19035
+ } catch {
19036
+ return raw;
19037
+ }
18896
19038
  }
18897
19039
 
18898
19040
  // ../sdk/dist/sdk/types.js
@@ -19047,6 +19189,7 @@ class InlineSdkClient {
19047
19189
  saveInFlight = null;
19048
19190
  catchUpInFlightByChatId = new Map;
19049
19191
  catchUpRequestedByChatId = new Map;
19192
+ userCatchUpInFlight = null;
19050
19193
  constructor(options) {
19051
19194
  this.options = options;
19052
19195
  this.log = options.logger ?? noopLogger;
@@ -19114,6 +19257,13 @@ class InlineSdkClient {
19114
19257
  await this.flushStateSave();
19115
19258
  await this.protocol.stopTransport();
19116
19259
  }
19260
+ getDiagnostics() {
19261
+ return {
19262
+ started: this.started,
19263
+ baseUrl: this.httpBaseUrl,
19264
+ protocol: this.protocol.getDiagnostics()
19265
+ };
19266
+ }
19117
19267
  rejectOpen(error) {
19118
19268
  this.openRejecter?.(error);
19119
19269
  this.openResolver = null;
@@ -19126,7 +19276,8 @@ class InlineSdkClient {
19126
19276
  return {
19127
19277
  version: 1,
19128
19278
  ...this.state.dateCursor != null ? { dateCursor: this.state.dateCursor } : {},
19129
- ...this.state.lastSeqByChatId != null ? { lastSeqByChatId: { ...this.state.lastSeqByChatId } } : {}
19279
+ ...this.state.lastSeqByChatId != null ? { lastSeqByChatId: { ...this.state.lastSeqByChatId } } : {},
19280
+ ...this.state.lastUserSeq != null ? { lastUserSeq: this.state.lastUserSeq } : {}
19130
19281
  };
19131
19282
  }
19132
19283
  async getMe() {
@@ -19392,6 +19543,7 @@ class InlineSdkClient {
19392
19543
  this.openResolver = null;
19393
19544
  this.openRejecter = null;
19394
19545
  this.initializeDateCursor();
19546
+ this.requestCatchUpUser();
19395
19547
  }
19396
19548
  async initializeDateCursor() {
19397
19549
  const date = this.state.dateCursor ?? nowSeconds();
@@ -19490,6 +19642,10 @@ class InlineSdkClient {
19490
19642
  }
19491
19643
  case "messageActionInvoked": {
19492
19644
  const payload = update.update.messageActionInvoked;
19645
+ if (this.shouldSkipUserSeq(seq)) {
19646
+ return;
19647
+ }
19648
+ this.bumpUserSeq(seq);
19493
19649
  await this.eventStream.send({
19494
19650
  kind: "message.action.invoke",
19495
19651
  interactionId: payload.interactionId,
@@ -19505,6 +19661,10 @@ class InlineSdkClient {
19505
19661
  }
19506
19662
  case "messageActionAnswered": {
19507
19663
  const payload = update.update.messageActionAnswered;
19664
+ if (this.shouldSkipUserSeq(seq)) {
19665
+ return;
19666
+ }
19667
+ this.bumpUserSeq(seq);
19508
19668
  await this.eventStream.send({
19509
19669
  kind: "message.action.answered",
19510
19670
  interactionId: payload.interactionId,
@@ -19551,6 +19711,86 @@ class InlineSdkClient {
19551
19711
  this.scheduleStateSave();
19552
19712
  }
19553
19713
  }
19714
+ shouldSkipUserSeq(seq) {
19715
+ if (!Number.isFinite(seq))
19716
+ return false;
19717
+ const lastUserSeq = this.state.lastUserSeq ?? 0;
19718
+ return seq > 0 && seq <= lastUserSeq;
19719
+ }
19720
+ bumpUserSeq(seq) {
19721
+ if (!Number.isFinite(seq) || seq <= 0)
19722
+ return;
19723
+ const prev = this.state.lastUserSeq ?? 0;
19724
+ if (seq > prev) {
19725
+ this.state.lastUserSeq = seq;
19726
+ this.scheduleStateSave();
19727
+ }
19728
+ }
19729
+ requestCatchUpUser() {
19730
+ const lastUserSeq = this.state.lastUserSeq ?? 0;
19731
+ if (lastUserSeq <= 0) {
19732
+ return;
19733
+ }
19734
+ if (this.userCatchUpInFlight) {
19735
+ return;
19736
+ }
19737
+ this.userCatchUpInFlight = this.doCatchUpUser(lastUserSeq).catch((error) => {
19738
+ this.log.warn?.("GET_UPDATES user catch-up failed; continuing live delivery", {
19739
+ error: extractErrorMessage(error)
19740
+ });
19741
+ }).finally(() => {
19742
+ this.userCatchUpInFlight = null;
19743
+ });
19744
+ }
19745
+ async doCatchUpUser(startSeq) {
19746
+ let cursor = startSeq;
19747
+ while (true) {
19748
+ const result = await this.invoke(Method.GET_UPDATES, {
19749
+ oneofKind: "getUpdates",
19750
+ getUpdates: GetUpdatesInput.create({
19751
+ bucket: UpdateBucket.create({
19752
+ type: {
19753
+ oneofKind: "user",
19754
+ user: {}
19755
+ }
19756
+ }),
19757
+ startSeq: BigInt(cursor),
19758
+ totalLimit: defaultCatchUpTotalLimit
19759
+ })
19760
+ });
19761
+ const payload = result.getUpdates;
19762
+ const deliveredSeq = Number(payload.seq ?? 0n);
19763
+ if (!Number.isSafeInteger(deliveredSeq)) {
19764
+ this.log.warn?.("GET_UPDATES user catch-up returned non-integer seq; aborting", { deliveredSeq });
19765
+ return;
19766
+ }
19767
+ if (payload.resultType === GetUpdatesResult_ResultType.TOO_LONG) {
19768
+ this.log.warn?.("GET_UPDATES user catch-up too long; fast-forwarding cursor", { seq: deliveredSeq });
19769
+ this.bumpUserSeq(deliveredSeq);
19770
+ if (payload.date !== 0n) {
19771
+ this.state.dateCursor = payload.date;
19772
+ }
19773
+ this.scheduleStateSave();
19774
+ return;
19775
+ }
19776
+ for (const update of payload.updates) {
19777
+ await this.handleUpdate(update);
19778
+ }
19779
+ this.bumpUserSeq(deliveredSeq);
19780
+ if (payload.date !== 0n) {
19781
+ this.state.dateCursor = payload.date;
19782
+ }
19783
+ this.scheduleStateSave();
19784
+ if (payload.final) {
19785
+ return;
19786
+ }
19787
+ if (deliveredSeq <= cursor) {
19788
+ this.log.warn?.("GET_UPDATES user catch-up made no progress; aborting", { cursor, deliveredSeq });
19789
+ return;
19790
+ }
19791
+ cursor = deliveredSeq;
19792
+ }
19793
+ }
19554
19794
  requestCatchUpChat(params) {
19555
19795
  const previous = this.catchUpRequestedByChatId.get(params.chatId);
19556
19796
  this.catchUpRequestedByChatId.set(params.chatId, {
@@ -19715,7 +19955,8 @@ class InlineSdkClient {
19715
19955
  const snapshot = {
19716
19956
  version: 1,
19717
19957
  ...this.state.dateCursor != null ? { dateCursor: this.state.dateCursor } : {},
19718
- ...this.state.lastSeqByChatId != null ? { lastSeqByChatId: { ...this.state.lastSeqByChatId } } : {}
19958
+ ...this.state.lastSeqByChatId != null ? { lastSeqByChatId: { ...this.state.lastSeqByChatId } } : {},
19959
+ ...this.state.lastUserSeq != null ? { lastUserSeq: this.state.lastUserSeq } : {}
19719
19960
  };
19720
19961
  this.saveInFlight = store.save(snapshot).catch((error) => {
19721
19962
  this.log.warn?.("Failed to persist SDK state", error);
@@ -34029,9 +34270,21 @@ function looksLikeInlineTargetId(raw, normalizedInput) {
34029
34270
  // src/inline/monitor.ts
34030
34271
  import { mkdir } from "node:fs/promises";
34031
34272
  import path2 from "node:path";
34273
+ import {
34274
+ buildCommandTextFromArgs,
34275
+ findCommandByNativeName,
34276
+ parseCommandArgs,
34277
+ resolveCommandArgMenu
34278
+ } from "openclaw/plugin-sdk/native-command-registry";
34279
+ import {
34280
+ createChannelInboundDebouncer,
34281
+ shouldDebounceTextInbound
34282
+ } from "openclaw/plugin-sdk/channel-inbound";
34283
+ import { resolveDefaultModelForAgent } from "openclaw/plugin-sdk/agent-runtime";
34284
+ import { applyModelOverrideToSessionEntry, updateSessionStore } from "openclaw/plugin-sdk/config-runtime";
34285
+ import { buildModelsProviderData } from "openclaw/plugin-sdk/models-provider-runtime";
34032
34286
 
34033
34287
  // src/sdk-runtime-compat.ts
34034
- import { createMessageToolButtonsSchema } from "openclaw/plugin-sdk/channel-actions";
34035
34288
  var HISTORY_CONTEXT_MARKER = "[Chat messages since your last reply - for context]";
34036
34289
  var CURRENT_MESSAGE_MARKER = "[Current message - respond to this]";
34037
34290
  var MAX_HISTORY_KEYS = 1000;
@@ -34122,14 +34375,6 @@ function recordPendingHistoryEntryIfEnabled(params) {
34122
34375
  limit: params.limit
34123
34376
  });
34124
34377
  }
34125
- var TYPEBOX_OPTIONAL_SYMBOL = Symbol.for("TypeBox.Optional");
34126
- function markTypeBoxOptional(schema) {
34127
- schema[TYPEBOX_OPTIONAL_SYMBOL] = "Optional";
34128
- return schema;
34129
- }
34130
- function createMessageToolButtonsSchemaCompat() {
34131
- return markTypeBoxOptional(createMessageToolButtonsSchema());
34132
- }
34133
34378
  function extensionForMimeCompat(mime) {
34134
34379
  const normalized = mime?.trim().toLowerCase();
34135
34380
  if (!normalized)
@@ -34230,44 +34475,6 @@ async function createChannelReplyPipelineCompat(params) {
34230
34475
  };
34231
34476
  }
34232
34477
  }
34233
- async function loadNativeCommandHelpersCompat() {
34234
- try {
34235
- const sdk = await import("openclaw/plugin-sdk/command-auth");
34236
- const listNativeCommandSpecsForConfig = typeof sdk.listNativeCommandSpecsForConfig === "function" ? sdk.listNativeCommandSpecsForConfig : null;
34237
- const listSkillCommandsForAgents = typeof sdk.listSkillCommandsForAgents === "function" ? sdk.listSkillCommandsForAgents : null;
34238
- if (!listNativeCommandSpecsForConfig || !listSkillCommandsForAgents) {
34239
- throw new Error("command-auth helpers unavailable");
34240
- }
34241
- return {
34242
- available: true,
34243
- listNativeCommandSpecsForConfig,
34244
- listSkillCommandsForAgents
34245
- };
34246
- } catch {
34247
- return {
34248
- available: false,
34249
- listNativeCommandSpecsForConfig: () => [],
34250
- listSkillCommandsForAgents: () => []
34251
- };
34252
- }
34253
- }
34254
- async function loadPluginCommandSpecsCompat(provider) {
34255
- try {
34256
- const sdk = await import("openclaw/plugin-sdk/plugin-runtime");
34257
- if (typeof sdk.getPluginCommandSpecs !== "function") {
34258
- throw new Error("plugin runtime command helper unavailable");
34259
- }
34260
- return {
34261
- available: true,
34262
- specs: sdk.getPluginCommandSpecs(provider)
34263
- };
34264
- } catch {
34265
- return {
34266
- available: false,
34267
- specs: []
34268
- };
34269
- }
34270
- }
34271
34478
 
34272
34479
  // src/inline/message-formatting.ts
34273
34480
  var INLINE_FORMATTING_NOTE = "Inline formatting note: prefer bullet lists over markdown tables. If a table is necessary, render it inside a fenced code block. Do not wrap bare URLs in inline code or backticks. Use plain URLs or markdown links. Use inline code only for actual code, commands, file paths, env vars, or identifiers.";
@@ -34926,246 +35133,28 @@ function summarizeInlineMessageContent(message) {
34926
35133
  };
34927
35134
  }
34928
35135
 
34929
- // src/inline/command-menu-compat.ts
34930
- var THINKING_LEVEL_CHOICES = ["off", "minimal", "low", "medium", "high", "xhigh"];
34931
- var COMPAT_COMMANDS = [
34932
- {
34933
- key: "tts",
34934
- nativeName: "tts",
34935
- args: [
34936
- {
34937
- name: "action",
34938
- description: "TTS action",
34939
- choices: [
34940
- { value: "on", label: "On" },
34941
- { value: "off", label: "Off" },
34942
- { value: "status", label: "Status" },
34943
- { value: "provider", label: "Provider" },
34944
- { value: "limit", label: "Limit" },
34945
- { value: "summary", label: "Summary" },
34946
- { value: "audio", label: "Audio" },
34947
- { value: "help", label: "Help" }
34948
- ]
34949
- },
34950
- { name: "value", description: "Provider, limit, or text" }
34951
- ],
34952
- argsMenu: {
34953
- arg: "action",
34954
- title: "Choose TTS action:"
34955
- }
34956
- },
34957
- {
34958
- key: "session",
34959
- nativeName: "session",
34960
- args: [
34961
- { name: "action", description: "idle | max-age", choices: ["idle", "max-age"] },
34962
- { name: "value", description: "Duration or off" }
34963
- ],
34964
- argsMenu: "auto"
34965
- },
34966
- {
34967
- key: "subagents",
34968
- nativeName: "subagents",
34969
- args: [
34970
- {
34971
- name: "action",
34972
- description: "list | kill | log | info | send | steer | spawn",
34973
- choices: ["list", "kill", "log", "info", "send", "steer", "spawn"]
34974
- },
34975
- { name: "target", description: "Run id, index, or session key" },
34976
- { name: "value", description: "Additional input" }
34977
- ],
34978
- argsMenu: "auto"
34979
- },
34980
- {
34981
- key: "acp",
34982
- nativeName: "acp",
34983
- args: [
34984
- {
34985
- name: "action",
34986
- description: "Action to run",
34987
- choices: [
34988
- "spawn",
34989
- "cancel",
34990
- "steer",
34991
- "close",
34992
- "sessions",
34993
- "status",
34994
- "set-mode",
34995
- "set",
34996
- "cwd",
34997
- "permissions",
34998
- "timeout",
34999
- "model",
35000
- "reset-options",
35001
- "doctor",
35002
- "install",
35003
- "help"
35004
- ]
35005
- },
35006
- { name: "value", description: "Action arguments" }
35007
- ],
35008
- argsMenu: "auto"
35009
- },
35010
- {
35011
- key: "usage",
35012
- nativeName: "usage",
35013
- args: [{ name: "mode", description: "off, tokens, full, or cost", choices: ["off", "tokens", "full", "cost"] }],
35014
- argsMenu: "auto"
35015
- },
35016
- {
35017
- key: "activation",
35018
- nativeName: "activation",
35019
- args: [{ name: "mode", description: "mention or always", choices: ["mention", "always"] }],
35020
- argsMenu: "auto"
35021
- },
35022
- {
35023
- key: "send",
35024
- nativeName: "send",
35025
- args: [{ name: "mode", description: "on, off, or inherit", choices: ["on", "off", "inherit"] }],
35026
- argsMenu: "auto"
35027
- },
35028
- {
35029
- key: "think",
35030
- nativeName: "think",
35031
- args: [{ name: "level", description: "thinking level", choices: [...THINKING_LEVEL_CHOICES] }],
35032
- argsMenu: "auto"
35033
- },
35034
- {
35035
- key: "verbose",
35036
- nativeName: "verbose",
35037
- args: [{ name: "mode", description: "on or off", choices: ["on", "off"] }],
35038
- argsMenu: "auto"
35039
- },
35040
- {
35041
- key: "fast",
35042
- nativeName: "fast",
35043
- args: [{ name: "mode", description: "status, on, or off", choices: ["status", "on", "off"] }],
35044
- argsMenu: "auto"
35045
- },
35046
- {
35047
- key: "reasoning",
35048
- nativeName: "reasoning",
35049
- args: [{ name: "mode", description: "on, off, or stream", choices: ["on", "off", "stream"] }],
35050
- argsMenu: "auto"
35051
- },
35052
- {
35053
- key: "elevated",
35054
- nativeName: "elevated",
35055
- args: [{ name: "mode", description: "on, off, ask, or full", choices: ["on", "off", "ask", "full"] }],
35056
- argsMenu: "auto"
35057
- }
35058
- ];
35059
- function parsePositionalArgs(definitions, raw) {
35060
- const values = {};
35061
- const trimmed = raw.trim();
35062
- if (!trimmed)
35063
- return values;
35064
- const tokens = trimmed.split(/\s+/).filter(Boolean);
35065
- let index = 0;
35066
- for (const definition of definitions) {
35067
- if (index >= tokens.length)
35068
- break;
35069
- const token = tokens[index];
35070
- if (!token)
35071
- break;
35072
- values[definition.name] = token;
35073
- index += 1;
35074
- }
35075
- return values;
35076
- }
35077
- function parseCommandArgs(command, raw) {
35078
- const trimmed = raw?.trim();
35079
- if (!trimmed)
35080
- return;
35081
- if (!command.args || command.argsParsing === "none") {
35082
- return { raw: trimmed };
35083
- }
35084
- return {
35085
- raw: trimmed,
35086
- values: parsePositionalArgs(command.args, trimmed)
35087
- };
35088
- }
35089
- function findCommandByNativeName(name) {
35090
- const normalized = name.trim().toLowerCase();
35091
- return COMPAT_COMMANDS.find((command) => command.nativeName.toLowerCase() === normalized);
35092
- }
35093
- function resolveCommandArgChoices(arg) {
35094
- const raw = arg.choices ?? [];
35095
- return raw.map((choice) => typeof choice === "string" ? { value: choice, label: choice } : choice);
35096
- }
35097
- function resolveCommandArgMenu(params) {
35098
- const { command, args } = params;
35099
- if (!command.args || !command.argsMenu)
35100
- return null;
35101
- if (command.argsParsing === "none")
35102
- return null;
35103
- const argName = command.argsMenu === "auto" ? command.args.find((arg2) => resolveCommandArgChoices(arg2).length > 0)?.name : command.argsMenu.arg;
35104
- if (!argName)
35105
- return null;
35106
- if (args?.values && args.values[argName] != null)
35107
- return null;
35108
- if (args?.raw && !args.values)
35109
- return null;
35110
- const arg = command.args.find((entry) => entry.name === argName);
35111
- if (!arg)
35112
- return null;
35113
- const choices = resolveCommandArgChoices(arg);
35114
- if (choices.length === 0)
35115
- return null;
35116
- const title = command.argsMenu !== "auto" ? command.argsMenu.title : undefined;
35117
- return {
35118
- arg,
35119
- choices,
35120
- ...title ? { title } : {}
35121
- };
35122
- }
35123
- function buildCommandTextFromArgs(command, args) {
35124
- const values = args?.values ?? {};
35125
- const argDefs = command.args ?? [];
35126
- const renderedArgs = [];
35127
- for (const argDef of argDefs) {
35128
- const value = values[argDef.name];
35129
- if (value == null)
35130
- continue;
35131
- const normalized = typeof value === "string" ? value.trim() : String(value);
35132
- if (!normalized)
35133
- continue;
35134
- renderedArgs.push(normalized);
35136
+ // src/inline/monitor.ts
35137
+ var CHANNEL_ID = "inline";
35138
+ function summarizeSdkMeta(meta3) {
35139
+ if (meta3 == null)
35140
+ return "";
35141
+ if (meta3 instanceof Error)
35142
+ return `${meta3.name}: ${meta3.message}`;
35143
+ if (typeof meta3 === "string")
35144
+ return meta3;
35145
+ try {
35146
+ const json2 = JSON.stringify(meta3);
35147
+ return json2 === undefined ? String(meta3) : json2;
35148
+ } catch {
35149
+ return String(meta3);
35135
35150
  }
35136
- return renderedArgs.length > 0 ? `/${command.nativeName} ${renderedArgs.join(" ")}` : `/${command.nativeName}`;
35137
35151
  }
35138
- function resolveInlineCompatNativeCommandMenu(commandBody) {
35139
- const normalized = commandBody.trim();
35140
- const match = normalized.match(/^\/([^\s]+)(?:\s+([\s\S]+))?$/);
35141
- if (!match?.[1])
35142
- return null;
35143
- const command = findCommandByNativeName(match[1]);
35144
- if (!command)
35145
- return null;
35146
- const args = parseCommandArgs(command, match[2]);
35147
- const menu = resolveCommandArgMenu({
35148
- command,
35149
- ...args ? { args } : {}
35150
- });
35151
- if (!menu)
35152
- return null;
35153
- const title = menu.title ?? `Choose ${menu.arg.description || menu.arg.name} for /${command.nativeName}.`;
35154
- const rows = [];
35155
- for (let index = 0;index < menu.choices.length; index += 2) {
35156
- const slice = menu.choices.slice(index, index + 2);
35157
- rows.push(slice.map((choice) => ({
35158
- text: choice.label,
35159
- callback_data: buildCommandTextFromArgs(command, {
35160
- values: { [menu.arg.name]: choice.value }
35161
- })
35162
- })));
35163
- }
35164
- return { title, buttons: rows };
35152
+ function formatSdkLogLine(message, meta3) {
35153
+ const detail = summarizeSdkMeta(meta3);
35154
+ if (!detail)
35155
+ return message;
35156
+ return `${message} ${detail}`;
35165
35157
  }
35166
-
35167
- // src/inline/monitor.ts
35168
- var CHANNEL_ID = "inline";
35169
35158
  var DEFAULT_DM_HISTORY_LIMIT = 6;
35170
35159
  var HISTORY_LINE_MAX_CHARS = 280;
35171
35160
  var URL_LIKE_PATTERN = /https?:\/\/\S+/i;
@@ -35228,35 +35217,137 @@ function callbackDataToUtf8(data) {
35228
35217
  return;
35229
35218
  }
35230
35219
  }
35220
+ function buildInlineInboundMessageSid(params) {
35221
+ if (params.callbackActionEvent) {
35222
+ return `callback:${String(params.callbackActionEvent.targetMessageId)}:${String(params.callbackActionEvent.interactionId)}`;
35223
+ }
35224
+ return String(params.msgId);
35225
+ }
35226
+ function buildInlineDebounceKey(params) {
35227
+ if (params.senderId == null)
35228
+ return null;
35229
+ return `inline:${params.accountId}:${String(params.chatId)}:${String(params.senderId)}`;
35230
+ }
35231
+ function buildSyntheticInlineTextMessage(params) {
35232
+ return {
35233
+ ...params.base,
35234
+ message: params.text,
35235
+ ...params.mentioned !== undefined ? { mentioned: params.mentioned } : {}
35236
+ };
35237
+ }
35231
35238
  var INLINE_ACTION_MAX_ROWS = 8;
35232
35239
  var INLINE_ACTION_MAX_PER_ROW = 8;
35233
35240
  function isRecord3(value) {
35234
35241
  return typeof value === "object" && value !== null;
35235
35242
  }
35243
+ function resolveInlineNativeCommandMenu(params) {
35244
+ const normalized = params.commandBody.trim();
35245
+ const match = normalized.match(/^\/([^\s]+)(?:\s+([\s\S]+))?$/);
35246
+ if (!match?.[1])
35247
+ return null;
35248
+ const command = findCommandByNativeName(match[1], "telegram");
35249
+ if (!command)
35250
+ return null;
35251
+ const args = parseCommandArgs(command, match[2]);
35252
+ const menu = resolveCommandArgMenu({
35253
+ command,
35254
+ ...args ? { args } : {},
35255
+ cfg: params.cfg
35256
+ });
35257
+ if (!menu)
35258
+ return null;
35259
+ const title = menu.title ?? `Choose ${menu.arg.description || menu.arg.name} for /${command.nativeName}.`;
35260
+ const rows = [];
35261
+ for (let index = 0;index < menu.choices.length; index += 2) {
35262
+ const slice = menu.choices.slice(index, index + 2);
35263
+ rows.push(slice.map((choice) => ({
35264
+ text: choice.label,
35265
+ callback_data: buildCommandTextFromArgs(command, {
35266
+ values: { [menu.arg.name]: choice.value }
35267
+ })
35268
+ })));
35269
+ }
35270
+ return { title, buttons: rows };
35271
+ }
35236
35272
  function mapInlineModelPickerCallbackToCommand(raw) {
35237
- const trimmed = raw.trim();
35238
- if (!trimmed)
35273
+ const callback = parseInlineModelPickerCallback(raw);
35274
+ if (!callback)
35239
35275
  return;
35240
- if (trimmed === "mdl_prov" || trimmed === "mdl_back") {
35241
- return "/models";
35276
+ switch (callback.type) {
35277
+ case "providers":
35278
+ case "back":
35279
+ return "/models";
35280
+ case "list":
35281
+ return `/models ${callback.provider} ${String(callback.page)}`;
35282
+ case "select":
35283
+ return callback.provider ? `/model ${callback.provider}/${callback.model}` : `/model ${callback.model}`;
35242
35284
  }
35285
+ }
35286
+ function parseInlineModelPickerCallback(raw) {
35287
+ const trimmed = raw.trim();
35288
+ if (!trimmed)
35289
+ return null;
35290
+ if (trimmed === "mdl_prov")
35291
+ return { type: "providers" };
35292
+ if (trimmed === "mdl_back")
35293
+ return { type: "back" };
35243
35294
  const listMatch = trimmed.match(/^mdl_list_([a-z0-9_-]+)_(\d+)$/i);
35244
35295
  if (listMatch?.[1] && listMatch[2]) {
35245
35296
  const provider = listMatch[1].trim();
35246
35297
  const page = Number.parseInt(listMatch[2], 10);
35247
35298
  if (provider && Number.isFinite(page) && page > 0) {
35248
- return `/models ${provider} ${String(page)}`;
35299
+ return { type: "list", provider, page };
35249
35300
  }
35250
35301
  }
35251
35302
  const standardSelectionMatch = trimmed.match(/^mdl_sel_(.+)$/);
35252
35303
  if (standardSelectionMatch?.[1]?.trim()) {
35253
- return `/model ${standardSelectionMatch[1].trim()}`;
35304
+ const modelRef = standardSelectionMatch[1].trim();
35305
+ const slashIndex = modelRef.indexOf("/");
35306
+ if (slashIndex > 0 && slashIndex < modelRef.length - 1) {
35307
+ return {
35308
+ type: "select",
35309
+ provider: modelRef.slice(0, slashIndex),
35310
+ model: modelRef.slice(slashIndex + 1)
35311
+ };
35312
+ }
35254
35313
  }
35255
35314
  const compactSelectionMatch = trimmed.match(/^mdl_sel\/(.+)$/);
35256
35315
  if (compactSelectionMatch?.[1]?.trim()) {
35257
- return `/model ${compactSelectionMatch[1].trim()}`;
35316
+ return { type: "select", model: compactSelectionMatch[1].trim() };
35258
35317
  }
35259
- return;
35318
+ return null;
35319
+ }
35320
+ function resolveInlineModelPickerSelection(params) {
35321
+ if (params.callback.provider) {
35322
+ return {
35323
+ kind: "resolved",
35324
+ provider: params.callback.provider,
35325
+ model: params.callback.model
35326
+ };
35327
+ }
35328
+ const matchingProviders = params.providers.filter((id) => params.byProvider.get(id)?.has(params.callback.model));
35329
+ if (matchingProviders.length === 1 && matchingProviders[0]) {
35330
+ return {
35331
+ kind: "resolved",
35332
+ provider: matchingProviders[0],
35333
+ model: params.callback.model
35334
+ };
35335
+ }
35336
+ return {
35337
+ kind: "ambiguous",
35338
+ model: params.callback.model
35339
+ };
35340
+ }
35341
+ function buildInlineModelProviderButtons(providers) {
35342
+ const rows = [];
35343
+ for (let index = 0;index < providers.length; index += 2) {
35344
+ const slice = providers.slice(index, index + 2);
35345
+ rows.push(slice.map((provider) => ({
35346
+ text: `${provider.id} (${provider.count})`,
35347
+ callback_data: `mdl_list_${provider.id}_1`
35348
+ })));
35349
+ }
35350
+ return rows;
35260
35351
  }
35261
35352
  function normalizeInlineActionCallbackData(raw) {
35262
35353
  const trimmed = raw.trim();
@@ -35264,6 +35355,14 @@ function normalizeInlineActionCallbackData(raw) {
35264
35355
  return "";
35265
35356
  return mapInlineModelPickerCallbackToCommand(trimmed) ?? trimmed;
35266
35357
  }
35358
+ function normalizeInlineTelegramButtonCallbackData(raw) {
35359
+ const trimmed = raw.trim();
35360
+ if (!trimmed)
35361
+ return "";
35362
+ if (parseInlineModelPickerCallback(trimmed))
35363
+ return trimmed;
35364
+ return normalizeInlineActionCallbackData(trimmed);
35365
+ }
35267
35366
  function normalizeReplyMarkupButtonsWith(raw, options) {
35268
35367
  if (!Array.isArray(raw))
35269
35368
  return [];
@@ -35305,7 +35404,7 @@ function resolveInlineReplyActions(payload) {
35305
35404
  } else if (telegramData && Object.prototype.hasOwnProperty.call(telegramData, "buttons")) {
35306
35405
  rawButtons = telegramData.buttons;
35307
35406
  hasExplicitButtons = true;
35308
- mapCallbackData = normalizeInlineActionCallbackData;
35407
+ mapCallbackData = normalizeInlineTelegramButtonCallbackData;
35309
35408
  } else if (Object.prototype.hasOwnProperty.call(payload, "buttons")) {
35310
35409
  rawButtons = payload.buttons;
35311
35410
  hasExplicitButtons = true;
@@ -35870,19 +35969,35 @@ async function monitorInlineProvider(params) {
35870
35969
  const stateDir = core3.state.resolveStateDir();
35871
35970
  const statePath = path2.join(stateDir, "channels", "inline", `${account.accountId}.json`);
35872
35971
  await mkdir(path2.dirname(statePath), { recursive: true });
35972
+ let client = null;
35973
+ const pushDiagnostics = (patch) => {
35974
+ statusSink?.({
35975
+ ...patch ?? {},
35976
+ ...client ? { diagnostics: client.getDiagnostics() } : {}
35977
+ });
35978
+ };
35873
35979
  const sdkLog = {
35874
- debug: (msg) => log?.debug?.(msg),
35875
- info: (msg) => log?.info(msg),
35876
- warn: (msg) => log?.warn(msg),
35877
- error: (msg) => log?.error(msg)
35980
+ debug: (msg, meta3) => log?.debug?.(formatSdkLogLine(msg, meta3)),
35981
+ info: (msg, meta3) => log?.info(formatSdkLogLine(msg, meta3)),
35982
+ warn: (msg, meta3) => {
35983
+ const line = formatSdkLogLine(msg, meta3);
35984
+ log?.warn(line);
35985
+ pushDiagnostics({ lastError: line });
35986
+ },
35987
+ error: (msg, meta3) => {
35988
+ const line = formatSdkLogLine(msg, meta3);
35989
+ log?.error(line);
35990
+ pushDiagnostics({ lastError: line });
35991
+ }
35878
35992
  };
35879
- const client = new InlineSdkClient({
35993
+ client = new InlineSdkClient({
35880
35994
  baseUrl: account.baseUrl,
35881
35995
  token,
35882
35996
  logger: sdkLog,
35883
35997
  state: new JsonFileStateStore(statePath)
35884
35998
  });
35885
35999
  await client.connect(abortSignal);
36000
+ pushDiagnostics();
35886
36001
  const meResult = await client.invokeRaw(Method.GET_ME, {
35887
36002
  oneofKind: "getMe",
35888
36003
  getMe: {}
@@ -35893,6 +36008,7 @@ async function monitorInlineProvider(params) {
35893
36008
  const meId = meResult.getMe.user.id;
35894
36009
  const botUsername = normalizeInlineUsername(meResult.getMe.user.username)?.toLowerCase();
35895
36010
  log?.info(`[${account.accountId}] inline connected (me=${String(meId)})`);
36011
+ pushDiagnostics();
35896
36012
  const chatCache = new Map;
35897
36013
  const senderProfilesById = new Map;
35898
36014
  const botMessageIdsByChat = new Map;
@@ -35937,852 +36053,1078 @@ async function monitorInlineProvider(params) {
35937
36053
  participantFetches.set(chatKey, run);
35938
36054
  await run;
35939
36055
  };
35940
- const loop = (async () => {
36056
+ const handleInboundNow = async (input) => {
36057
+ const chatId = input.chatId;
36058
+ const msg = input.msg;
36059
+ const rawBodyOverride = input.rawBodyOverride ?? null;
36060
+ const reactionEvent = input.reactionEvent ?? null;
36061
+ const callbackActionEvent = input.callbackActionEvent ?? null;
36062
+ let rawBody = "";
36063
+ let currentContent = null;
36064
+ let currentAttachmentText = null;
36065
+ let currentEntityText = null;
36066
+ if (!reactionEvent && !callbackActionEvent) {
36067
+ if (rawBodyOverride != null) {
36068
+ rawBody = rawBodyOverride.trim();
36069
+ } else {
36070
+ currentContent = summarizeInlineMessageContent(msg);
36071
+ rawBody = buildInlineInboundBodyText(currentContent);
36072
+ currentAttachmentText = currentContent.attachmentText || null;
36073
+ currentEntityText = currentContent.entityText || null;
36074
+ }
36075
+ if (!rawBody)
36076
+ return;
36077
+ }
36078
+ statusSink?.({ lastInboundAt: Date.now() });
36079
+ let chatInfo;
35941
36080
  try {
35942
- for await (const event of client.events()) {
35943
- if (abortSignal.aborted)
35944
- break;
35945
- const rawEvent = event;
35946
- let msg;
35947
- let rawBody = "";
35948
- let currentContent = null;
35949
- let currentAttachmentText = null;
35950
- let currentEntityText = null;
35951
- let reactionEvent = null;
35952
- let inboundChatId = null;
35953
- let callbackActionEvent = null;
35954
- if (event.kind === "message.new") {
35955
- inboundChatId = event.chatId;
35956
- msg = {
35957
- ...event.message,
35958
- chatId: event.chatId
35959
- };
35960
- currentContent = summarizeInlineMessageContent(msg);
35961
- rawBody = buildInlineInboundBodyText(currentContent);
35962
- currentAttachmentText = currentContent.attachmentText || null;
35963
- currentEntityText = currentContent.entityText || null;
35964
- if (!rawBody)
35965
- continue;
35966
- if (msg.out || msg.fromId === meId)
35967
- continue;
35968
- } else if (event.kind === "reaction.add") {
35969
- inboundChatId = event.chatId;
35970
- if (event.reaction.userId === meId)
35971
- continue;
35972
- const onBotMessage = await isReactionTargetBotMessage({
35973
- client,
35974
- chatId: event.chatId,
35975
- messageId: event.reaction.messageId,
35976
- meId,
35977
- botMessageIdsByChat
35978
- }).catch((err) => {
35979
- statusSink?.({ lastError: `getChatHistory (reaction target) failed: ${String(err)}` });
35980
- return false;
35981
- });
35982
- if (!onBotMessage)
35983
- continue;
35984
- reactionEvent = {
35985
- action: "added",
35986
- emoji: event.reaction.emoji,
35987
- targetMessageId: event.reaction.messageId
35988
- };
35989
- msg = {
35990
- id: event.reaction.messageId,
35991
- chatId: event.chatId,
35992
- date: event.date,
35993
- fromId: event.reaction.userId,
35994
- message: "",
35995
- out: false,
35996
- mentioned: false,
35997
- replyToMsgId: event.reaction.messageId
35998
- };
35999
- } else if (event.kind === "reaction.delete") {
36000
- inboundChatId = event.chatId;
36001
- if (event.userId === meId)
36002
- continue;
36003
- const onBotMessage = await isReactionTargetBotMessage({
36004
- client,
36005
- chatId: event.chatId,
36006
- messageId: event.messageId,
36007
- meId,
36008
- botMessageIdsByChat
36009
- }).catch((err) => {
36010
- statusSink?.({ lastError: `getChatHistory (reaction target) failed: ${String(err)}` });
36011
- return false;
36012
- });
36013
- if (!onBotMessage)
36014
- continue;
36015
- reactionEvent = {
36016
- action: "removed",
36017
- emoji: event.emoji,
36018
- targetMessageId: event.messageId
36019
- };
36020
- msg = {
36021
- id: event.messageId,
36022
- chatId: event.chatId,
36023
- date: event.date,
36024
- fromId: event.userId,
36025
- message: "",
36026
- out: false,
36027
- mentioned: false,
36028
- replyToMsgId: event.messageId
36029
- };
36030
- } else if (rawEvent["kind"] === "message.action.invoke") {
36031
- const actorUserId = rawEvent["actorUserId"];
36032
- const interactionId = rawEvent["interactionId"];
36033
- const actionId = rawEvent["actionId"];
36034
- const targetMessageId = rawEvent["messageId"];
36035
- const data = rawEvent["data"];
36036
- const eventChatId = rawEvent["chatId"];
36037
- const eventDate = rawEvent["date"];
36038
- if (!actorUserId || !interactionId || !actionId || !targetMessageId || !eventChatId || !eventDate || !data) {
36039
- continue;
36040
- }
36041
- inboundChatId = eventChatId;
36042
- if (actorUserId === meId)
36043
- continue;
36044
- callbackActionEvent = {
36045
- interactionId,
36046
- actionId,
36047
- targetMessageId,
36048
- data
36049
- };
36050
- msg = {
36051
- id: targetMessageId,
36052
- chatId: eventChatId,
36053
- date: eventDate,
36054
- fromId: actorUserId,
36055
- message: "",
36056
- out: false,
36057
- mentioned: false,
36058
- replyToMsgId: targetMessageId
36059
- };
36060
- } else {
36061
- continue;
36062
- }
36063
- if (!inboundChatId)
36064
- continue;
36065
- const chatId = inboundChatId;
36066
- statusSink?.({ lastInboundAt: Date.now() });
36067
- let chatInfo;
36068
- try {
36069
- chatInfo = await resolveChatInfo(client, chatCache, chatId);
36070
- } catch (err) {
36071
- chatInfo = { kind: "group", title: null };
36072
- statusSink?.({ lastError: `getChat failed: ${String(err)}` });
36073
- }
36074
- const isGroup = chatInfo.kind !== "direct";
36075
- const replyThreadsEnabled = account.config.capabilities?.replyThreads === true || isInlineReplyThreadsEnabled({ cfg, accountId: account.accountId });
36076
- const replyThreadContext = await resolveInlineInboundReplyThreadContext({
36077
- replyThreadsEnabled,
36078
- client,
36079
- chatId,
36080
- chatInfo,
36081
- chatCache
36082
- }).catch((err) => {
36083
- statusSink?.({ lastError: `getChat (reply thread) failed: ${String(err)}` });
36084
- return null;
36085
- });
36086
- const effectiveChatId = replyThreadContext?.parentChatId ?? chatId;
36087
- const effectiveGroupTitle = replyThreadContext?.parentChatTitle ?? chatInfo.title ?? null;
36088
- const senderId = String(msg.fromId);
36089
- await hydrateChatParticipants(chatId);
36090
- const senderProfile = senderProfilesById.get(senderId);
36091
- const senderUsername = senderProfile?.username;
36092
- const senderName = senderProfile?.name ?? (!isGroup ? chatInfo.title ?? undefined : undefined);
36093
- if (reactionEvent) {
36094
- const actor = senderUsername != null && senderUsername.length > 0 ? `@${senderUsername}` : senderName ?? `user:${senderId}`;
36095
- const emoji3 = reactionEvent.emoji.trim() || "a reaction";
36096
- const messageId = String(reactionEvent.targetMessageId);
36097
- if (reactionEvent.action === "added") {
36098
- rawBody = `${actor} reacted with ${emoji3} to your message #${messageId}`;
36099
- } else {
36100
- rawBody = `${actor} removed ${emoji3} from your message #${messageId}`;
36101
- }
36102
- } else if (callbackActionEvent) {
36103
- const actor = senderUsername != null && senderUsername.length > 0 ? `@${senderUsername}` : senderName ?? `user:${senderId}`;
36104
- const payload = {
36105
- type: "inline_message_action_callback",
36106
- interaction_id: String(callbackActionEvent.interactionId),
36107
- actor_user_id: senderId,
36108
- chat_id: String(chatId),
36109
- message_id: String(callbackActionEvent.targetMessageId),
36110
- action_id: callbackActionEvent.actionId,
36111
- data_base64: callbackDataToBase64(callbackActionEvent.data),
36112
- data_utf8: callbackDataToUtf8(callbackActionEvent.data) ?? null
36113
- };
36114
- rawBody = `${actor} pressed a button on message #${String(callbackActionEvent.targetMessageId)}
36081
+ chatInfo = await resolveChatInfo(client, chatCache, chatId);
36082
+ } catch (err) {
36083
+ chatInfo = { kind: "group", title: null };
36084
+ statusSink?.({ lastError: `getChat failed: ${String(err)}` });
36085
+ }
36086
+ const isGroup = chatInfo.kind !== "direct";
36087
+ const replyThreadsEnabled = account.config.capabilities?.replyThreads === true || isInlineReplyThreadsEnabled({ cfg, accountId: account.accountId });
36088
+ const replyThreadContext = await resolveInlineInboundReplyThreadContext({
36089
+ replyThreadsEnabled,
36090
+ client,
36091
+ chatId,
36092
+ chatInfo,
36093
+ chatCache
36094
+ }).catch((err) => {
36095
+ statusSink?.({ lastError: `getChat (reply thread) failed: ${String(err)}` });
36096
+ return null;
36097
+ });
36098
+ const effectiveChatId = replyThreadContext?.parentChatId ?? chatId;
36099
+ const effectiveGroupTitle = replyThreadContext?.parentChatTitle ?? chatInfo.title ?? null;
36100
+ const senderId = String(msg.fromId);
36101
+ await hydrateChatParticipants(chatId);
36102
+ const senderProfile = senderProfilesById.get(senderId);
36103
+ const senderUsername = senderProfile?.username;
36104
+ const senderName = senderProfile?.name ?? (!isGroup ? chatInfo.title ?? undefined : undefined);
36105
+ if (reactionEvent) {
36106
+ const actor = senderUsername != null && senderUsername.length > 0 ? `@${senderUsername}` : senderName ?? `user:${senderId}`;
36107
+ const emoji3 = reactionEvent.emoji.trim() || "a reaction";
36108
+ const messageId = String(reactionEvent.targetMessageId);
36109
+ if (reactionEvent.action === "added") {
36110
+ rawBody = `${actor} reacted with ${emoji3} to your message #${messageId}`;
36111
+ } else {
36112
+ rawBody = `${actor} removed ${emoji3} from your message #${messageId}`;
36113
+ }
36114
+ } else if (callbackActionEvent) {
36115
+ const actor = senderUsername != null && senderUsername.length > 0 ? `@${senderUsername}` : senderName ?? `user:${senderId}`;
36116
+ const payload = {
36117
+ type: "inline_message_action_callback",
36118
+ interaction_id: String(callbackActionEvent.interactionId),
36119
+ actor_user_id: senderId,
36120
+ chat_id: String(chatId),
36121
+ message_id: String(callbackActionEvent.targetMessageId),
36122
+ action_id: callbackActionEvent.actionId,
36123
+ data_base64: callbackDataToBase64(callbackActionEvent.data),
36124
+ data_utf8: callbackDataToUtf8(callbackActionEvent.data) ?? null
36125
+ };
36126
+ rawBody = `${actor} pressed a button on message #${String(callbackActionEvent.targetMessageId)}
36115
36127
  ${JSON.stringify(payload)}`;
36116
- }
36117
- const dmPolicy = account.config.dmPolicy ?? "pairing";
36118
- const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
36119
- const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
36120
- const configAllowFrom = normalizeAllowlist(account.config.allowFrom);
36121
- const configGroupAllowFrom = normalizeAllowlist(account.config.groupAllowFrom);
36122
- const storeAllowFrom = await core3.channel.pairing.readAllowFromStore({
36123
- channel: CHANNEL_ID,
36124
- accountId: account.accountId
36125
- }).catch(() => []);
36126
- const storeAllowList = normalizeAllowlist(storeAllowFrom);
36127
- const effectiveAllowFrom = [...configAllowFrom, ...storeAllowList].filter(Boolean);
36128
- const effectiveGroupAllowFrom = [
36129
- ...configGroupAllowFrom.length > 0 ? configGroupAllowFrom : configAllowFrom,
36130
- ...storeAllowList
36131
- ].filter(Boolean);
36132
- const callbackCommandBody = callbackActionEvent ? resolveCallbackCommandBodyFromActionData({
36133
- data: callbackActionEvent.data,
36134
- ...botUsername ? { botUsername } : {}
36135
- }) : undefined;
36136
- let callbackActionAnswered = false;
36137
- const answerCallbackIfNeeded = async () => {
36138
- if (!callbackActionEvent || callbackActionAnswered)
36139
- return;
36140
- await answerInlineMessageAction(client, callbackActionEvent.interactionId);
36141
- callbackActionAnswered = true;
36142
- };
36143
- const shouldEditCallbackTargetInPlace = false;
36144
- const normalizedCommandBody = callbackCommandBody ?? normalizeInlineCommandBody(rawBody, botUsername);
36145
- const allowTextCommands = core3.channel.commands.shouldHandleTextCommands({
36146
- cfg,
36147
- surface: CHANNEL_ID
36128
+ }
36129
+ const dmPolicy = account.config.dmPolicy ?? "pairing";
36130
+ const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
36131
+ const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
36132
+ const configAllowFrom = normalizeAllowlist(account.config.allowFrom);
36133
+ const configGroupAllowFrom = normalizeAllowlist(account.config.groupAllowFrom);
36134
+ const storeAllowFrom = await core3.channel.pairing.readAllowFromStore({
36135
+ channel: CHANNEL_ID,
36136
+ accountId: account.accountId
36137
+ }).catch(() => []);
36138
+ const storeAllowList = normalizeAllowlist(storeAllowFrom);
36139
+ const effectiveAllowFrom = [...configAllowFrom, ...storeAllowList].filter(Boolean);
36140
+ const effectiveGroupAllowFrom = [
36141
+ ...configGroupAllowFrom.length > 0 ? configGroupAllowFrom : configAllowFrom,
36142
+ ...storeAllowList
36143
+ ].filter(Boolean);
36144
+ const callbackCommandBody = callbackActionEvent ? resolveCallbackCommandBodyFromActionData({
36145
+ data: callbackActionEvent.data,
36146
+ ...botUsername ? { botUsername } : {}
36147
+ }) : undefined;
36148
+ let callbackActionAnswered = false;
36149
+ const answerCallbackIfNeeded = async () => {
36150
+ if (!callbackActionEvent || callbackActionAnswered)
36151
+ return;
36152
+ await answerInlineMessageAction(client, callbackActionEvent.interactionId);
36153
+ callbackActionAnswered = true;
36154
+ };
36155
+ if (callbackActionEvent) {
36156
+ await answerCallbackIfNeeded().catch((error48) => {
36157
+ runtime2.error?.(`inline callback answer failed: ${String(error48)}`);
36158
+ });
36159
+ }
36160
+ const shouldEditCallbackTargetInPlace = callbackActionEvent != null;
36161
+ const normalizedCommandBody = callbackCommandBody ?? normalizeInlineCommandBody(rawBody, botUsername);
36162
+ const allowTextCommands = core3.channel.commands.shouldHandleTextCommands({
36163
+ cfg,
36164
+ surface: CHANNEL_ID
36165
+ });
36166
+ const useAccessGroups = cfg.commands?.useAccessGroups !== false;
36167
+ const allowForCommands = isGroup ? effectiveGroupAllowFrom : effectiveAllowFrom;
36168
+ const senderAllowedForCommands = allowlistMatch({ allowFrom: allowForCommands, senderId });
36169
+ const hasControlCommand = core3.channel.text.hasControlCommand(callbackCommandBody ?? rawBody, cfg, botUsername ? { botUsername } : undefined);
36170
+ const commandGate = resolveControlCommandGate({
36171
+ useAccessGroups,
36172
+ authorizers: [{ configured: allowForCommands.length > 0, allowed: senderAllowedForCommands }],
36173
+ allowTextCommands,
36174
+ hasControlCommand
36175
+ });
36176
+ const commandAuthorized = commandGate.commandAuthorized;
36177
+ if (isGroup) {
36178
+ if (groupPolicy === "disabled") {
36179
+ log?.info(`[${account.accountId}] inline: drop group chat=${String(chatId)} (groupPolicy=disabled)`);
36180
+ await answerCallbackIfNeeded().catch((error48) => {
36181
+ runtime2.error?.(`inline callback answer failed: ${String(error48)}`);
36148
36182
  });
36149
- const useAccessGroups = cfg.commands?.useAccessGroups !== false;
36150
- const allowForCommands = isGroup ? effectiveGroupAllowFrom : effectiveAllowFrom;
36151
- const senderAllowedForCommands = allowlistMatch({ allowFrom: allowForCommands, senderId });
36152
- const hasControlCommand = core3.channel.text.hasControlCommand(callbackCommandBody ?? rawBody, cfg, botUsername ? { botUsername } : undefined);
36153
- const commandGate = resolveControlCommandGate({
36154
- useAccessGroups,
36155
- authorizers: [{ configured: allowForCommands.length > 0, allowed: senderAllowedForCommands }],
36156
- allowTextCommands,
36157
- hasControlCommand
36183
+ return;
36184
+ }
36185
+ if (groupPolicy === "allowlist") {
36186
+ const allowed = allowlistMatch({ allowFrom: effectiveGroupAllowFrom, senderId });
36187
+ if (!allowed) {
36188
+ log?.info(`[${account.accountId}] inline: drop group sender=${senderId} (groupPolicy=allowlist)`);
36189
+ await answerCallbackIfNeeded().catch((error48) => {
36190
+ runtime2.error?.(`inline callback answer failed: ${String(error48)}`);
36191
+ });
36192
+ return;
36193
+ }
36194
+ }
36195
+ } else {
36196
+ if (dmPolicy === "disabled") {
36197
+ log?.info(`[${account.accountId}] inline: drop DM sender=${senderId} (dmPolicy=disabled)`);
36198
+ await answerCallbackIfNeeded().catch((error48) => {
36199
+ runtime2.error?.(`inline callback answer failed: ${String(error48)}`);
36158
36200
  });
36159
- const commandAuthorized = commandGate.commandAuthorized;
36160
- if (isGroup) {
36161
- if (groupPolicy === "disabled") {
36162
- log?.info(`[${account.accountId}] inline: drop group chat=${String(chatId)} (groupPolicy=disabled)`);
36163
- await answerCallbackIfNeeded().catch((error48) => {
36164
- runtime2.error?.(`inline callback answer failed: ${String(error48)}`);
36165
- });
36166
- continue;
36167
- }
36168
- if (groupPolicy === "allowlist") {
36169
- const allowed = allowlistMatch({ allowFrom: effectiveGroupAllowFrom, senderId });
36170
- if (!allowed) {
36171
- log?.info(`[${account.accountId}] inline: drop group sender=${senderId} (groupPolicy=allowlist)`);
36172
- await answerCallbackIfNeeded().catch((error48) => {
36173
- runtime2.error?.(`inline callback answer failed: ${String(error48)}`);
36174
- });
36175
- continue;
36176
- }
36177
- }
36178
- } else {
36179
- if (dmPolicy === "disabled") {
36180
- log?.info(`[${account.accountId}] inline: drop DM sender=${senderId} (dmPolicy=disabled)`);
36181
- await answerCallbackIfNeeded().catch((error48) => {
36182
- runtime2.error?.(`inline callback answer failed: ${String(error48)}`);
36201
+ return;
36202
+ }
36203
+ if (dmPolicy !== "open") {
36204
+ const allowed = allowlistMatch({ allowFrom: effectiveAllowFrom, senderId });
36205
+ if (!allowed) {
36206
+ if (dmPolicy === "pairing") {
36207
+ const { code, created } = await core3.channel.pairing.upsertPairingRequest({
36208
+ channel: CHANNEL_ID,
36209
+ id: senderId,
36210
+ accountId: account.accountId,
36211
+ meta: {},
36212
+ pairingAdapter: { idLabel: "inlineUserId", normalizeAllowEntry }
36183
36213
  });
36184
- continue;
36185
- }
36186
- if (dmPolicy !== "open") {
36187
- const allowed = allowlistMatch({ allowFrom: effectiveAllowFrom, senderId });
36188
- if (!allowed) {
36189
- if (dmPolicy === "pairing") {
36190
- const { code, created } = await core3.channel.pairing.upsertPairingRequest({
36191
- channel: CHANNEL_ID,
36192
- id: senderId,
36193
- accountId: account.accountId,
36194
- meta: {},
36195
- pairingAdapter: { idLabel: "inlineUserId", normalizeAllowEntry }
36214
+ if (created) {
36215
+ try {
36216
+ await client.sendMessage({
36217
+ chatId,
36218
+ text: core3.channel.pairing.buildPairingReply({
36219
+ channel: CHANNEL_ID,
36220
+ idLine: `Your Inline user id: ${senderId}`,
36221
+ code
36222
+ })
36196
36223
  });
36197
- if (created) {
36198
- try {
36199
- await client.sendMessage({
36200
- chatId,
36201
- text: core3.channel.pairing.buildPairingReply({
36202
- channel: CHANNEL_ID,
36203
- idLine: `Your Inline user id: ${senderId}`,
36204
- code
36205
- })
36206
- });
36207
- statusSink?.({ lastOutboundAt: Date.now() });
36208
- } catch (err) {
36209
- runtime2.error?.(`inline: pairing reply failed for ${senderId}: ${String(err)}`);
36210
- }
36211
- }
36224
+ statusSink?.({ lastOutboundAt: Date.now() });
36225
+ } catch (err) {
36226
+ runtime2.error?.(`inline: pairing reply failed for ${senderId}: ${String(err)}`);
36212
36227
  }
36213
- log?.info(`[${account.accountId}] inline: drop DM sender=${senderId} (dmPolicy=${dmPolicy})`);
36214
- await answerCallbackIfNeeded().catch((error48) => {
36215
- runtime2.error?.(`inline callback answer failed: ${String(error48)}`);
36216
- });
36217
- continue;
36218
36228
  }
36219
36229
  }
36220
- }
36221
- if (isGroup && commandGate.shouldBlock) {
36222
- logInboundDrop({
36223
- log: (m) => runtime2.log?.(m),
36224
- channel: CHANNEL_ID,
36225
- reason: "control command (unauthorized)",
36226
- target: senderId
36227
- });
36230
+ log?.info(`[${account.accountId}] inline: drop DM sender=${senderId} (dmPolicy=${dmPolicy})`);
36228
36231
  await answerCallbackIfNeeded().catch((error48) => {
36229
36232
  runtime2.error?.(`inline callback answer failed: ${String(error48)}`);
36230
36233
  });
36231
- continue;
36234
+ return;
36232
36235
  }
36233
- const route = core3.channel.routing.resolveAgentRoute({
36234
- cfg,
36235
- channel: CHANNEL_ID,
36236
- accountId: account.accountId,
36237
- peer: {
36238
- kind: isGroup ? "group" : "direct",
36239
- id: isGroup ? String(effectiveChatId) : senderId
36236
+ }
36237
+ }
36238
+ if (isGroup && commandGate.shouldBlock) {
36239
+ logInboundDrop({
36240
+ log: (m) => runtime2.log?.(m),
36241
+ channel: CHANNEL_ID,
36242
+ reason: "control command (unauthorized)",
36243
+ target: senderId
36244
+ });
36245
+ await answerCallbackIfNeeded().catch((error48) => {
36246
+ runtime2.error?.(`inline callback answer failed: ${String(error48)}`);
36247
+ });
36248
+ return;
36249
+ }
36250
+ const route = core3.channel.routing.resolveAgentRoute({
36251
+ cfg,
36252
+ channel: CHANNEL_ID,
36253
+ accountId: account.accountId,
36254
+ peer: {
36255
+ kind: isGroup ? "group" : "direct",
36256
+ id: isGroup ? String(effectiveChatId) : senderId
36257
+ }
36258
+ });
36259
+ const mentionRegexes = core3.channel.mentions.buildMentionRegexes(cfg, route.agentId);
36260
+ const nativeMentioned = typeof msg.mentioned === "boolean" ? msg.mentioned : false;
36261
+ const patternMentioned = mentionRegexes.length ? core3.channel.mentions.matchesMentionPatterns(rawBody, mentionRegexes) : false;
36262
+ const wasMentioned = nativeMentioned || patternMentioned;
36263
+ const messageTimestamp = Number(msg.date) * 1000;
36264
+ const groupHistoryKey = isGroup ? replyThreadContext ? `${route.sessionKey}:thread:${String(replyThreadContext.childChatId)}` : route.sessionKey : null;
36265
+ const pendingHistorySender = senderUsername ? `@${senderUsername}` : senderName ?? `user:${senderId}`;
36266
+ const historyLimit = resolveHistoryLimit({
36267
+ cfg,
36268
+ isGroup,
36269
+ historyLimit: account.config.historyLimit,
36270
+ dmHistoryLimit: account.config.dmHistoryLimit
36271
+ });
36272
+ const historyContext = await buildHistoryContext2({
36273
+ client,
36274
+ chatId,
36275
+ currentMessageId: msg.id,
36276
+ replyToMsgId: msg.replyToMsgId,
36277
+ senderProfilesById,
36278
+ meId,
36279
+ historyLimit,
36280
+ botMessageIdsByChat
36281
+ }).catch((err) => {
36282
+ statusSink?.({ lastError: `getChatHistory failed: ${String(err)}` });
36283
+ return {
36284
+ historyText: null,
36285
+ attachmentText: null,
36286
+ entityText: null,
36287
+ inboundHistory: [],
36288
+ repliedToBot: false,
36289
+ replyToSenderId: null
36290
+ };
36291
+ });
36292
+ const effectiveHistoryContext = replyThreadContext?.anchorMessage != null ? prependInlineReplyThreadAnchor({
36293
+ historyContext,
36294
+ anchorMessage: replyThreadContext.anchorMessage,
36295
+ parentChatId: replyThreadContext.parentChatId,
36296
+ senderProfilesById,
36297
+ meId
36298
+ }) : historyContext;
36299
+ const implicitMention = (reactionEvent != null || callbackActionEvent != null) && isGroup || isGroup && (account.config.replyToBotWithoutMention ?? false) && msg.replyToMsgId != null && effectiveHistoryContext.repliedToBot;
36300
+ const requireMention = isGroup ? resolveInlineGroupRequireMention({
36301
+ cfg,
36302
+ groupId: String(effectiveChatId),
36303
+ accountId: account.accountId,
36304
+ requireMentionDefault: account.config.requireMention ?? false
36305
+ }) : false;
36306
+ const mentionGate = resolveMentionGatingWithBypass({
36307
+ isGroup,
36308
+ requireMention,
36309
+ canDetectMention: typeof msg.mentioned === "boolean" || mentionRegexes.length > 0,
36310
+ wasMentioned,
36311
+ implicitMention,
36312
+ allowTextCommands,
36313
+ hasControlCommand,
36314
+ commandAuthorized
36315
+ });
36316
+ if (isGroup && mentionGate.shouldSkip) {
36317
+ runtime2.log?.(`inline: drop group chat ${String(chatId)} (no mention)`);
36318
+ const pendingBody = normalizeHistoryText(currentContent?.text) ?? normalizeHistoryText(rawBody);
36319
+ recordPendingHistoryEntryIfEnabled({
36320
+ historyMap: groupPendingHistories,
36321
+ historyKey: groupHistoryKey ?? "",
36322
+ limit: historyLimit,
36323
+ entry: groupHistoryKey && pendingBody ? {
36324
+ sender: pendingHistorySender,
36325
+ body: pendingBody,
36326
+ timestamp: messageTimestamp || Date.now(),
36327
+ messageId: String(msg.id)
36328
+ } : null
36329
+ });
36330
+ await answerCallbackIfNeeded().catch((error48) => {
36331
+ runtime2.error?.(`inline callback answer failed: ${String(error48)}`);
36332
+ });
36333
+ return;
36334
+ }
36335
+ const parseMarkdown = account.config.parseMarkdown ?? true;
36336
+ const nativeCommandMenu = resolveInlineNativeCommandMenu({
36337
+ commandBody: normalizedCommandBody,
36338
+ cfg
36339
+ });
36340
+ if (nativeCommandMenu) {
36341
+ const menuActions = resolveInlineReplyActions({
36342
+ channelData: {
36343
+ inline: {
36344
+ buttons: nativeCommandMenu.buttons
36240
36345
  }
36241
- });
36242
- const mentionRegexes = core3.channel.mentions.buildMentionRegexes(cfg, route.agentId);
36243
- const nativeMentioned = typeof msg.mentioned === "boolean" ? msg.mentioned : false;
36244
- const patternMentioned = mentionRegexes.length ? core3.channel.mentions.matchesMentionPatterns(rawBody, mentionRegexes) : false;
36245
- const wasMentioned = nativeMentioned || patternMentioned;
36246
- const messageTimestamp = Number(msg.date) * 1000;
36247
- const groupHistoryKey = isGroup ? replyThreadContext ? `${route.sessionKey}:thread:${String(replyThreadContext.childChatId)}` : route.sessionKey : null;
36248
- const pendingHistorySender = senderUsername ? `@${senderUsername}` : senderName ?? `user:${senderId}`;
36249
- const historyLimit = resolveHistoryLimit({
36250
- cfg,
36251
- isGroup,
36252
- historyLimit: account.config.historyLimit,
36253
- dmHistoryLimit: account.config.dmHistoryLimit
36254
- });
36255
- const historyContext = await buildHistoryContext2({
36256
- client,
36346
+ }
36347
+ });
36348
+ let deliveredNativeMenu = false;
36349
+ if (shouldEditCallbackTargetInPlace && callbackActionEvent) {
36350
+ try {
36351
+ const result = await client.invokeRaw(Method.EDIT_MESSAGE, {
36352
+ oneofKind: "editMessage",
36353
+ editMessage: {
36354
+ messageId: callbackActionEvent.targetMessageId,
36355
+ peerId: buildChatPeer2(chatId),
36356
+ text: nativeCommandMenu.title,
36357
+ ...menuActions ? { actions: menuActions } : {},
36358
+ parseMarkdown
36359
+ }
36360
+ });
36361
+ if (result.oneofKind !== "editMessage") {
36362
+ throw new Error(`inline native command menu: expected editMessage result, got ${String(result.oneofKind)}`);
36363
+ }
36364
+ deliveredNativeMenu = true;
36365
+ } catch (error48) {
36366
+ runtime2.error?.(`inline native command menu edit failed; falling back to send (${String(error48)})`);
36367
+ }
36368
+ }
36369
+ if (!deliveredNativeMenu) {
36370
+ const sent = await client.sendMessage({
36257
36371
  chatId,
36258
- currentMessageId: msg.id,
36259
- replyToMsgId: msg.replyToMsgId,
36260
- senderProfilesById,
36261
- meId,
36262
- historyLimit,
36263
- botMessageIdsByChat
36264
- }).catch((err) => {
36265
- statusSink?.({ lastError: `getChatHistory failed: ${String(err)}` });
36266
- return {
36267
- historyText: null,
36268
- attachmentText: null,
36269
- entityText: null,
36270
- inboundHistory: [],
36271
- repliedToBot: false,
36272
- replyToSenderId: null
36273
- };
36372
+ text: nativeCommandMenu.title,
36373
+ ...menuActions ? { actions: menuActions } : {}
36274
36374
  });
36275
- const effectiveHistoryContext = replyThreadContext?.anchorMessage != null ? prependInlineReplyThreadAnchor({
36276
- historyContext,
36277
- anchorMessage: replyThreadContext.anchorMessage,
36278
- parentChatId: replyThreadContext.parentChatId,
36279
- senderProfilesById,
36280
- meId
36281
- }) : historyContext;
36282
- const implicitMention = (reactionEvent != null || callbackActionEvent != null) && isGroup || isGroup && (account.config.replyToBotWithoutMention ?? false) && msg.replyToMsgId != null && effectiveHistoryContext.repliedToBot;
36283
- const requireMention = isGroup ? resolveInlineGroupRequireMention({
36284
- cfg,
36285
- groupId: String(effectiveChatId),
36286
- accountId: account.accountId,
36287
- requireMentionDefault: account.config.requireMention ?? false
36288
- }) : false;
36289
- const mentionGate = resolveMentionGatingWithBypass({
36290
- isGroup,
36291
- requireMention,
36292
- canDetectMention: typeof msg.mentioned === "boolean" || mentionRegexes.length > 0,
36293
- wasMentioned,
36294
- implicitMention,
36295
- allowTextCommands,
36296
- hasControlCommand,
36297
- commandAuthorized
36298
- });
36299
- if (isGroup && mentionGate.shouldSkip) {
36300
- runtime2.log?.(`inline: drop group chat ${String(chatId)} (no mention)`);
36301
- const pendingBody = normalizeHistoryText(currentContent?.text) ?? normalizeHistoryText(rawBody);
36302
- recordPendingHistoryEntryIfEnabled({
36303
- historyMap: groupPendingHistories,
36304
- historyKey: groupHistoryKey ?? "",
36305
- limit: historyLimit,
36306
- entry: groupHistoryKey && pendingBody ? {
36307
- sender: pendingHistorySender,
36308
- body: pendingBody,
36309
- timestamp: messageTimestamp || Date.now(),
36310
- messageId: String(msg.id)
36311
- } : null
36312
- });
36313
- await answerCallbackIfNeeded().catch((error48) => {
36314
- runtime2.error?.(`inline callback answer failed: ${String(error48)}`);
36315
- });
36316
- continue;
36375
+ if (sent.messageId != null) {
36376
+ rememberBotMessageId(botMessageIdsByChat, chatId, sent.messageId);
36317
36377
  }
36318
- const parseMarkdown = account.config.parseMarkdown ?? true;
36319
- const nativeCommandMenu = resolveInlineCompatNativeCommandMenu(normalizedCommandBody);
36320
- if (nativeCommandMenu) {
36321
- const menuActions = resolveInlineReplyActions({
36322
- channelData: {
36323
- inline: {
36324
- buttons: nativeCommandMenu.buttons
36325
- }
36378
+ }
36379
+ statusSink?.({ lastOutboundAt: Date.now() });
36380
+ await answerCallbackIfNeeded().catch((error48) => {
36381
+ runtime2.error?.(`inline callback answer failed: ${String(error48)}`);
36382
+ });
36383
+ return;
36384
+ }
36385
+ const modelPickerCallbackData = callbackActionEvent ? callbackDataToUtf8(callbackActionEvent.data) : undefined;
36386
+ const modelPickerCallback = modelPickerCallbackData ? parseInlineModelPickerCallback(modelPickerCallbackData) : null;
36387
+ if (shouldEditCallbackTargetInPlace && callbackActionEvent && modelPickerCallback?.type === "select") {
36388
+ const deliverModelPickerEdit = async (text, buttons) => {
36389
+ const actions = resolveInlineReplyActions({
36390
+ channelData: {
36391
+ inline: {
36392
+ buttons
36393
+ }
36394
+ }
36395
+ }) ?? { rows: [] };
36396
+ try {
36397
+ const result = await client.invokeRaw(Method.EDIT_MESSAGE, {
36398
+ oneofKind: "editMessage",
36399
+ editMessage: {
36400
+ messageId: callbackActionEvent.targetMessageId,
36401
+ peerId: buildChatPeer2(chatId),
36402
+ text,
36403
+ actions,
36404
+ parseMarkdown
36326
36405
  }
36327
36406
  });
36407
+ if (result.oneofKind !== "editMessage") {
36408
+ throw new Error(`inline model picker edit: expected editMessage result, got ${String(result.oneofKind)}`);
36409
+ }
36410
+ } catch (error48) {
36411
+ runtime2.error?.(`inline model picker edit failed; falling back to send (${String(error48)})`);
36328
36412
  const sent = await client.sendMessage({
36329
36413
  chatId,
36330
- text: nativeCommandMenu.title,
36331
- ...menuActions ? { actions: menuActions } : {}
36414
+ text,
36415
+ actions,
36416
+ parseMarkdown
36332
36417
  });
36333
36418
  if (sent.messageId != null) {
36334
36419
  rememberBotMessageId(botMessageIdsByChat, chatId, sent.messageId);
36335
36420
  }
36336
- statusSink?.({ lastOutboundAt: Date.now() });
36337
- await answerCallbackIfNeeded().catch((error48) => {
36338
- runtime2.error?.(`inline callback answer failed: ${String(error48)}`);
36339
- });
36340
- continue;
36341
36421
  }
36342
- const inboundMedia = reactionEvent ? [] : await resolveInlineInboundMedia({
36343
- core: core3,
36344
- message: msg,
36345
- maxBytes: inboundMediaMaxBytes,
36346
- ...log ? { log } : {}
36347
- });
36348
- const timestamp = messageTimestamp;
36349
- const fromLabel = isGroup ? `chat:${effectiveGroupTitle ?? String(effectiveChatId)}` : `user:${senderId}`;
36350
- const storePath = core3.channel.session.resolveStorePath(cfg.session?.store, { agentId: route.agentId });
36351
- const envelopeOptions = core3.channel.reply.resolveEnvelopeFormatOptions(cfg);
36352
- const previousTimestamp = core3.channel.session.readSessionUpdatedAt({ storePath, sessionKey: route.sessionKey });
36353
- const combinedBody = [
36354
- effectiveHistoryContext.historyText,
36355
- effectiveHistoryContext.attachmentText,
36356
- effectiveHistoryContext.entityText,
36357
- INLINE_FORMATTING_NOTE,
36358
- `Current message:
36422
+ };
36423
+ const { byProvider, providers } = await buildModelsProviderData(cfg, route.agentId);
36424
+ const providerButtons = buildInlineModelProviderButtons(providers.map((provider) => ({
36425
+ id: provider,
36426
+ count: byProvider.get(provider)?.size ?? 0
36427
+ })));
36428
+ const selection = resolveInlineModelPickerSelection({
36429
+ callback: modelPickerCallback,
36430
+ providers,
36431
+ byProvider
36432
+ });
36433
+ if (selection.kind !== "resolved") {
36434
+ await deliverModelPickerEdit(`Could not resolve model "${selection.model}".
36435
+
36436
+ Select a provider:`, providerButtons);
36437
+ } else {
36438
+ const modelSet = byProvider.get(selection.provider);
36439
+ if (!modelSet?.has(selection.model)) {
36440
+ await deliverModelPickerEdit(`❌ Model "${selection.provider}/${selection.model}" is not allowed.`, []);
36441
+ } else {
36442
+ try {
36443
+ const storePath2 = core3.channel.session.resolveStorePath(cfg.session?.store, {
36444
+ agentId: route.agentId
36445
+ });
36446
+ const resolvedDefault = resolveDefaultModelForAgent({
36447
+ cfg,
36448
+ agentId: route.agentId
36449
+ });
36450
+ const isDefaultSelection = selection.provider === resolvedDefault.provider && selection.model === resolvedDefault.model;
36451
+ await updateSessionStore(storePath2, (store) => {
36452
+ const entry = store[route.sessionKey] ?? {
36453
+ sessionId: route.sessionKey,
36454
+ updatedAt: Date.now()
36455
+ };
36456
+ store[route.sessionKey] = entry;
36457
+ applyModelOverrideToSessionEntry({
36458
+ entry,
36459
+ selection: {
36460
+ provider: selection.provider,
36461
+ model: selection.model,
36462
+ isDefault: isDefaultSelection
36463
+ }
36464
+ });
36465
+ });
36466
+ const actionText = isDefaultSelection ? "reset to default" : `changed to **${selection.provider}/${selection.model}**`;
36467
+ await deliverModelPickerEdit(`✅ Model ${actionText}
36468
+
36469
+ This model will be used for your next message.`, []);
36470
+ } catch (error48) {
36471
+ await deliverModelPickerEdit(`❌ Failed to change model: ${String(error48)}`, []);
36472
+ }
36473
+ }
36474
+ }
36475
+ statusSink?.({ lastOutboundAt: Date.now() });
36476
+ await answerCallbackIfNeeded().catch((error48) => {
36477
+ runtime2.error?.(`inline callback answer failed: ${String(error48)}`);
36478
+ });
36479
+ return;
36480
+ }
36481
+ const inboundMedia = reactionEvent ? [] : await resolveInlineInboundMedia({
36482
+ core: core3,
36483
+ message: msg,
36484
+ maxBytes: inboundMediaMaxBytes,
36485
+ ...log ? { log } : {}
36486
+ });
36487
+ const timestamp = messageTimestamp;
36488
+ const fromLabel = isGroup ? `chat:${effectiveGroupTitle ?? String(effectiveChatId)}` : `user:${senderId}`;
36489
+ const storePath = core3.channel.session.resolveStorePath(cfg.session?.store, { agentId: route.agentId });
36490
+ const envelopeOptions = core3.channel.reply.resolveEnvelopeFormatOptions(cfg);
36491
+ const previousTimestamp = core3.channel.session.readSessionUpdatedAt({ storePath, sessionKey: route.sessionKey });
36492
+ const combinedBody = [
36493
+ effectiveHistoryContext.historyText,
36494
+ effectiveHistoryContext.attachmentText,
36495
+ effectiveHistoryContext.entityText,
36496
+ INLINE_FORMATTING_NOTE,
36497
+ `Current message:
36359
36498
  ${rawBody}`,
36360
- currentAttachmentText && currentAttachmentText !== rawBody ? `Current media/attachments:
36499
+ currentAttachmentText && currentAttachmentText !== rawBody ? `Current media/attachments:
36361
36500
  ${currentAttachmentText}` : null,
36362
- currentEntityText ? `Current message entities:
36501
+ currentEntityText ? `Current message entities:
36363
36502
  ${currentEntityText}` : null
36364
- ].filter(Boolean).join(`
36503
+ ].filter(Boolean).join(`
36365
36504
 
36366
36505
  `);
36367
- let body = core3.channel.reply.formatAgentEnvelope({
36506
+ let body = core3.channel.reply.formatAgentEnvelope({
36507
+ channel: "Inline",
36508
+ from: fromLabel,
36509
+ timestamp,
36510
+ ...previousTimestamp != null ? { previousTimestamp } : {},
36511
+ envelope: envelopeOptions,
36512
+ body: combinedBody || rawBody
36513
+ });
36514
+ if (isGroup && groupHistoryKey) {
36515
+ body = buildPendingHistoryContextFromMap({
36516
+ historyMap: groupPendingHistories,
36517
+ historyKey: groupHistoryKey,
36518
+ limit: historyLimit,
36519
+ currentMessage: body,
36520
+ formatEntry: (entry) => core3.channel.reply.formatAgentEnvelope({
36368
36521
  channel: "Inline",
36369
36522
  from: fromLabel,
36370
- timestamp,
36371
- ...previousTimestamp != null ? { previousTimestamp } : {},
36523
+ ...entry.timestamp != null ? { timestamp: entry.timestamp } : {},
36372
36524
  envelope: envelopeOptions,
36373
- body: combinedBody || rawBody
36374
- });
36375
- if (isGroup && groupHistoryKey) {
36376
- body = buildPendingHistoryContextFromMap({
36377
- historyMap: groupPendingHistories,
36378
- historyKey: groupHistoryKey,
36379
- limit: historyLimit,
36380
- currentMessage: body,
36381
- formatEntry: (entry) => core3.channel.reply.formatAgentEnvelope({
36382
- channel: "Inline",
36383
- from: fromLabel,
36384
- ...entry.timestamp != null ? { timestamp: entry.timestamp } : {},
36385
- envelope: envelopeOptions,
36386
- body: `${entry.body}${entry.messageId ? ` [id:${entry.messageId} chat:${String(chatId)}]` : ""}`
36387
- })
36388
- });
36525
+ body: `${entry.body}${entry.messageId ? ` [id:${entry.messageId} chat:${String(chatId)}]` : ""}`
36526
+ })
36527
+ });
36528
+ }
36529
+ const inboundHistory = isGroup && groupHistoryKey ? mergeInboundHistoryEntries({
36530
+ historyContextEntries: effectiveHistoryContext.inboundHistory,
36531
+ pendingEntries: groupPendingHistories.get(groupHistoryKey) ?? [],
36532
+ limit: historyLimit
36533
+ }) : [];
36534
+ const bodyForAgent = buildInlineBodyForAgent({
36535
+ rawBody,
36536
+ currentAttachmentText,
36537
+ currentEntityText
36538
+ });
36539
+ const effectiveSurface = shouldUseTelegramSurfaceForModelCommands(normalizedCommandBody) ? "telegram" : CHANNEL_ID;
36540
+ const systemPrompt = resolveInlineSystemPrompt({
36541
+ account,
36542
+ ...isGroup ? { groupId: String(effectiveChatId) } : {}
36543
+ });
36544
+ const ctxPayload = core3.channel.reply.finalizeInboundContext({
36545
+ Body: body,
36546
+ BodyForAgent: bodyForAgent,
36547
+ ...isGroup ? { InboundHistory: inboundHistory } : {},
36548
+ RawBody: rawBody,
36549
+ CommandBody: normalizedCommandBody,
36550
+ From: isGroup ? `inline:chat:${String(effectiveChatId)}` : `inline:${senderId}`,
36551
+ To: `inline:${String(effectiveChatId)}`,
36552
+ SessionKey: route.sessionKey,
36553
+ ...replyThreadContext ? { ParentSessionKey: route.sessionKey } : {},
36554
+ AccountId: route.accountId,
36555
+ ChatType: isGroup ? "group" : "direct",
36556
+ ConversationLabel: fromLabel,
36557
+ ...isGroup ? { GroupSubject: effectiveGroupTitle ?? String(effectiveChatId) } : {},
36558
+ SenderId: senderId,
36559
+ ...senderName ? { SenderName: senderName } : {},
36560
+ ...senderUsername ? { SenderUsername: senderUsername } : {},
36561
+ Provider: CHANNEL_ID,
36562
+ Surface: effectiveSurface,
36563
+ MessageSid: buildInlineInboundMessageSid({
36564
+ msgId: msg.id,
36565
+ ...callbackActionEvent ? { callbackActionEvent } : {}
36566
+ }),
36567
+ ...replyThreadContext ? { MessageThreadId: String(replyThreadContext.childChatId) } : {},
36568
+ ...replyThreadContext?.threadLabel ? { ThreadLabel: replyThreadContext.threadLabel } : {},
36569
+ ...msg.replyToMsgId != null ? { ReplyToId: String(msg.replyToMsgId) } : {},
36570
+ ...effectiveHistoryContext.replyToSenderId != null ? { ReplyToSenderId: effectiveHistoryContext.replyToSenderId } : {},
36571
+ ...msg.replyToMsgId != null ? { ReplyToWasBot: effectiveHistoryContext.repliedToBot } : {},
36572
+ ...callbackActionEvent ? {
36573
+ MessageActionInteractionId: String(callbackActionEvent.interactionId),
36574
+ MessageActionId: callbackActionEvent.actionId,
36575
+ MessageActionDataBase64: callbackDataToBase64(callbackActionEvent.data),
36576
+ ...callbackDataToUtf8(callbackActionEvent.data) ? { MessageActionDataUtf8: callbackDataToUtf8(callbackActionEvent.data) } : {}
36577
+ } : {},
36578
+ ...buildInlineInboundMediaPayload(inboundMedia),
36579
+ Timestamp: timestamp || Date.now(),
36580
+ WasMentioned: mentionGate.effectiveWasMentioned,
36581
+ CommandAuthorized: commandAuthorized,
36582
+ GroupSystemPrompt: systemPrompt,
36583
+ OriginatingChannel: CHANNEL_ID,
36584
+ OriginatingTo: `inline:${String(effectiveChatId)}`
36585
+ });
36586
+ await core3.channel.session.recordInboundSession({
36587
+ storePath,
36588
+ sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
36589
+ ctx: ctxPayload,
36590
+ ...!isGroup ? {
36591
+ updateLastRoute: {
36592
+ sessionKey: route.mainSessionKey,
36593
+ channel: CHANNEL_ID,
36594
+ to: `inline:${String(effectiveChatId)}`,
36595
+ accountId: route.accountId
36389
36596
  }
36390
- const inboundHistory = isGroup && groupHistoryKey ? mergeInboundHistoryEntries({
36391
- historyContextEntries: effectiveHistoryContext.inboundHistory,
36392
- pendingEntries: groupPendingHistories.get(groupHistoryKey) ?? [],
36393
- limit: historyLimit
36394
- }) : [];
36395
- const bodyForAgent = buildInlineBodyForAgent({
36396
- rawBody,
36397
- currentAttachmentText,
36398
- currentEntityText
36399
- });
36400
- const effectiveSurface = shouldUseTelegramSurfaceForModelCommands(normalizedCommandBody) ? "telegram" : CHANNEL_ID;
36401
- const systemPrompt = resolveInlineSystemPrompt({
36402
- account,
36403
- ...isGroup ? { groupId: String(effectiveChatId) } : {}
36404
- });
36405
- const ctxPayload = core3.channel.reply.finalizeInboundContext({
36406
- Body: body,
36407
- BodyForAgent: bodyForAgent,
36408
- ...isGroup ? { InboundHistory: inboundHistory } : {},
36409
- RawBody: rawBody,
36410
- CommandBody: normalizedCommandBody,
36411
- From: isGroup ? `inline:chat:${String(effectiveChatId)}` : `inline:${senderId}`,
36412
- To: `inline:${String(effectiveChatId)}`,
36413
- SessionKey: route.sessionKey,
36414
- ...replyThreadContext ? { ParentSessionKey: route.sessionKey } : {},
36415
- AccountId: route.accountId,
36416
- ChatType: isGroup ? "group" : "direct",
36417
- ConversationLabel: fromLabel,
36418
- ...isGroup ? { GroupSubject: effectiveGroupTitle ?? String(effectiveChatId) } : {},
36419
- SenderId: senderId,
36420
- ...senderName ? { SenderName: senderName } : {},
36421
- ...senderUsername ? { SenderUsername: senderUsername } : {},
36422
- Provider: CHANNEL_ID,
36423
- Surface: effectiveSurface,
36424
- MessageSid: String(msg.id),
36425
- ...replyThreadContext ? { MessageThreadId: String(replyThreadContext.childChatId) } : {},
36426
- ...replyThreadContext?.threadLabel ? { ThreadLabel: replyThreadContext.threadLabel } : {},
36427
- ...msg.replyToMsgId != null ? { ReplyToId: String(msg.replyToMsgId) } : {},
36428
- ...effectiveHistoryContext.replyToSenderId != null ? { ReplyToSenderId: effectiveHistoryContext.replyToSenderId } : {},
36429
- ...msg.replyToMsgId != null ? { ReplyToWasBot: effectiveHistoryContext.repliedToBot } : {},
36430
- ...callbackActionEvent ? {
36431
- MessageActionInteractionId: String(callbackActionEvent.interactionId),
36432
- MessageActionId: callbackActionEvent.actionId,
36433
- MessageActionDataBase64: callbackDataToBase64(callbackActionEvent.data),
36434
- ...callbackDataToUtf8(callbackActionEvent.data) ? { MessageActionDataUtf8: callbackDataToUtf8(callbackActionEvent.data) } : {}
36435
- } : {},
36436
- ...buildInlineInboundMediaPayload(inboundMedia),
36437
- Timestamp: timestamp || Date.now(),
36438
- WasMentioned: mentionGate.effectiveWasMentioned,
36439
- CommandAuthorized: commandAuthorized,
36440
- GroupSystemPrompt: systemPrompt,
36441
- OriginatingChannel: CHANNEL_ID,
36442
- OriginatingTo: `inline:${String(effectiveChatId)}`
36443
- });
36444
- await core3.channel.session.recordInboundSession({
36445
- storePath,
36446
- sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
36447
- ctx: ctxPayload,
36448
- ...!isGroup ? {
36449
- updateLastRoute: {
36450
- sessionKey: route.mainSessionKey,
36451
- channel: CHANNEL_ID,
36452
- to: `inline:${String(effectiveChatId)}`,
36453
- accountId: route.accountId
36597
+ } : {},
36598
+ onRecordError: (err) => runtime2.error?.(`inline: failed updating session meta: ${String(err)}`)
36599
+ });
36600
+ const replyPipeline = await createChannelReplyPipelineCompat({
36601
+ cfg,
36602
+ agentId: route.agentId,
36603
+ channel: CHANNEL_ID,
36604
+ accountId: account.accountId,
36605
+ typing: {
36606
+ start: () => client.sendTyping({ chatId, typing: true }),
36607
+ stop: () => client.sendTyping({ chatId, typing: false }),
36608
+ onStartError: (err) => runtime2.error?.(`inline typing start failed: ${String(err)}`),
36609
+ onStopError: (err) => runtime2.error?.(`inline typing stop failed: ${String(err)}`)
36610
+ }
36611
+ });
36612
+ const onModelSelected = replyPipeline.onModelSelected;
36613
+ const typingCallbacks = replyPipeline.typingCallbacks;
36614
+ const prefixOptions = {
36615
+ ...replyPipeline.responsePrefix !== undefined ? { responsePrefix: replyPipeline.responsePrefix } : {},
36616
+ ...replyPipeline.enableSlackInteractiveReplies !== undefined ? { enableSlackInteractiveReplies: replyPipeline.enableSlackInteractiveReplies } : {},
36617
+ ...replyPipeline.responsePrefixContextProvider ? {
36618
+ responsePrefixContextProvider: replyPipeline.responsePrefixContextProvider
36619
+ } : {}
36620
+ };
36621
+ const callbackTargetMessage = shouldEditCallbackTargetInPlace && callbackActionEvent ? await findChatMessageById({
36622
+ client,
36623
+ chatId,
36624
+ messageId: callbackActionEvent.targetMessageId,
36625
+ limit: REPLY_TARGET_LOOKUP_LIMIT,
36626
+ meId,
36627
+ botMessageIdsByChat
36628
+ }).catch(() => null) : null;
36629
+ const streamViaEditMessage = account.config.streamViaEditMessage === true && !shouldEditCallbackTargetInPlace;
36630
+ const defaultReplyToMsgId = isGroup && msg.replyToMsgId != null ? msg.id : undefined;
36631
+ const disableBlockStreaming = streamViaEditMessage ? true : typeof account.config.blockStreaming === "boolean" ? !account.config.blockStreaming : undefined;
36632
+ const editStreamState = {
36633
+ messageId: shouldEditCallbackTargetInPlace ? callbackActionEvent?.targetMessageId ?? null : null,
36634
+ accumulatedText: callbackTargetMessage?.message ?? "",
36635
+ lastPartialText: "",
36636
+ finalTextAccumulator: "",
36637
+ failed: false,
36638
+ opChain: Promise.resolve()
36639
+ };
36640
+ let finalDeliveredForCurrentAssistantMessage = false;
36641
+ const resetEditStreamForAssistantMessage = async () => {
36642
+ await editStreamState.opChain;
36643
+ const hasActiveState = editStreamState.messageId != null || editStreamState.accumulatedText.length > 0 || editStreamState.lastPartialText.length > 0 || editStreamState.finalTextAccumulator.length > 0;
36644
+ if (!hasActiveState)
36645
+ return;
36646
+ editStreamState.messageId = null;
36647
+ editStreamState.accumulatedText = "";
36648
+ editStreamState.lastPartialText = "";
36649
+ editStreamState.finalTextAccumulator = "";
36650
+ editStreamState.failed = false;
36651
+ finalDeliveredForCurrentAssistantMessage = false;
36652
+ };
36653
+ const resetEditStreamOnBoundary = async () => {
36654
+ if (!streamViaEditMessage)
36655
+ return;
36656
+ await resetEditStreamForAssistantMessage();
36657
+ };
36658
+ const handlePartialStreamPayload = async (payload) => {
36659
+ if (editStreamState.failed)
36660
+ return;
36661
+ if ((payload.mediaUrls?.length ?? 0) > 0)
36662
+ return;
36663
+ const partialText = typeof payload.text === "string" ? payload.text : "";
36664
+ if (!partialText || partialText === editStreamState.lastPartialText)
36665
+ return;
36666
+ editStreamState.lastPartialText = partialText;
36667
+ const nextText = rewriteNumericMentionsToUsernames(extractCompleteParagraphText(partialText), senderProfilesById).trim();
36668
+ if (!nextText || nextText === editStreamState.accumulatedText)
36669
+ return;
36670
+ editStreamState.opChain = editStreamState.opChain.then(async () => {
36671
+ if (editStreamState.failed)
36672
+ return;
36673
+ if (!nextText || nextText === editStreamState.accumulatedText)
36674
+ return;
36675
+ try {
36676
+ if (editStreamState.messageId == null) {
36677
+ const sent = await client.sendMessage({
36678
+ chatId,
36679
+ text: nextText,
36680
+ ...defaultReplyToMsgId != null ? { replyToMsgId: defaultReplyToMsgId } : {},
36681
+ parseMarkdown
36682
+ });
36683
+ if (sent.messageId == null) {
36684
+ throw new Error("inline edit stream: sendMessage returned no messageId");
36685
+ }
36686
+ editStreamState.messageId = sent.messageId;
36687
+ rememberBotMessageId(botMessageIdsByChat, chatId, sent.messageId);
36688
+ } else {
36689
+ const result = await client.invokeRaw(Method.EDIT_MESSAGE, {
36690
+ oneofKind: "editMessage",
36691
+ editMessage: {
36692
+ messageId: editStreamState.messageId,
36693
+ peerId: buildChatPeer2(chatId),
36694
+ text: nextText,
36695
+ parseMarkdown
36696
+ }
36697
+ });
36698
+ if (result.oneofKind !== "editMessage") {
36699
+ throw new Error(`inline edit stream: expected editMessage result, got ${String(result.oneofKind)}`);
36454
36700
  }
36455
- } : {},
36456
- onRecordError: (err) => runtime2.error?.(`inline: failed updating session meta: ${String(err)}`)
36457
- });
36458
- const replyPipeline = await createChannelReplyPipelineCompat({
36459
- cfg,
36460
- agentId: route.agentId,
36461
- channel: CHANNEL_ID,
36462
- accountId: account.accountId,
36463
- typing: {
36464
- start: () => client.sendTyping({ chatId, typing: true }),
36465
- stop: () => client.sendTyping({ chatId, typing: false }),
36466
- onStartError: (err) => runtime2.error?.(`inline typing start failed: ${String(err)}`),
36467
- onStopError: (err) => runtime2.error?.(`inline typing stop failed: ${String(err)}`)
36468
36701
  }
36469
- });
36470
- const onModelSelected = replyPipeline.onModelSelected;
36471
- const typingCallbacks = replyPipeline.typingCallbacks;
36472
- const prefixOptions = {
36473
- ...replyPipeline.responsePrefix !== undefined ? { responsePrefix: replyPipeline.responsePrefix } : {},
36474
- ...replyPipeline.enableSlackInteractiveReplies !== undefined ? { enableSlackInteractiveReplies: replyPipeline.enableSlackInteractiveReplies } : {},
36475
- ...replyPipeline.responsePrefixContextProvider ? {
36476
- responsePrefixContextProvider: replyPipeline.responsePrefixContextProvider
36477
- } : {}
36478
- };
36479
- const streamViaEditMessage = account.config.streamViaEditMessage === true && !shouldEditCallbackTargetInPlace;
36480
- const defaultReplyToMsgId = isGroup && msg.replyToMsgId != null ? msg.id : undefined;
36481
- const disableBlockStreaming = streamViaEditMessage ? true : typeof account.config.blockStreaming === "boolean" ? !account.config.blockStreaming : undefined;
36482
- const editStreamState = {
36483
- messageId: null,
36484
- accumulatedText: "",
36485
- lastPartialText: "",
36486
- finalTextAccumulator: "",
36487
- failed: false,
36488
- opChain: Promise.resolve()
36489
- };
36490
- let finalDeliveredForCurrentAssistantMessage = false;
36491
- const resetEditStreamForAssistantMessage = async () => {
36702
+ editStreamState.accumulatedText = nextText;
36703
+ statusSink?.({ lastOutboundAt: Date.now() });
36704
+ } catch (error48) {
36705
+ editStreamState.failed = true;
36706
+ runtime2.error?.(`inline edit stream failed: ${String(error48)}`);
36707
+ }
36708
+ });
36709
+ await editStreamState.opChain;
36710
+ };
36711
+ const replyOptions = {
36712
+ ...onModelSelected ? { onModelSelected } : {},
36713
+ blockReplyTimeoutMs: 25000,
36714
+ ...streamViaEditMessage ? {
36715
+ onAssistantMessageStart: async () => {
36716
+ await resetEditStreamOnBoundary();
36717
+ }
36718
+ } : {},
36719
+ ...streamViaEditMessage ? {
36720
+ onPartialReply: async (payload) => {
36721
+ await handlePartialStreamPayload(payload);
36722
+ }
36723
+ } : {},
36724
+ ...streamViaEditMessage ? {
36725
+ onReasoningStream: async (payload) => {
36726
+ await handlePartialStreamPayload(payload);
36727
+ }
36728
+ } : {},
36729
+ ...streamViaEditMessage ? {
36730
+ onReasoningEnd: async () => {
36492
36731
  await editStreamState.opChain;
36493
- const hasActiveState = editStreamState.messageId != null || editStreamState.accumulatedText.length > 0 || editStreamState.lastPartialText.length > 0 || editStreamState.finalTextAccumulator.length > 0;
36494
- if (!hasActiveState)
36495
- return;
36496
- editStreamState.messageId = null;
36497
- editStreamState.accumulatedText = "";
36498
- editStreamState.lastPartialText = "";
36499
- editStreamState.finalTextAccumulator = "";
36500
- editStreamState.failed = false;
36501
- finalDeliveredForCurrentAssistantMessage = false;
36502
- };
36503
- const resetEditStreamOnBoundary = async () => {
36504
- if (!streamViaEditMessage)
36505
- return;
36506
- await resetEditStreamForAssistantMessage();
36507
- };
36508
- const handlePartialStreamPayload = async (payload) => {
36509
- if (editStreamState.failed)
36510
- return;
36511
- if ((payload.mediaUrls?.length ?? 0) > 0)
36512
- return;
36513
- const partialText = typeof payload.text === "string" ? payload.text : "";
36514
- if (!partialText || partialText === editStreamState.lastPartialText)
36515
- return;
36516
- editStreamState.lastPartialText = partialText;
36517
- const nextText = rewriteNumericMentionsToUsernames(extractCompleteParagraphText(partialText), senderProfilesById).trim();
36518
- if (!nextText || nextText === editStreamState.accumulatedText)
36519
- return;
36520
- editStreamState.opChain = editStreamState.opChain.then(async () => {
36521
- if (editStreamState.failed)
36522
- return;
36523
- if (!nextText || nextText === editStreamState.accumulatedText)
36524
- return;
36525
- try {
36526
- if (editStreamState.messageId == null) {
36732
+ }
36733
+ } : {},
36734
+ ...streamViaEditMessage ? {
36735
+ onToolStart: async () => {
36736
+ await resetEditStreamOnBoundary();
36737
+ }
36738
+ } : {},
36739
+ ...streamViaEditMessage ? {
36740
+ onCompactionStart: async () => {
36741
+ await resetEditStreamOnBoundary();
36742
+ }
36743
+ } : {},
36744
+ ...streamViaEditMessage ? {
36745
+ onCompactionEnd: async () => {
36746
+ await resetEditStreamOnBoundary();
36747
+ }
36748
+ } : {},
36749
+ ...typeof disableBlockStreaming === "boolean" ? { disableBlockStreaming } : {}
36750
+ };
36751
+ try {
36752
+ let delivered = false;
36753
+ let skippedNonSilent = false;
36754
+ let failedNonSilent = false;
36755
+ let dispatchError = null;
36756
+ try {
36757
+ await core3.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
36758
+ ctx: ctxPayload,
36759
+ cfg,
36760
+ dispatcherOptions: {
36761
+ ...prefixOptions,
36762
+ ...typingCallbacks ? { typingCallbacks } : {},
36763
+ deliver: async (payload, info) => {
36764
+ const rawText = payload.text ?? "";
36765
+ const mediaList = payload.mediaUrls?.length ? payload.mediaUrls : payload.mediaUrl ? [payload.mediaUrl] : [];
36766
+ const outboundText = rewriteNumericMentionsToUsernames(rawText, senderProfilesById);
36767
+ const outboundActions = resolveInlineReplyActions(payload);
36768
+ const infoKind = typeof info?.kind === "string" ? info.kind : undefined;
36769
+ let replyToMsgId;
36770
+ if (payload.replyToId != null) {
36771
+ try {
36772
+ replyToMsgId = BigInt(payload.replyToId);
36773
+ } catch {}
36774
+ }
36775
+ if (replyToMsgId == null && isGroup && msg.replyToMsgId != null) {
36776
+ replyToMsgId = msg.id;
36777
+ }
36778
+ const rememberSent = (messageId) => {
36779
+ if (messageId != null) {
36780
+ rememberBotMessageId(botMessageIdsByChat, chatId, messageId);
36781
+ }
36782
+ };
36783
+ const sendTextFallback = async (text, includeReplyTo, includeActions) => {
36784
+ if (!text.trim())
36785
+ return;
36527
36786
  const sent = await client.sendMessage({
36528
36787
  chatId,
36529
- text: nextText,
36530
- ...defaultReplyToMsgId != null ? { replyToMsgId: defaultReplyToMsgId } : {},
36788
+ text,
36789
+ ...includeReplyTo && replyToMsgId != null ? { replyToMsgId } : {},
36790
+ ...includeActions && outboundActions !== undefined ? { actions: outboundActions } : {},
36531
36791
  parseMarkdown
36532
36792
  });
36533
- if (sent.messageId == null) {
36534
- throw new Error("inline edit stream: sendMessage returned no messageId");
36535
- }
36536
- editStreamState.messageId = sent.messageId;
36537
- rememberBotMessageId(botMessageIdsByChat, chatId, sent.messageId);
36538
- } else {
36793
+ rememberSent(sent.messageId);
36794
+ delivered = true;
36795
+ };
36796
+ const updateStreamedMessage = async (text, actions) => {
36797
+ await editStreamState.opChain;
36798
+ if (editStreamState.messageId == null)
36799
+ return false;
36800
+ const nextText = text.trim();
36801
+ const textForEdit = nextText || editStreamState.accumulatedText;
36802
+ if (!textForEdit && actions === undefined)
36803
+ return true;
36804
+ const shouldSkipTextUpdate = !editStreamState.failed && textForEdit === editStreamState.accumulatedText;
36805
+ if (shouldSkipTextUpdate && actions === undefined)
36806
+ return true;
36539
36807
  const result = await client.invokeRaw(Method.EDIT_MESSAGE, {
36540
36808
  oneofKind: "editMessage",
36541
36809
  editMessage: {
36542
36810
  messageId: editStreamState.messageId,
36543
36811
  peerId: buildChatPeer2(chatId),
36544
- text: nextText,
36545
- parseMarkdown
36812
+ text: textForEdit,
36813
+ parseMarkdown,
36814
+ ...actions !== undefined ? { actions } : {}
36546
36815
  }
36547
36816
  });
36548
36817
  if (result.oneofKind !== "editMessage") {
36549
36818
  throw new Error(`inline edit stream: expected editMessage result, got ${String(result.oneofKind)}`);
36550
36819
  }
36551
- }
36552
- editStreamState.accumulatedText = nextText;
36553
- statusSink?.({ lastOutboundAt: Date.now() });
36554
- } catch (error48) {
36555
- editStreamState.failed = true;
36556
- runtime2.error?.(`inline edit stream failed: ${String(error48)}`);
36557
- }
36558
- });
36559
- await editStreamState.opChain;
36560
- };
36561
- const replyOptions = {
36562
- ...onModelSelected ? { onModelSelected } : {},
36563
- blockReplyTimeoutMs: 25000,
36564
- ...streamViaEditMessage ? {
36565
- onAssistantMessageStart: async () => {
36566
- await resetEditStreamOnBoundary();
36567
- }
36568
- } : {},
36569
- ...streamViaEditMessage ? {
36570
- onPartialReply: async (payload) => {
36571
- await handlePartialStreamPayload(payload);
36572
- }
36573
- } : {},
36574
- ...streamViaEditMessage ? {
36575
- onReasoningStream: async (payload) => {
36576
- await handlePartialStreamPayload(payload);
36577
- }
36578
- } : {},
36579
- ...streamViaEditMessage ? {
36580
- onReasoningEnd: async () => {
36581
- await editStreamState.opChain;
36582
- }
36583
- } : {},
36584
- ...streamViaEditMessage ? {
36585
- onToolStart: async () => {
36586
- await resetEditStreamOnBoundary();
36587
- }
36588
- } : {},
36589
- ...streamViaEditMessage ? {
36590
- onCompactionStart: async () => {
36591
- await resetEditStreamOnBoundary();
36592
- }
36593
- } : {},
36594
- ...streamViaEditMessage ? {
36595
- onCompactionEnd: async () => {
36596
- await resetEditStreamOnBoundary();
36597
- }
36598
- } : {},
36599
- ...typeof disableBlockStreaming === "boolean" ? { disableBlockStreaming } : {}
36600
- };
36601
- try {
36602
- let delivered = false;
36603
- let skippedNonSilent = false;
36604
- let failedNonSilent = false;
36605
- let dispatchError = null;
36606
- try {
36607
- await core3.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
36608
- ctx: ctxPayload,
36609
- cfg,
36610
- dispatcherOptions: {
36611
- ...prefixOptions,
36612
- ...typingCallbacks ? { typingCallbacks } : {},
36613
- deliver: async (payload, info) => {
36614
- const rawText = payload.text ?? "";
36615
- const mediaList = payload.mediaUrls?.length ? payload.mediaUrls : payload.mediaUrl ? [payload.mediaUrl] : [];
36616
- const outboundText = rewriteNumericMentionsToUsernames(rawText, senderProfilesById);
36617
- const outboundActions = resolveInlineReplyActions(payload);
36618
- const infoKind = typeof info?.kind === "string" ? info.kind : undefined;
36619
- let replyToMsgId;
36620
- if (payload.replyToId != null) {
36621
- try {
36622
- replyToMsgId = BigInt(payload.replyToId);
36623
- } catch {}
36820
+ if (!shouldSkipTextUpdate) {
36821
+ editStreamState.accumulatedText = textForEdit;
36822
+ editStreamState.lastPartialText = textForEdit;
36823
+ }
36824
+ editStreamState.failed = false;
36825
+ return true;
36826
+ };
36827
+ if (mediaList.length === 0) {
36828
+ if (shouldEditCallbackTargetInPlace && editStreamState.messageId != null) {
36829
+ const callbackEditActions = outboundActions ?? { rows: [] };
36830
+ if (!outboundText.trim() && outboundActions === undefined) {
36831
+ return;
36624
36832
  }
36625
- if (replyToMsgId == null && isGroup && msg.replyToMsgId != null) {
36626
- replyToMsgId = msg.id;
36833
+ await updateStreamedMessage(outboundText, callbackEditActions);
36834
+ delivered = true;
36835
+ statusSink?.({ lastOutboundAt: Date.now() });
36836
+ return;
36837
+ }
36838
+ if (streamViaEditMessage && infoKind === "final" && finalDeliveredForCurrentAssistantMessage && editStreamState.messageId != null) {
36839
+ await resetEditStreamForAssistantMessage();
36840
+ }
36841
+ if (streamViaEditMessage && editStreamState.messageId != null) {
36842
+ if (outboundText.trim()) {
36843
+ editStreamState.finalTextAccumulator += outboundText;
36627
36844
  }
36628
- const rememberSent = (messageId) => {
36629
- if (messageId != null) {
36630
- rememberBotMessageId(botMessageIdsByChat, chatId, messageId);
36631
- }
36632
- };
36633
- const sendTextFallback = async (text, includeReplyTo, includeActions) => {
36634
- if (!text.trim())
36635
- return;
36636
- const sent = await client.sendMessage({
36637
- chatId,
36638
- text,
36639
- ...includeReplyTo && replyToMsgId != null ? { replyToMsgId } : {},
36640
- ...includeActions && outboundActions !== undefined ? { actions: outboundActions } : {},
36641
- parseMarkdown
36642
- });
36643
- rememberSent(sent.messageId);
36644
- delivered = true;
36645
- };
36646
- const updateStreamedMessage = async (text, actions) => {
36647
- await editStreamState.opChain;
36648
- if (editStreamState.messageId == null)
36649
- return false;
36650
- const nextText = text.trim();
36651
- const textForEdit = nextText || editStreamState.accumulatedText;
36652
- if (!textForEdit)
36653
- return true;
36654
- const shouldSkipTextUpdate = !editStreamState.failed && textForEdit === editStreamState.accumulatedText;
36655
- if (shouldSkipTextUpdate && actions === undefined)
36656
- return true;
36657
- const result = await client.invokeRaw(Method.EDIT_MESSAGE, {
36658
- oneofKind: "editMessage",
36659
- editMessage: {
36660
- messageId: editStreamState.messageId,
36661
- peerId: buildChatPeer2(chatId),
36662
- text: textForEdit,
36663
- parseMarkdown,
36664
- ...actions !== undefined ? { actions } : {}
36665
- }
36666
- });
36667
- if (result.oneofKind !== "editMessage") {
36668
- throw new Error(`inline edit stream: expected editMessage result, got ${String(result.oneofKind)}`);
36669
- }
36670
- if (!shouldSkipTextUpdate) {
36671
- editStreamState.accumulatedText = textForEdit;
36672
- editStreamState.lastPartialText = textForEdit;
36673
- }
36674
- editStreamState.failed = false;
36675
- return true;
36676
- };
36677
- if (mediaList.length === 0) {
36678
- if (streamViaEditMessage && infoKind === "final" && finalDeliveredForCurrentAssistantMessage && editStreamState.messageId != null) {
36679
- await resetEditStreamForAssistantMessage();
36680
- }
36681
- if (streamViaEditMessage && editStreamState.messageId != null) {
36682
- if (outboundText.trim()) {
36683
- editStreamState.finalTextAccumulator += outboundText;
36684
- }
36685
- if (!editStreamState.finalTextAccumulator.trim() && outboundActions === undefined) {
36686
- return;
36687
- }
36688
- await updateStreamedMessage(editStreamState.finalTextAccumulator, outboundActions);
36689
- delivered = true;
36690
- if (infoKind === "final") {
36691
- finalDeliveredForCurrentAssistantMessage = true;
36692
- }
36693
- statusSink?.({ lastOutboundAt: Date.now() });
36694
- return;
36695
- }
36696
- if (!outboundText.trim())
36697
- return;
36698
- await sendTextFallback(outboundText, true, true);
36699
- statusSink?.({ lastOutboundAt: Date.now() });
36845
+ if (!editStreamState.finalTextAccumulator.trim() && outboundActions === undefined) {
36700
36846
  return;
36701
36847
  }
36702
- if (streamViaEditMessage && editStreamState.messageId != null && outboundText.trim()) {
36703
- await updateStreamedMessage(outboundText, outboundActions);
36848
+ await updateStreamedMessage(editStreamState.finalTextAccumulator, outboundActions);
36849
+ delivered = true;
36850
+ if (infoKind === "final") {
36851
+ finalDeliveredForCurrentAssistantMessage = true;
36704
36852
  }
36705
- for (let index = 0;index < mediaList.length; index++) {
36706
- const mediaUrl = mediaList[index];
36707
- if (!mediaUrl?.trim())
36708
- continue;
36709
- const isFirst = index === 0;
36710
- const shouldAttachActionsToMedia = isFirst && (!(streamViaEditMessage && editStreamState.messageId != null) || !outboundText.trim());
36711
- const caption = isFirst && !(streamViaEditMessage && editStreamState.messageId != null) ? outboundText : "";
36712
- try {
36713
- const media = await uploadInlineMediaFromUrl({
36714
- client,
36715
- cfg,
36716
- accountId: account.accountId,
36717
- mediaUrl
36718
- });
36719
- const sent = await client.sendMessage({
36720
- chatId,
36721
- ...caption ? { text: caption } : {},
36722
- media,
36723
- ...isFirst && replyToMsgId != null ? { replyToMsgId } : {},
36724
- ...shouldAttachActionsToMedia && outboundActions !== undefined ? { actions: outboundActions } : {},
36725
- ...caption ? { parseMarkdown } : {}
36726
- });
36727
- rememberSent(sent.messageId);
36728
- delivered = true;
36729
- } catch (error48) {
36730
- runtime2.error?.(`inline media upload failed; falling back to url text (${String(error48)})`);
36731
- const fallbackText = caption ? `${caption}
36853
+ statusSink?.({ lastOutboundAt: Date.now() });
36854
+ return;
36855
+ }
36856
+ if (!outboundText.trim())
36857
+ return;
36858
+ await sendTextFallback(outboundText, true, true);
36859
+ statusSink?.({ lastOutboundAt: Date.now() });
36860
+ return;
36861
+ }
36862
+ if (streamViaEditMessage && editStreamState.messageId != null && outboundText.trim()) {
36863
+ await updateStreamedMessage(outboundText, outboundActions);
36864
+ }
36865
+ for (let index = 0;index < mediaList.length; index++) {
36866
+ const mediaUrl = mediaList[index];
36867
+ if (!mediaUrl?.trim())
36868
+ continue;
36869
+ const isFirst = index === 0;
36870
+ const shouldAttachActionsToMedia = isFirst && (!(streamViaEditMessage && editStreamState.messageId != null) || !outboundText.trim());
36871
+ const caption = isFirst && !(streamViaEditMessage && editStreamState.messageId != null) ? outboundText : "";
36872
+ try {
36873
+ const media = await uploadInlineMediaFromUrl({
36874
+ client,
36875
+ cfg,
36876
+ accountId: account.accountId,
36877
+ mediaUrl
36878
+ });
36879
+ const sent = await client.sendMessage({
36880
+ chatId,
36881
+ ...caption ? { text: caption } : {},
36882
+ media,
36883
+ ...isFirst && replyToMsgId != null ? { replyToMsgId } : {},
36884
+ ...shouldAttachActionsToMedia && outboundActions !== undefined ? { actions: outboundActions } : {},
36885
+ ...caption ? { parseMarkdown } : {}
36886
+ });
36887
+ rememberSent(sent.messageId);
36888
+ delivered = true;
36889
+ } catch (error48) {
36890
+ runtime2.error?.(`inline media upload failed; falling back to url text (${String(error48)})`);
36891
+ const fallbackText = caption ? `${caption}
36732
36892
 
36733
36893
  Attachment: ${mediaUrl}` : `Attachment: ${mediaUrl}`;
36734
- await sendTextFallback(fallbackText, isFirst, isFirst);
36735
- }
36736
- }
36737
- statusSink?.({ lastOutboundAt: Date.now() });
36738
- },
36739
- onSkip: (_payload, info) => {
36740
- if (info?.reason !== "silent") {
36741
- skippedNonSilent = true;
36742
- }
36743
- },
36744
- onError: (err, info) => {
36745
- failedNonSilent = true;
36746
- runtime2.error?.(`inline ${info?.kind ?? "final"} reply failed: ${String(err)}`);
36894
+ await sendTextFallback(fallbackText, isFirst, isFirst);
36747
36895
  }
36748
- },
36749
- replyOptions
36750
- });
36751
- } catch (error48) {
36752
- dispatchError = error48;
36753
- runtime2.error?.(`inline dispatch failed: ${String(error48)}`);
36754
- }
36755
- if (!delivered && streamViaEditMessage && editStreamState.messageId != null) {
36756
- delivered = true;
36757
- }
36758
- if (!delivered && (dispatchError != null || skippedNonSilent || failedNonSilent)) {
36759
- const fallbackText = dispatchError != null ? "Something went wrong while processing your request. Please try again." : EMPTY_RESPONSE_FALLBACK;
36760
- const sent = await client.sendMessage({
36761
- chatId,
36762
- text: fallbackText,
36763
- ...defaultReplyToMsgId != null ? { replyToMsgId: defaultReplyToMsgId } : {},
36764
- parseMarkdown
36765
- });
36766
- if (sent.messageId != null) {
36767
- rememberBotMessageId(botMessageIdsByChat, chatId, sent.messageId);
36896
+ }
36897
+ statusSink?.({ lastOutboundAt: Date.now() });
36898
+ },
36899
+ onSkip: (_payload, info) => {
36900
+ if (info?.reason !== "silent") {
36901
+ skippedNonSilent = true;
36902
+ }
36903
+ },
36904
+ onError: (err, info) => {
36905
+ failedNonSilent = true;
36906
+ runtime2.error?.(`inline ${info?.kind ?? "final"} reply failed: ${String(err)}`);
36768
36907
  }
36769
- statusSink?.({ lastOutboundAt: Date.now() });
36770
- }
36771
- } finally {
36772
- if (callbackActionEvent && !callbackActionAnswered) {
36773
- try {
36774
- await answerCallbackIfNeeded();
36775
- } catch (error48) {
36776
- runtime2.error?.(`inline callback answer failed: ${String(error48)}`);
36908
+ },
36909
+ replyOptions
36910
+ });
36911
+ } catch (error48) {
36912
+ dispatchError = error48;
36913
+ runtime2.error?.(`inline dispatch failed: ${String(error48)}`);
36914
+ }
36915
+ if (!delivered && streamViaEditMessage && editStreamState.messageId != null) {
36916
+ delivered = true;
36917
+ }
36918
+ if (!delivered && (dispatchError != null || skippedNonSilent || failedNonSilent)) {
36919
+ const fallbackText = dispatchError != null ? "Something went wrong while processing your request. Please try again." : EMPTY_RESPONSE_FALLBACK;
36920
+ const sent = await client.sendMessage({
36921
+ chatId,
36922
+ text: fallbackText,
36923
+ ...defaultReplyToMsgId != null ? { replyToMsgId: defaultReplyToMsgId } : {},
36924
+ parseMarkdown
36925
+ });
36926
+ if (sent.messageId != null) {
36927
+ rememberBotMessageId(botMessageIdsByChat, chatId, sent.messageId);
36928
+ }
36929
+ statusSink?.({ lastOutboundAt: Date.now() });
36930
+ }
36931
+ } finally {
36932
+ if (callbackActionEvent && !callbackActionAnswered) {
36933
+ try {
36934
+ await answerCallbackIfNeeded();
36935
+ } catch (error48) {
36936
+ runtime2.error?.(`inline callback answer failed: ${String(error48)}`);
36937
+ }
36938
+ }
36939
+ }
36940
+ if (isGroup && groupHistoryKey) {
36941
+ clearHistoryEntriesIfEnabled({
36942
+ historyMap: groupPendingHistories,
36943
+ historyKey: groupHistoryKey,
36944
+ limit: historyLimit
36945
+ });
36946
+ }
36947
+ };
36948
+ const { debouncer: inboundDebouncer } = createChannelInboundDebouncer({
36949
+ cfg,
36950
+ channel: CHANNEL_ID,
36951
+ buildKey: (entry) => buildInlineDebounceKey({
36952
+ accountId: account.accountId,
36953
+ chatId: entry.chatId,
36954
+ senderId: entry.msg.fromId
36955
+ }),
36956
+ shouldDebounce: (entry) => {
36957
+ const content = summarizeInlineMessageContent(entry.msg);
36958
+ return shouldDebounceTextInbound({
36959
+ text: buildInlineInboundBodyText(content),
36960
+ cfg,
36961
+ hasMedia: Boolean(content.media || content.attachments.length > 0),
36962
+ ...botUsername ? { commandOptions: { botUsername } } : {}
36963
+ });
36964
+ },
36965
+ onFlush: async (entries) => {
36966
+ const last = entries.at(-1);
36967
+ if (!last)
36968
+ return;
36969
+ if (entries.length === 1) {
36970
+ await handleInboundNow({
36971
+ chatId: last.chatId,
36972
+ msg: last.msg
36973
+ });
36974
+ return;
36975
+ }
36976
+ const combinedText = entries.map((entry) => buildInlineInboundBodyText(summarizeInlineMessageContent(entry.msg))).filter(Boolean).join(`
36977
+ `);
36978
+ if (!combinedText.trim()) {
36979
+ return;
36980
+ }
36981
+ await handleInboundNow({
36982
+ chatId: last.chatId,
36983
+ msg: buildSyntheticInlineTextMessage({
36984
+ base: last.msg,
36985
+ text: combinedText,
36986
+ mentioned: entries.some((entry) => entry.msg.mentioned === true)
36987
+ }),
36988
+ rawBodyOverride: combinedText
36989
+ });
36990
+ },
36991
+ onError: (err, items) => {
36992
+ runtime2.error?.(`inline debounce flush failed: ${String(err)}`);
36993
+ const chatId = items[0]?.chatId;
36994
+ if (chatId == null)
36995
+ return;
36996
+ client.sendMessage({
36997
+ chatId,
36998
+ text: "Something went wrong while processing your message. Please try again."
36999
+ }).then(() => {
37000
+ statusSink?.({ lastOutboundAt: Date.now() });
37001
+ }).catch((sendErr) => {
37002
+ runtime2.error?.(`inline debounce fallback send failed: ${String(sendErr)}`);
37003
+ });
37004
+ }
37005
+ });
37006
+ const loop = (async () => {
37007
+ try {
37008
+ for await (const event of client.events()) {
37009
+ if (abortSignal.aborted)
37010
+ break;
37011
+ const rawEvent = event;
37012
+ if (event.kind === "message.new") {
37013
+ const msg = {
37014
+ ...event.message,
37015
+ chatId: event.chatId
37016
+ };
37017
+ if (msg.out || msg.fromId === meId)
37018
+ continue;
37019
+ await inboundDebouncer.enqueue({
37020
+ chatId: event.chatId,
37021
+ msg
37022
+ });
37023
+ continue;
37024
+ }
37025
+ if (event.kind === "reaction.add") {
37026
+ if (event.reaction.userId === meId)
37027
+ continue;
37028
+ const onBotMessage = await isReactionTargetBotMessage({
37029
+ client,
37030
+ chatId: event.chatId,
37031
+ messageId: event.reaction.messageId,
37032
+ meId,
37033
+ botMessageIdsByChat
37034
+ }).catch((err) => {
37035
+ statusSink?.({ lastError: `getChatHistory (reaction target) failed: ${String(err)}` });
37036
+ return false;
37037
+ });
37038
+ if (!onBotMessage)
37039
+ continue;
37040
+ await handleInboundNow({
37041
+ chatId: event.chatId,
37042
+ msg: {
37043
+ id: event.reaction.messageId,
37044
+ chatId: event.chatId,
37045
+ date: event.date,
37046
+ fromId: event.reaction.userId,
37047
+ message: "",
37048
+ out: false,
37049
+ mentioned: false,
37050
+ replyToMsgId: event.reaction.messageId
37051
+ },
37052
+ reactionEvent: {
37053
+ action: "added",
37054
+ emoji: event.reaction.emoji,
37055
+ targetMessageId: event.reaction.messageId
36777
37056
  }
36778
- }
37057
+ });
37058
+ continue;
36779
37059
  }
36780
- if (isGroup && groupHistoryKey) {
36781
- clearHistoryEntriesIfEnabled({
36782
- historyMap: groupPendingHistories,
36783
- historyKey: groupHistoryKey,
36784
- limit: historyLimit
37060
+ if (event.kind === "reaction.delete") {
37061
+ if (event.userId === meId)
37062
+ continue;
37063
+ const onBotMessage = await isReactionTargetBotMessage({
37064
+ client,
37065
+ chatId: event.chatId,
37066
+ messageId: event.messageId,
37067
+ meId,
37068
+ botMessageIdsByChat
37069
+ }).catch((err) => {
37070
+ statusSink?.({ lastError: `getChatHistory (reaction target) failed: ${String(err)}` });
37071
+ return false;
37072
+ });
37073
+ if (!onBotMessage)
37074
+ continue;
37075
+ await handleInboundNow({
37076
+ chatId: event.chatId,
37077
+ msg: {
37078
+ id: event.messageId,
37079
+ chatId: event.chatId,
37080
+ date: event.date,
37081
+ fromId: event.userId,
37082
+ message: "",
37083
+ out: false,
37084
+ mentioned: false,
37085
+ replyToMsgId: event.messageId
37086
+ },
37087
+ reactionEvent: {
37088
+ action: "removed",
37089
+ emoji: event.emoji,
37090
+ targetMessageId: event.messageId
37091
+ }
36785
37092
  });
37093
+ continue;
37094
+ }
37095
+ if (rawEvent["kind"] === "message.action.invoke") {
37096
+ const actorUserId = rawEvent["actorUserId"];
37097
+ const interactionId = rawEvent["interactionId"];
37098
+ const actionId = rawEvent["actionId"];
37099
+ const targetMessageId = rawEvent["messageId"];
37100
+ const data = rawEvent["data"];
37101
+ const eventChatId = rawEvent["chatId"];
37102
+ const eventDate = rawEvent["date"];
37103
+ if (!actorUserId || !interactionId || !actionId || !targetMessageId || !eventChatId || !eventDate || !data) {
37104
+ continue;
37105
+ }
37106
+ if (actorUserId === meId)
37107
+ continue;
37108
+ await handleInboundNow({
37109
+ chatId: eventChatId,
37110
+ msg: {
37111
+ id: targetMessageId,
37112
+ chatId: eventChatId,
37113
+ date: eventDate,
37114
+ fromId: actorUserId,
37115
+ message: "",
37116
+ out: false,
37117
+ mentioned: false,
37118
+ replyToMsgId: targetMessageId
37119
+ },
37120
+ callbackActionEvent: {
37121
+ interactionId,
37122
+ actionId,
37123
+ targetMessageId,
37124
+ data
37125
+ }
37126
+ });
37127
+ continue;
36786
37128
  }
36787
37129
  }
36788
37130
  } catch (err) {
@@ -36790,6 +37132,10 @@ Attachment: ${mediaUrl}` : `Attachment: ${mediaUrl}`;
36790
37132
  runtime2.error?.(`inline monitor loop crashed: ${String(err)}`);
36791
37133
  }
36792
37134
  })();
37135
+ const diagnosticsTimer = setInterval(() => {
37136
+ pushDiagnostics();
37137
+ }, 15000);
37138
+ diagnosticsTimer.unref?.();
36793
37139
  let stopPromise = null;
36794
37140
  const stop = async () => {
36795
37141
  if (stopPromise) {
@@ -36797,6 +37143,7 @@ Attachment: ${mediaUrl}` : `Attachment: ${mediaUrl}`;
36797
37143
  return;
36798
37144
  }
36799
37145
  stopPromise = (async () => {
37146
+ clearInterval(diagnosticsTimer);
36800
37147
  await client.close().catch(() => {});
36801
37148
  await loop.catch(() => {});
36802
37149
  })();
@@ -36809,6 +37156,7 @@ Attachment: ${mediaUrl}` : `Attachment: ${mediaUrl}`;
36809
37156
  }
36810
37157
 
36811
37158
  // src/inline/actions.ts
37159
+ import { createMessageToolButtonsSchema } from "openclaw/plugin-sdk/channel-actions";
36812
37160
  import {
36813
37161
  normalizeInteractiveReply,
36814
37162
  reduceInteractiveReply
@@ -37597,7 +37945,7 @@ function describeInlineMessageTool({
37597
37945
  const schema = buttonsEnabled ? [
37598
37946
  {
37599
37947
  properties: {
37600
- buttons: createMessageToolButtonsSchemaCompat()
37948
+ buttons: createMessageToolButtonsSchema()
37601
37949
  }
37602
37950
  }
37603
37951
  ] : [];
@@ -38649,6 +38997,7 @@ async function probeInlineAccount(account, timeoutMs) {
38649
38997
 
38650
38998
  // src/inline/status-issues.ts
38651
38999
  import { asString, isRecord as isRecord5 } from "openclaw/plugin-sdk/status-helpers";
39000
+ var RECENT_RUNTIME_ISSUE_MS = 30 * 60 * 1000;
38652
39001
  function readInlineProbeSummary(value) {
38653
39002
  if (!isRecord5(value)) {
38654
39003
  return {};
@@ -38666,6 +39015,38 @@ function readInlineProbeSummary(value) {
38666
39015
  function looksLikeAuthError(text) {
38667
39016
  return /(401|403|unauth|forbidden|invalid token|token invalid|unauthorized)/i.test(text);
38668
39017
  }
39018
+ function readInlineDiagnosticsSummary(value) {
39019
+ if (!isRecord5(value)) {
39020
+ return {};
39021
+ }
39022
+ const protocolValue = isRecord5(value.protocol) ? value.protocol : undefined;
39023
+ const transportValue = (protocolValue && isRecord5(protocolValue.transport) ? protocolValue.transport : undefined) ?? (isRecord5(value.transport) ? value.transport : undefined);
39024
+ const pingValue = protocolValue && isRecord5(protocolValue.ping) ? protocolValue.ping : undefined;
39025
+ return {
39026
+ ...protocolValue ? {
39027
+ protocol: {
39028
+ ...typeof protocolValue.lastFailureAt === "number" ? { lastFailureAt: protocolValue.lastFailureAt } : {},
39029
+ ...asString(protocolValue.lastFailureReason) ? { lastFailureReason: asString(protocolValue.lastFailureReason) } : {},
39030
+ ...pingValue && typeof pingValue.lastTimeoutAt === "number" ? {
39031
+ ping: {
39032
+ lastTimeoutAt: pingValue.lastTimeoutAt
39033
+ }
39034
+ } : {}
39035
+ }
39036
+ } : {},
39037
+ ...transportValue ? {
39038
+ transport: {
39039
+ ...typeof transportValue.reconnectCount === "number" ? { reconnectCount: transportValue.reconnectCount } : {},
39040
+ ...asString(transportValue.lastReconnectCause) ? { lastReconnectCause: asString(transportValue.lastReconnectCause) } : {}
39041
+ }
39042
+ } : {}
39043
+ };
39044
+ }
39045
+ function isRecentTimestamp(timestamp) {
39046
+ if (typeof timestamp !== "number" || !Number.isFinite(timestamp) || timestamp <= 0)
39047
+ return false;
39048
+ return Date.now() - timestamp <= RECENT_RUNTIME_ISSUE_MS;
39049
+ }
38669
39050
  function collectInlineStatusIssues(accounts) {
38670
39051
  const issues = [];
38671
39052
  for (const entry of accounts) {
@@ -38721,6 +39102,25 @@ function collectInlineStatusIssues(accounts) {
38721
39102
  fix: "Verify token/baseUrl connectivity, then re-run channel status."
38722
39103
  });
38723
39104
  }
39105
+ const diagnostics = readInlineDiagnosticsSummary(entry.diagnostics);
39106
+ if (isRecentTimestamp(diagnostics.protocol?.lastFailureAt) && (diagnostics.transport?.reconnectCount ?? 0) >= 3) {
39107
+ issues.push({
39108
+ channel: "inline",
39109
+ accountId,
39110
+ kind: "runtime",
39111
+ message: `Inline connection is flapping (${diagnostics.transport?.reconnectCount ?? 0} reconnects). ` + `${diagnostics.protocol?.lastFailureReason ?? diagnostics.transport?.lastReconnectCause ?? "Recent reconnect failures detected."}`,
39112
+ fix: "Inspect gateway logs for websocket close/error details and verify Inline API/network stability."
39113
+ });
39114
+ }
39115
+ if (isRecentTimestamp(diagnostics.protocol?.ping?.lastTimeoutAt)) {
39116
+ issues.push({
39117
+ channel: "inline",
39118
+ accountId,
39119
+ kind: "runtime",
39120
+ message: "Inline ping watchdog triggered a reconnect recently.",
39121
+ fix: "Check websocket latency/packet loss and compare last pong timing in the Inline diagnostics snapshot."
39122
+ });
39123
+ }
38724
39124
  }
38725
39125
  return issues;
38726
39126
  }
@@ -39735,22 +40135,29 @@ var inlineChannelPlugin = {
39735
40135
  buildChannelSummary: ({ snapshot }) => buildTokenChannelStatusSummary(snapshot),
39736
40136
  probeAccount: async ({ account, timeoutMs }) => await probeInlineAccount(account, timeoutMs),
39737
40137
  formatCapabilitiesProbe: ({ probe }) => formatInlineCapabilitiesProbeLines(probe),
39738
- buildAccountSnapshot: ({ account, runtime: runtime2, probe }) => ({
39739
- accountId: account.accountId,
39740
- name: account.name,
39741
- enabled: account.enabled,
39742
- configured: account.configured,
39743
- baseUrl: account.baseUrl ? "[set]" : "[missing]",
39744
- tokenSource: account.token ? "config" : account.tokenFile ? "file" : "missing",
39745
- running: runtime2?.running ?? false,
39746
- lastStartAt: runtime2?.lastStartAt ?? null,
39747
- lastStopAt: runtime2?.lastStopAt ?? null,
39748
- lastError: runtime2?.lastError ?? null,
39749
- lastInboundAt: runtime2?.lastInboundAt ?? null,
39750
- lastOutboundAt: runtime2?.lastOutboundAt ?? null,
39751
- lastProbeAt: runtime2?.lastProbeAt ?? null,
39752
- ...probe !== undefined ? { probe } : {}
39753
- })
40138
+ buildAccountSnapshot: ({ account, runtime: runtime2, probe }) => {
40139
+ const snapshot = {
40140
+ accountId: account.accountId,
40141
+ name: account.name,
40142
+ enabled: account.enabled,
40143
+ configured: account.configured,
40144
+ baseUrl: account.baseUrl ? "[set]" : "[missing]",
40145
+ tokenSource: account.token ? "config" : account.tokenFile ? "file" : "missing",
40146
+ running: runtime2?.running ?? false,
40147
+ lastStartAt: runtime2?.lastStartAt ?? null,
40148
+ lastStopAt: runtime2?.lastStopAt ?? null,
40149
+ lastError: runtime2?.lastError ?? null,
40150
+ lastInboundAt: runtime2?.lastInboundAt ?? null,
40151
+ lastOutboundAt: runtime2?.lastOutboundAt ?? null,
40152
+ lastProbeAt: runtime2?.lastProbeAt ?? null,
40153
+ ...probe !== undefined ? { probe } : {}
40154
+ };
40155
+ const diagnostics = runtime2?.diagnostics;
40156
+ if (diagnostics !== undefined) {
40157
+ snapshot.diagnostics = diagnostics;
40158
+ }
40159
+ return snapshot;
40160
+ }
39754
40161
  },
39755
40162
  gateway: {
39756
40163
  startAccount: async (ctx) => {
@@ -40516,7 +40923,12 @@ async function callInlineBotApi(params) {
40516
40923
  if (params.body !== undefined) {
40517
40924
  request.body = JSON.stringify(params.body);
40518
40925
  }
40519
- const response = await fetch(url2, request);
40926
+ let response;
40927
+ try {
40928
+ response = await fetch(url2, request);
40929
+ } catch (error48) {
40930
+ throw new Error(`inline_bot_commands: ${params.method} ${redactInlineBotApiUrl(url2)} fetch failed: ${summarizeInlineBotApiError(error48)}`);
40931
+ }
40520
40932
  const payload = await response.json().catch(() => null);
40521
40933
  return { response, payload };
40522
40934
  };
@@ -40546,6 +40958,20 @@ async function callInlineBotApi(params) {
40546
40958
  }
40547
40959
  return resolve(headerResult);
40548
40960
  }
40961
+ function summarizeInlineBotApiError(error48) {
40962
+ if (error48 instanceof Error) {
40963
+ return `${error48.name}: ${error48.message}`;
40964
+ }
40965
+ return String(error48);
40966
+ }
40967
+ function redactInlineBotApiUrl(raw) {
40968
+ try {
40969
+ const url2 = new URL(raw);
40970
+ return `${url2.protocol}//${url2.host}${url2.pathname.replace(/\/bot[^/]*\//, "/bot<redacted>/")}`;
40971
+ } catch {
40972
+ return raw.replace(/\/bot[^/]*\//, "/bot<redacted>/");
40973
+ }
40974
+ }
40549
40975
 
40550
40976
  // src/inline/bot-commands-tool.ts
40551
40977
  var BOT_COMMAND_LIMIT = 100;
@@ -40706,6 +41132,9 @@ function createInlineBotCommandsTool(ctx) {
40706
41132
  }
40707
41133
 
40708
41134
  // src/inline/bot-commands-sync.ts
41135
+ import { listNativeCommandSpecsForConfig } from "openclaw/plugin-sdk/native-command-registry";
41136
+ import { getPluginCommandSpecs } from "openclaw/plugin-sdk/plugin-runtime";
41137
+ import { listSkillCommandsForAgents } from "openclaw/plugin-sdk/skill-commands-runtime";
40709
41138
  var INLINE_BASE_NATIVE_COMMANDS = [
40710
41139
  { command: "help", description: "Show available commands." },
40711
41140
  { command: "commands", description: "List all slash commands." },
@@ -40735,15 +41164,6 @@ var INLINE_BASE_NATIVE_COMMANDS = [
40735
41164
  ];
40736
41165
  var INLINE_COMMAND_NAME_RE = /^[a-z0-9_]{1,32}$/;
40737
41166
  var INLINE_COMMAND_LIMIT = 100;
40738
- var FALLBACK_NATIVE_COMMAND_HELPERS = {
40739
- available: false,
40740
- listNativeCommandSpecsForConfig: () => [],
40741
- listSkillCommandsForAgents: () => []
40742
- };
40743
- var FALLBACK_PLUGIN_COMMAND_SPECS = {
40744
- available: false,
40745
- specs: []
40746
- };
40747
41167
  function normalizeDynamicCommandName(raw) {
40748
41168
  const trimmed = raw.trim().toLowerCase();
40749
41169
  const withoutSlash = trimmed.startsWith("/") ? trimmed.slice(1) : trimmed;
@@ -40777,10 +41197,12 @@ async function buildInlineNativeCommandsForConfig(params) {
40777
41197
  if (params.cfg.commands?.debug === true) {
40778
41198
  commands.push({ command: "debug", description: "Set runtime debug overrides." });
40779
41199
  }
40780
- const { listNativeCommandSpecsForConfig, listSkillCommandsForAgents } = params.nativeHelpers;
40781
41200
  const skillCommands = shouldSyncInlineNativeSkills(params.cfg) ? listSkillCommandsForAgents({ cfg: params.cfg }) : [];
40782
- const nativeSpecs = listNativeCommandSpecsForConfig(params.cfg, { skillCommands });
40783
- const { specs: pluginSpecs } = params.pluginSpecs;
41201
+ const nativeSpecs = listNativeCommandSpecsForConfig(params.cfg, {
41202
+ skillCommands,
41203
+ provider: "telegram"
41204
+ });
41205
+ const pluginSpecs = getPluginCommandSpecs("inline");
40784
41206
  const seen = new Set;
40785
41207
  const resolved = [];
40786
41208
  for (const base of commands) {
@@ -40797,19 +41219,13 @@ async function buildInlineNativeCommandsForConfig(params) {
40797
41219
  async function syncInlineNativeCommands(params) {
40798
41220
  const accountIds = listInlineAccountIds(params.cfg);
40799
41221
  const nativeEnabled = shouldSyncInlineNativeCommands(params.cfg);
40800
- const nativeHelpers = nativeEnabled ? await loadNativeCommandHelpersCompat() : FALLBACK_NATIVE_COMMAND_HELPERS;
40801
- const pluginSpecs = nativeEnabled ? await loadPluginCommandSpecsCompat("inline") : FALLBACK_PLUGIN_COMMAND_SPECS;
40802
- const usingSdkSource = nativeHelpers.available || pluginSpecs.available;
40803
41222
  const allCommands = nativeEnabled ? await buildInlineNativeCommandsForConfig({
40804
- cfg: params.cfg,
40805
- nativeHelpers,
40806
- pluginSpecs
41223
+ cfg: params.cfg
40807
41224
  }) : [];
40808
41225
  const commands = allCommands.slice(0, INLINE_COMMAND_LIMIT);
40809
41226
  if (allCommands.length > INLINE_COMMAND_LIMIT) {
40810
41227
  params.logger?.warn?.(`[inline] native command sync truncating ${allCommands.length} commands to ${INLINE_COMMAND_LIMIT}`);
40811
41228
  }
40812
- params.logger?.info?.(`[inline] native command source: ${usingSdkSource ? "plugin-sdk" : "fallback"}`);
40813
41229
  let synced = 0;
40814
41230
  let failed = 0;
40815
41231
  for (const accountId of accountIds) {
@@ -40895,5 +41311,5 @@ export {
40895
41311
  src_default as default
40896
41312
  };
40897
41313
 
40898
- //# debugId=CE0AB31A68AAA68C64756E2164756E21
41314
+ //# debugId=1C8239561365068664756E2164756E21
40899
41315
  //# sourceMappingURL=index.js.map