@actagent/feishu 2026.6.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/README.md +11 -0
- package/actagent.plugin.json +224 -0
- package/api.ts +33 -0
- package/channel-entry.ts +21 -0
- package/channel-plugin-api.ts +2 -0
- package/contract-api.ts +17 -0
- package/index.ts +83 -0
- package/legacy-state-migrations-api.ts +2 -0
- package/npm-shrinkwrap.json +539 -0
- package/package.json +64 -0
- package/runtime-api.ts +58 -0
- package/runtime-setter-api.ts +3 -0
- package/secret-contract-api.ts +6 -0
- package/security-contract-api.ts +2 -0
- package/session-key-api.ts +2 -0
- package/setup-api.ts +4 -0
- package/setup-entry.test.ts +33 -0
- package/setup-entry.ts +25 -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.test.ts +481 -0
- package/src/accounts.ts +380 -0
- package/src/agent-config.ts +22 -0
- package/src/app-registration.test.ts +62 -0
- package/src/app-registration.ts +355 -0
- package/src/approval-auth.test.ts +25 -0
- package/src/approval-auth.ts +26 -0
- package/src/async.test.ts +68 -0
- package/src/async.ts +109 -0
- package/src/audio-preflight.runtime.ts +10 -0
- package/src/bitable.test.ts +174 -0
- package/src/bitable.ts +781 -0
- package/src/bot-content.ts +488 -0
- package/src/bot-group-name.test.ts +148 -0
- package/src/bot-runtime-api.ts +13 -0
- package/src/bot-sender-name.test.ts +68 -0
- package/src/bot-sender-name.ts +137 -0
- package/src/bot.broadcast.test.ts +643 -0
- package/src/bot.card-action.test.ts +647 -0
- package/src/bot.checkBotMentioned.test.ts +266 -0
- package/src/bot.helpers.test.ts +136 -0
- package/src/bot.stripBotMention.test.ts +127 -0
- package/src/bot.test.ts +3817 -0
- package/src/bot.ts +1788 -0
- package/src/card-action.ts +515 -0
- package/src/card-interaction.test.ts +132 -0
- package/src/card-interaction.ts +160 -0
- package/src/card-test-helpers.ts +55 -0
- package/src/card-ux-approval.ts +66 -0
- package/src/card-ux-launcher.test.ts +126 -0
- package/src/card-ux-launcher.ts +136 -0
- package/src/card-ux-shared.ts +34 -0
- package/src/channel-runtime-api.ts +17 -0
- package/src/channel.runtime.ts +48 -0
- package/src/channel.test.ts +1337 -0
- package/src/channel.ts +1401 -0
- package/src/chat-schema.ts +30 -0
- package/src/chat.test.ts +295 -0
- package/src/chat.ts +198 -0
- package/src/client-timeout.ts +44 -0
- package/src/client.test.ts +463 -0
- package/src/client.ts +263 -0
- package/src/comment-dispatcher-runtime-api.ts +7 -0
- package/src/comment-dispatcher.test.ts +186 -0
- package/src/comment-dispatcher.ts +108 -0
- package/src/comment-handler-runtime-api.ts +4 -0
- package/src/comment-handler.test.ts +588 -0
- package/src/comment-handler.ts +304 -0
- package/src/comment-reaction.test.ts +139 -0
- package/src/comment-reaction.ts +260 -0
- package/src/comment-shared.test.ts +184 -0
- package/src/comment-shared.ts +405 -0
- package/src/comment-target.ts +45 -0
- package/src/config-schema.test.ts +327 -0
- package/src/config-schema.ts +338 -0
- package/src/conversation-id.test.ts +19 -0
- package/src/conversation-id.ts +199 -0
- package/src/dedup-migrations.test.ts +90 -0
- package/src/dedup-migrations.ts +103 -0
- package/src/dedup.test.ts +95 -0
- package/src/dedup.ts +304 -0
- package/src/dedupe-key.ts +68 -0
- package/src/directory.static.ts +62 -0
- package/src/directory.test.ts +142 -0
- package/src/directory.ts +125 -0
- package/src/doc-schema.ts +183 -0
- package/src/doctor.test.ts +382 -0
- package/src/doctor.ts +876 -0
- package/src/docx-batch-insert.test.ts +117 -0
- package/src/docx-batch-insert.ts +223 -0
- package/src/docx-color-text.ts +154 -0
- package/src/docx-table-ops.test.ts +54 -0
- package/src/docx-table-ops.ts +316 -0
- package/src/docx-types.ts +39 -0
- package/src/docx.account-selection.test.ts +96 -0
- package/src/docx.test.ts +706 -0
- package/src/docx.ts +1598 -0
- package/src/drive-schema.ts +93 -0
- package/src/drive.test.ts +1240 -0
- package/src/drive.ts +830 -0
- package/src/dynamic-agent.test.ts +156 -0
- package/src/dynamic-agent.ts +144 -0
- package/src/event-types.ts +46 -0
- package/src/external-keys.test.ts +21 -0
- package/src/external-keys.ts +20 -0
- package/src/lifecycle.test-support.ts +223 -0
- package/src/media.test.ts +956 -0
- package/src/media.ts +1106 -0
- package/src/mention-target.types.ts +6 -0
- package/src/mention.ts +115 -0
- package/src/message-action-contract.ts +14 -0
- package/src/monitor-state-runtime-api.ts +8 -0
- package/src/monitor-transport-runtime-api.ts +11 -0
- package/src/monitor.account.ts +501 -0
- package/src/monitor.acp-init-failure.lifecycle.test-support.ts +215 -0
- package/src/monitor.bot-identity.ts +87 -0
- package/src/monitor.bot-menu-handler.ts +164 -0
- package/src/monitor.bot-menu.lifecycle.test-support.ts +221 -0
- package/src/monitor.bot-menu.test.ts +200 -0
- package/src/monitor.broadcast.reply-once.lifecycle.test-support.ts +265 -0
- package/src/monitor.card-action.lifecycle.test-support.ts +418 -0
- package/src/monitor.cleanup.test.ts +384 -0
- package/src/monitor.comment-notice-handler.ts +106 -0
- package/src/monitor.comment.test.ts +968 -0
- package/src/monitor.comment.ts +1386 -0
- package/src/monitor.lifecycle.test.ts +5 -0
- package/src/monitor.message-handler.ts +346 -0
- package/src/monitor.reaction.test.ts +770 -0
- package/src/monitor.startup.test.ts +232 -0
- package/src/monitor.startup.ts +76 -0
- package/src/monitor.state.defaults.test.ts +47 -0
- package/src/monitor.state.ts +171 -0
- package/src/monitor.synthetic-error.ts +19 -0
- package/src/monitor.test-mocks.ts +47 -0
- package/src/monitor.transport.ts +451 -0
- package/src/monitor.ts +104 -0
- package/src/monitor.webhook-e2e.test.ts +284 -0
- package/src/monitor.webhook-security.test.ts +394 -0
- package/src/monitor.webhook.test-helpers.ts +138 -0
- package/src/outbound-runtime-api.ts +2 -0
- package/src/outbound.test.ts +1255 -0
- package/src/outbound.ts +742 -0
- package/src/perm-schema.ts +53 -0
- package/src/perm.ts +171 -0
- package/src/pins.ts +109 -0
- package/src/policy.test.ts +224 -0
- package/src/policy.ts +322 -0
- package/src/post.test.ts +106 -0
- package/src/post.ts +276 -0
- package/src/presentation-card.ts +204 -0
- package/src/probe.test.ts +310 -0
- package/src/probe.ts +181 -0
- package/src/processing-claims.ts +60 -0
- package/src/qr-terminal.ts +2 -0
- package/src/reactions.ts +124 -0
- package/src/reasoning-preview.test.ts +114 -0
- package/src/reasoning-preview.ts +29 -0
- package/src/reply-dispatcher-runtime-api.ts +8 -0
- package/src/reply-dispatcher.test.ts +2009 -0
- package/src/reply-dispatcher.ts +865 -0
- package/src/runtime.ts +10 -0
- package/src/secret-contract.ts +146 -0
- package/src/secret-input.ts +2 -0
- package/src/security-audit-shared.ts +70 -0
- package/src/security-audit.test.ts +60 -0
- package/src/security-audit.ts +2 -0
- package/src/send-result.ts +81 -0
- package/src/send-target.test.ts +87 -0
- package/src/send-target.ts +36 -0
- package/src/send.reply-fallback.test.ts +418 -0
- package/src/send.test.ts +661 -0
- package/src/send.ts +860 -0
- package/src/sequential-key.test.ts +73 -0
- package/src/sequential-key.ts +29 -0
- package/src/sequential-queue.test.ts +184 -0
- package/src/sequential-queue.ts +90 -0
- package/src/session-conversation.ts +42 -0
- package/src/session-route.ts +49 -0
- package/src/setup-core.ts +52 -0
- package/src/setup-surface.test.ts +485 -0
- package/src/setup-surface.ts +620 -0
- package/src/streaming-card.test.ts +549 -0
- package/src/streaming-card.ts +611 -0
- package/src/subagent-hooks.test.ts +632 -0
- package/src/subagent-hooks.ts +414 -0
- package/src/targets.ts +98 -0
- package/src/test-support/lifecycle-test-support.ts +459 -0
- package/src/thread-bindings.test.ts +181 -0
- package/src/thread-bindings.ts +332 -0
- package/src/tool-account-routing.test.ts +419 -0
- package/src/tool-account.test.ts +45 -0
- package/src/tool-account.ts +98 -0
- package/src/tool-factory-test-harness.ts +83 -0
- package/src/tool-result.test.ts +33 -0
- package/src/tool-result.ts +17 -0
- package/src/tools-config.test.ts +52 -0
- package/src/tools-config.ts +29 -0
- package/src/types.ts +111 -0
- package/src/typing.test.ts +145 -0
- package/src/typing.ts +215 -0
- package/src/wiki-schema.ts +70 -0
- package/src/wiki.ts +271 -0
- package/subagent-hooks-api.ts +22 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Feishu tests cover bot sender name plugin behavior.
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { resolveFeishuSenderName } from "./bot-sender-name.js";
|
|
4
|
+
import { FeishuConfigSchema } from "./config-schema.js";
|
|
5
|
+
import type { ResolvedFeishuAccount } from "./types.js";
|
|
6
|
+
|
|
7
|
+
const createFeishuClientMock = vi.hoisted(() => vi.fn());
|
|
8
|
+
|
|
9
|
+
vi.mock("./client.js", () => ({
|
|
10
|
+
createFeishuClient: createFeishuClientMock,
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
const account = {
|
|
14
|
+
accountId: "main",
|
|
15
|
+
selectionSource: "explicit",
|
|
16
|
+
enabled: true,
|
|
17
|
+
configured: true,
|
|
18
|
+
appId: "app-id",
|
|
19
|
+
appSecret: "secret",
|
|
20
|
+
domain: "feishu",
|
|
21
|
+
config: FeishuConfigSchema.parse({}),
|
|
22
|
+
} satisfies ResolvedFeishuAccount;
|
|
23
|
+
|
|
24
|
+
function mockUserNames(...names: string[]): ReturnType<typeof vi.fn> {
|
|
25
|
+
const get = vi.fn();
|
|
26
|
+
for (const name of names) {
|
|
27
|
+
get.mockResolvedValueOnce({ data: { user: { name } } });
|
|
28
|
+
}
|
|
29
|
+
createFeishuClientMock.mockReturnValue({
|
|
30
|
+
contact: { user: { get } },
|
|
31
|
+
});
|
|
32
|
+
return get;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe("resolveFeishuSenderName", () => {
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
vi.useRealTimers();
|
|
38
|
+
createFeishuClientMock.mockReset();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("reuses a cached sender name within the TTL", async () => {
|
|
42
|
+
const get = mockUserNames("Ada");
|
|
43
|
+
|
|
44
|
+
await expect(
|
|
45
|
+
resolveFeishuSenderName({ account, senderId: "ou_sender_cache", log: vi.fn() }),
|
|
46
|
+
).resolves.toEqual({ name: "Ada" });
|
|
47
|
+
await expect(
|
|
48
|
+
resolveFeishuSenderName({ account, senderId: "ou_sender_cache", log: vi.fn() }),
|
|
49
|
+
).resolves.toEqual({ name: "Ada" });
|
|
50
|
+
|
|
51
|
+
expect(get).toHaveBeenCalledTimes(1);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("does not cache sender names when the expiry would exceed Date range", async () => {
|
|
55
|
+
vi.useFakeTimers();
|
|
56
|
+
vi.setSystemTime(new Date(8_640_000_000_000_000));
|
|
57
|
+
const get = mockUserNames("Ada", "Grace");
|
|
58
|
+
|
|
59
|
+
await expect(
|
|
60
|
+
resolveFeishuSenderName({ account, senderId: "ou_sender_overflow", log: vi.fn() }),
|
|
61
|
+
).resolves.toEqual({ name: "Ada" });
|
|
62
|
+
await expect(
|
|
63
|
+
resolveFeishuSenderName({ account, senderId: "ou_sender_overflow", log: vi.fn() }),
|
|
64
|
+
).resolves.toEqual({ name: "Grace" });
|
|
65
|
+
|
|
66
|
+
expect(get).toHaveBeenCalledTimes(2);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// Feishu plugin module implements bot sender name behavior.
|
|
2
|
+
import {
|
|
3
|
+
asDateTimestampMs,
|
|
4
|
+
resolveExpiresAtMsFromDurationMs,
|
|
5
|
+
} from "actagent/plugin-sdk/number-runtime";
|
|
6
|
+
import { normalizeLowercaseStringOrEmpty } from "actagent/plugin-sdk/string-coerce-runtime";
|
|
7
|
+
import { createFeishuClient } from "./client.js";
|
|
8
|
+
import type { ResolvedFeishuAccount } from "./types.js";
|
|
9
|
+
|
|
10
|
+
export type FeishuPermissionError = {
|
|
11
|
+
code: number;
|
|
12
|
+
message: string;
|
|
13
|
+
grantUrl?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type SenderNameResult = {
|
|
17
|
+
name?: string;
|
|
18
|
+
permissionError?: FeishuPermissionError;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type FeishuContactUserGetResponse = Awaited<
|
|
22
|
+
ReturnType<ReturnType<typeof createFeishuClient>["contact"]["user"]["get"]>
|
|
23
|
+
>;
|
|
24
|
+
|
|
25
|
+
type FeishuLogger = (...args: unknown[]) => void;
|
|
26
|
+
|
|
27
|
+
const IGNORED_PERMISSION_SCOPE_TOKENS = ["contact:contact.base:readonly"];
|
|
28
|
+
const FEISHU_SCOPE_CORRECTIONS: Record<string, string> = {
|
|
29
|
+
"contact:contact.base:readonly": "contact:user.base:readonly",
|
|
30
|
+
};
|
|
31
|
+
const SENDER_NAME_TTL_MS = 10 * 60 * 1000;
|
|
32
|
+
const senderNameCache = new Map<string, { name: string; expireAt: number }>();
|
|
33
|
+
|
|
34
|
+
function correctFeishuScopeInUrl(url: string): string {
|
|
35
|
+
let corrected = url;
|
|
36
|
+
for (const [wrong, right] of Object.entries(FEISHU_SCOPE_CORRECTIONS)) {
|
|
37
|
+
corrected = corrected.replaceAll(encodeURIComponent(wrong), encodeURIComponent(right));
|
|
38
|
+
corrected = corrected.replaceAll(wrong, right);
|
|
39
|
+
}
|
|
40
|
+
return corrected;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function shouldSuppressPermissionErrorNotice(permissionError: FeishuPermissionError): boolean {
|
|
44
|
+
const message = normalizeLowercaseStringOrEmpty(permissionError.message);
|
|
45
|
+
return IGNORED_PERMISSION_SCOPE_TOKENS.some((token) => message.includes(token));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function extractPermissionError(err: unknown): FeishuPermissionError | null {
|
|
49
|
+
if (!err || typeof err !== "object") {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const axiosErr = err as { response?: { data?: unknown } };
|
|
53
|
+
const data = axiosErr.response?.data;
|
|
54
|
+
if (!data || typeof data !== "object") {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const feishuErr = data as { code?: number; msg?: string };
|
|
58
|
+
if (feishuErr.code !== 99991672) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const msg = feishuErr.msg ?? "";
|
|
62
|
+
const urlMatch = msg.match(/https:\/\/[^\s,]+\/app\/[^\s,]+/);
|
|
63
|
+
return {
|
|
64
|
+
code: feishuErr.code,
|
|
65
|
+
message: msg,
|
|
66
|
+
grantUrl: urlMatch?.[0] ? correctFeishuScopeInUrl(urlMatch[0]) : undefined,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function resolveSenderLookupIdType(senderId: string): "open_id" | "user_id" | "union_id" {
|
|
71
|
+
const trimmed = senderId.trim();
|
|
72
|
+
if (trimmed.startsWith("ou_")) {
|
|
73
|
+
return "open_id";
|
|
74
|
+
}
|
|
75
|
+
if (trimmed.startsWith("on_")) {
|
|
76
|
+
return "union_id";
|
|
77
|
+
}
|
|
78
|
+
return "user_id";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function resolveFeishuSenderName(params: {
|
|
82
|
+
account: ResolvedFeishuAccount;
|
|
83
|
+
senderId: string;
|
|
84
|
+
log: FeishuLogger;
|
|
85
|
+
}): Promise<SenderNameResult> {
|
|
86
|
+
const { account, senderId, log } = params;
|
|
87
|
+
if (!account.configured) {
|
|
88
|
+
return {};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const normalizedSenderId = senderId.trim();
|
|
92
|
+
if (!normalizedSenderId) {
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const cached = senderNameCache.get(normalizedSenderId);
|
|
97
|
+
const now = asDateTimestampMs(Date.now());
|
|
98
|
+
const cachedExpireAt = cached ? asDateTimestampMs(cached.expireAt) : undefined;
|
|
99
|
+
if (cached && now !== undefined && cachedExpireAt !== undefined && cachedExpireAt > now) {
|
|
100
|
+
return { name: cached.name };
|
|
101
|
+
}
|
|
102
|
+
if (cached) {
|
|
103
|
+
senderNameCache.delete(normalizedSenderId);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const client = createFeishuClient(account);
|
|
108
|
+
const userIdType = resolveSenderLookupIdType(normalizedSenderId);
|
|
109
|
+
const res: FeishuContactUserGetResponse = await client.contact.user.get({
|
|
110
|
+
path: { user_id: normalizedSenderId },
|
|
111
|
+
params: { user_id_type: userIdType },
|
|
112
|
+
});
|
|
113
|
+
const user = res.data?.user;
|
|
114
|
+
const name = user?.name ?? user?.nickname ?? user?.en_name;
|
|
115
|
+
|
|
116
|
+
if (name) {
|
|
117
|
+
const expireAt = resolveExpiresAtMsFromDurationMs(SENDER_NAME_TTL_MS);
|
|
118
|
+
if (expireAt !== undefined) {
|
|
119
|
+
senderNameCache.set(normalizedSenderId, { name, expireAt });
|
|
120
|
+
}
|
|
121
|
+
return { name };
|
|
122
|
+
}
|
|
123
|
+
return {};
|
|
124
|
+
} catch (err) {
|
|
125
|
+
const permErr = extractPermissionError(err);
|
|
126
|
+
if (permErr) {
|
|
127
|
+
if (shouldSuppressPermissionErrorNotice(permErr)) {
|
|
128
|
+
log(`feishu: ignoring stale permission scope error: ${permErr.message}`);
|
|
129
|
+
return {};
|
|
130
|
+
}
|
|
131
|
+
log(`feishu: permission error resolving sender name: code=${permErr.code}`);
|
|
132
|
+
return { permissionError: permErr };
|
|
133
|
+
}
|
|
134
|
+
log(`feishu: failed to resolve sender name for ${normalizedSenderId}: ${String(err)}`);
|
|
135
|
+
return {};
|
|
136
|
+
}
|
|
137
|
+
}
|