@openclaw/feishu 2026.3.1 → 2026.3.2
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/package.json +1 -1
- package/src/accounts.test.ts +74 -3
- package/src/accounts.ts +69 -10
- package/src/bot.checkBotMentioned.test.ts +1 -1
- package/src/bot.test.ts +390 -29
- package/src/bot.ts +131 -61
- package/src/channel.ts +20 -4
- package/src/client.test.ts +14 -0
- package/src/config-schema.test.ts +19 -0
- package/src/config-schema.ts +13 -9
- package/src/dedup.ts +47 -1
- package/src/doc-schema.ts +16 -22
- package/src/docx.account-selection.test.ts +7 -13
- package/src/docx.test.ts +41 -189
- package/src/media.test.ts +104 -1
- package/src/media.ts +21 -1
- package/src/mention.ts +1 -1
- package/src/monitor.account.ts +266 -18
- package/src/monitor.reaction.test.ts +345 -2
- package/src/monitor.startup.test.ts +17 -1
- package/src/monitor.state.defaults.test.ts +46 -0
- package/src/monitor.state.ts +84 -8
- package/src/monitor.test-mocks.ts +12 -0
- package/src/monitor.webhook-security.test.ts +26 -9
- package/src/onboarding.status.test.ts +25 -0
- package/src/onboarding.ts +144 -52
- package/src/probe.test.ts +38 -20
- package/src/probe.ts +57 -37
- package/src/reply-dispatcher.test.ts +41 -0
- package/src/reply-dispatcher.ts +26 -7
- package/src/secret-input.ts +19 -0
- package/src/send-target.test.ts +74 -0
- package/src/send-target.ts +6 -2
- package/src/targets.test.ts +29 -0
- package/src/targets.ts +21 -1
- package/src/types.ts +9 -1
package/package.json
CHANGED
package/src/accounts.test.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
resolveDefaultFeishuAccountId,
|
|
4
|
+
resolveDefaultFeishuAccountSelection,
|
|
5
|
+
resolveFeishuAccount,
|
|
6
|
+
} from "./accounts.js";
|
|
3
7
|
|
|
4
8
|
describe("resolveDefaultFeishuAccountId", () => {
|
|
5
9
|
it("prefers channels.feishu.defaultAccount when configured", () => {
|
|
@@ -33,11 +37,26 @@ describe("resolveDefaultFeishuAccountId", () => {
|
|
|
33
37
|
expect(resolveDefaultFeishuAccountId(cfg as never)).toBe("router-d");
|
|
34
38
|
});
|
|
35
39
|
|
|
36
|
-
it("
|
|
40
|
+
it("keeps configured defaultAccount even when not present in accounts map", () => {
|
|
41
|
+
const cfg = {
|
|
42
|
+
channels: {
|
|
43
|
+
feishu: {
|
|
44
|
+
defaultAccount: "router-d",
|
|
45
|
+
accounts: {
|
|
46
|
+
default: { appId: "cli_default", appSecret: "secret_default" },
|
|
47
|
+
zeta: { appId: "cli_zeta", appSecret: "secret_zeta" },
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
expect(resolveDefaultFeishuAccountId(cfg as never)).toBe("router-d");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("falls back to literal default account id when present", () => {
|
|
37
57
|
const cfg = {
|
|
38
58
|
channels: {
|
|
39
59
|
feishu: {
|
|
40
|
-
defaultAccount: "missing",
|
|
41
60
|
accounts: {
|
|
42
61
|
default: { appId: "cli_default", appSecret: "secret_default" },
|
|
43
62
|
zeta: { appId: "cli_zeta", appSecret: "secret_zeta" },
|
|
@@ -48,9 +67,59 @@ describe("resolveDefaultFeishuAccountId", () => {
|
|
|
48
67
|
|
|
49
68
|
expect(resolveDefaultFeishuAccountId(cfg as never)).toBe("default");
|
|
50
69
|
});
|
|
70
|
+
|
|
71
|
+
it("reports selection source for configured defaults and mapped defaults", () => {
|
|
72
|
+
const explicitDefaultCfg = {
|
|
73
|
+
channels: {
|
|
74
|
+
feishu: {
|
|
75
|
+
defaultAccount: "router-d",
|
|
76
|
+
accounts: {},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
expect(resolveDefaultFeishuAccountSelection(explicitDefaultCfg as never)).toEqual({
|
|
81
|
+
accountId: "router-d",
|
|
82
|
+
source: "explicit-default",
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const mappedDefaultCfg = {
|
|
86
|
+
channels: {
|
|
87
|
+
feishu: {
|
|
88
|
+
accounts: {
|
|
89
|
+
default: { appId: "cli_default", appSecret: "secret_default" },
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
expect(resolveDefaultFeishuAccountSelection(mappedDefaultCfg as never)).toEqual({
|
|
95
|
+
accountId: "default",
|
|
96
|
+
source: "mapped-default",
|
|
97
|
+
});
|
|
98
|
+
});
|
|
51
99
|
});
|
|
52
100
|
|
|
53
101
|
describe("resolveFeishuAccount", () => {
|
|
102
|
+
it("uses top-level credentials with configured default account id even without account map entry", () => {
|
|
103
|
+
const cfg = {
|
|
104
|
+
channels: {
|
|
105
|
+
feishu: {
|
|
106
|
+
defaultAccount: "router-d",
|
|
107
|
+
appId: "top_level_app",
|
|
108
|
+
appSecret: "top_level_secret",
|
|
109
|
+
accounts: {
|
|
110
|
+
default: { appId: "cli_default", appSecret: "secret_default" },
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const account = resolveFeishuAccount({ cfg: cfg as never, accountId: undefined });
|
|
117
|
+
expect(account.accountId).toBe("router-d");
|
|
118
|
+
expect(account.selectionSource).toBe("explicit-default");
|
|
119
|
+
expect(account.configured).toBe(true);
|
|
120
|
+
expect(account.appId).toBe("top_level_app");
|
|
121
|
+
});
|
|
122
|
+
|
|
54
123
|
it("uses configured default account when accountId is omitted", () => {
|
|
55
124
|
const cfg = {
|
|
56
125
|
channels: {
|
|
@@ -66,6 +135,7 @@ describe("resolveFeishuAccount", () => {
|
|
|
66
135
|
|
|
67
136
|
const account = resolveFeishuAccount({ cfg: cfg as never, accountId: undefined });
|
|
68
137
|
expect(account.accountId).toBe("router-d");
|
|
138
|
+
expect(account.selectionSource).toBe("explicit-default");
|
|
69
139
|
expect(account.configured).toBe(true);
|
|
70
140
|
expect(account.appId).toBe("cli_router");
|
|
71
141
|
});
|
|
@@ -85,6 +155,7 @@ describe("resolveFeishuAccount", () => {
|
|
|
85
155
|
|
|
86
156
|
const account = resolveFeishuAccount({ cfg: cfg as never, accountId: "default" });
|
|
87
157
|
expect(account.accountId).toBe("default");
|
|
158
|
+
expect(account.selectionSource).toBe("explicit");
|
|
88
159
|
expect(account.appId).toBe("cli_default");
|
|
89
160
|
});
|
|
90
161
|
});
|
package/src/accounts.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
2
2
|
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
|
3
|
+
import { normalizeResolvedSecretInputString, normalizeSecretInputString } from "./secret-input.js";
|
|
3
4
|
import type {
|
|
4
5
|
FeishuConfig,
|
|
5
6
|
FeishuAccountConfig,
|
|
7
|
+
FeishuDefaultAccountSelectionSource,
|
|
6
8
|
FeishuDomain,
|
|
7
9
|
ResolvedFeishuAccount,
|
|
8
10
|
} from "./types.js";
|
|
@@ -32,19 +34,38 @@ export function listFeishuAccountIds(cfg: ClawdbotConfig): string[] {
|
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
|
-
* Resolve the default account
|
|
37
|
+
* Resolve the default account selection and its source.
|
|
36
38
|
*/
|
|
37
|
-
export function
|
|
39
|
+
export function resolveDefaultFeishuAccountSelection(cfg: ClawdbotConfig): {
|
|
40
|
+
accountId: string;
|
|
41
|
+
source: FeishuDefaultAccountSelectionSource;
|
|
42
|
+
} {
|
|
38
43
|
const preferredRaw = (cfg.channels?.feishu as FeishuConfig | undefined)?.defaultAccount?.trim();
|
|
39
44
|
const preferred = preferredRaw ? normalizeAccountId(preferredRaw) : undefined;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
if (preferred) {
|
|
46
|
+
return {
|
|
47
|
+
accountId: preferred,
|
|
48
|
+
source: "explicit-default",
|
|
49
|
+
};
|
|
43
50
|
}
|
|
51
|
+
const ids = listFeishuAccountIds(cfg);
|
|
44
52
|
if (ids.includes(DEFAULT_ACCOUNT_ID)) {
|
|
45
|
-
return
|
|
53
|
+
return {
|
|
54
|
+
accountId: DEFAULT_ACCOUNT_ID,
|
|
55
|
+
source: "mapped-default",
|
|
56
|
+
};
|
|
46
57
|
}
|
|
47
|
-
return
|
|
58
|
+
return {
|
|
59
|
+
accountId: ids[0] ?? DEFAULT_ACCOUNT_ID,
|
|
60
|
+
source: "fallback",
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Resolve the default account ID.
|
|
66
|
+
*/
|
|
67
|
+
export function resolveDefaultFeishuAccountId(cfg: ClawdbotConfig): string {
|
|
68
|
+
return resolveDefaultFeishuAccountSelection(cfg).accountId;
|
|
48
69
|
}
|
|
49
70
|
|
|
50
71
|
/**
|
|
@@ -87,9 +108,34 @@ export function resolveFeishuCredentials(cfg?: FeishuConfig): {
|
|
|
87
108
|
encryptKey?: string;
|
|
88
109
|
verificationToken?: string;
|
|
89
110
|
domain: FeishuDomain;
|
|
111
|
+
} | null;
|
|
112
|
+
export function resolveFeishuCredentials(
|
|
113
|
+
cfg: FeishuConfig | undefined,
|
|
114
|
+
options: { allowUnresolvedSecretRef?: boolean },
|
|
115
|
+
): {
|
|
116
|
+
appId: string;
|
|
117
|
+
appSecret: string;
|
|
118
|
+
encryptKey?: string;
|
|
119
|
+
verificationToken?: string;
|
|
120
|
+
domain: FeishuDomain;
|
|
121
|
+
} | null;
|
|
122
|
+
export function resolveFeishuCredentials(
|
|
123
|
+
cfg?: FeishuConfig,
|
|
124
|
+
options?: { allowUnresolvedSecretRef?: boolean },
|
|
125
|
+
): {
|
|
126
|
+
appId: string;
|
|
127
|
+
appSecret: string;
|
|
128
|
+
encryptKey?: string;
|
|
129
|
+
verificationToken?: string;
|
|
130
|
+
domain: FeishuDomain;
|
|
90
131
|
} | null {
|
|
91
132
|
const appId = cfg?.appId?.trim();
|
|
92
|
-
const appSecret =
|
|
133
|
+
const appSecret = options?.allowUnresolvedSecretRef
|
|
134
|
+
? normalizeSecretInputString(cfg?.appSecret)
|
|
135
|
+
: normalizeResolvedSecretInputString({
|
|
136
|
+
value: cfg?.appSecret,
|
|
137
|
+
path: "channels.feishu.appSecret",
|
|
138
|
+
});
|
|
93
139
|
if (!appId || !appSecret) {
|
|
94
140
|
return null;
|
|
95
141
|
}
|
|
@@ -97,7 +143,13 @@ export function resolveFeishuCredentials(cfg?: FeishuConfig): {
|
|
|
97
143
|
appId,
|
|
98
144
|
appSecret,
|
|
99
145
|
encryptKey: cfg?.encryptKey?.trim() || undefined,
|
|
100
|
-
verificationToken:
|
|
146
|
+
verificationToken:
|
|
147
|
+
(options?.allowUnresolvedSecretRef
|
|
148
|
+
? normalizeSecretInputString(cfg?.verificationToken)
|
|
149
|
+
: normalizeResolvedSecretInputString({
|
|
150
|
+
value: cfg?.verificationToken,
|
|
151
|
+
path: "channels.feishu.verificationToken",
|
|
152
|
+
})) || undefined,
|
|
101
153
|
domain: cfg?.domain ?? "feishu",
|
|
102
154
|
};
|
|
103
155
|
}
|
|
@@ -111,9 +163,15 @@ export function resolveFeishuAccount(params: {
|
|
|
111
163
|
}): ResolvedFeishuAccount {
|
|
112
164
|
const hasExplicitAccountId =
|
|
113
165
|
typeof params.accountId === "string" && params.accountId.trim() !== "";
|
|
166
|
+
const defaultSelection = hasExplicitAccountId
|
|
167
|
+
? null
|
|
168
|
+
: resolveDefaultFeishuAccountSelection(params.cfg);
|
|
114
169
|
const accountId = hasExplicitAccountId
|
|
115
170
|
? normalizeAccountId(params.accountId)
|
|
116
|
-
:
|
|
171
|
+
: (defaultSelection?.accountId ?? DEFAULT_ACCOUNT_ID);
|
|
172
|
+
const selectionSource = hasExplicitAccountId
|
|
173
|
+
? "explicit"
|
|
174
|
+
: (defaultSelection?.source ?? "fallback");
|
|
117
175
|
const feishuCfg = params.cfg.channels?.feishu as FeishuConfig | undefined;
|
|
118
176
|
|
|
119
177
|
// Base enabled state (top-level)
|
|
@@ -131,6 +189,7 @@ export function resolveFeishuAccount(params: {
|
|
|
131
189
|
|
|
132
190
|
return {
|
|
133
191
|
accountId,
|
|
192
|
+
selectionSource,
|
|
134
193
|
enabled,
|
|
135
194
|
configured: Boolean(creds),
|
|
136
195
|
name: (merged as FeishuAccountConfig).name?.trim() || undefined,
|
|
@@ -3,7 +3,7 @@ import { parseFeishuMessageEvent } from "./bot.js";
|
|
|
3
3
|
|
|
4
4
|
// Helper to build a minimal FeishuMessageEvent for testing
|
|
5
5
|
function makeEvent(
|
|
6
|
-
chatType: "p2p" | "group",
|
|
6
|
+
chatType: "p2p" | "group" | "private",
|
|
7
7
|
mentions?: Array<{ key: string; name: string; id: { open_id?: string } }>,
|
|
8
8
|
text = "hello",
|
|
9
9
|
) {
|