@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 +24 -0
- package/dist/src/channel.js +147 -141
- package/dist/src/channel.js.map +1 -1
- package/dist/src/client.js +56 -4
- package/dist/src/client.js.map +1 -1
- package/dist/src/runtime.js +301 -278
- package/dist/src/runtime.js.map +1 -1
- package/dist/src/setup-i18n.js +1 -1
- package/dist/src/setup-i18n.js.map +1 -1
- package/dist/src/tools.js +0 -1
- package/dist/src/tools.js.map +1 -1
- package/package.json +1 -1
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
|
package/dist/src/channel.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
|
477
|
-
const
|
|
478
|
-
const
|
|
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
|
|
560
|
-
const
|
|
561
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
576
|
-
|
|
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
|
-
//
|
|
845
|
-
|
|
846
|
-
const
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
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
|
-
|
|
969
|
-
|
|
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: "
|
|
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,
|