@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,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isAbortRequestText,
|
|
3
|
+
isBtwRequestText,
|
|
4
|
+
} from "autobot/plugin-sdk/command-primitives-runtime";
|
|
5
|
+
import { parseFeishuMessageEvent, type FeishuMessageEvent } from "./bot.js";
|
|
6
|
+
|
|
7
|
+
export function getFeishuSequentialKey(params: {
|
|
8
|
+
accountId: string;
|
|
9
|
+
event: FeishuMessageEvent;
|
|
10
|
+
botOpenId?: string;
|
|
11
|
+
botName?: string;
|
|
12
|
+
}): string {
|
|
13
|
+
const { accountId, event, botOpenId, botName } = params;
|
|
14
|
+
const chatId = event.message.chat_id?.trim() || "unknown";
|
|
15
|
+
const baseKey = `feishu:${accountId}:${chatId}`;
|
|
16
|
+
const parsed = parseFeishuMessageEvent(event, botOpenId, botName);
|
|
17
|
+
const text = parsed.content.trim();
|
|
18
|
+
|
|
19
|
+
if (isAbortRequestText(text)) {
|
|
20
|
+
return `${baseKey}:control`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (isBtwRequestText(text)) {
|
|
24
|
+
return `${baseKey}:btw`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return baseKey;
|
|
28
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-key serial task queue for Feishu inbound message handling.
|
|
3
|
+
*
|
|
4
|
+
* Tasks enqueued under the same key run in FIFO order. Different keys run
|
|
5
|
+
* concurrently. This preserves the channel's same-chat ordering contract
|
|
6
|
+
* (see #64324) while letting cross-chat work proceed in parallel.
|
|
7
|
+
*
|
|
8
|
+
* `taskTimeoutMs` bounds how long the queue will block subsequent same-key
|
|
9
|
+
* tasks behind a single in-flight task. After the cap, the in-flight task
|
|
10
|
+
* is evicted from the blocking chain so newer messages for the same key
|
|
11
|
+
* can proceed. The original task is NOT aborted — it continues running in
|
|
12
|
+
* the background; it just stops starving the queue.
|
|
13
|
+
*
|
|
14
|
+
* Without this cap, a single hung dispatch (e.g. an agent call that never
|
|
15
|
+
* resolves) keeps later same-chat messages in `queued` state until the
|
|
16
|
+
* gateway is restarted. See #70133.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const DEFAULT_TASK_TIMEOUT_MS = 5 * 60 * 1000;
|
|
20
|
+
|
|
21
|
+
export interface SequentialQueueOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Maximum time (ms) to block subsequent same-key tasks behind a single
|
|
24
|
+
* in-flight task. Pass 0 (or a non-finite value) to disable the cap and
|
|
25
|
+
* restore unbounded legacy behavior.
|
|
26
|
+
*
|
|
27
|
+
* Default: 5 minutes.
|
|
28
|
+
*/
|
|
29
|
+
taskTimeoutMs?: number;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Optional callback fired when a task exceeds `taskTimeoutMs`. The task
|
|
33
|
+
* itself is not awaited further; this callback is the only signal the
|
|
34
|
+
* caller gets that the queue moved on without it.
|
|
35
|
+
*/
|
|
36
|
+
onTaskTimeout?: (key: string, timeoutMs: number) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function createSequentialQueue(options: SequentialQueueOptions = {}) {
|
|
40
|
+
const queues = new Map<string, Promise<void>>();
|
|
41
|
+
const taskTimeoutMs = options.taskTimeoutMs ?? DEFAULT_TASK_TIMEOUT_MS;
|
|
42
|
+
const onTaskTimeout = options.onTaskTimeout;
|
|
43
|
+
|
|
44
|
+
return (key: string, task: () => Promise<void>): Promise<void> => {
|
|
45
|
+
const previous = queues.get(key) ?? Promise.resolve();
|
|
46
|
+
const wrapped = () => boundedRun(key, task, taskTimeoutMs, onTaskTimeout);
|
|
47
|
+
const next = previous.then(wrapped, wrapped);
|
|
48
|
+
queues.set(key, next);
|
|
49
|
+
const cleanup = () => {
|
|
50
|
+
if (queues.get(key) === next) {
|
|
51
|
+
queues.delete(key);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
next.then(cleanup, cleanup);
|
|
55
|
+
return next;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function boundedRun(
|
|
60
|
+
key: string,
|
|
61
|
+
task: () => Promise<void>,
|
|
62
|
+
timeoutMs: number,
|
|
63
|
+
onTaskTimeout: ((key: string, timeoutMs: number) => void) | undefined,
|
|
64
|
+
): Promise<void> {
|
|
65
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
66
|
+
return task();
|
|
67
|
+
}
|
|
68
|
+
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
69
|
+
const timeoutPromise = new Promise<void>((resolve) => {
|
|
70
|
+
timeoutHandle = setTimeout(() => {
|
|
71
|
+
try {
|
|
72
|
+
onTaskTimeout?.(key, timeoutMs);
|
|
73
|
+
} catch {
|
|
74
|
+
// Swallow logging errors so they cannot poison the queue chain.
|
|
75
|
+
}
|
|
76
|
+
resolve();
|
|
77
|
+
}, timeoutMs);
|
|
78
|
+
});
|
|
79
|
+
try {
|
|
80
|
+
await Promise.race([task(), timeoutPromise]);
|
|
81
|
+
} finally {
|
|
82
|
+
if (timeoutHandle) {
|
|
83
|
+
clearTimeout(timeoutHandle);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { buildFeishuConversationId, parseFeishuConversationId } from "./conversation-id.js";
|
|
2
|
+
|
|
3
|
+
function resolveFeishuParentConversationCandidates(rawId: string): string[] {
|
|
4
|
+
const parsed = parseFeishuConversationId({ conversationId: rawId });
|
|
5
|
+
if (!parsed) {
|
|
6
|
+
return [];
|
|
7
|
+
}
|
|
8
|
+
switch (parsed.scope) {
|
|
9
|
+
case "group_topic_sender":
|
|
10
|
+
return [
|
|
11
|
+
buildFeishuConversationId({
|
|
12
|
+
chatId: parsed.chatId,
|
|
13
|
+
scope: "group_topic",
|
|
14
|
+
topicId: parsed.topicId,
|
|
15
|
+
}),
|
|
16
|
+
parsed.chatId,
|
|
17
|
+
];
|
|
18
|
+
case "group_topic":
|
|
19
|
+
case "group_sender":
|
|
20
|
+
return [parsed.chatId];
|
|
21
|
+
case "group":
|
|
22
|
+
default:
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function resolveFeishuSessionConversation(params: {
|
|
28
|
+
kind: "group" | "channel";
|
|
29
|
+
rawId: string;
|
|
30
|
+
}) {
|
|
31
|
+
const parsed = parseFeishuConversationId({ conversationId: params.rawId });
|
|
32
|
+
if (!parsed) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
id: parsed.canonicalConversationId,
|
|
37
|
+
baseConversationId: parsed.chatId,
|
|
38
|
+
parentConversationCandidates: resolveFeishuParentConversationCandidates(
|
|
39
|
+
parsed.canonicalConversationId,
|
|
40
|
+
),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildChannelOutboundSessionRoute,
|
|
3
|
+
stripChannelTargetPrefix,
|
|
4
|
+
type ChannelOutboundSessionRouteParams,
|
|
5
|
+
} from "autobot/plugin-sdk/channel-core";
|
|
6
|
+
import { normalizeLowercaseStringOrEmpty } from "autobot/plugin-sdk/string-coerce-runtime";
|
|
7
|
+
|
|
8
|
+
export function resolveFeishuOutboundSessionRoute(params: ChannelOutboundSessionRouteParams) {
|
|
9
|
+
let trimmed = stripChannelTargetPrefix(params.target, "feishu", "lark");
|
|
10
|
+
if (!trimmed) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const lower = normalizeLowercaseStringOrEmpty(trimmed);
|
|
15
|
+
let isGroup = false;
|
|
16
|
+
let typeExplicit = false;
|
|
17
|
+
|
|
18
|
+
if (lower.startsWith("group:") || lower.startsWith("chat:") || lower.startsWith("channel:")) {
|
|
19
|
+
trimmed = trimmed.replace(/^(group|chat|channel):/i, "").trim();
|
|
20
|
+
isGroup = true;
|
|
21
|
+
typeExplicit = true;
|
|
22
|
+
} else if (lower.startsWith("user:") || lower.startsWith("dm:")) {
|
|
23
|
+
trimmed = trimmed.replace(/^(user|dm):/i, "").trim();
|
|
24
|
+
isGroup = false;
|
|
25
|
+
typeExplicit = true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!typeExplicit) {
|
|
29
|
+
const idLower = normalizeLowercaseStringOrEmpty(trimmed);
|
|
30
|
+
if (idLower.startsWith("ou_") || idLower.startsWith("on_")) {
|
|
31
|
+
isGroup = false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return buildChannelOutboundSessionRoute({
|
|
36
|
+
cfg: params.cfg,
|
|
37
|
+
agentId: params.agentId,
|
|
38
|
+
channel: "feishu",
|
|
39
|
+
accountId: params.accountId,
|
|
40
|
+
peer: {
|
|
41
|
+
kind: isGroup ? "group" : "direct",
|
|
42
|
+
id: trimmed,
|
|
43
|
+
},
|
|
44
|
+
chatType: isGroup ? "group" : "direct",
|
|
45
|
+
from: isGroup ? `feishu:group:${trimmed}` : `feishu:${trimmed}`,
|
|
46
|
+
to: trimmed,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_ACCOUNT_ID,
|
|
3
|
+
type ChannelSetupAdapter,
|
|
4
|
+
type AutoBotConfig,
|
|
5
|
+
} from "autobot/plugin-sdk/setup";
|
|
6
|
+
import { resolveDefaultFeishuAccountId } from "./accounts.js";
|
|
7
|
+
import type { FeishuConfig } from "./types.js";
|
|
8
|
+
|
|
9
|
+
export function setFeishuNamedAccountEnabled(
|
|
10
|
+
cfg: AutoBotConfig,
|
|
11
|
+
accountId: string,
|
|
12
|
+
enabled: boolean,
|
|
13
|
+
): AutoBotConfig {
|
|
14
|
+
const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
|
|
15
|
+
return {
|
|
16
|
+
...cfg,
|
|
17
|
+
channels: {
|
|
18
|
+
...cfg.channels,
|
|
19
|
+
feishu: {
|
|
20
|
+
...feishuCfg,
|
|
21
|
+
accounts: {
|
|
22
|
+
...feishuCfg?.accounts,
|
|
23
|
+
[accountId]: {
|
|
24
|
+
...feishuCfg?.accounts?.[accountId],
|
|
25
|
+
enabled,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const feishuSetupAdapter: ChannelSetupAdapter = {
|
|
34
|
+
resolveAccountId: ({ cfg, accountId }) => accountId?.trim() || resolveDefaultFeishuAccountId(cfg),
|
|
35
|
+
applyAccountConfig: ({ cfg, accountId }) => {
|
|
36
|
+
const isDefault = !accountId || accountId === DEFAULT_ACCOUNT_ID;
|
|
37
|
+
if (isDefault) {
|
|
38
|
+
return {
|
|
39
|
+
...cfg,
|
|
40
|
+
channels: {
|
|
41
|
+
...cfg.channels,
|
|
42
|
+
feishu: {
|
|
43
|
+
...cfg.channels?.feishu,
|
|
44
|
+
enabled: true,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return setFeishuNamedAccountEnabled(cfg, accountId, true);
|
|
50
|
+
},
|
|
51
|
+
};
|