@lansenger-pm/openclaw-lansenger-channel 3.16.22 → 3.17.0

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/CHANGELOG.md CHANGED
@@ -4,6 +4,30 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [3.17.0] - 2026-07-02
8
+
9
+ ### Changed
10
+
11
+ - **Refactor**: Extracted helper functions and added section headers to `channel.ts`, `client.ts`, and `runtime.ts` for improved readability and maintainability. Pure code reorganization, no behavior changes.
12
+
13
+ ## [3.16.23] - 2026-06-30
14
+
15
+ ### Fixed
16
+
17
+ - **Duplicate approval text messages before approval card**: Two text messages ("🔒 Exec approval required" and "Approval required") were sent before the approval card. Root causes:
18
+ - SDK's text fallback was not suppressed because `delivery.shouldSuppressForwardingFallback` was missing from `approvalCapability`.
19
+ - `shouldSuppressLocalPayloadPrompt` was placed outside the `base` outbound object, causing `resolveChatChannelOutbound` to silently drop it during flattening. Moved inside `base`.
20
+
21
+ ### Added
22
+
23
+ - **`AccountId` to ctxPayload**: Injected into message context so LLM tools can resolve the correct account for the current session.
24
+ - **`expired` strategy kind** for card status updates, covering expiry denial display.
25
+
26
+ ### Changed
27
+
28
+ - **Channel**: Removed dead `approval-pending` suppression code in `beforeDeliverPayload` that was guarded by the never-set `nativeRouteActive` flag.
29
+ - **Cleanup**: Removed unused `MAX_MESSAGE_LENGTH`, `AppCardOptions` type, `LOCALE` export, and dead imports.
30
+
7
31
  ## [3.16.22] - 2026-06-30
8
32
 
9
33
  ### Fixed
@@ -1,4 +1,5 @@
1
1
  import { createChatChannelPlugin, createChannelPluginBase, } from "openclaw/plugin-sdk/channel-core";
2
+ import { createChannelMessageAdapterFromOutbound } from "openclaw/plugin-sdk/channel-outbound";
2
3
  import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
3
4
  import { createChannelApprovalCapability } from "openclaw/plugin-sdk/approval-runtime";
4
5
  import { createResolvedApproverActionAuthAdapter } from "openclaw/plugin-sdk/approval-auth-runtime";
@@ -12,11 +13,14 @@ import * as os from "node:os";
12
13
  import * as path from "node:path";
13
14
  import * as crypto from "node:crypto";
14
15
  import { LansengerClient, DEFAULT_API_GATEWAY_URL } from "./client.js";
15
- import { getRunningClient, getRunningClientByAccountId, getLastInboundTime, getLastInboundTimeByAccountId, stripOpenClawUuidSuffix, gatewayStartAccount, gatewayStopAccount } from "./runtime.js";
16
+ import { getRunningClientByAccountId, getSessionAccountId, getLastInboundTimeByAccountId, stripOpenClawUuidSuffix, gatewayStartAccount, gatewayStopAccount } from "./runtime.js";
16
17
  import { lansengerSetupWizard } from "./setup-wizard.js";
17
18
  const log = createSubsystemLogger("lansenger");
18
19
  const LANSENGER_TEXT_CHUNK_LIMIT = 4000;
19
20
  import { PersistentStore } from "./persistent-store.js";
21
+ // ══════════════════════════════════════════════
22
+ // PERSISTENT STORES
23
+ // ══════════════════════════════════════════════
20
24
  const APPROVAL_CARD_FILE = path.join(os.homedir(), ".openclaw", "lansenger-approval-cards.json");
21
25
  class PersistentApprovalCardStore extends PersistentStore {
22
26
  constructor() {
@@ -31,6 +35,9 @@ class PersistentApprovalCallbackStore extends PersistentStore {
31
35
  }
32
36
  }
33
37
  const pendingApprovalCallbacks = new PersistentApprovalCallbackStore();
38
+ // ══════════════════════════════════════════════
39
+ // PATH UTILS
40
+ // ══════════════════════════════════════════════
34
41
  function isPathAllowed(filePath, roots) {
35
42
  if (roots.length === 0)
36
43
  return true;
@@ -42,6 +49,9 @@ function isPathAllowed(filePath, roots) {
42
49
  }
43
50
  return false;
44
51
  }
52
+ // ══════════════════════════════════════════════
53
+ // ACCOUNT RESOLUTION
54
+ // ══════════════════════════════════════════════
45
55
  function resolveSecretValue(raw) {
46
56
  if (typeof raw === "string" && raw.trim())
47
57
  return raw;
@@ -162,6 +172,9 @@ function resolveAccount(cfg, accountId) {
162
172
  execApprovals,
163
173
  };
164
174
  }
175
+ // ══════════════════════════════════════════════
176
+ // CLIENT & APPROVER FACTORY
177
+ // ══════════════════════════════════════════════
165
178
  function makeClient(account, logger) {
166
179
  const client = new LansengerClient({
167
180
  appId: account.appId,
@@ -170,7 +183,10 @@ function makeClient(account, logger) {
170
183
  dangerouslyAllowPrivateNetwork: account.dangerouslyAllowPrivateNetwork,
171
184
  logger,
172
185
  });
173
- client.ownerId = account.homeChannel || "";
186
+ // Set ownerId so isGroupChat() can correctly identify group vs DM endpoints.
187
+ // Without this, fresh clients default to chatId.startsWith("group:") which
188
+ // never matches Lansenger IDs. The running client sets this from WS data.
189
+ client.ownerId = account.appId;
174
190
  return client;
175
191
  }
176
192
  function resolveLansengerApprovers({ cfg, accountId }) {
@@ -198,6 +214,9 @@ const approverAuth = createResolvedApproverActionAuthAdapter({
198
214
  channelLabel: "Lansenger",
199
215
  resolveApprovers: resolveLansengerApprovers,
200
216
  });
217
+ // ══════════════════════════════════════════════
218
+ // PROBE
219
+ // ══════════════════════════════════════════════
201
220
  async function probeLansengerAccount(account) {
202
221
  if (!account.appId || !account.appSecret) {
203
222
  return { ok: false, error: "missing credentials (appId, appSecret)" };
@@ -218,6 +237,9 @@ async function probeLansengerAccount(account) {
218
237
  };
219
238
  }
220
239
  }
240
+ // ══════════════════════════════════════════════
241
+ // CHAT PLUGIN (base)
242
+ // ══════════════════════════════════════════════
221
243
  const chatPlugin = createChatChannelPlugin({
222
244
  base: createChannelPluginBase({
223
245
  id: "lansenger",
@@ -473,11 +495,13 @@ const chatPlugin = createChatChannelPlugin({
473
495
  attachedResults: {
474
496
  channel: "lansenger",
475
497
  sendText: async (ctx) => {
476
- const sessionKey = ctx.sessionKey;
477
- const account = resolveAccount(ctx.cfg, ctx.accountId ?? undefined);
478
- const client = makeClient(account);
498
+ const sdkSessionKey = ctx.sessionKey;
499
+ const sessionAccountId = sdkSessionKey ? getSessionAccountId(sdkSessionKey) : undefined;
500
+ const account = resolveAccount(ctx.cfg, sessionAccountId ?? ctx.accountId);
501
+ const client = getRunningClientByAccountId(account.appId) ?? makeClient(account);
502
+ log.debug(`sendText: to=${ctx.to} textLen=${ctx.text?.length ?? 0} accountId=${ctx.accountId ?? "n/a"}`);
479
503
  const result = await client.sendFormatText(ctx.to, ctx.text);
480
- return { messageId: result.messageId ?? "", sessionKey };
504
+ return { messageId: result.messageId ?? "", sessionKey: sdkSessionKey };
481
505
  },
482
506
  sendMedia: async (ctx) => {
483
507
  const sessionKey = ctx.sessionKey;
@@ -536,7 +560,6 @@ const chatPlugin = createChatChannelPlugin({
536
560
  const target = params.target;
537
561
  const payload = params.payload;
538
562
  const hint = params.hint;
539
- const sessionKey = params.sessionKey ?? "";
540
563
  if (hint?.kind === "approval-resolved") {
541
564
  const chatId = target?.to;
542
565
  const cardKey = target?.accountId ? `${target.accountId}:${chatId}` : chatId;
@@ -556,11 +579,14 @@ const chatPlugin = createChatChannelPlugin({
556
579
  }
557
580
  },
558
581
  sendFormattedText: async (ctx) => {
559
- const sessionKey = ctx.sessionKey;
560
- const account = resolveAccount(ctx.cfg, ctx.accountId ?? undefined);
561
- const client = makeClient(account);
582
+ const sdkSessionKey = ctx.sessionKey;
583
+ const sessionAccountId = sdkSessionKey ? getSessionAccountId(sdkSessionKey) : undefined;
584
+ const account = resolveAccount(ctx.cfg, sessionAccountId ?? ctx.accountId);
585
+ const client = getRunningClientByAccountId(account.appId) ?? makeClient(account);
586
+ log.debug(`sendFormattedText: to=${ctx.to} textLen=${ctx.text?.length ?? 0} accountId=${account.appId ?? "n/a"}`);
562
587
  const result = await client.sendFormatText(ctx.to, ctx.text);
563
- return [{ channel: "lansenger", messageId: result.messageId ?? "", sessionKey }];
588
+ log.debug(`sendFormattedText: result success=${result.success} messageId=${result.messageId ?? "none"} error=${result.error ?? "none"}`);
589
+ return [{ channel: "lansenger", messageId: result.messageId ?? "", sessionKey: sdkSessionKey }];
564
590
  },
565
591
  textChunkLimit: LANSENGER_TEXT_CHUNK_LIMIT,
566
592
  chunkerMode: "markdown",
@@ -572,15 +598,15 @@ const chatPlugin = createChatChannelPlugin({
572
598
  return resolveTextChunkLimit(params.cfg, "lansenger", params.accountId, { fallbackLimit: LANSENGER_TEXT_CHUNK_LIMIT });
573
599
  },
574
600
  shouldSuppressLocalPayloadPrompt: (params) => {
575
- const hint = params.hint;
576
- if (hint?.kind === "approval-pending" && hint?.nativeRouteActive) {
577
- return true;
578
- }
579
- return false;
601
+ // Suppress the native approval text — we use approval cards instead.
602
+ return params.hint?.kind === "approval-pending";
580
603
  },
581
604
  },
582
605
  },
583
606
  });
607
+ // ══════════════════════════════════════════════
608
+ // ONBOARDING
609
+ // ══════════════════════════════════════════════
584
610
  const lansengerOnboarding = {
585
611
  configuredCheck: (cfg) => {
586
612
  const section = cfg.channels?.["lansenger"];
@@ -746,6 +772,73 @@ const lansengerOnboarding = {
746
772
  return { ...cfg, channels };
747
773
  },
748
774
  };
775
+ function buildApprovalCards(params) {
776
+ const { sessionKey, commandPreview, requestId, shortId, buttonScope, expireTime, allowedDecisions } = params;
777
+ const showSession = allowedDecisions.includes("allow-session");
778
+ const showAlways = allowedDecisions.includes("allow-always");
779
+ // --- zh card ---
780
+ const zhCard = {
781
+ head: {
782
+ title: "⚠️ 命令审批",
783
+ headStatus: { describe: "待审批", colour: "#FFB116", statusIcon: 1 },
784
+ },
785
+ body: {
786
+ title: "危险命令审批请求",
787
+ content: {
788
+ formatType: 1,
789
+ text: [
790
+ "**会话 ID**", sessionKey, "",
791
+ "**命令**", "```", commandPreview, "```", "",
792
+ `> 按钮不可用时请发送命令审批:`, "",
793
+ `> 执行一次`, "> ```", `> /approve ${shortId} allow-once`, "> ```", "",
794
+ ...(showSession ? [`> 本会话有效`, "> ```", `> /approve ${shortId} allow-session`, "> ```", ""] : []),
795
+ ...(showAlways ? [`> 永久允许`, "> ```", `> /approve ${shortId} allow-always`, "> ```", ""] : []),
796
+ `> 拒绝执行`, "> ```", `> /approve ${shortId} deny`, "> ```",
797
+ ].join("\n"),
798
+ },
799
+ },
800
+ buttons: [
801
+ { text: "执行一次", buttonTheme: 1, permissionScope: buttonScope, callbackInfo: `once:${requestId}` },
802
+ ...(showSession ? [{ text: "本会话有效", buttonTheme: 2, permissionScope: buttonScope, callbackInfo: `session:${requestId}` }] : []),
803
+ ...(showAlways ? [{ text: "永久允许", buttonTheme: 3, permissionScope: buttonScope, callbackInfo: `always:${requestId}` }] : []),
804
+ { text: "拒绝执行", buttonTheme: 4, permissionScope: buttonScope, callbackInfo: `deny:${requestId}` },
805
+ ],
806
+ expireTime,
807
+ };
808
+ // --- en card ---
809
+ const enCard = {
810
+ head: {
811
+ title: "⚠️ Command Approval",
812
+ headStatus: { describe: "Pending", colour: "#FFB116", statusIcon: 1 },
813
+ },
814
+ body: {
815
+ title: "Dangerous Command Approval Request",
816
+ content: {
817
+ formatType: 1,
818
+ text: [
819
+ "**Session**", sessionKey, "",
820
+ "**Command**", "```", commandPreview, "```", "",
821
+ `> If buttons are unavailable, use:`, "",
822
+ `> Approve once`, "> ```", `> /approve ${shortId} allow-once`, "> ```", "",
823
+ ...(showSession ? [`> This session`, "> ```", `> /approve ${shortId} allow-session`, "> ```", ""] : []),
824
+ ...(showAlways ? [`> Always allow`, "> ```", `> /approve ${shortId} allow-always`, "> ```", ""] : []),
825
+ `> Deny`, "> ```", `> /approve ${shortId} deny`, "> ```",
826
+ ].join("\n"),
827
+ },
828
+ },
829
+ buttons: [
830
+ { text: "Approve Once", buttonTheme: 1, permissionScope: buttonScope, callbackInfo: `once:${requestId}` },
831
+ ...(showSession ? [{ text: "This Session", buttonTheme: 2, permissionScope: buttonScope, callbackInfo: `session:${requestId}` }] : []),
832
+ ...(showAlways ? [{ text: "Always Allow", buttonTheme: 3, permissionScope: buttonScope, callbackInfo: `always:${requestId}` }] : []),
833
+ { text: "Deny", buttonTheme: 4, permissionScope: buttonScope, callbackInfo: `deny:${requestId}` },
834
+ ],
835
+ expireTime,
836
+ };
837
+ return { zh: zhCard, en: enCard };
838
+ }
839
+ // ══════════════════════════════════════════════
840
+ // MAIN PLUGIN
841
+ // ══════════════════════════════════════════════
749
842
  export const lansengerPlugin = {
750
843
  ...chatPlugin,
751
844
  setupWizard: lansengerSetupWizard,
@@ -801,6 +894,9 @@ export const lansengerPlugin = {
801
894
  return { kind: "unsupported" };
802
895
  return { kind: "enabled" };
803
896
  },
897
+ delivery: {
898
+ shouldSuppressForwardingFallback: () => true,
899
+ },
804
900
  native: {
805
901
  describeDeliveryCapabilities: ({ cfg, accountId }) => ({
806
902
  enabled: true,
@@ -833,6 +929,7 @@ export const lansengerPlugin = {
833
929
  const req = request?.request ?? request;
834
930
  const commandPreview = (req?.command ?? req?.summary ?? "").slice(0, 300);
835
931
  const sessionKey = req?.sessionKey ?? "unknown";
932
+ log.debug(`buildPendingPayload: command="${commandPreview.slice(0, 60)}" sessionKey="${sessionKey.slice(0, 40)}" requestKeys=${Object.keys(request ?? {}).join(",")} reqKeys=${Object.keys(req ?? {}).join(",")}`);
836
933
  const requestId = request?.id ?? req?.id ?? "unknown";
837
934
  const shortId = requestId.length > 8 ? requestId.slice(0, 8) : requestId;
838
935
  const approverIds = resolveLansengerApprovers({ cfg, accountId: target?.accountId ?? null });
@@ -841,136 +938,31 @@ export const lansengerPlugin = {
841
938
  : undefined;
842
939
  // Expiration: view.expiresAtMs is absolute ms, compute remaining seconds
843
940
  const expireTime = view?.expiresAtMs ? Math.max(1, Math.ceil((view.expiresAtMs - nowMs) / 1000)) : undefined;
844
- // "allow-always" is only available when the policy allows permanent approval
845
- const execAskPolicy = cfg?.approvals?.exec?.ask ?? "dangerous";
846
- const showAlways = execAskPolicy !== "always";
847
- const zhCard = {
848
- head: {
849
- title: "⚠️ 命令审批",
850
- headStatus: {
851
- describe: "待审批",
852
- colour: "#FFB116",
853
- statusIcon: 1,
854
- },
855
- },
856
- body: {
857
- title: "危险命令审批请求",
858
- content: {
859
- formatType: 1,
860
- text: [
861
- `**会话 ID**`,
862
- sessionKey,
863
- "",
864
- `**命令**`,
865
- "```",
866
- commandPreview,
867
- "```",
868
- "",
869
- `> 按钮不可用时请发送命令审批:`,
870
- "",
871
- `> 执行一次`,
872
- "> ```",
873
- `> /approve ${shortId} allow-once`,
874
- "> ```",
875
- "",
876
- `> 本会话有效`,
877
- "> ```",
878
- `> /approve ${shortId} allow-session`,
879
- "> ```",
880
- "",
881
- ...(showAlways ? [
882
- `> 永久允许`,
883
- "> ```",
884
- `> /approve ${shortId} allow-always`,
885
- "> ```",
886
- "",
887
- ] : []),
888
- `> 拒绝执行`,
889
- "> ```",
890
- `> /approve ${shortId} deny`,
891
- "> ```",
892
- ].join("\n"),
893
- },
894
- },
895
- buttons: [
896
- { text: "执行一次", buttonTheme: 1, permissionScope: buttonScope, callbackInfo: `once:${requestId}` },
897
- { text: "本会话有效", buttonTheme: 2, permissionScope: buttonScope, callbackInfo: `session:${requestId}` },
898
- ...(showAlways ? [{ text: "永久允许", buttonTheme: 3, permissionScope: buttonScope, callbackInfo: `always:${requestId}` }] : []),
899
- { text: "拒绝执行", buttonTheme: 4, permissionScope: buttonScope, callbackInfo: `deny:${requestId}` },
900
- ],
941
+ // Use the framework's allowedDecisions to determine which buttons to show.
942
+ // When exec.ask is "always", only allow-once + deny are available (no session/always).
943
+ const allowedDecisions = req?.allowedDecisions ?? ["allow-once", "allow-session", "allow-always", "deny"];
944
+ log.debug(`buildPendingPayload: allowedDecisions=${allowedDecisions.join(",")}`);
945
+ const { zh: zhCard, en: enCard } = buildApprovalCards({
946
+ sessionKey,
947
+ commandPreview,
948
+ requestId,
949
+ shortId,
950
+ buttonScope,
901
951
  expireTime,
902
- };
903
- const enCard = {
904
- head: {
905
- title: "⚠️ Command Approval",
906
- headStatus: {
907
- describe: "Pending",
908
- colour: "#FFB116",
909
- statusIcon: 1,
910
- },
911
- },
912
- body: {
913
- title: "Dangerous Command Approval Request",
914
- content: {
915
- formatType: 1,
916
- text: [
917
- `**Session**`,
918
- sessionKey,
919
- "",
920
- `**Command**`,
921
- "```",
922
- commandPreview,
923
- "```",
924
- "",
925
- `> If buttons are unavailable, use:`,
926
- "",
927
- `> Approve once`,
928
- "> ```",
929
- `> /approve ${shortId} allow-once`,
930
- "> ```",
931
- "",
932
- `> This session`,
933
- "> ```",
934
- `> /approve ${shortId} allow-session`,
935
- "> ```",
936
- "",
937
- ...(showAlways ? [
938
- `> Always allow`,
939
- "> ```",
940
- `> /approve ${shortId} allow-always`,
941
- "> ```",
942
- "",
943
- ] : []),
944
- `> Deny`,
945
- "> ```",
946
- `> /approve ${shortId} deny`,
947
- "> ```",
948
- ].join("\n"),
949
- },
950
- },
951
- buttons: [
952
- { text: "Approve Once", buttonTheme: 1, permissionScope: buttonScope, callbackInfo: `once:${requestId}` },
953
- { text: "This Session", buttonTheme: 2, permissionScope: buttonScope, callbackInfo: `session:${requestId}` },
954
- ...(showAlways ? [{ text: "Always Allow", buttonTheme: 3, permissionScope: buttonScope, callbackInfo: `always:${requestId}` }] : []),
955
- { text: "Deny", buttonTheme: 4, permissionScope: buttonScope, callbackInfo: `deny:${requestId}` },
956
- ],
957
- expireTime,
958
- };
952
+ allowedDecisions,
953
+ });
959
954
  return { type: "approveCard", zh: zhCard, en: enCard, isDynamic: true, requestId, sessionKey };
960
955
  },
961
956
  buildResolvedResult: ({ cfg, resolved, entry }) => {
962
- // When resolved.kind and strategy are both undefined, the resolution
963
- // hasn't completed yet (e.g. resolveApprovalOverGateway is still in
964
- // progress). Skip the update the callback handler already updated
965
- // the card, and the SDK will call us again once the resolution is final.
957
+ // Dump all params to understand the resolved structure from native approval runtime
958
+ const resolvedType = typeof resolved;
959
+ const resolvedKeys = resolved && typeof resolved === "object" ? Object.keys(resolved) : [];
960
+ const resolvedRaw = resolved && typeof resolved === "object" ? JSON.stringify(resolved).slice(0, 200) : String(resolved);
961
+ log.debug(`buildResolvedResult: resolved.type=${resolvedType} keys=[${resolvedKeys.join(",")}] raw=${resolvedRaw} entryKeys=${entry ? Object.keys(entry).join(",") : "no-entry"}`);
966
962
  const kindFromStrategy = resolved?.strategy?.kind;
967
963
  const kindFromResolved = resolved?.kind;
968
- if (kindFromStrategy === undefined && kindFromResolved === undefined) {
969
- log.debug(`buildResolvedResult: resolution not yet complete, skipping update`);
970
- return { kind: "noop" };
971
- }
972
- const strategyKind = kindFromStrategy ?? kindFromResolved ?? "deny";
973
- log.debug(`buildResolvedResult: resolved.kind=${kindFromResolved} resolved.strategy?.kind=${kindFromStrategy} strategyKind=${strategyKind}`);
964
+ const decisionFromResolved = resolved?.decision; // webchat/control-ui resolution
965
+ const strategyKind = kindFromStrategy ?? kindFromResolved ?? decisionFromResolved ?? "deny";
974
966
  // Button theme: 1=primary (allow-once), 2=secondary (allow-session), 3=secondary-black (allow-always), 4=warning (deny)
975
967
  const buttonThemeMap = { "allow-once": 1, "allow-session": 2, "allow-always": 3, deny: 4 };
976
968
  const resolvedButtonTheme = buttonThemeMap[strategyKind] ?? 4;
@@ -983,7 +975,7 @@ export const lansengerPlugin = {
983
975
  return { kind: "update", payload: { status: "denied", strategyKind: "deny", buttonTheme: 4 } };
984
976
  },
985
977
  buildExpiredResult: ({ cfg, request, entry }) => {
986
- return { kind: "delete" };
978
+ return { kind: "update", payload: { status: "denied", strategyKind: "expired" } };
987
979
  },
988
980
  },
989
981
  transport: {
@@ -1063,6 +1055,20 @@ export const lansengerPlugin = {
1063
1055
  },
1064
1056
  },
1065
1057
  }),
1058
+ message: createChannelMessageAdapterFromOutbound({
1059
+ outbound: {
1060
+ sendText: async (ctx) => {
1061
+ const sdkSessionKey = ctx.sessionKey;
1062
+ const sessionAccountId = sdkSessionKey ? getSessionAccountId(sdkSessionKey) : undefined;
1063
+ const account = resolveAccount(ctx.cfg, sessionAccountId ?? ctx.accountId);
1064
+ const client = getRunningClientByAccountId(account.appId) ?? makeClient(account);
1065
+ log.debug(`message.sendText: to=${ctx.to} textLen=${ctx.text?.length ?? 0}`);
1066
+ const result = await client.sendFormatText(ctx.to, ctx.text);
1067
+ log.debug(`message.sendText: result — success=${result.success} messageId=${result.messageId ?? "none"}`);
1068
+ return { messageId: result.messageId ?? "" };
1069
+ },
1070
+ },
1071
+ }),
1066
1072
  commands: {
1067
1073
  nativeCommandsAutoEnabled: true,
1068
1074
  nativeSkillsAutoEnabled: true,