@gakr-gakr/feishu 0.1.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/api.ts +32 -0
- package/autobot.plugin.json +180 -0
- package/channel-entry.ts +20 -0
- package/channel-plugin-api.ts +1 -0
- package/contract-api.ts +16 -0
- package/index.ts +82 -0
- package/package.json +62 -0
- package/runtime-api.ts +55 -0
- package/secret-contract-api.ts +5 -0
- package/security-contract-api.ts +1 -0
- package/session-key-api.ts +1 -0
- package/setup-api.ts +3 -0
- package/setup-entry.ts +13 -0
- package/skills/feishu-doc/SKILL.md +211 -0
- package/skills/feishu-doc/references/block-types.md +103 -0
- package/skills/feishu-drive/SKILL.md +97 -0
- package/skills/feishu-perm/SKILL.md +119 -0
- package/skills/feishu-wiki/SKILL.md +113 -0
- package/src/accounts.ts +333 -0
- package/src/agent-config.ts +21 -0
- package/src/app-registration.ts +331 -0
- package/src/approval-auth.ts +25 -0
- package/src/async.ts +104 -0
- package/src/audio-preflight.runtime.ts +9 -0
- package/src/bitable.ts +762 -0
- package/src/bot-content.ts +485 -0
- package/src/bot-runtime-api.ts +12 -0
- package/src/bot-sender-name.ts +125 -0
- package/src/bot.ts +1703 -0
- package/src/card-action.ts +447 -0
- package/src/card-interaction.ts +159 -0
- package/src/card-test-helpers.ts +54 -0
- package/src/card-ux-approval.ts +65 -0
- package/src/card-ux-launcher.ts +121 -0
- package/src/card-ux-shared.ts +33 -0
- package/src/channel-runtime-api.ts +16 -0
- package/src/channel.runtime.ts +47 -0
- package/src/channel.ts +1423 -0
- package/src/chat-schema.ts +25 -0
- package/src/chat.ts +188 -0
- package/src/client-timeout.ts +42 -0
- package/src/client.ts +262 -0
- package/src/comment-dispatcher-runtime-api.ts +6 -0
- package/src/comment-dispatcher.ts +107 -0
- package/src/comment-handler-runtime-api.ts +3 -0
- package/src/comment-handler.ts +303 -0
- package/src/comment-reaction.ts +259 -0
- package/src/comment-shared.ts +406 -0
- package/src/comment-target.ts +44 -0
- package/src/config-schema.ts +335 -0
- package/src/conversation-id.ts +199 -0
- package/src/dedup-runtime-api.ts +1 -0
- package/src/dedup.ts +141 -0
- package/src/dedupe-key.ts +72 -0
- package/src/directory.static.ts +61 -0
- package/src/directory.ts +124 -0
- package/src/doc-schema.ts +182 -0
- package/src/docx-batch-insert.ts +223 -0
- package/src/docx-color-text.ts +154 -0
- package/src/docx-table-ops.ts +316 -0
- package/src/docx-types.ts +38 -0
- package/src/docx.ts +1596 -0
- package/src/drive-schema.ts +92 -0
- package/src/drive.ts +829 -0
- package/src/dynamic-agent.ts +143 -0
- package/src/event-types.ts +45 -0
- package/src/external-keys.ts +19 -0
- package/src/lifecycle.test-support.ts +220 -0
- package/src/media.ts +1105 -0
- package/src/mention-target.types.ts +5 -0
- package/src/mention.ts +114 -0
- package/src/message-action-contract.ts +13 -0
- package/src/monitor-state-runtime-api.ts +7 -0
- package/src/monitor-transport-runtime-api.ts +10 -0
- package/src/monitor.account.ts +492 -0
- package/src/monitor.acp-init-failure.lifecycle.test-support.ts +219 -0
- package/src/monitor.bot-identity.ts +86 -0
- package/src/monitor.bot-menu-handler.ts +165 -0
- package/src/monitor.bot-menu.lifecycle.test-support.ts +224 -0
- package/src/monitor.broadcast.reply-once.lifecycle.test-support.ts +264 -0
- package/src/monitor.card-action.lifecycle.test-support.ts +421 -0
- package/src/monitor.comment-notice-handler.ts +105 -0
- package/src/monitor.comment.ts +1386 -0
- package/src/monitor.message-handler.ts +350 -0
- package/src/monitor.reaction.lifecycle.test-support.ts +68 -0
- package/src/monitor.startup.ts +74 -0
- package/src/monitor.state.ts +170 -0
- package/src/monitor.synthetic-error.ts +18 -0
- package/src/monitor.test-mocks.ts +46 -0
- package/src/monitor.transport.ts +451 -0
- package/src/monitor.ts +100 -0
- package/src/outbound-runtime-api.ts +1 -0
- package/src/outbound.ts +785 -0
- package/src/perm-schema.ts +52 -0
- package/src/perm.ts +170 -0
- package/src/pins.ts +108 -0
- package/src/policy.ts +321 -0
- package/src/post.ts +275 -0
- package/src/probe.ts +166 -0
- package/src/processing-claims.ts +59 -0
- package/src/qr-terminal.ts +1 -0
- package/src/reactions.ts +123 -0
- package/src/reasoning-preview.ts +28 -0
- package/src/reply-dispatcher-runtime-api.ts +7 -0
- package/src/reply-dispatcher.ts +748 -0
- package/src/runtime.ts +9 -0
- package/src/secret-contract.ts +145 -0
- package/src/secret-input.ts +1 -0
- package/src/security-audit-shared.ts +69 -0
- package/src/security-audit.ts +1 -0
- package/src/send-result.ts +80 -0
- package/src/send-target.ts +35 -0
- package/src/send.ts +861 -0
- package/src/sequential-key.ts +28 -0
- package/src/sequential-queue.ts +86 -0
- package/src/session-conversation.ts +42 -0
- package/src/session-route.ts +48 -0
- package/src/setup-core.ts +51 -0
- package/src/setup-surface.ts +618 -0
- package/src/streaming-card.ts +571 -0
- package/src/subagent-hooks.ts +413 -0
- package/src/targets.ts +97 -0
- package/src/thread-bindings.ts +331 -0
- package/src/tool-account.ts +93 -0
- package/src/tool-factory-test-harness.ts +79 -0
- package/src/tool-result.ts +16 -0
- package/src/tools-config.ts +22 -0
- package/src/types.ts +106 -0
- package/src/typing.ts +214 -0
- package/src/wiki-schema.ts +69 -0
- package/src/wiki.ts +270 -0
- package/subagent-hooks-api.ts +31 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Type, type Static } from "typebox";
|
|
2
|
+
|
|
3
|
+
const TokenType = Type.Union([
|
|
4
|
+
Type.Literal("doc"),
|
|
5
|
+
Type.Literal("docx"),
|
|
6
|
+
Type.Literal("sheet"),
|
|
7
|
+
Type.Literal("bitable"),
|
|
8
|
+
Type.Literal("folder"),
|
|
9
|
+
Type.Literal("file"),
|
|
10
|
+
Type.Literal("wiki"),
|
|
11
|
+
Type.Literal("mindnote"),
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
const MemberType = Type.Union([
|
|
15
|
+
Type.Literal("email"),
|
|
16
|
+
Type.Literal("openid"),
|
|
17
|
+
Type.Literal("userid"),
|
|
18
|
+
Type.Literal("unionid"),
|
|
19
|
+
Type.Literal("openchat"),
|
|
20
|
+
Type.Literal("opendepartmentid"),
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
const Permission = Type.Union([
|
|
24
|
+
Type.Literal("view"),
|
|
25
|
+
Type.Literal("edit"),
|
|
26
|
+
Type.Literal("full_access"),
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
export const FeishuPermSchema = Type.Union([
|
|
30
|
+
Type.Object({
|
|
31
|
+
action: Type.Literal("list"),
|
|
32
|
+
token: Type.String({ description: "File token" }),
|
|
33
|
+
type: TokenType,
|
|
34
|
+
}),
|
|
35
|
+
Type.Object({
|
|
36
|
+
action: Type.Literal("add"),
|
|
37
|
+
token: Type.String({ description: "File token" }),
|
|
38
|
+
type: TokenType,
|
|
39
|
+
member_type: MemberType,
|
|
40
|
+
member_id: Type.String({ description: "Member ID (email, open_id, user_id, etc.)" }),
|
|
41
|
+
perm: Permission,
|
|
42
|
+
}),
|
|
43
|
+
Type.Object({
|
|
44
|
+
action: Type.Literal("remove"),
|
|
45
|
+
token: Type.String({ description: "File token" }),
|
|
46
|
+
type: TokenType,
|
|
47
|
+
member_type: MemberType,
|
|
48
|
+
member_id: Type.String({ description: "Member ID to remove" }),
|
|
49
|
+
}),
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
export type FeishuPermParams = Static<typeof FeishuPermSchema>;
|
package/src/perm.ts
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type * as Lark from "@larksuiteoapi/node-sdk";
|
|
2
|
+
import type { AutoBotPluginApi } from "../runtime-api.js";
|
|
3
|
+
import { listEnabledFeishuAccounts } from "./accounts.js";
|
|
4
|
+
import { FeishuPermSchema, type FeishuPermParams } from "./perm-schema.js";
|
|
5
|
+
import { createFeishuToolClient, resolveAnyEnabledFeishuToolsConfig } from "./tool-account.js";
|
|
6
|
+
import {
|
|
7
|
+
jsonToolResult,
|
|
8
|
+
toolExecutionErrorResult,
|
|
9
|
+
unknownToolActionResult,
|
|
10
|
+
} from "./tool-result.js";
|
|
11
|
+
|
|
12
|
+
type ListTokenType =
|
|
13
|
+
| "doc"
|
|
14
|
+
| "sheet"
|
|
15
|
+
| "file"
|
|
16
|
+
| "wiki"
|
|
17
|
+
| "bitable"
|
|
18
|
+
| "docx"
|
|
19
|
+
| "mindnote"
|
|
20
|
+
| "minutes"
|
|
21
|
+
| "slides";
|
|
22
|
+
type CreateTokenType =
|
|
23
|
+
| "doc"
|
|
24
|
+
| "sheet"
|
|
25
|
+
| "file"
|
|
26
|
+
| "wiki"
|
|
27
|
+
| "bitable"
|
|
28
|
+
| "docx"
|
|
29
|
+
| "folder"
|
|
30
|
+
| "mindnote"
|
|
31
|
+
| "minutes"
|
|
32
|
+
| "slides";
|
|
33
|
+
type MemberType =
|
|
34
|
+
| "email"
|
|
35
|
+
| "openid"
|
|
36
|
+
| "unionid"
|
|
37
|
+
| "openchat"
|
|
38
|
+
| "opendepartmentid"
|
|
39
|
+
| "userid"
|
|
40
|
+
| "groupid"
|
|
41
|
+
| "wikispaceid";
|
|
42
|
+
type PermType = "view" | "edit" | "full_access";
|
|
43
|
+
|
|
44
|
+
// ============ Actions ============
|
|
45
|
+
|
|
46
|
+
async function listMembers(client: Lark.Client, token: string, type: string) {
|
|
47
|
+
const res = await client.drive.permissionMember.list({
|
|
48
|
+
path: { token },
|
|
49
|
+
params: { type: type as ListTokenType },
|
|
50
|
+
});
|
|
51
|
+
if (res.code !== 0) {
|
|
52
|
+
throw new Error(res.msg);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
members:
|
|
57
|
+
res.data?.items?.map((m) => ({
|
|
58
|
+
member_type: m.member_type,
|
|
59
|
+
member_id: m.member_id,
|
|
60
|
+
perm: m.perm,
|
|
61
|
+
name: m.name,
|
|
62
|
+
})) ?? [],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function addMember(
|
|
67
|
+
client: Lark.Client,
|
|
68
|
+
token: string,
|
|
69
|
+
type: string,
|
|
70
|
+
memberType: string,
|
|
71
|
+
memberId: string,
|
|
72
|
+
perm: string,
|
|
73
|
+
) {
|
|
74
|
+
const res = await client.drive.permissionMember.create({
|
|
75
|
+
path: { token },
|
|
76
|
+
params: { type: type as CreateTokenType, need_notification: false },
|
|
77
|
+
data: {
|
|
78
|
+
member_type: memberType as MemberType,
|
|
79
|
+
member_id: memberId,
|
|
80
|
+
perm: perm as PermType,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
if (res.code !== 0) {
|
|
84
|
+
throw new Error(res.msg);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
success: true,
|
|
89
|
+
member: res.data?.member,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function removeMember(
|
|
94
|
+
client: Lark.Client,
|
|
95
|
+
token: string,
|
|
96
|
+
type: string,
|
|
97
|
+
memberType: string,
|
|
98
|
+
memberId: string,
|
|
99
|
+
) {
|
|
100
|
+
const res = await client.drive.permissionMember.delete({
|
|
101
|
+
path: { token, member_id: memberId },
|
|
102
|
+
params: { type: type as CreateTokenType, member_type: memberType as MemberType },
|
|
103
|
+
});
|
|
104
|
+
if (res.code !== 0) {
|
|
105
|
+
throw new Error(res.msg);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
success: true,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ============ Tool Registration ============
|
|
114
|
+
|
|
115
|
+
export function registerFeishuPermTools(api: AutoBotPluginApi) {
|
|
116
|
+
if (!api.config) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const accounts = listEnabledFeishuAccounts(api.config);
|
|
121
|
+
if (accounts.length === 0) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const toolsCfg = resolveAnyEnabledFeishuToolsConfig(accounts);
|
|
126
|
+
if (!toolsCfg.perm) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
type FeishuPermExecuteParams = FeishuPermParams & { accountId?: string };
|
|
131
|
+
|
|
132
|
+
api.registerTool(
|
|
133
|
+
(ctx) => {
|
|
134
|
+
const defaultAccountId = ctx.agentAccountId;
|
|
135
|
+
return {
|
|
136
|
+
name: "feishu_perm",
|
|
137
|
+
label: "Feishu Perm",
|
|
138
|
+
description: "Feishu permission management. Actions: list, add, remove",
|
|
139
|
+
parameters: FeishuPermSchema,
|
|
140
|
+
async execute(_toolCallId, params) {
|
|
141
|
+
const p = params as FeishuPermExecuteParams;
|
|
142
|
+
try {
|
|
143
|
+
const client = createFeishuToolClient({
|
|
144
|
+
api,
|
|
145
|
+
executeParams: p,
|
|
146
|
+
defaultAccountId,
|
|
147
|
+
});
|
|
148
|
+
switch (p.action) {
|
|
149
|
+
case "list":
|
|
150
|
+
return jsonToolResult(await listMembers(client, p.token, p.type));
|
|
151
|
+
case "add":
|
|
152
|
+
return jsonToolResult(
|
|
153
|
+
await addMember(client, p.token, p.type, p.member_type, p.member_id, p.perm),
|
|
154
|
+
);
|
|
155
|
+
case "remove":
|
|
156
|
+
return jsonToolResult(
|
|
157
|
+
await removeMember(client, p.token, p.type, p.member_type, p.member_id),
|
|
158
|
+
);
|
|
159
|
+
default:
|
|
160
|
+
return unknownToolActionResult((p as { action?: unknown }).action);
|
|
161
|
+
}
|
|
162
|
+
} catch (err) {
|
|
163
|
+
return toolExecutionErrorResult(err);
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
},
|
|
168
|
+
{ name: "feishu_perm" },
|
|
169
|
+
);
|
|
170
|
+
}
|
package/src/pins.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { ClawdbotConfig } from "../runtime-api.js";
|
|
2
|
+
import { resolveFeishuRuntimeAccount } from "./accounts.js";
|
|
3
|
+
import { createFeishuClient } from "./client.js";
|
|
4
|
+
|
|
5
|
+
type FeishuPin = {
|
|
6
|
+
messageId: string;
|
|
7
|
+
chatId?: string;
|
|
8
|
+
operatorId?: string;
|
|
9
|
+
operatorIdType?: string;
|
|
10
|
+
createTime?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function assertFeishuPinApiSuccess(response: { code?: number; msg?: string }, action: string) {
|
|
14
|
+
if (response.code !== 0) {
|
|
15
|
+
throw new Error(`Feishu ${action} failed: ${response.msg || `code ${response.code}`}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizePin(pin: {
|
|
20
|
+
message_id: string;
|
|
21
|
+
chat_id?: string;
|
|
22
|
+
operator_id?: string;
|
|
23
|
+
operator_id_type?: string;
|
|
24
|
+
create_time?: string;
|
|
25
|
+
}): FeishuPin {
|
|
26
|
+
return {
|
|
27
|
+
messageId: pin.message_id,
|
|
28
|
+
chatId: pin.chat_id,
|
|
29
|
+
operatorId: pin.operator_id,
|
|
30
|
+
operatorIdType: pin.operator_id_type,
|
|
31
|
+
createTime: pin.create_time,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function createPinFeishu(params: {
|
|
36
|
+
cfg: ClawdbotConfig;
|
|
37
|
+
messageId: string;
|
|
38
|
+
accountId?: string;
|
|
39
|
+
}): Promise<FeishuPin | null> {
|
|
40
|
+
const account = resolveFeishuRuntimeAccount({ cfg: params.cfg, accountId: params.accountId });
|
|
41
|
+
if (!account.configured) {
|
|
42
|
+
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const client = createFeishuClient(account);
|
|
46
|
+
const response = await client.im.pin.create({
|
|
47
|
+
data: {
|
|
48
|
+
message_id: params.messageId,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
assertFeishuPinApiSuccess(response, "pin create");
|
|
52
|
+
return response.data?.pin ? normalizePin(response.data.pin) : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function removePinFeishu(params: {
|
|
56
|
+
cfg: ClawdbotConfig;
|
|
57
|
+
messageId: string;
|
|
58
|
+
accountId?: string;
|
|
59
|
+
}): Promise<void> {
|
|
60
|
+
const account = resolveFeishuRuntimeAccount({ cfg: params.cfg, accountId: params.accountId });
|
|
61
|
+
if (!account.configured) {
|
|
62
|
+
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const client = createFeishuClient(account);
|
|
66
|
+
const response = await client.im.pin.delete({
|
|
67
|
+
path: {
|
|
68
|
+
message_id: params.messageId,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
assertFeishuPinApiSuccess(response, "pin delete");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function listPinsFeishu(params: {
|
|
75
|
+
cfg: ClawdbotConfig;
|
|
76
|
+
chatId: string;
|
|
77
|
+
startTime?: string;
|
|
78
|
+
endTime?: string;
|
|
79
|
+
pageSize?: number;
|
|
80
|
+
pageToken?: string;
|
|
81
|
+
accountId?: string;
|
|
82
|
+
}): Promise<{ chatId: string; pins: FeishuPin[]; hasMore: boolean; pageToken?: string }> {
|
|
83
|
+
const account = resolveFeishuRuntimeAccount({ cfg: params.cfg, accountId: params.accountId });
|
|
84
|
+
if (!account.configured) {
|
|
85
|
+
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const client = createFeishuClient(account);
|
|
89
|
+
const response = await client.im.pin.list({
|
|
90
|
+
params: {
|
|
91
|
+
chat_id: params.chatId,
|
|
92
|
+
...(params.startTime ? { start_time: params.startTime } : {}),
|
|
93
|
+
...(params.endTime ? { end_time: params.endTime } : {}),
|
|
94
|
+
...(typeof params.pageSize === "number"
|
|
95
|
+
? { page_size: Math.max(1, Math.min(100, Math.floor(params.pageSize))) }
|
|
96
|
+
: {}),
|
|
97
|
+
...(params.pageToken ? { page_token: params.pageToken } : {}),
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
assertFeishuPinApiSuccess(response, "pin list");
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
chatId: params.chatId,
|
|
104
|
+
pins: (response.data?.items ?? []).map(normalizePin),
|
|
105
|
+
hasMore: response.data?.has_more === true,
|
|
106
|
+
pageToken: response.data?.page_token,
|
|
107
|
+
};
|
|
108
|
+
}
|
package/src/policy.ts
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeAccountId,
|
|
3
|
+
resolveMergedAccountConfig,
|
|
4
|
+
} from "autobot/plugin-sdk/account-resolution";
|
|
5
|
+
import {
|
|
6
|
+
createChannelIngressResolver,
|
|
7
|
+
defineStableChannelIngressIdentity,
|
|
8
|
+
type ChannelIngressIdentitySubjectInput,
|
|
9
|
+
type ResolveChannelMessageIngressParams,
|
|
10
|
+
} from "autobot/plugin-sdk/channel-ingress-runtime";
|
|
11
|
+
import type { AutoBotConfig } from "autobot/plugin-sdk/core";
|
|
12
|
+
import { normalizeOptionalLowercaseString } from "autobot/plugin-sdk/string-coerce-runtime";
|
|
13
|
+
import type { ChannelGroupContext } from "../runtime-api.js";
|
|
14
|
+
import { detectIdType } from "./targets.js";
|
|
15
|
+
import type { FeishuConfig } from "./types.js";
|
|
16
|
+
|
|
17
|
+
type FeishuDmPolicy = "open" | "pairing" | "allowlist" | "disabled";
|
|
18
|
+
type FeishuGroupPolicy = "open" | "allowlist" | "disabled" | "allowall";
|
|
19
|
+
type NormalizedFeishuGroupPolicy = Exclude<FeishuGroupPolicy, "allowall">;
|
|
20
|
+
|
|
21
|
+
const FEISHU_PROVIDER_PREFIX_RE = /^(feishu|lark):/i;
|
|
22
|
+
const FEISHU_TYPED_PREFIX_RE = /^(chat|group|channel|user|dm|open_id):/i;
|
|
23
|
+
const FEISHU_ID_KIND = "plugin:feishu-id" as const;
|
|
24
|
+
const feishuIngressIdentity = defineStableChannelIngressIdentity({
|
|
25
|
+
key: "feishu-id",
|
|
26
|
+
kind: FEISHU_ID_KIND,
|
|
27
|
+
normalize: normalizeFeishuAllowEntry,
|
|
28
|
+
sensitivity: "pii",
|
|
29
|
+
aliases: [
|
|
30
|
+
{
|
|
31
|
+
key: "feishu-alt-id",
|
|
32
|
+
kind: FEISHU_ID_KIND,
|
|
33
|
+
normalizeEntry: () => null,
|
|
34
|
+
normalizeSubject: normalizeFeishuAllowEntry,
|
|
35
|
+
sensitivity: "pii",
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
isWildcardEntry: (entry) => normalizeFeishuAllowEntry(entry) === "*",
|
|
39
|
+
resolveEntryId: ({ entryIndex }) => `feishu-entry-${entryIndex + 1}`,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export function normalizeFeishuAllowEntry(raw: string): string {
|
|
43
|
+
const trimmed = raw.trim();
|
|
44
|
+
if (!trimmed) {
|
|
45
|
+
return "";
|
|
46
|
+
}
|
|
47
|
+
if (trimmed === "*") {
|
|
48
|
+
return "*";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let withoutProviderPrefix = trimmed;
|
|
52
|
+
while (FEISHU_PROVIDER_PREFIX_RE.test(withoutProviderPrefix)) {
|
|
53
|
+
withoutProviderPrefix = withoutProviderPrefix.replace(FEISHU_PROVIDER_PREFIX_RE, "").trim();
|
|
54
|
+
}
|
|
55
|
+
if (withoutProviderPrefix === "*") {
|
|
56
|
+
return "*";
|
|
57
|
+
}
|
|
58
|
+
const lowered = normalizeOptionalLowercaseString(withoutProviderPrefix) ?? "";
|
|
59
|
+
if (!lowered) {
|
|
60
|
+
return "";
|
|
61
|
+
}
|
|
62
|
+
const prefixed = lowered.match(FEISHU_TYPED_PREFIX_RE);
|
|
63
|
+
if (prefixed?.[1]) {
|
|
64
|
+
const kind = ["chat", "group", "channel"].includes(prefixed[1]) ? "chat" : "user";
|
|
65
|
+
const value = withoutProviderPrefix.slice(prefixed[0].length).trim();
|
|
66
|
+
return value === "*" ? "*" : value ? `${kind}:${value}` : "";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const detectedType = detectIdType(withoutProviderPrefix);
|
|
70
|
+
if (detectedType === "chat_id") {
|
|
71
|
+
return `chat:${withoutProviderPrefix}`;
|
|
72
|
+
}
|
|
73
|
+
if (detectedType === "open_id" || detectedType === "user_id") {
|
|
74
|
+
return `user:${withoutProviderPrefix}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function normalizeFeishuDmPolicy(policy: string | null | undefined): FeishuDmPolicy {
|
|
81
|
+
return policy === "open" ||
|
|
82
|
+
policy === "pairing" ||
|
|
83
|
+
policy === "allowlist" ||
|
|
84
|
+
policy === "disabled"
|
|
85
|
+
? policy
|
|
86
|
+
: "pairing";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function normalizeFeishuGroupPolicy(policy: FeishuGroupPolicy): NormalizedFeishuGroupPolicy {
|
|
90
|
+
return policy === "allowall" ? "open" : policy;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function createFeishuIngressSubject(params: {
|
|
94
|
+
primaryId?: string | null;
|
|
95
|
+
alternateIds?: Array<string | null | undefined>;
|
|
96
|
+
}): ChannelIngressIdentitySubjectInput {
|
|
97
|
+
const ids = [params.primaryId, ...(params.alternateIds ?? [])]
|
|
98
|
+
.map((value) => value?.trim())
|
|
99
|
+
.filter((value): value is string => Boolean(value));
|
|
100
|
+
return {
|
|
101
|
+
stableId: ids[0],
|
|
102
|
+
aliases: {
|
|
103
|
+
"feishu-alt-id": ids[1],
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function createFeishuIngressResolver(params: {
|
|
109
|
+
cfg?: AutoBotConfig;
|
|
110
|
+
accountId?: string | null;
|
|
111
|
+
readAllowFromStore?: ResolveChannelMessageIngressParams["readStoreAllowFrom"];
|
|
112
|
+
}) {
|
|
113
|
+
return createChannelIngressResolver({
|
|
114
|
+
channelId: "feishu",
|
|
115
|
+
accountId: normalizeAccountId(params.accountId) ?? "default",
|
|
116
|
+
identity: feishuIngressIdentity,
|
|
117
|
+
cfg: params.cfg,
|
|
118
|
+
...(params.readAllowFromStore ? { readStoreAllowFrom: params.readAllowFromStore } : {}),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function resolveFeishuDmIngressAccess(params: {
|
|
123
|
+
cfg: AutoBotConfig;
|
|
124
|
+
accountId?: string | null;
|
|
125
|
+
dmPolicy?: string | null;
|
|
126
|
+
allowFrom?: Array<string | number> | null;
|
|
127
|
+
readAllowFromStore?: () => Promise<Array<string | number>>;
|
|
128
|
+
senderOpenId: string;
|
|
129
|
+
senderUserId?: string | null;
|
|
130
|
+
conversationId: string;
|
|
131
|
+
mayPair: boolean;
|
|
132
|
+
command?: { hasControlCommand: boolean };
|
|
133
|
+
}) {
|
|
134
|
+
return await createFeishuIngressResolver({
|
|
135
|
+
cfg: params.cfg,
|
|
136
|
+
accountId: params.accountId,
|
|
137
|
+
readAllowFromStore: params.readAllowFromStore,
|
|
138
|
+
}).message({
|
|
139
|
+
subject: createFeishuIngressSubject({
|
|
140
|
+
primaryId: params.senderOpenId,
|
|
141
|
+
alternateIds: [params.senderUserId],
|
|
142
|
+
}),
|
|
143
|
+
conversation: {
|
|
144
|
+
kind: "direct",
|
|
145
|
+
id: params.conversationId,
|
|
146
|
+
},
|
|
147
|
+
event: {
|
|
148
|
+
mayPair: params.mayPair,
|
|
149
|
+
},
|
|
150
|
+
dmPolicy: normalizeFeishuDmPolicy(params.dmPolicy),
|
|
151
|
+
groupPolicy: "disabled",
|
|
152
|
+
allowFrom: params.allowFrom ?? [],
|
|
153
|
+
...(params.command ? { command: params.command } : {}),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function resolveFeishuGroupConversationIngressAccess(params: {
|
|
158
|
+
cfg: AutoBotConfig;
|
|
159
|
+
accountId?: string | null;
|
|
160
|
+
chatId: string;
|
|
161
|
+
groupPolicy: FeishuGroupPolicy;
|
|
162
|
+
groupAllowFrom?: Array<string | number> | null;
|
|
163
|
+
groupExplicitlyConfigured?: boolean;
|
|
164
|
+
}) {
|
|
165
|
+
const groupPolicy = normalizeFeishuGroupPolicy(params.groupPolicy);
|
|
166
|
+
const groupAllowFrom =
|
|
167
|
+
groupPolicy === "allowlist" && params.groupExplicitlyConfigured
|
|
168
|
+
? [...(params.groupAllowFrom ?? []), params.chatId]
|
|
169
|
+
: (params.groupAllowFrom ?? []);
|
|
170
|
+
return await createFeishuIngressResolver({
|
|
171
|
+
cfg: params.cfg,
|
|
172
|
+
accountId: params.accountId,
|
|
173
|
+
}).message({
|
|
174
|
+
subject: createFeishuIngressSubject({
|
|
175
|
+
primaryId: params.chatId,
|
|
176
|
+
}),
|
|
177
|
+
conversation: {
|
|
178
|
+
kind: "group",
|
|
179
|
+
id: params.chatId,
|
|
180
|
+
},
|
|
181
|
+
dmPolicy: "disabled",
|
|
182
|
+
groupPolicy,
|
|
183
|
+
groupAllowFrom,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export async function resolveFeishuGroupSenderActivationIngressAccess(params: {
|
|
188
|
+
cfg: AutoBotConfig;
|
|
189
|
+
accountId?: string | null;
|
|
190
|
+
chatId: string;
|
|
191
|
+
allowFrom?: Array<string | number> | null;
|
|
192
|
+
senderOpenId: string;
|
|
193
|
+
senderUserId?: string | null;
|
|
194
|
+
requireMention: boolean;
|
|
195
|
+
mentionedBot: boolean;
|
|
196
|
+
command?: { hasControlCommand: boolean };
|
|
197
|
+
}) {
|
|
198
|
+
const groupAllowFrom = params.allowFrom ?? [];
|
|
199
|
+
return await createFeishuIngressResolver({
|
|
200
|
+
cfg: params.cfg,
|
|
201
|
+
accountId: params.accountId,
|
|
202
|
+
}).message({
|
|
203
|
+
subject: createFeishuIngressSubject({
|
|
204
|
+
primaryId: params.senderOpenId,
|
|
205
|
+
alternateIds: [params.senderUserId],
|
|
206
|
+
}),
|
|
207
|
+
conversation: {
|
|
208
|
+
kind: "group",
|
|
209
|
+
id: params.chatId,
|
|
210
|
+
},
|
|
211
|
+
dmPolicy: "disabled",
|
|
212
|
+
groupPolicy: groupAllowFrom.length > 0 ? "allowlist" : "open",
|
|
213
|
+
groupAllowFrom,
|
|
214
|
+
mentionFacts: {
|
|
215
|
+
canDetectMention: true,
|
|
216
|
+
wasMentioned: params.mentionedBot,
|
|
217
|
+
},
|
|
218
|
+
policy: {
|
|
219
|
+
activation: {
|
|
220
|
+
requireMention: params.requireMention,
|
|
221
|
+
allowTextCommands: false,
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
...(params.command ? { command: params.command } : {}),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function resolveFeishuGroupConfig(params: { cfg?: FeishuConfig; groupId?: string | null }) {
|
|
229
|
+
const groups = params.cfg?.groups ?? {};
|
|
230
|
+
const wildcard = groups["*"];
|
|
231
|
+
const groupId = params.groupId?.trim();
|
|
232
|
+
if (!groupId) {
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const direct = groups[groupId];
|
|
237
|
+
if (direct) {
|
|
238
|
+
return direct;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const lowered = normalizeOptionalLowercaseString(groupId) ?? "";
|
|
242
|
+
const matchKey = Object.keys(groups).find(
|
|
243
|
+
(key) => normalizeOptionalLowercaseString(key) === lowered,
|
|
244
|
+
);
|
|
245
|
+
if (matchKey) {
|
|
246
|
+
return groups[matchKey];
|
|
247
|
+
}
|
|
248
|
+
return wildcard;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function hasExplicitFeishuGroupConfig(params: {
|
|
252
|
+
cfg?: FeishuConfig;
|
|
253
|
+
groupId?: string | null;
|
|
254
|
+
}): boolean {
|
|
255
|
+
const groups = params.cfg?.groups ?? {};
|
|
256
|
+
const groupId = params.groupId?.trim();
|
|
257
|
+
if (!groupId) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
if (Object.prototype.hasOwnProperty.call(groups, groupId) && groupId !== "*") {
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const lowered = normalizeOptionalLowercaseString(groupId) ?? "";
|
|
265
|
+
return Object.keys(groups).some(
|
|
266
|
+
(key) => key !== "*" && normalizeOptionalLowercaseString(key) === lowered,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function resolveFeishuGroupToolPolicy(params: ChannelGroupContext) {
|
|
271
|
+
const cfg = params.cfg.channels?.feishu;
|
|
272
|
+
if (!cfg) {
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const groupConfig = resolveFeishuGroupConfig({
|
|
277
|
+
cfg,
|
|
278
|
+
groupId: params.groupId,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
return groupConfig?.tools;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function resolveFeishuReplyPolicy(params: {
|
|
285
|
+
isDirectMessage: boolean;
|
|
286
|
+
cfg: AutoBotConfig;
|
|
287
|
+
accountId?: string | null;
|
|
288
|
+
groupId?: string | null;
|
|
289
|
+
/**
|
|
290
|
+
* Effective group policy resolved for this chat. When "open", requireMention
|
|
291
|
+
* defaults to false so that non-text messages (e.g. images) that cannot carry
|
|
292
|
+
* @-mentions are still delivered to the agent.
|
|
293
|
+
*/
|
|
294
|
+
groupPolicy?: "open" | "allowlist" | "disabled" | "allowall";
|
|
295
|
+
}): { requireMention: boolean } {
|
|
296
|
+
if (params.isDirectMessage) {
|
|
297
|
+
return { requireMention: false };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const feishuCfg = params.cfg.channels?.feishu;
|
|
301
|
+
const resolvedCfg = resolveMergedAccountConfig<FeishuConfig>({
|
|
302
|
+
channelConfig: feishuCfg,
|
|
303
|
+
accounts: feishuCfg?.accounts as Record<string, Partial<FeishuConfig>> | undefined,
|
|
304
|
+
accountId: normalizeAccountId(params.accountId),
|
|
305
|
+
normalizeAccountId,
|
|
306
|
+
omitKeys: ["defaultAccount"],
|
|
307
|
+
});
|
|
308
|
+
const groupRequireMention = resolveFeishuGroupConfig({
|
|
309
|
+
cfg: resolvedCfg,
|
|
310
|
+
groupId: params.groupId,
|
|
311
|
+
})?.requireMention;
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
requireMention:
|
|
315
|
+
typeof groupRequireMention === "boolean"
|
|
316
|
+
? groupRequireMention
|
|
317
|
+
: typeof resolvedCfg.requireMention === "boolean"
|
|
318
|
+
? resolvedCfg.requireMention
|
|
319
|
+
: params.groupPolicy !== "open",
|
|
320
|
+
};
|
|
321
|
+
}
|