@keyou007/agent-bot 0.1.14-alpha.0 → 0.1.14-alpha.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/CHANGELOG.md +26 -0
- package/README.md +82 -55
- package/README.zh.md +82 -55
- package/config.example.yaml +5 -1
- package/dist/cli/AutostartManager.js +431 -0
- package/dist/cli/AutostartManager.js.map +1 -0
- package/dist/cli/FeishuAppConfiguration.js +17 -13
- package/dist/cli/FeishuAppConfiguration.js.map +1 -1
- package/dist/cli/Initializer.js +35 -0
- package/dist/cli/Initializer.js.map +1 -1
- package/dist/cli/SelfUpdateRunner.js +552 -0
- package/dist/cli/SelfUpdateRunner.js.map +1 -0
- package/dist/cli/SelfUpdater.js +431 -0
- package/dist/cli/SelfUpdater.js.map +1 -0
- package/dist/cli/autostartOutput.js +63 -0
- package/dist/cli/autostartOutput.js.map +1 -0
- package/dist/cli/controlProtocol.js.map +1 -1
- package/dist/cli/help.js +92 -18
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/taskGroupOptions.js +14 -11
- package/dist/cli/taskGroupOptions.js.map +1 -1
- package/dist/cli/taskListOutput.js +3 -0
- package/dist/cli/taskListOutput.js.map +1 -1
- package/dist/cli/taskTarget.js +99 -0
- package/dist/cli/taskTarget.js.map +1 -0
- package/dist/cli.js +643 -58
- package/dist/cli.js.map +1 -1
- package/dist/codex/CodexRuntime.js +16 -5
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/commands/CommandRouter.js +20 -1
- package/dist/commands/CommandRouter.js.map +1 -1
- package/dist/config/loadConfig.js +2 -0
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/config/schema.js +2 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/console/ConsoleTurnPresenter.js +2 -0
- package/dist/console/ConsoleTurnPresenter.js.map +1 -1
- package/dist/feishu/CardRenderer.js +584 -16
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuConnector.js +29 -8
- package/dist/feishu/FeishuConnector.js.map +1 -1
- package/dist/feishu/FeishuMarkdown.js +25 -1
- package/dist/feishu/FeishuMarkdown.js.map +1 -1
- package/dist/feishu/FeishuMessageClient.js +43 -0
- package/dist/feishu/FeishuMessageClient.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +29 -1
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/feishu/ownerAccess.js +7 -0
- package/dist/feishu/ownerAccess.js.map +1 -0
- package/dist/index.js +148 -19
- package/dist/index.js.map +1 -1
- package/dist/presentation/OutboundRouter.js +19 -1
- package/dist/presentation/OutboundRouter.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +1444 -66
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/startup/StartupNotifier.js +65 -16
- package/dist/startup/StartupNotifier.js.map +1 -1
- package/dist/startup/startFeishu.js +2 -2
- package/dist/startup/startFeishu.js.map +1 -1
- package/dist/state/StateStore.js +268 -67
- package/dist/state/StateStore.js.map +1 -1
- package/dist/state/migrations.js +26 -0
- package/dist/state/migrations.js.map +1 -1
- package/dist/supervision/SafeRestartNotifier.js +17 -16
- package/dist/supervision/SafeRestartNotifier.js.map +1 -1
- package/dist/supervision/SafeRestartScheduler.js +12 -7
- package/dist/supervision/SafeRestartScheduler.js.map +1 -1
- package/dist/supervision/replacementSupervisor.js +45 -4
- package/dist/supervision/replacementSupervisor.js.map +1 -1
- package/dist/supervisor.js +30 -18
- package/dist/supervisor.js.map +1 -1
- package/docs/technical-reference.md +33 -10
- package/docs/technical-reference.zh.md +33 -10
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -2
- package/skills/agent-bot/SKILL.md +107 -182
- package/src/cli/AutostartManager.ts +579 -0
- package/src/cli/FeishuAppConfiguration.ts +22 -7
- package/src/cli/Initializer.ts +57 -0
- package/src/cli/SelfUpdateRunner.ts +817 -0
- package/src/cli/SelfUpdater.ts +688 -0
- package/src/cli/autostartOutput.ts +75 -0
- package/src/cli/controlProtocol.ts +111 -1
- package/src/cli/help.ts +92 -18
- package/src/cli/taskGroupOptions.ts +27 -17
- package/src/cli/taskListOutput.ts +10 -0
- package/src/cli/taskTarget.ts +168 -0
- package/src/cli.ts +781 -65
- package/src/codex/CodexRuntime.ts +17 -5
- package/src/commands/CommandRouter.ts +19 -1
- package/src/commands/commandTypes.ts +3 -0
- package/src/config/loadConfig.ts +2 -0
- package/src/config/schema.ts +2 -0
- package/src/console/ConsoleTurnPresenter.ts +4 -1
- package/src/feishu/CardRenderer.ts +716 -16
- package/src/feishu/FeishuConnector.ts +44 -10
- package/src/feishu/FeishuMarkdown.ts +27 -1
- package/src/feishu/FeishuMessageClient.ts +58 -0
- package/src/feishu/FeishuTurnPresenter.ts +35 -2
- package/src/feishu/ownerAccess.ts +7 -0
- package/src/feishu/types.ts +7 -0
- package/src/index.ts +192 -22
- package/src/presentation/OutboundRouter.ts +28 -3
- package/src/proxy/ProxySessionController.ts +1707 -70
- package/src/runtime/types.ts +1 -0
- package/src/startup/StartupNotifier.ts +67 -17
- package/src/startup/startFeishu.ts +5 -3
- package/src/state/StateStore.ts +413 -65
- package/src/state/migrations.ts +26 -0
- package/src/supervision/SafeRestartNotifier.ts +17 -17
- package/src/supervision/SafeRestartScheduler.ts +13 -6
- package/src/supervision/replacementSupervisor.ts +50 -4
- package/src/supervisor.ts +35 -16
|
@@ -2,6 +2,7 @@ import type { Logger } from "pino";
|
|
|
2
2
|
import type { AppConfig } from "../config/schema.js";
|
|
3
3
|
import { threadContextKey } from "./contextKey.js";
|
|
4
4
|
import { resolveFeishuBotOpenId } from "./FeishuBotIdentity.js";
|
|
5
|
+
import { allowsFeishuUser } from "./ownerAccess.js";
|
|
5
6
|
import type { ChatUpdatedEvent, FeishuEventHandler, IncomingMessage } from "./types.js";
|
|
6
7
|
|
|
7
8
|
type BotOpenIdResolver = (appId: string, appSecret: string) => Promise<string>;
|
|
@@ -33,18 +34,31 @@ export class FeishuConnector {
|
|
|
33
34
|
this.logger.warn({ error }, "Failed to resolve the Lark bot Open ID for the Agent environment.");
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
|
-
await this.startFeishuWs(appId, appSecret, respondToAllGroupMessages
|
|
37
|
+
await this.startFeishuWs(appId, appSecret, botOpenId, !respondToAllGroupMessages);
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
stop(): void {
|
|
40
41
|
// SDK connector currently relies on process lifetime.
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
private async startFeishuWs(
|
|
44
|
+
private async startFeishuWs(
|
|
45
|
+
appId: string,
|
|
46
|
+
appSecret: string,
|
|
47
|
+
botOpenId?: string,
|
|
48
|
+
requireMention = false,
|
|
49
|
+
): Promise<void> {
|
|
44
50
|
const lark = (await import("@larksuiteoapi/node-sdk")) as Record<string, any>;
|
|
45
51
|
const eventDispatcher = new lark.EventDispatcher({}).register({
|
|
46
52
|
"im.message.receive_v1": async (data: unknown) => {
|
|
47
|
-
const
|
|
53
|
+
const senderOpenId = getFeishuEvent(data)?.sender?.sender_id?.open_id;
|
|
54
|
+
if (!allowsFeishuUser(this.config, senderOpenId)) {
|
|
55
|
+
this.logger.debug(
|
|
56
|
+
{ messageId: getFeishuEvent(data)?.message?.message_id },
|
|
57
|
+
"Ignored a Feishu message from a non-owner.",
|
|
58
|
+
);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const message = toIncomingMessage(data, botOpenId, requireMention);
|
|
48
62
|
if (!message) {
|
|
49
63
|
this.logger.debug({ data }, "Ignored unsupported Feishu message event.");
|
|
50
64
|
return;
|
|
@@ -74,6 +88,14 @@ export class FeishuConnector {
|
|
|
74
88
|
});
|
|
75
89
|
},
|
|
76
90
|
"card.action.trigger": async (data: unknown) => {
|
|
91
|
+
const operatorOpenId = getFeishuEvent(data)?.operator?.open_id;
|
|
92
|
+
if (!allowsFeishuUser(this.config, operatorOpenId)) {
|
|
93
|
+
this.logger.debug(
|
|
94
|
+
{ actionId: getEventId(data) },
|
|
95
|
+
"Ignored a Feishu card action from a non-owner.",
|
|
96
|
+
);
|
|
97
|
+
return {};
|
|
98
|
+
}
|
|
77
99
|
const action = toCardAction(data);
|
|
78
100
|
if (!action) {
|
|
79
101
|
this.logger.debug({ data }, "Ignored unsupported Feishu card action.");
|
|
@@ -115,7 +137,11 @@ function toChatUpdatedEvent(data: unknown): ChatUpdatedEvent | undefined {
|
|
|
115
137
|
};
|
|
116
138
|
}
|
|
117
139
|
|
|
118
|
-
function toIncomingMessage(
|
|
140
|
+
function toIncomingMessage(
|
|
141
|
+
data: unknown,
|
|
142
|
+
botOpenId?: string,
|
|
143
|
+
requireMention = false,
|
|
144
|
+
): IncomingMessage | undefined {
|
|
119
145
|
const event = getFeishuEvent(data);
|
|
120
146
|
const message = event?.message;
|
|
121
147
|
if (!message) {
|
|
@@ -128,11 +154,10 @@ function toIncomingMessage(data: unknown, requiredMentionOpenId?: string): Incom
|
|
|
128
154
|
if (!parsed) return undefined;
|
|
129
155
|
const chatId = message.chat_id;
|
|
130
156
|
const chatType = message.chat_type === "group" ? "group" : "p2p";
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
) {
|
|
157
|
+
const mentionedBot = chatType === "group"
|
|
158
|
+
&& Boolean(botOpenId)
|
|
159
|
+
&& mentionsOpenId(message.mentions, botOpenId!);
|
|
160
|
+
if (chatType === "group" && requireMention && !mentionedBot) {
|
|
136
161
|
return undefined;
|
|
137
162
|
}
|
|
138
163
|
const threadId = typeof message.thread_id === "string" && message.thread_id ? message.thread_id : undefined;
|
|
@@ -161,6 +186,7 @@ function toIncomingMessage(data: unknown, requiredMentionOpenId?: string): Incom
|
|
|
161
186
|
...(threadId ? { threadId } : {}),
|
|
162
187
|
...(rootMessageId ? { rootMessageId } : {}),
|
|
163
188
|
...(parentMessageId ? { parentMessageId } : {}),
|
|
189
|
+
...(mentionedBot ? { mentionedBot: true as const } : {}),
|
|
164
190
|
text: parsed.text,
|
|
165
191
|
...(parsed.images.length > 0 ? { images: parsed.images.map((imageKey) => ({ imageKey })) } : {}),
|
|
166
192
|
};
|
|
@@ -250,10 +276,18 @@ function toCardAction(data: unknown) {
|
|
|
250
276
|
contextKey: chatId ? `chat_id:${chatId}` : `open_id:${openId}`,
|
|
251
277
|
userId: openId,
|
|
252
278
|
...(typeof event.context?.open_message_id === "string" ? { messageId: event.context.open_message_id } : {}),
|
|
253
|
-
value:
|
|
279
|
+
value: getCardActionValue(event.action),
|
|
254
280
|
};
|
|
255
281
|
}
|
|
256
282
|
|
|
283
|
+
function getCardActionValue(action: Record<string, unknown>): Record<string, unknown> {
|
|
284
|
+
if (action.tag === "overflow" && typeof action.option === "string") {
|
|
285
|
+
const option = parseJsonObject(action.option);
|
|
286
|
+
if (Object.keys(option).length > 0) return option;
|
|
287
|
+
}
|
|
288
|
+
return isRecord(action.value) ? action.value : {};
|
|
289
|
+
}
|
|
290
|
+
|
|
257
291
|
function getEventId(data: unknown): string | undefined {
|
|
258
292
|
const value = getNested<unknown>(data, ["header", "event_id"]) ?? getNested(data, ["data", "header", "event_id"]);
|
|
259
293
|
return typeof value === "string" ? value : undefined;
|
|
@@ -6,6 +6,8 @@ interface FenceState {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
const FENCE_OPENER = /^([ \t]*)((?:`{3,})|(?:~{3,}))(.*)$/;
|
|
9
|
+
const MARKDOWN_LINK = /(!?)\[([^\]\r\n]*)\]\((<?[^)\r\n]+>?)\)/g;
|
|
10
|
+
const LOCAL_FILE_LINE_REFERENCE = /(:\d+(?::\d+)?)$/;
|
|
9
11
|
|
|
10
12
|
export function normalizeFeishuMarkdown(markdown: string): string {
|
|
11
13
|
const lines = markdown.match(/[^\r\n]*(?:\r\n|\n)|[^\r\n]+$/g) ?? [];
|
|
@@ -17,7 +19,7 @@ export function normalizeFeishuMarkdown(markdown: string): string {
|
|
|
17
19
|
|
|
18
20
|
if (!fence) {
|
|
19
21
|
const opener = FENCE_OPENER.exec(body);
|
|
20
|
-
if (!opener?.[2]) return
|
|
22
|
+
if (!opener?.[2]) return `${includeLocalFileLineReferences(body)}${ending}`;
|
|
21
23
|
|
|
22
24
|
const indent = opener[1] ?? "";
|
|
23
25
|
const delimiter = opener[2];
|
|
@@ -38,6 +40,30 @@ export function normalizeFeishuMarkdown(markdown: string): string {
|
|
|
38
40
|
}).join("");
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
function includeLocalFileLineReferences(markdown: string): string {
|
|
44
|
+
return markdown.replace(MARKDOWN_LINK, (link, imageMarker: string, label: string, rawTarget: string) => {
|
|
45
|
+
if (imageMarker) return link;
|
|
46
|
+
|
|
47
|
+
const target = unwrapMarkdownTarget(rawTarget.trim());
|
|
48
|
+
if (!isLocalFileTarget(target)) return link;
|
|
49
|
+
|
|
50
|
+
const reference = LOCAL_FILE_LINE_REFERENCE.exec(target)?.[1];
|
|
51
|
+
if (!reference || label.endsWith(reference)) return link;
|
|
52
|
+
return `[${label}${reference}](${rawTarget})`;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function unwrapMarkdownTarget(target: string): string {
|
|
57
|
+
return target.startsWith("<") && target.endsWith(">") ? target.slice(1, -1) : target;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isLocalFileTarget(target: string): boolean {
|
|
61
|
+
return /^file:\/\//i.test(target)
|
|
62
|
+
|| /^\/?[a-z]:[\\/]/i.test(target)
|
|
63
|
+
|| /^\\\\/.test(target)
|
|
64
|
+
|| /^\/(?!\/)/.test(target);
|
|
65
|
+
}
|
|
66
|
+
|
|
41
67
|
function isFenceCloser(line: string, fence: FenceState): boolean {
|
|
42
68
|
const match = /^[ \t]*(`+|~+)[ \t]*$/.exec(line);
|
|
43
69
|
const delimiter = match?.[1];
|
|
@@ -48,6 +48,15 @@ interface UploadImageResponse {
|
|
|
48
48
|
error?: unknown;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
interface UploadFileResponse {
|
|
52
|
+
code: number;
|
|
53
|
+
msg: string;
|
|
54
|
+
data?: {
|
|
55
|
+
file_key?: string;
|
|
56
|
+
};
|
|
57
|
+
error?: unknown;
|
|
58
|
+
}
|
|
59
|
+
|
|
51
60
|
interface ReactionResponse {
|
|
52
61
|
code: number;
|
|
53
62
|
msg: string;
|
|
@@ -178,6 +187,11 @@ export class FeishuMessageClient implements FeishuOutbound {
|
|
|
178
187
|
return this.sendMessage(contextKey, "text", { text });
|
|
179
188
|
}
|
|
180
189
|
|
|
190
|
+
async sendFile(contextKey: string, filePath: string): Promise<string | undefined> {
|
|
191
|
+
const fileKey = await this.uploadFile(filePath);
|
|
192
|
+
return this.sendMessage(contextKey, "file", { file_key: fileKey });
|
|
193
|
+
}
|
|
194
|
+
|
|
181
195
|
async sendMarkdown(contextKey: string, markdown: string, idempotencyKey?: string): Promise<string | undefined> {
|
|
182
196
|
const normalizedMarkdown = normalizeFeishuMarkdown(markdown);
|
|
183
197
|
const elements = await renderMarkdownWithLocalImages(
|
|
@@ -210,6 +224,15 @@ export class FeishuMessageClient implements FeishuOutbound {
|
|
|
210
224
|
return this.replyMessage(contextKey, target, "text", { text }, idempotencyKey);
|
|
211
225
|
}
|
|
212
226
|
|
|
227
|
+
async replyFile(
|
|
228
|
+
contextKey: string,
|
|
229
|
+
target: MessageReplyTarget,
|
|
230
|
+
filePath: string,
|
|
231
|
+
): Promise<string | undefined> {
|
|
232
|
+
const fileKey = await this.uploadFile(filePath);
|
|
233
|
+
return this.replyMessage(contextKey, target, "file", { file_key: fileKey });
|
|
234
|
+
}
|
|
235
|
+
|
|
213
236
|
async replyMarkdown(
|
|
214
237
|
contextKey: string,
|
|
215
238
|
target: MessageReplyTarget,
|
|
@@ -554,6 +577,40 @@ export class FeishuMessageClient implements FeishuOutbound {
|
|
|
554
577
|
}
|
|
555
578
|
}
|
|
556
579
|
|
|
580
|
+
private async uploadFile(filePath: string): Promise<string> {
|
|
581
|
+
const fileName = path.basename(filePath);
|
|
582
|
+
try {
|
|
583
|
+
const fileStats = await stat(filePath);
|
|
584
|
+
if (!fileStats.isFile()) throw new Error(`Not a regular file: ${fileName}`);
|
|
585
|
+
if (fileStats.size === 0) throw new Error(`Feishu does not accept empty files: ${fileName}`);
|
|
586
|
+
if (fileStats.size > 30 * 1024 * 1024) {
|
|
587
|
+
throw new Error(`File exceeds Feishu's 30 MiB limit: ${fileName}`);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const contents = await readFile(filePath);
|
|
591
|
+
const token = await this.getTenantAccessToken();
|
|
592
|
+
const form = new FormData();
|
|
593
|
+
form.append("file_type", "stream");
|
|
594
|
+
form.append("file_name", fileName);
|
|
595
|
+
const bytes = new ArrayBuffer(contents.byteLength);
|
|
596
|
+
new Uint8Array(bytes).set(contents);
|
|
597
|
+
form.append("file", new Blob([bytes]), fileName);
|
|
598
|
+
const response = await fetch("https://open.feishu.cn/open-apis/im/v1/files", {
|
|
599
|
+
method: "POST",
|
|
600
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
601
|
+
body: form,
|
|
602
|
+
});
|
|
603
|
+
const payload = (await response.json()) as UploadFileResponse;
|
|
604
|
+
const fileKey = payload.data?.file_key;
|
|
605
|
+
if (!response.ok || payload.code !== 0 || !fileKey) {
|
|
606
|
+
throw new FeishuApiError(payload.msg || response.statusText, payload.code, payload, "upload file", response.status);
|
|
607
|
+
}
|
|
608
|
+
return fileKey;
|
|
609
|
+
} catch (error) {
|
|
610
|
+
throw normalizeTransportError(error, "upload file");
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
557
614
|
private async uploadImageBytes(
|
|
558
615
|
contents: Uint8Array,
|
|
559
616
|
imageType: "message" | "avatar",
|
|
@@ -675,6 +732,7 @@ export class FeishuApiError extends Error {
|
|
|
675
732
|
| SendMessageResponse
|
|
676
733
|
| CreateGroupResponse
|
|
677
734
|
| UploadImageResponse
|
|
735
|
+
| UploadFileResponse
|
|
678
736
|
| ReactionResponse
|
|
679
737
|
| DownloadImageErrorResponse,
|
|
680
738
|
operation = "send",
|
|
@@ -116,7 +116,7 @@ export class FeishuTurnPresenter {
|
|
|
116
116
|
taskTitle?: string,
|
|
117
117
|
replyTarget?: MessageReplyTarget,
|
|
118
118
|
prompt?: string,
|
|
119
|
-
): Promise<
|
|
119
|
+
): Promise<string | undefined> {
|
|
120
120
|
const existing = this.pendingEntries.get(sessionId);
|
|
121
121
|
if (existing) {
|
|
122
122
|
await existing.initializing;
|
|
@@ -125,7 +125,7 @@ export class FeishuTurnPresenter {
|
|
|
125
125
|
this.store.saveTurnSnapshot(existing.state.turnId, sessionId, existing.state, existing.contextKey);
|
|
126
126
|
if (!existing.historySnapshot) existing.scheduler?.update(existing.state, "critical");
|
|
127
127
|
}
|
|
128
|
-
return;
|
|
128
|
+
return existing.state.turnId;
|
|
129
129
|
}
|
|
130
130
|
this.sessionContexts.set(sessionId, contextKey);
|
|
131
131
|
if (taskTitle) this.sessionTitles.set(sessionId, taskTitle);
|
|
@@ -146,6 +146,7 @@ export class FeishuTurnPresenter {
|
|
|
146
146
|
entry.initializing = this.initializeEntry(entry);
|
|
147
147
|
try {
|
|
148
148
|
await entry.initializing;
|
|
149
|
+
return state.turnId;
|
|
149
150
|
} catch (error) {
|
|
150
151
|
if (this.pendingEntries.get(sessionId) === entry) this.pendingEntries.delete(sessionId);
|
|
151
152
|
this.entries.delete(state.turnId);
|
|
@@ -169,6 +170,34 @@ export class FeishuTurnPresenter {
|
|
|
169
170
|
await this.syncThinkingCardReaction(entry);
|
|
170
171
|
}
|
|
171
172
|
|
|
173
|
+
async interruptTurnForRecovery(
|
|
174
|
+
sessionId: string,
|
|
175
|
+
contextKey: string,
|
|
176
|
+
turnId: string,
|
|
177
|
+
message: string,
|
|
178
|
+
): Promise<void> {
|
|
179
|
+
const snapshot = this.store.getTurnSnapshot(turnId);
|
|
180
|
+
if (!isTurnViewState(snapshot) || snapshot.sessionId !== sessionId || isTerminalViewStatus(snapshot.status)) return;
|
|
181
|
+
const state: TurnViewState = {
|
|
182
|
+
...snapshot,
|
|
183
|
+
status: "cancelled",
|
|
184
|
+
progressText: message,
|
|
185
|
+
activeTool: undefined,
|
|
186
|
+
approval: undefined,
|
|
187
|
+
completedAt: Date.now(),
|
|
188
|
+
};
|
|
189
|
+
this.store.saveTurnSnapshot(turnId, sessionId, state, contextKey);
|
|
190
|
+
const delivery = this.store.getTurnDelivery(turnId);
|
|
191
|
+
if (delivery?.progressMessageId) {
|
|
192
|
+
await this.outbound.updateInteractiveCard(delivery.progressMessageId, this.renderer.renderTurn(state));
|
|
193
|
+
}
|
|
194
|
+
const entry = this.entries.get(turnId);
|
|
195
|
+
if (entry) {
|
|
196
|
+
this.entries.delete(turnId);
|
|
197
|
+
if (this.pendingEntries.get(sessionId) === entry) this.pendingEntries.delete(sessionId);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
172
201
|
async appendSteerMessage(
|
|
173
202
|
sessionId: string,
|
|
174
203
|
turnId: string,
|
|
@@ -519,6 +548,10 @@ function isTerminalEvent(event: AgentEvent): boolean {
|
|
|
519
548
|
return event.type === "turn_completed" || event.type === "turn_cancelled" || event.type === "turn_failed";
|
|
520
549
|
}
|
|
521
550
|
|
|
551
|
+
function isTerminalViewStatus(status: TurnViewState["status"]): boolean {
|
|
552
|
+
return status === "completed" || status === "cancelled" || status === "failed";
|
|
553
|
+
}
|
|
554
|
+
|
|
522
555
|
function thinkingCardReactionForStatus(status: TurnViewState["status"]): string | undefined {
|
|
523
556
|
if (status === "completed") return undefined;
|
|
524
557
|
if (status === "failed") return THINKING_CARD_FAILED_REACTION;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AppConfig } from "../config/schema.js";
|
|
2
|
+
|
|
3
|
+
export function allowsFeishuUser(config: AppConfig, userOpenId: string | undefined): boolean {
|
|
4
|
+
if (config.feishu?.respondToOwnerOnly !== true) return true;
|
|
5
|
+
const ownerOpenId = config.feishu.userOpenId?.trim();
|
|
6
|
+
return Boolean(ownerOpenId && userOpenId === ownerOpenId);
|
|
7
|
+
}
|
package/src/feishu/types.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface IncomingMessage {
|
|
|
9
9
|
threadId?: string;
|
|
10
10
|
rootMessageId?: string;
|
|
11
11
|
parentMessageId?: string;
|
|
12
|
+
mentionedBot?: true;
|
|
12
13
|
text: string;
|
|
13
14
|
images?: IncomingImageReference[];
|
|
14
15
|
}
|
|
@@ -53,6 +54,7 @@ export interface FeishuOutbound {
|
|
|
53
54
|
deleteReaction?(messageId: string, reactionId: string): Promise<void>;
|
|
54
55
|
downloadImage?(messageId: string, imageKey: string): Promise<string>;
|
|
55
56
|
sendText(contextKey: string, text: string): Promise<string | undefined>;
|
|
57
|
+
sendFile?(contextKey: string, filePath: string): Promise<string | undefined>;
|
|
56
58
|
sendMarkdown(contextKey: string, markdown: string, idempotencyKey?: string): Promise<string | undefined>;
|
|
57
59
|
sendInteractiveCard(contextKey: string, card: Record<string, unknown>): Promise<string | undefined>;
|
|
58
60
|
replyText?(
|
|
@@ -61,6 +63,11 @@ export interface FeishuOutbound {
|
|
|
61
63
|
text: string,
|
|
62
64
|
idempotencyKey?: string,
|
|
63
65
|
): Promise<string | undefined>;
|
|
66
|
+
replyFile?(
|
|
67
|
+
contextKey: string,
|
|
68
|
+
target: MessageReplyTarget,
|
|
69
|
+
filePath: string,
|
|
70
|
+
): Promise<string | undefined>;
|
|
64
71
|
replyMarkdown?(
|
|
65
72
|
contextKey: string,
|
|
66
73
|
target: MessageReplyTarget,
|