@inline-openclaw/inline 0.0.25 → 0.0.27

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
@@ -5547,6 +5547,14 @@ var require_websocket_server = __commonJS((exports, module) => {
5547
5547
  }
5548
5548
  });
5549
5549
 
5550
+ // src/inline/channel.ts
5551
+ import {
5552
+ deleteAccountFromConfigSection,
5553
+ setAccountEnabledInConfigSection
5554
+ } from "openclaw/plugin-sdk/core";
5555
+ import { buildDmGroupAccountAllowlistAdapter } from "openclaw/plugin-sdk/allowlist-config-edit";
5556
+ import { buildTokenChannelStatusSummary } from "openclaw/plugin-sdk/status-helpers";
5557
+
5550
5558
  // ../protocol/dist/core.js
5551
5559
  var import_runtime = __toESM(require_commonjs(), 1);
5552
5560
  var import_runtime2 = __toESM(require_commonjs(), 1);
@@ -34023,6 +34031,7 @@ import { mkdir } from "node:fs/promises";
34023
34031
  import path2 from "node:path";
34024
34032
 
34025
34033
  // src/sdk-runtime-compat.ts
34034
+ import { createMessageToolButtonsSchema } from "openclaw/plugin-sdk/channel-actions";
34026
34035
  var HISTORY_CONTEXT_MARKER = "[Chat messages since your last reply - for context]";
34027
34036
  var CURRENT_MESSAGE_MARKER = "[Current message - respond to this]";
34028
34037
  var MAX_HISTORY_KEYS = 1000;
@@ -34113,24 +34122,13 @@ function recordPendingHistoryEntryIfEnabled(params) {
34113
34122
  limit: params.limit
34114
34123
  });
34115
34124
  }
34125
+ var TYPEBOX_OPTIONAL_SYMBOL = Symbol.for("TypeBox.Optional");
34126
+ function markTypeBoxOptional(schema) {
34127
+ schema[TYPEBOX_OPTIONAL_SYMBOL] = "Optional";
34128
+ return schema;
34129
+ }
34116
34130
  function createMessageToolButtonsSchemaCompat() {
34117
- return {
34118
- type: "array",
34119
- description: "Button rows for channels that support button-style actions.",
34120
- items: {
34121
- type: "array",
34122
- items: {
34123
- type: "object",
34124
- additionalProperties: false,
34125
- required: ["text", "callback_data"],
34126
- properties: {
34127
- text: { type: "string" },
34128
- callback_data: { type: "string" },
34129
- style: { type: "string", enum: ["danger", "success", "primary"] }
34130
- }
34131
- }
34132
- }
34133
- };
34131
+ return markTypeBoxOptional(createMessageToolButtonsSchema());
34134
34132
  }
34135
34133
  function extensionForMimeCompat(mime) {
34136
34134
  const normalized = mime?.trim().toLowerCase();
@@ -35176,6 +35174,7 @@ var REACTION_TARGET_LOOKUP_LIMIT = 8;
35176
35174
  var REPLY_TARGET_LOOKUP_LIMIT = 8;
35177
35175
  var ATTACHMENT_CONTEXT_LIMIT = 6;
35178
35176
  var DEFAULT_INLINE_MEDIA_MAX_BYTES = 300 * 1024 * 1024;
35177
+ var EMPTY_RESPONSE_FALLBACK = "No response generated. Please try again.";
35179
35178
  var GET_MESSAGES_METHOD2 = typeof Method.GET_MESSAGES === "number" && Number.isInteger(Method.GET_MESSAGES) && Method.GET_MESSAGES > 0 ? Method.GET_MESSAGES : null;
35180
35179
  function normalizeAllowEntry(raw) {
35181
35180
  return raw.trim().replace(/^inline:/i, "").replace(/^user:/i, "");
@@ -36488,193 +36487,287 @@ ${currentEntityText}` : null
36488
36487
  failed: false,
36489
36488
  opChain: Promise.resolve()
36490
36489
  };
36490
+ let finalDeliveredForCurrentAssistantMessage = false;
36491
+ const resetEditStreamForAssistantMessage = async () => {
36492
+ 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) {
36527
+ const sent = await client.sendMessage({
36528
+ chatId,
36529
+ text: nextText,
36530
+ ...defaultReplyToMsgId != null ? { replyToMsgId: defaultReplyToMsgId } : {},
36531
+ parseMarkdown
36532
+ });
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 {
36539
+ const result = await client.invokeRaw(Method.EDIT_MESSAGE, {
36540
+ oneofKind: "editMessage",
36541
+ editMessage: {
36542
+ messageId: editStreamState.messageId,
36543
+ peerId: buildChatPeer2(chatId),
36544
+ text: nextText,
36545
+ parseMarkdown
36546
+ }
36547
+ });
36548
+ if (result.oneofKind !== "editMessage") {
36549
+ throw new Error(`inline edit stream: expected editMessage result, got ${String(result.oneofKind)}`);
36550
+ }
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
+ };
36491
36561
  const replyOptions = {
36492
36562
  ...onModelSelected ? { onModelSelected } : {},
36493
36563
  blockReplyTimeoutMs: 25000,
36564
+ ...streamViaEditMessage ? {
36565
+ onAssistantMessageStart: async () => {
36566
+ await resetEditStreamOnBoundary();
36567
+ }
36568
+ } : {},
36494
36569
  ...streamViaEditMessage ? {
36495
36570
  onPartialReply: async (payload) => {
36496
- if (editStreamState.failed)
36497
- return;
36498
- if ((payload.mediaUrls?.length ?? 0) > 0)
36499
- return;
36500
- const partialText = typeof payload.text === "string" ? payload.text : "";
36501
- if (!partialText || partialText === editStreamState.lastPartialText)
36502
- return;
36503
- editStreamState.lastPartialText = partialText;
36504
- const nextText = rewriteNumericMentionsToUsernames(extractCompleteParagraphText(partialText), senderProfilesById).trim();
36505
- if (!nextText || nextText === editStreamState.accumulatedText)
36506
- return;
36507
- editStreamState.opChain = editStreamState.opChain.then(async () => {
36508
- if (editStreamState.failed)
36509
- return;
36510
- if (!nextText || nextText === editStreamState.accumulatedText)
36511
- return;
36512
- try {
36513
- if (editStreamState.messageId == null) {
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 {}
36624
+ }
36625
+ if (replyToMsgId == null && isGroup && msg.replyToMsgId != null) {
36626
+ replyToMsgId = msg.id;
36627
+ }
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;
36514
36636
  const sent = await client.sendMessage({
36515
36637
  chatId,
36516
- text: nextText,
36517
- ...defaultReplyToMsgId != null ? { replyToMsgId: defaultReplyToMsgId } : {},
36638
+ text,
36639
+ ...includeReplyTo && replyToMsgId != null ? { replyToMsgId } : {},
36640
+ ...includeActions && outboundActions !== undefined ? { actions: outboundActions } : {},
36518
36641
  parseMarkdown
36519
36642
  });
36520
- if (sent.messageId == null) {
36521
- throw new Error("inline edit stream: sendMessage returned no messageId");
36522
- }
36523
- editStreamState.messageId = sent.messageId;
36524
- rememberBotMessageId(botMessageIdsByChat, chatId, sent.messageId);
36525
- } else {
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;
36526
36657
  const result = await client.invokeRaw(Method.EDIT_MESSAGE, {
36527
36658
  oneofKind: "editMessage",
36528
36659
  editMessage: {
36529
36660
  messageId: editStreamState.messageId,
36530
36661
  peerId: buildChatPeer2(chatId),
36531
- text: nextText,
36532
- parseMarkdown
36662
+ text: textForEdit,
36663
+ parseMarkdown,
36664
+ ...actions !== undefined ? { actions } : {}
36533
36665
  }
36534
36666
  });
36535
36667
  if (result.oneofKind !== "editMessage") {
36536
36668
  throw new Error(`inline edit stream: expected editMessage result, got ${String(result.oneofKind)}`);
36537
36669
  }
36538
- }
36539
- editStreamState.accumulatedText = nextText;
36540
- statusSink?.({ lastOutboundAt: Date.now() });
36541
- } catch (error48) {
36542
- editStreamState.failed = true;
36543
- runtime2.error?.(`inline edit stream failed: ${String(error48)}`);
36544
- }
36545
- });
36546
- await editStreamState.opChain;
36547
- }
36548
- } : {},
36549
- ...typeof disableBlockStreaming === "boolean" ? { disableBlockStreaming } : {}
36550
- };
36551
- try {
36552
- await core3.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
36553
- ctx: ctxPayload,
36554
- cfg,
36555
- dispatcherOptions: {
36556
- ...prefixOptions,
36557
- ...typingCallbacks ? { typingCallbacks } : {},
36558
- deliver: async (payload) => {
36559
- const rawText = payload.text ?? "";
36560
- const mediaList = payload.mediaUrls?.length ? payload.mediaUrls : payload.mediaUrl ? [payload.mediaUrl] : [];
36561
- const outboundText = rewriteNumericMentionsToUsernames(rawText, senderProfilesById);
36562
- const outboundActions = resolveInlineReplyActions(payload);
36563
- let replyToMsgId;
36564
- if (payload.replyToId != null) {
36565
- try {
36566
- replyToMsgId = BigInt(payload.replyToId);
36567
- } catch {}
36568
- }
36569
- if (replyToMsgId == null && isGroup && msg.replyToMsgId != null) {
36570
- replyToMsgId = msg.id;
36571
- }
36572
- const rememberSent = (messageId) => {
36573
- if (messageId != null) {
36574
- rememberBotMessageId(botMessageIdsByChat, chatId, messageId);
36575
- }
36576
- };
36577
- const sendTextFallback = async (text, includeReplyTo, includeActions) => {
36578
- if (!text.trim())
36579
- return;
36580
- const sent = await client.sendMessage({
36581
- chatId,
36582
- text,
36583
- ...includeReplyTo && replyToMsgId != null ? { replyToMsgId } : {},
36584
- ...includeActions && outboundActions !== undefined ? { actions: outboundActions } : {},
36585
- parseMarkdown
36586
- });
36587
- rememberSent(sent.messageId);
36588
- };
36589
- const updateStreamedMessage = async (text, actions) => {
36590
- await editStreamState.opChain;
36591
- if (editStreamState.messageId == null)
36592
- return false;
36593
- const nextText = text.trim();
36594
- const textForEdit = nextText || editStreamState.accumulatedText;
36595
- if (!textForEdit)
36596
- return true;
36597
- const shouldSkipTextUpdate = !editStreamState.failed && textForEdit === editStreamState.accumulatedText;
36598
- if (shouldSkipTextUpdate && actions === undefined)
36599
- return true;
36600
- const result = await client.invokeRaw(Method.EDIT_MESSAGE, {
36601
- oneofKind: "editMessage",
36602
- editMessage: {
36603
- messageId: editStreamState.messageId,
36604
- peerId: buildChatPeer2(chatId),
36605
- text: textForEdit,
36606
- parseMarkdown,
36607
- ...actions !== undefined ? { actions } : {}
36670
+ if (!shouldSkipTextUpdate) {
36671
+ editStreamState.accumulatedText = textForEdit;
36672
+ editStreamState.lastPartialText = textForEdit;
36608
36673
  }
36609
- });
36610
- if (result.oneofKind !== "editMessage") {
36611
- throw new Error(`inline edit stream: expected editMessage result, got ${String(result.oneofKind)}`);
36612
- }
36613
- if (!shouldSkipTextUpdate) {
36614
- editStreamState.accumulatedText = textForEdit;
36615
- editStreamState.lastPartialText = textForEdit;
36616
- }
36617
- editStreamState.failed = false;
36618
- return true;
36619
- };
36620
- if (mediaList.length === 0) {
36621
- if (streamViaEditMessage && editStreamState.messageId != null) {
36622
- if (outboundText.trim()) {
36623
- editStreamState.finalTextAccumulator += outboundText;
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();
36624
36680
  }
36625
- if (!editStreamState.finalTextAccumulator.trim() && outboundActions === undefined) {
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() });
36626
36694
  return;
36627
36695
  }
36628
- await updateStreamedMessage(editStreamState.finalTextAccumulator, outboundActions);
36696
+ if (!outboundText.trim())
36697
+ return;
36698
+ await sendTextFallback(outboundText, true, true);
36629
36699
  statusSink?.({ lastOutboundAt: Date.now() });
36630
36700
  return;
36631
36701
  }
36632
- if (!outboundText.trim())
36633
- return;
36634
- await sendTextFallback(outboundText, true, true);
36635
- statusSink?.({ lastOutboundAt: Date.now() });
36636
- return;
36637
- }
36638
- if (streamViaEditMessage && editStreamState.messageId != null && outboundText.trim()) {
36639
- await updateStreamedMessage(outboundText, outboundActions);
36640
- }
36641
- for (let index = 0;index < mediaList.length; index++) {
36642
- const mediaUrl = mediaList[index];
36643
- if (!mediaUrl?.trim())
36644
- continue;
36645
- const isFirst = index === 0;
36646
- const shouldAttachActionsToMedia = isFirst && (!(streamViaEditMessage && editStreamState.messageId != null) || !outboundText.trim());
36647
- const caption = isFirst && !(streamViaEditMessage && editStreamState.messageId != null) ? outboundText : "";
36648
- try {
36649
- const media = await uploadInlineMediaFromUrl({
36650
- client,
36651
- cfg,
36652
- accountId: account.accountId,
36653
- mediaUrl
36654
- });
36655
- const sent = await client.sendMessage({
36656
- chatId,
36657
- ...caption ? { text: caption } : {},
36658
- media,
36659
- ...isFirst && replyToMsgId != null ? { replyToMsgId } : {},
36660
- ...shouldAttachActionsToMedia && outboundActions !== undefined ? { actions: outboundActions } : {},
36661
- ...caption ? { parseMarkdown } : {}
36662
- });
36663
- rememberSent(sent.messageId);
36664
- } catch (error48) {
36665
- runtime2.error?.(`inline media upload failed; falling back to url text (${String(error48)})`);
36666
- const fallbackText = caption ? `${caption}
36702
+ if (streamViaEditMessage && editStreamState.messageId != null && outboundText.trim()) {
36703
+ await updateStreamedMessage(outboundText, outboundActions);
36704
+ }
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}
36667
36732
 
36668
36733
  Attachment: ${mediaUrl}` : `Attachment: ${mediaUrl}`;
36669
- await sendTextFallback(fallbackText, isFirst, isFirst);
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;
36670
36742
  }
36743
+ },
36744
+ onError: (err, info) => {
36745
+ failedNonSilent = true;
36746
+ runtime2.error?.(`inline ${info?.kind ?? "final"} reply failed: ${String(err)}`);
36671
36747
  }
36672
- statusSink?.({ lastOutboundAt: Date.now() });
36673
36748
  },
36674
- onError: (err, info) => runtime2.error?.(`inline ${info.kind} reply failed: ${String(err)}`)
36675
- },
36676
- replyOptions
36677
- });
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);
36768
+ }
36769
+ statusSink?.({ lastOutboundAt: Date.now() });
36770
+ }
36678
36771
  } finally {
36679
36772
  if (callbackActionEvent && !callbackActionAnswered) {
36680
36773
  try {
@@ -36715,6 +36808,12 @@ Attachment: ${mediaUrl}` : `Attachment: ${mediaUrl}`;
36715
36808
  return { stop, done: loop.catch(() => {}) };
36716
36809
  }
36717
36810
 
36811
+ // src/inline/actions.ts
36812
+ import {
36813
+ normalizeInteractiveReply,
36814
+ reduceInteractiveReply
36815
+ } from "openclaw/plugin-sdk/interactive-runtime";
36816
+
36718
36817
  // src/inline/space-members.ts
36719
36818
  function buildInlineUserDisplayName(user) {
36720
36819
  const explicit = [user.firstName?.trim(), user.lastName?.trim()].filter(Boolean).join(" ");
@@ -36843,9 +36942,78 @@ function normalizeReplyMarkupButtons(raw) {
36843
36942
  }
36844
36943
  return rows;
36845
36944
  }
36945
+ function chunkInteractiveButtons(buttons, rows) {
36946
+ for (let i = 0;i < buttons.length; i += INLINE_ACTION_MAX_PER_ROW2) {
36947
+ const row = buttons.slice(i, i + INLINE_ACTION_MAX_PER_ROW2).map((button) => {
36948
+ const text = button.label.trim();
36949
+ const callbackData = button.value.trim();
36950
+ if (!text || !callbackData) {
36951
+ return null;
36952
+ }
36953
+ return { text, callback_data: callbackData };
36954
+ }).filter((button) => button != null);
36955
+ if (row.length === 0)
36956
+ continue;
36957
+ rows.push(row);
36958
+ if (rows.length >= INLINE_ACTION_MAX_ROWS2)
36959
+ return;
36960
+ }
36961
+ }
36962
+ function resolveInlineInteractiveButtonsParam(params) {
36963
+ if (!Object.prototype.hasOwnProperty.call(params, "interactive")) {
36964
+ return;
36965
+ }
36966
+ let rawInteractive = params.interactive;
36967
+ if (typeof rawInteractive === "string") {
36968
+ const trimmed = rawInteractive.trim();
36969
+ if (!trimmed) {
36970
+ return;
36971
+ }
36972
+ try {
36973
+ rawInteractive = JSON.parse(trimmed);
36974
+ } catch {
36975
+ throw new Error("inline action: interactive must be valid JSON");
36976
+ }
36977
+ }
36978
+ const interactive = normalizeInteractiveReply(rawInteractive);
36979
+ if (!interactive) {
36980
+ return;
36981
+ }
36982
+ const rows = reduceInteractiveReply(interactive, [], (state, block) => {
36983
+ if (state.length >= INLINE_ACTION_MAX_ROWS2) {
36984
+ return state;
36985
+ }
36986
+ if (block.type === "buttons") {
36987
+ chunkInteractiveButtons(block.buttons, state);
36988
+ return state;
36989
+ }
36990
+ if (block.type === "select") {
36991
+ chunkInteractiveButtons(block.options.map((option) => ({ label: option.label, value: option.value })), state);
36992
+ }
36993
+ return state;
36994
+ });
36995
+ return rows.length > 0 ? rows : undefined;
36996
+ }
36997
+ function toInlineMessageActions(rows) {
36998
+ return {
36999
+ rows: rows.map((row, rowIndex) => ({
37000
+ actions: row.map((button, buttonIndex) => ({
37001
+ actionId: `btn_${rowIndex + 1}_${buttonIndex + 1}`,
37002
+ text: button.text,
37003
+ action: {
37004
+ oneofKind: "callback",
37005
+ callback: {
37006
+ data: new TextEncoder().encode(button.callback_data)
37007
+ }
37008
+ }
37009
+ }))
37010
+ }))
37011
+ };
37012
+ }
36846
37013
  function resolveInlineMessageActionsParam(params) {
36847
37014
  if (!Object.prototype.hasOwnProperty.call(params, "buttons")) {
36848
- return;
37015
+ const interactiveRows = resolveInlineInteractiveButtonsParam(params);
37016
+ return interactiveRows ? toInlineMessageActions(interactiveRows) : undefined;
36849
37017
  }
36850
37018
  let rawButtons = params.buttons;
36851
37019
  if (typeof rawButtons === "string") {
@@ -36867,20 +37035,7 @@ function resolveInlineMessageActionsParam(params) {
36867
37035
  throw new Error("inline action: buttons must be an array of button rows");
36868
37036
  }
36869
37037
  const rows = normalizeReplyMarkupButtons(rawButtons);
36870
- return {
36871
- rows: rows.map((row, rowIndex) => ({
36872
- actions: row.map((button, buttonIndex) => ({
36873
- actionId: `btn_${rowIndex + 1}_${buttonIndex + 1}`,
36874
- text: button.text,
36875
- action: {
36876
- oneofKind: "callback",
36877
- callback: {
36878
- data: new TextEncoder().encode(button.callback_data)
36879
- }
36880
- }
36881
- }))
36882
- }))
36883
- };
37038
+ return toInlineMessageActions(rows);
36884
37039
  }
36885
37040
  function normalizeChatId(raw) {
36886
37041
  const normalized = normalizeInlineTarget(raw) ?? raw.trim();
@@ -36936,12 +37091,33 @@ function parseInlineId(raw, label) {
36936
37091
  throw new Error(`inline action: missing ${label}`);
36937
37092
  }
36938
37093
  if (!/^[0-9]+$/.test(trimmed)) {
37094
+ if (/message/i.test(label)) {
37095
+ const prefixed = trimmed.match(/^(?:message|msg)\s*#?\s*([0-9]+)$/i)?.[1];
37096
+ if (prefixed) {
37097
+ return BigInt(prefixed);
37098
+ }
37099
+ }
36939
37100
  throw new Error(`inline action: invalid ${label} "${raw}"`);
36940
37101
  }
36941
37102
  return BigInt(trimmed);
36942
37103
  }
36943
37104
  throw new Error(`inline action: missing ${label}`);
36944
37105
  }
37106
+ function resolveReactionMessageId(params) {
37107
+ const explicit = readFlexibleId(params.args, "messageId") ?? readStringParam(params.args, "messageId");
37108
+ if (explicit) {
37109
+ return explicit;
37110
+ }
37111
+ const fromContext = params.toolContext?.currentMessageId;
37112
+ if (typeof fromContext === "number" && Number.isFinite(fromContext)) {
37113
+ return String(Math.trunc(fromContext));
37114
+ }
37115
+ if (typeof fromContext === "string") {
37116
+ const trimmed = fromContext.trim();
37117
+ return trimmed || undefined;
37118
+ }
37119
+ return;
37120
+ }
36945
37121
  function parseOptionalInlineId(raw, label) {
36946
37122
  if (raw == null)
36947
37123
  return;
@@ -37195,6 +37371,7 @@ function mapChatEntry(params) {
37195
37371
  }
37196
37372
  return {
37197
37373
  id: String(params.chat.id),
37374
+ target: `chat:${String(params.chat.id)}`,
37198
37375
  title: params.chat.title,
37199
37376
  spaceId: params.chat.spaceId != null ? String(params.chat.spaceId) : null,
37200
37377
  isPublic: params.chat.isPublic ?? false,
@@ -37206,11 +37383,29 @@ function mapChatEntry(params) {
37206
37383
  peer: peer?.oneofKind === "user" ? {
37207
37384
  kind: "user",
37208
37385
  id: String(peer.user.userId),
37386
+ target: `user:${String(peer.user.userId)}`,
37209
37387
  username: peerUser?.username ?? null,
37210
37388
  name: peerUser ? buildInlineUserDisplayName(peerUser) : null
37211
- } : peer?.oneofKind === "chat" ? { kind: "chat", id: String(peer.chat.chatId) } : null
37389
+ } : peer?.oneofKind === "chat" ? { kind: "chat", id: String(peer.chat.chatId), target: `chat:${String(peer.chat.chatId)}` } : null
37212
37390
  };
37213
37391
  }
37392
+ function normalizeInlineListQuery(query) {
37393
+ return query?.trim().toLowerCase() ?? "";
37394
+ }
37395
+ function mapUserPeerEntry(user) {
37396
+ return {
37397
+ id: String(user.id),
37398
+ target: `user:${String(user.id)}`,
37399
+ username: user.username ?? null,
37400
+ name: buildInlineUserDisplayName(user),
37401
+ bot: user.bot ?? false
37402
+ };
37403
+ }
37404
+ function matchesInlineListQuery(text, query) {
37405
+ if (!query)
37406
+ return true;
37407
+ return text.toLowerCase().includes(query);
37408
+ }
37214
37409
  async function loadMessageReactions(params) {
37215
37410
  const target = await findMessageById({
37216
37411
  client: params.client,
@@ -37239,6 +37434,28 @@ async function loadMessageReactions(params) {
37239
37434
  }
37240
37435
  return Array.from(byEmoji.values());
37241
37436
  }
37437
+ function getErrorMessage(error48) {
37438
+ if (error48 instanceof Error)
37439
+ return error48.message;
37440
+ if (typeof error48 === "string")
37441
+ return error48;
37442
+ if (error48 && typeof error48 === "object" && "message" in error48 && typeof error48.message === "string") {
37443
+ return error48.message;
37444
+ }
37445
+ return String(error48);
37446
+ }
37447
+ function isDuplicateReactionError(error48) {
37448
+ const text = getErrorMessage(error48).toLowerCase();
37449
+ return text.includes("unique_reaction_per_emoji") || text.includes("duplicate") && text.includes("reaction") || text.includes("duplicate key value violates unique constraint");
37450
+ }
37451
+ async function reactionAlreadyExists(params) {
37452
+ const me = await params.client.getMe().catch(() => null);
37453
+ if (!me?.userId)
37454
+ return false;
37455
+ const myId = String(me.userId);
37456
+ const reactions = await loadMessageReactions(params).catch(() => []);
37457
+ return reactions.some((reaction) => reaction.emoji === params.emoji && reaction.userIds.includes(myId));
37458
+ }
37242
37459
  async function findMessageById(params) {
37243
37460
  const directResult = GET_MESSAGES_METHOD3 == null ? null : await params.client.invokeRaw(GET_MESSAGES_METHOD3, {
37244
37461
  oneofKind: "getMessages",
@@ -37348,13 +37565,12 @@ function listAllActions() {
37348
37565
  return Array.from(out);
37349
37566
  }
37350
37567
  function listEnabledInlineActions(cfg) {
37351
- const account = resolveInlineAccount({ cfg, accountId: null });
37352
- if (!account.enabled || !account.configured)
37568
+ const gates = listInlineAccountIds(cfg).map((accountId) => resolveInlineAccount({ cfg, accountId })).filter((account) => account.enabled && account.configured).map((account) => createActionGate(account.config.actions ?? {}));
37569
+ if (gates.length === 0)
37353
37570
  return [];
37354
- const gate = createActionGate(account.config.actions ?? {});
37355
37571
  const actions = new Set;
37356
37572
  for (const group of ACTION_GROUPS) {
37357
- if (!gate(group.key, group.defaultEnabled))
37573
+ if (!gates.some((gate) => gate(group.key, group.defaultEnabled)))
37358
37574
  continue;
37359
37575
  for (const action of group.actions) {
37360
37576
  actions.add(action);
@@ -37423,11 +37639,18 @@ var inlineMessageActions = {
37423
37639
  return null;
37424
37640
  return { to: normalized };
37425
37641
  },
37426
- handleAction: async ({ action, params, cfg, accountId }) => {
37642
+ handleAction: async ({ action, params, cfg, accountId, toolContext }) => {
37427
37643
  if (!SUPPORTED_ACTIONS.includes(action)) {
37428
37644
  throw new Error(`Action ${action} is not supported for provider inline.`);
37429
37645
  }
37430
37646
  if (!isActionEnabled({ cfg, accountId: accountId ?? null, action })) {
37647
+ if (action === "react") {
37648
+ return jsonResult({
37649
+ ok: false,
37650
+ reason: "disabled",
37651
+ hint: "Inline reactions are disabled via channels.inline.actions.reactions. Do not retry."
37652
+ });
37653
+ }
37431
37654
  throw new Error(`inline action: ${action} is disabled by channels.inline.actions`);
37432
37655
  }
37433
37656
  const normalizedAction = action;
@@ -37552,7 +37775,24 @@ var inlineMessageActions = {
37552
37775
  accountId,
37553
37776
  fn: async (client) => {
37554
37777
  const chatId = resolveChatIdFromParams(params);
37555
- const messageId = parseInlineId(readFlexibleId(params, "messageId") ?? readStringParam(params, "messageId", { required: true }), "messageId");
37778
+ const rawMessageId = resolveReactionMessageId(toolContext != null ? { args: params, toolContext } : { args: params });
37779
+ if (!rawMessageId) {
37780
+ return jsonResult({
37781
+ ok: false,
37782
+ reason: "missing_message_id",
37783
+ hint: "Inline reaction requires a valid messageId (or inbound context fallback). Do not retry."
37784
+ });
37785
+ }
37786
+ let messageId;
37787
+ try {
37788
+ messageId = parseInlineId(rawMessageId, "messageId");
37789
+ } catch {
37790
+ return jsonResult({
37791
+ ok: false,
37792
+ reason: "missing_message_id",
37793
+ hint: "Inline reaction requires a valid messageId (or inbound context fallback). Do not retry."
37794
+ });
37795
+ }
37556
37796
  const { emoji: emoji3, remove, isEmpty } = readReactionParams(params, {
37557
37797
  removeErrorMessage: "Emoji is required to remove an Inline reaction."
37558
37798
  });
@@ -37560,28 +37800,72 @@ var inlineMessageActions = {
37560
37800
  throw new Error("inline action: react requires emoji");
37561
37801
  }
37562
37802
  if (remove) {
37563
- const result = await client.invokeRaw(Method.DELETE_REACTION, {
37564
- oneofKind: "deleteReaction",
37565
- deleteReaction: {
37566
- emoji: emoji3,
37567
- peerId: buildChatPeer3(chatId),
37568
- messageId
37803
+ try {
37804
+ const result = await client.invokeRaw(Method.DELETE_REACTION, {
37805
+ oneofKind: "deleteReaction",
37806
+ deleteReaction: {
37807
+ emoji: emoji3,
37808
+ peerId: buildChatPeer3(chatId),
37809
+ messageId
37810
+ }
37811
+ });
37812
+ if (result.oneofKind !== "deleteReaction") {
37813
+ throw new Error(`inline action: expected deleteReaction result, got ${String(result.oneofKind)}`);
37569
37814
  }
37570
- });
37571
- if (result.oneofKind !== "deleteReaction") {
37572
- throw new Error(`inline action: expected deleteReaction result, got ${String(result.oneofKind)}`);
37815
+ } catch {
37816
+ return jsonResult({
37817
+ ok: false,
37818
+ reason: "error",
37819
+ emoji: emoji3,
37820
+ remove: true,
37821
+ hint: "Reaction failed. Do not retry."
37822
+ });
37573
37823
  }
37574
37824
  } else {
37575
- const result = await client.invokeRaw(Method.ADD_REACTION, {
37576
- oneofKind: "addReaction",
37577
- addReaction: {
37825
+ if (await reactionAlreadyExists({
37826
+ client,
37827
+ chatId,
37828
+ messageId,
37829
+ emoji: emoji3
37830
+ })) {
37831
+ return jsonResult({
37832
+ ok: true,
37833
+ chatId: String(chatId),
37834
+ messageId: String(messageId),
37578
37835
  emoji: emoji3,
37579
- messageId,
37580
- peerId: buildChatPeer3(chatId)
37836
+ remove: false,
37837
+ alreadyPresent: true
37838
+ });
37839
+ }
37840
+ try {
37841
+ const result = await client.invokeRaw(Method.ADD_REACTION, {
37842
+ oneofKind: "addReaction",
37843
+ addReaction: {
37844
+ emoji: emoji3,
37845
+ messageId,
37846
+ peerId: buildChatPeer3(chatId)
37847
+ }
37848
+ });
37849
+ if (result.oneofKind !== "addReaction") {
37850
+ throw new Error(`inline action: expected addReaction result, got ${String(result.oneofKind)}`);
37581
37851
  }
37582
- });
37583
- if (result.oneofKind !== "addReaction") {
37584
- throw new Error(`inline action: expected addReaction result, got ${String(result.oneofKind)}`);
37852
+ } catch (error48) {
37853
+ if (!isDuplicateReactionError(error48)) {
37854
+ return jsonResult({
37855
+ ok: false,
37856
+ reason: "error",
37857
+ emoji: emoji3,
37858
+ hint: "Reaction failed. Do not retry."
37859
+ });
37860
+ }
37861
+ return jsonResult({
37862
+ ok: true,
37863
+ chatId: String(chatId),
37864
+ messageId: String(messageId),
37865
+ emoji: emoji3,
37866
+ remove: false,
37867
+ alreadyPresent: true
37868
+ });
37585
37869
  }
37586
37870
  }
37587
37871
  return jsonResult({
@@ -37755,6 +38039,7 @@ var inlineMessageActions = {
37755
38039
  fn: async (client) => {
37756
38040
  const query = readStringParam(params, "query") ?? readStringParam(params, "q") ?? undefined;
37757
38041
  const limit = Math.max(1, Math.min(200, readNumberParam(params, "limit", { integer: true }) ?? 50));
38042
+ const scope = (readStringParam(params, "scope") ?? readStringParam(params, "kind") ?? "all").toLowerCase();
37758
38043
  const result = await client.invokeRaw(Method.GET_CHATS, {
37759
38044
  oneofKind: "getChats",
37760
38045
  getChats: {}
@@ -37764,23 +38049,26 @@ var inlineMessageActions = {
37764
38049
  }
37765
38050
  const dialogByChatId = buildDialogMap(result.getChats.dialogs ?? []);
37766
38051
  const usersById = buildUserMap2(result.getChats.users ?? []);
37767
- const entries = (result.getChats.chats ?? []).map((chat) => mapChatEntry({ chat, dialogByChatId, usersById }));
37768
- const normalizedQuery = query?.trim().toLowerCase() ?? "";
37769
- const filtered = normalizedQuery ? entries.filter((entry) => {
37770
- const haystack = [
37771
- entry.id,
37772
- entry.title,
37773
- entry.peer?.kind === "user" ? entry.peer.username ?? "" : "",
37774
- entry.peer?.kind === "user" ? entry.peer.name ?? "" : ""
37775
- ].join(`
37776
- `).toLowerCase();
37777
- return haystack.includes(normalizedQuery);
37778
- }) : entries;
38052
+ const chats = (result.getChats.chats ?? []).map((chat) => mapChatEntry({ chat, dialogByChatId, usersById }));
38053
+ const groups = chats.filter((entry) => entry.peer?.kind !== "user");
38054
+ const peers = (result.getChats.users ?? []).map((user) => mapUserPeerEntry(user));
38055
+ const normalizedQuery = normalizeInlineListQuery(query);
38056
+ const filteredChats = chats.filter((entry) => matchesInlineListQuery([entry.id, entry.target, entry.title, entry.peer?.kind === "user" ? entry.peer.username ?? "" : "", entry.peer?.kind === "user" ? entry.peer.name ?? "" : ""].join(`
38057
+ `), normalizedQuery));
38058
+ const filteredGroups = groups.filter((entry) => matchesInlineListQuery([entry.id, entry.target, entry.title].join(`
38059
+ `), normalizedQuery));
38060
+ const filteredPeers = peers.filter((entry) => matchesInlineListQuery([entry.id, entry.target, entry.username ?? "", entry.name ?? ""].join(`
38061
+ `), normalizedQuery));
37779
38062
  return jsonResult(toJsonSafe({
37780
38063
  ok: true,
38064
+ scope,
37781
38065
  query: query ?? null,
37782
- count: filtered.length,
37783
- chats: filtered.slice(0, limit)
38066
+ count: filteredChats.length,
38067
+ groupsCount: filteredGroups.length,
38068
+ peersCount: filteredPeers.length,
38069
+ chats: scope === "groups" || scope === "group" || scope === "channels" || scope === "channel" ? [] : scope === "peers" || scope === "peer" || scope === "members" || scope === "member" || scope === "users" || scope === "user" ? [] : filteredChats.slice(0, limit),
38070
+ groups: scope === "peers" || scope === "peer" || scope === "members" || scope === "member" || scope === "users" || scope === "user" ? [] : filteredGroups.slice(0, limit),
38071
+ peers: scope === "groups" || scope === "group" || scope === "channels" || scope === "channel" ? [] : filteredPeers.slice(0, limit)
37784
38072
  }));
37785
38073
  }
37786
38074
  });
@@ -38204,6 +38492,239 @@ var inlineMessageActions = {
38204
38492
  };
38205
38493
  var inlineSupportedActions = listAllActions();
38206
38494
 
38495
+ // src/inline/setup-core.ts
38496
+ import { createEnvPatchedAccountSetupAdapter } from "openclaw/plugin-sdk/setup";
38497
+ var channel = "inline";
38498
+ var INLINE_TOKEN_HELP_LINES = [
38499
+ "1) Open Inline and generate a bot token for your workspace/account",
38500
+ "2) Copy the token",
38501
+ "3) Paste it here, or set INLINE_TOKEN in your environment",
38502
+ "Docs: https://inline.chat/docs/openclaw",
38503
+ "Website: https://openclaw.ai"
38504
+ ];
38505
+ var inlineSetupAdapter = createEnvPatchedAccountSetupAdapter({
38506
+ channelKey: channel,
38507
+ defaultAccountOnlyEnvError: "INLINE_TOKEN can only be used for the default account.",
38508
+ missingCredentialError: "Inline requires token or --token-file (or --use-env).",
38509
+ hasCredentials: (input) => Boolean(input.token || input.tokenFile),
38510
+ buildPatch: (input) => input.tokenFile ? { tokenFile: input.tokenFile } : input.token ? { token: input.token } : {}
38511
+ });
38512
+
38513
+ // src/inline/setup-surface.ts
38514
+ import {
38515
+ DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID2,
38516
+ setSetupChannelEnabled
38517
+ } from "openclaw/plugin-sdk/setup";
38518
+ var channel2 = "inline";
38519
+ var inlineSetupWizard = {
38520
+ channel: channel2,
38521
+ status: {
38522
+ configuredLabel: "configured",
38523
+ unconfiguredLabel: "needs token",
38524
+ configuredHint: "configured",
38525
+ unconfiguredHint: "recommended",
38526
+ configuredScore: 1,
38527
+ unconfiguredScore: 10,
38528
+ resolveConfigured: ({ cfg }) => listInlineAccountIds(cfg).some((accountId) => resolveInlineAccount({ cfg, accountId }).configured)
38529
+ },
38530
+ credentials: [
38531
+ {
38532
+ inputKey: "token",
38533
+ providerHint: channel2,
38534
+ credentialLabel: "Inline token",
38535
+ preferredEnvVar: "INLINE_TOKEN",
38536
+ helpTitle: "Inline token",
38537
+ helpLines: INLINE_TOKEN_HELP_LINES,
38538
+ envPrompt: "INLINE_TOKEN detected. Use env var?",
38539
+ keepPrompt: "Inline token already configured. Keep it?",
38540
+ inputPrompt: "Enter Inline token",
38541
+ allowEnv: ({ accountId }) => accountId === DEFAULT_ACCOUNT_ID2,
38542
+ inspect: ({ cfg, accountId }) => {
38543
+ const resolved = resolveInlineAccount({ cfg, accountId });
38544
+ const hasConfiguredValue = Boolean((resolved.config.token ?? "").trim() || (resolved.config.tokenFile ?? "").trim());
38545
+ const resolvedValue = resolved.token?.trim();
38546
+ const envValue = accountId === DEFAULT_ACCOUNT_ID2 ? process.env.INLINE_TOKEN?.trim() : undefined;
38547
+ return {
38548
+ accountConfigured: resolved.configured || hasConfiguredValue,
38549
+ hasConfiguredValue,
38550
+ ...resolvedValue ? { resolvedValue } : {},
38551
+ ...envValue ? { envValue } : {}
38552
+ };
38553
+ }
38554
+ }
38555
+ ],
38556
+ disable: (cfg) => setSetupChannelEnabled(cfg, channel2, false)
38557
+ };
38558
+
38559
+ // src/inline/probe.ts
38560
+ function formatInlineProbeUserName(user) {
38561
+ const explicit = [user.firstName?.trim(), user.lastName?.trim()].filter(Boolean).join(" ");
38562
+ if (explicit)
38563
+ return explicit;
38564
+ const username = user.username?.trim();
38565
+ if (username)
38566
+ return `@${username}`;
38567
+ return "Unknown";
38568
+ }
38569
+ async function withTimeout(promise2, timeoutMs) {
38570
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
38571
+ return await promise2;
38572
+ }
38573
+ return await new Promise((resolve, reject) => {
38574
+ const timer = setTimeout(() => {
38575
+ reject(new Error(`probe timeout after ${Math.trunc(timeoutMs)}ms`));
38576
+ }, timeoutMs);
38577
+ promise2.then((value) => {
38578
+ clearTimeout(timer);
38579
+ resolve(value);
38580
+ }, (error48) => {
38581
+ clearTimeout(timer);
38582
+ reject(error48);
38583
+ });
38584
+ });
38585
+ }
38586
+ async function probeInlineAccountDirect(account) {
38587
+ if (!account.baseUrl?.trim()) {
38588
+ throw new Error("missing baseUrl");
38589
+ }
38590
+ const token = await resolveInlineToken(account);
38591
+ const client = new InlineSdkClient({
38592
+ baseUrl: account.baseUrl,
38593
+ token
38594
+ });
38595
+ await client.connect();
38596
+ try {
38597
+ const result = await client.invokeRaw(Method.GET_ME, {
38598
+ oneofKind: "getMe",
38599
+ getMe: {}
38600
+ });
38601
+ if (result.oneofKind !== "getMe") {
38602
+ throw new Error(`expected getMe result, got ${String(result.oneofKind)}`);
38603
+ }
38604
+ if (!result.getMe.user) {
38605
+ throw new Error("missing current user from getMe");
38606
+ }
38607
+ const user = result.getMe.user;
38608
+ return {
38609
+ ok: true,
38610
+ accountId: account.accountId,
38611
+ baseUrl: account.baseUrl,
38612
+ user: {
38613
+ id: String(user.id),
38614
+ username: user.username?.trim() || null,
38615
+ name: formatInlineProbeUserName(user),
38616
+ bot: user.bot ?? false
38617
+ }
38618
+ };
38619
+ } finally {
38620
+ await client.close().catch(() => {});
38621
+ }
38622
+ }
38623
+ function toErrorText(error48) {
38624
+ if (error48 instanceof Error && error48.message.trim()) {
38625
+ return error48.message;
38626
+ }
38627
+ return String(error48);
38628
+ }
38629
+ async function probeInlineAccount(account, timeoutMs) {
38630
+ if (!account.configured) {
38631
+ return {
38632
+ ok: false,
38633
+ accountId: account.accountId,
38634
+ baseUrl: account.baseUrl,
38635
+ error: "missing token"
38636
+ };
38637
+ }
38638
+ try {
38639
+ return await withTimeout(probeInlineAccountDirect(account), timeoutMs);
38640
+ } catch (error48) {
38641
+ return {
38642
+ ok: false,
38643
+ accountId: account.accountId,
38644
+ baseUrl: account.baseUrl,
38645
+ error: toErrorText(error48)
38646
+ };
38647
+ }
38648
+ }
38649
+
38650
+ // src/inline/status-issues.ts
38651
+ import { asString, isRecord as isRecord5 } from "openclaw/plugin-sdk/status-helpers";
38652
+ function readInlineProbeSummary(value) {
38653
+ if (!isRecord5(value)) {
38654
+ return {};
38655
+ }
38656
+ const summary = {};
38657
+ if (typeof value.ok === "boolean") {
38658
+ summary.ok = value.ok;
38659
+ }
38660
+ const error48 = asString(value.error);
38661
+ if (error48) {
38662
+ summary.error = error48;
38663
+ }
38664
+ return summary;
38665
+ }
38666
+ function looksLikeAuthError(text) {
38667
+ return /(401|403|unauth|forbidden|invalid token|token invalid|unauthorized)/i.test(text);
38668
+ }
38669
+ function collectInlineStatusIssues(accounts) {
38670
+ const issues = [];
38671
+ for (const entry of accounts) {
38672
+ if (!isRecord5(entry)) {
38673
+ continue;
38674
+ }
38675
+ const accountId = asString(entry.accountId);
38676
+ if (!accountId) {
38677
+ continue;
38678
+ }
38679
+ const enabled = entry.enabled !== false;
38680
+ if (!enabled) {
38681
+ continue;
38682
+ }
38683
+ const configured = entry.configured !== false;
38684
+ if (!configured) {
38685
+ issues.push({
38686
+ channel: "inline",
38687
+ accountId,
38688
+ kind: "config",
38689
+ message: "Inline account is enabled but missing token/tokenFile.",
38690
+ fix: "Set channels.inline.token (or tokenFile), then restart the gateway."
38691
+ });
38692
+ continue;
38693
+ }
38694
+ const baseUrl = asString(entry.baseUrl);
38695
+ if (!baseUrl || baseUrl === "[missing]") {
38696
+ issues.push({
38697
+ channel: "inline",
38698
+ accountId,
38699
+ kind: "config",
38700
+ message: "Inline account is configured but baseUrl is missing.",
38701
+ fix: 'Set channels.inline.baseUrl (for example "https://api.inline.chat"), then restart the gateway.'
38702
+ });
38703
+ }
38704
+ const lastError = asString(entry.lastError);
38705
+ if (lastError) {
38706
+ issues.push({
38707
+ channel: "inline",
38708
+ accountId,
38709
+ kind: looksLikeAuthError(lastError) ? "auth" : "runtime",
38710
+ message: `Inline runtime error: ${lastError}`,
38711
+ fix: "Verify token/baseUrl and restart the gateway."
38712
+ });
38713
+ }
38714
+ const probe = readInlineProbeSummary(entry.probe);
38715
+ if (probe.ok === false && probe.error) {
38716
+ issues.push({
38717
+ channel: "inline",
38718
+ accountId,
38719
+ kind: looksLikeAuthError(probe.error) ? "auth" : "runtime",
38720
+ message: `Inline probe failed: ${probe.error}`,
38721
+ fix: "Verify token/baseUrl connectivity, then re-run channel status."
38722
+ });
38723
+ }
38724
+ }
38725
+ return issues;
38726
+ }
38727
+
38207
38728
  // src/inline/channel.ts
38208
38729
  var activeMonitors = new Map;
38209
38730
  var meta3 = {
@@ -38242,6 +38763,82 @@ function parseInlineId2(raw) {
38242
38763
  }
38243
38764
  return;
38244
38765
  }
38766
+ function asRecord(value) {
38767
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
38768
+ return null;
38769
+ }
38770
+ return value;
38771
+ }
38772
+ function clearInlineCredentialFields(record2) {
38773
+ let changed = false;
38774
+ let cleared = false;
38775
+ for (const key of ["token", "tokenFile"]) {
38776
+ if (!Object.hasOwn(record2, key)) {
38777
+ continue;
38778
+ }
38779
+ const raw = record2[key];
38780
+ if (typeof raw === "string" && raw.trim()) {
38781
+ cleared = true;
38782
+ }
38783
+ delete record2[key];
38784
+ changed = true;
38785
+ }
38786
+ return { changed, cleared };
38787
+ }
38788
+ function clearInlineAccountCredentials(params) {
38789
+ const channels = asRecord(params.cfg.channels);
38790
+ const inline = asRecord(channels?.inline);
38791
+ if (!channels || !inline) {
38792
+ return { cfg: params.cfg, changed: false, cleared: false };
38793
+ }
38794
+ const nextInline = { ...inline };
38795
+ let changed = false;
38796
+ let cleared = false;
38797
+ if (params.accountId === DEFAULT_ACCOUNT_ID) {
38798
+ const base = clearInlineCredentialFields(nextInline);
38799
+ changed = changed || base.changed;
38800
+ cleared = cleared || base.cleared;
38801
+ }
38802
+ const accounts = asRecord(nextInline.accounts);
38803
+ if (accounts) {
38804
+ const nextAccounts = { ...accounts };
38805
+ const accountEntry = asRecord(nextAccounts[params.accountId]);
38806
+ if (accountEntry) {
38807
+ const nextAccountEntry = { ...accountEntry };
38808
+ const entry = clearInlineCredentialFields(nextAccountEntry);
38809
+ if (entry.changed) {
38810
+ changed = true;
38811
+ cleared = cleared || entry.cleared;
38812
+ if (Object.keys(nextAccountEntry).length > 0) {
38813
+ nextAccounts[params.accountId] = nextAccountEntry;
38814
+ } else {
38815
+ delete nextAccounts[params.accountId];
38816
+ }
38817
+ }
38818
+ }
38819
+ if (changed) {
38820
+ if (Object.keys(nextAccounts).length > 0) {
38821
+ nextInline.accounts = nextAccounts;
38822
+ } else {
38823
+ delete nextInline.accounts;
38824
+ }
38825
+ }
38826
+ }
38827
+ if (!changed) {
38828
+ return { cfg: params.cfg, changed: false, cleared: false };
38829
+ }
38830
+ return {
38831
+ cfg: {
38832
+ ...params.cfg,
38833
+ channels: {
38834
+ ...channels,
38835
+ inline: nextInline
38836
+ }
38837
+ },
38838
+ changed: true,
38839
+ cleared
38840
+ };
38841
+ }
38245
38842
  function parseInlineOutboundTarget(params) {
38246
38843
  let normalizedTarget = params.raw.trim();
38247
38844
  const hadInlinePrefix = /^inline:/i.test(normalizedTarget);
@@ -38277,6 +38874,46 @@ function parseInlineOutboundTarget(params) {
38277
38874
  raw: params.raw
38278
38875
  };
38279
38876
  }
38877
+ function parseInlineExplicitTarget(raw) {
38878
+ const parsed = parseInlineOutboundTarget({
38879
+ raw,
38880
+ context: "sendText"
38881
+ });
38882
+ if (parsed.kind === "user") {
38883
+ return { to: `user:${parsed.normalizedNumeric}`, chatType: "direct" };
38884
+ }
38885
+ return { to: `chat:${parsed.normalizedNumeric}`, chatType: "group" };
38886
+ }
38887
+ function formatInlineTargetDisplay(params) {
38888
+ const explicit = params.display?.trim();
38889
+ if (explicit) {
38890
+ return explicit;
38891
+ }
38892
+ const parsed = parseInlineExplicitTarget(params.target.trim());
38893
+ if (!parsed) {
38894
+ return params.target.trim();
38895
+ }
38896
+ if (parsed.chatType === "direct" || params.kind === "user") {
38897
+ return parsed.to;
38898
+ }
38899
+ return parsed.to;
38900
+ }
38901
+ function normalizeInlineConversationId(raw) {
38902
+ const trimmed = raw.trim();
38903
+ if (!trimmed)
38904
+ return null;
38905
+ const withoutProvider = trimmed.replace(/^inline:/i, "").trim();
38906
+ if (!withoutProvider)
38907
+ return null;
38908
+ try {
38909
+ const parsed = parseInlineExplicitTarget(withoutProvider);
38910
+ if (!parsed)
38911
+ return null;
38912
+ return `inline:${parsed.to}`;
38913
+ } catch {
38914
+ return null;
38915
+ }
38916
+ }
38280
38917
  async function listInlineTargetIds(client) {
38281
38918
  const result = await client.invokeRaw(Method.GET_CHATS, {
38282
38919
  oneofKind: "getChats",
@@ -38350,6 +38987,31 @@ function buildInlineDisplayName(params) {
38350
38987
  return `@${username}`;
38351
38988
  return "Unknown";
38352
38989
  }
38990
+ function formatInlineCapabilitiesProbeLines(probe) {
38991
+ const details = probe;
38992
+ if (!details) {
38993
+ return [];
38994
+ }
38995
+ if (!details.ok) {
38996
+ if (details.error?.trim()) {
38997
+ return [{ text: `Probe failed: ${details.error}`, tone: "error" }];
38998
+ }
38999
+ return [{ text: "Probe failed", tone: "error" }];
39000
+ }
39001
+ const lines = [];
39002
+ if (details.user) {
39003
+ const username = details.user.username ? ` @${details.user.username}` : "";
39004
+ const botLabel = details.user.bot ? " [bot]" : "";
39005
+ lines.push({
39006
+ text: `Identity: ${details.user.name}${username} (${details.user.id})${botLabel}`,
39007
+ tone: "success"
39008
+ });
39009
+ }
39010
+ if (details.baseUrl) {
39011
+ lines.push({ text: `Base URL: ${details.baseUrl}` });
39012
+ }
39013
+ return lines;
39014
+ }
38353
39015
  function toInlineUserDirectoryEntry(user) {
38354
39016
  return {
38355
39017
  kind: "user",
@@ -38653,10 +39315,25 @@ var inlineChannelPlugin = {
38653
39315
  },
38654
39316
  reload: { configPrefixes: ["channels.inline"] },
38655
39317
  configSchema: buildChannelConfigSchema(InlineConfigSchema),
39318
+ setup: inlineSetupAdapter,
39319
+ setupWizard: inlineSetupWizard,
38656
39320
  config: {
38657
39321
  listAccountIds: (cfg) => listInlineAccountIds(cfg),
38658
39322
  resolveAccount: (cfg, accountId) => resolveInlineAccount({ cfg, accountId: accountId ?? null }),
38659
39323
  defaultAccountId: (cfg) => resolveDefaultInlineAccountId(cfg),
39324
+ setAccountEnabled: ({ cfg, accountId, enabled }) => setAccountEnabledInConfigSection({
39325
+ cfg,
39326
+ sectionKey: "inline",
39327
+ accountId,
39328
+ enabled,
39329
+ allowTopLevel: true
39330
+ }),
39331
+ deleteAccount: ({ cfg, accountId }) => deleteAccountFromConfigSection({
39332
+ cfg,
39333
+ sectionKey: "inline",
39334
+ accountId,
39335
+ clearBaseFields: ["token", "tokenFile", "name", "enabled"]
39336
+ }),
38660
39337
  isConfigured: (account) => account.configured,
38661
39338
  describeAccount: (account) => ({
38662
39339
  accountId: account.accountId,
@@ -38707,6 +39384,47 @@ var inlineChannelPlugin = {
38707
39384
  ];
38708
39385
  }
38709
39386
  },
39387
+ allowlist: buildDmGroupAccountAllowlistAdapter({
39388
+ channelId: "inline",
39389
+ resolveAccount: ({ cfg, accountId }) => resolveInlineAccount({ cfg, accountId: accountId ?? null }),
39390
+ normalize: ({ values }) => values.map((entry) => String(entry).trim()).filter(Boolean).map((entry) => normalizeInlineAllowEntry(entry)),
39391
+ resolveDmAllowFrom: (account) => account.config.allowFrom ?? [],
39392
+ resolveGroupAllowFrom: (account) => account.config.groupAllowFrom ?? [],
39393
+ resolveDmPolicy: (account) => account.config.dmPolicy,
39394
+ resolveGroupPolicy: (account) => account.config.groupPolicy
39395
+ }),
39396
+ bindings: {
39397
+ compileConfiguredBinding: ({ conversationId }) => {
39398
+ const normalized = normalizeInlineConversationId(conversationId);
39399
+ if (!normalized) {
39400
+ return null;
39401
+ }
39402
+ return { conversationId: normalized };
39403
+ },
39404
+ matchInboundConversation: ({ compiledBinding, conversationId, parentConversationId }) => {
39405
+ const expected = normalizeInlineConversationId(compiledBinding.conversationId);
39406
+ if (!expected) {
39407
+ return null;
39408
+ }
39409
+ const incoming = normalizeInlineConversationId(conversationId);
39410
+ const parent = parentConversationId ? normalizeInlineConversationId(parentConversationId) : null;
39411
+ if (incoming && incoming === expected) {
39412
+ return {
39413
+ conversationId: incoming,
39414
+ ...parent ? { parentConversationId: parent } : {},
39415
+ matchPriority: 2
39416
+ };
39417
+ }
39418
+ if (incoming && parent && parent === expected) {
39419
+ return {
39420
+ conversationId: incoming,
39421
+ parentConversationId: parent,
39422
+ matchPriority: 1
39423
+ };
39424
+ }
39425
+ return null;
39426
+ }
39427
+ },
38710
39428
  groups: {
38711
39429
  resolveRequireMention: ({ cfg, accountId, groupId }) => {
38712
39430
  const resolved = resolveInlineAccount({ cfg, accountId: accountId ?? null });
@@ -38759,7 +39477,9 @@ var inlineChannelPlugin = {
38759
39477
  messageToolHints: ({ cfg, accountId }) => [
38760
39478
  "- Inline targeting: omit `target` to reply in the current chat.",
38761
39479
  "- Inline explicit targets: `chat:<chatId>` for chats and `user:<userId>` for direct users. Prefer `user:` for DM user targets.",
38762
- "- Inline history tools: `read` and `search` return media-aware message payloads (`media`, `attachments`, `attachmentUrls`) so image-only history remains discoverable.",
39480
+ "- Inline discovery: use `channel-list` to discover available chats and users. Use `scope: groups|peers|all` when helpful, and reuse returned `target` values.",
39481
+ "- Inline reactions: pass `messageId` for `react` when you have it; on inbound turns, the current inbound message id can be used as fallback.",
39482
+ "- Inline history tools: `read` and `search` return media-aware message payloads (`media`, `attachments`, `attachmentUrls`) so image-only history remains discoverable. `search` is chat-scoped; run it per chat.",
38763
39483
  "- Inline special tools: use `inline_nudge` to send a nudge, and `inline_forward` to forward message ids between chats or users.",
38764
39484
  ...isInlineReplyThreadsEnabled({ cfg, accountId: accountId ?? null }) ? [
38765
39485
  "- Inline reply threads are enabled: use `thread-reply` to send into a real reply thread, with `threadId` set to the reply-thread chat id."
@@ -38768,6 +39488,29 @@ var inlineChannelPlugin = {
38768
39488
  },
38769
39489
  messaging: {
38770
39490
  normalizeTarget: normalizeInlineTarget,
39491
+ parseExplicitTarget: ({ raw }) => {
39492
+ try {
39493
+ const parsed = parseInlineExplicitTarget(raw);
39494
+ if (!parsed)
39495
+ return null;
39496
+ return { to: parsed.to, chatType: parsed.chatType };
39497
+ } catch {
39498
+ return null;
39499
+ }
39500
+ },
39501
+ inferTargetChatType: ({ to }) => {
39502
+ try {
39503
+ const parsed = parseInlineExplicitTarget(to);
39504
+ return parsed?.chatType;
39505
+ } catch {
39506
+ return;
39507
+ }
39508
+ },
39509
+ formatTargetDisplay: ({ target, display, kind }) => formatInlineTargetDisplay({
39510
+ target,
39511
+ ...display !== undefined ? { display } : {},
39512
+ ...kind !== undefined ? { kind } : {}
39513
+ }),
38771
39514
  targetResolver: {
38772
39515
  looksLikeId: looksLikeInlineTargetId,
38773
39516
  hint: "<chatId | chat:<chatId> | user:<userId>>"
@@ -38985,18 +39728,14 @@ var inlineChannelPlugin = {
38985
39728
  running: false,
38986
39729
  lastStartAt: null,
38987
39730
  lastStopAt: null,
38988
- lastError: null
39731
+ lastError: null,
39732
+ lastProbeAt: null
38989
39733
  },
38990
- buildChannelSummary: ({ snapshot }) => ({
38991
- configured: snapshot.configured ?? false,
38992
- running: snapshot.running ?? false,
38993
- lastStartAt: snapshot.lastStartAt ?? null,
38994
- lastStopAt: snapshot.lastStopAt ?? null,
38995
- lastError: snapshot.lastError ?? null,
38996
- lastInboundAt: snapshot.lastInboundAt ?? null,
38997
- lastOutboundAt: snapshot.lastOutboundAt ?? null
38998
- }),
38999
- buildAccountSnapshot: ({ account, runtime: runtime2 }) => ({
39734
+ collectStatusIssues: collectInlineStatusIssues,
39735
+ buildChannelSummary: ({ snapshot }) => buildTokenChannelStatusSummary(snapshot),
39736
+ probeAccount: async ({ account, timeoutMs }) => await probeInlineAccount(account, timeoutMs),
39737
+ formatCapabilitiesProbe: ({ probe }) => formatInlineCapabilitiesProbeLines(probe),
39738
+ buildAccountSnapshot: ({ account, runtime: runtime2, probe }) => ({
39000
39739
  accountId: account.accountId,
39001
39740
  name: account.name,
39002
39741
  enabled: account.enabled,
@@ -39008,7 +39747,9 @@ var inlineChannelPlugin = {
39008
39747
  lastStopAt: runtime2?.lastStopAt ?? null,
39009
39748
  lastError: runtime2?.lastError ?? null,
39010
39749
  lastInboundAt: runtime2?.lastInboundAt ?? null,
39011
- lastOutboundAt: runtime2?.lastOutboundAt ?? null
39750
+ lastOutboundAt: runtime2?.lastOutboundAt ?? null,
39751
+ lastProbeAt: runtime2?.lastProbeAt ?? null,
39752
+ ...probe !== undefined ? { probe } : {}
39012
39753
  })
39013
39754
  },
39014
39755
  gateway: {
@@ -39061,6 +39802,30 @@ var inlineChannelPlugin = {
39061
39802
  running: false,
39062
39803
  lastStopAt: Date.now()
39063
39804
  });
39805
+ },
39806
+ logoutAccount: async ({ accountId, cfg }) => {
39807
+ const cleanup = clearInlineAccountCredentials({ cfg, accountId });
39808
+ if (cleanup.changed) {
39809
+ return {
39810
+ cleared: cleanup.cleared,
39811
+ loggedOut: cleanup.cleared,
39812
+ cfg: cleanup.cfg,
39813
+ message: cleanup.cleared ? "Inline credentials cleared from config. Restart gateway to apply." : "Inline credential fields removed from config."
39814
+ };
39815
+ }
39816
+ const envToken = process.env.INLINE_TOKEN?.trim() ?? "";
39817
+ if (accountId === DEFAULT_ACCOUNT_ID && envToken) {
39818
+ return {
39819
+ cleared: false,
39820
+ loggedOut: false,
39821
+ message: "No Inline credentials found in config. INLINE_TOKEN is set in env; unset it and restart gateway to fully log out."
39822
+ };
39823
+ }
39824
+ return {
39825
+ cleared: false,
39826
+ loggedOut: false,
39827
+ message: "No Inline credentials found in config for this account."
39828
+ };
39064
39829
  }
39065
39830
  }
39066
39831
  };
@@ -40130,5 +40895,5 @@ export {
40130
40895
  src_default as default
40131
40896
  };
40132
40897
 
40133
- //# debugId=DA562915BABAD00064756E2164756E21
40898
+ //# debugId=CE0AB31A68AAA68C64756E2164756E21
40134
40899
  //# sourceMappingURL=index.js.map