@foxden-app/foxclaw 0.12.0 → 0.13.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/CHANGELOG.md +20 -0
- package/dist/antigravity/account_ui.d.ts +34 -0
- package/dist/antigravity/account_ui.js +354 -0
- package/dist/antigravity/adapter.js +1 -1
- package/dist/antigravity/auth.d.ts +4 -1
- package/dist/antigravity/auth.js +17 -5
- package/dist/antigravity/controller.d.ts +3 -108
- package/dist/antigravity/controller.js +2 -2129
- package/dist/antigravity/conversation_ui.d.ts +56 -0
- package/dist/antigravity/conversation_ui.js +509 -0
- package/dist/antigravity/ui_callbacks.d.ts +6 -0
- package/dist/antigravity/ui_callbacks.js +6 -0
- package/dist/bridge/unified_bridge.d.ts +114 -0
- package/dist/bridge/unified_bridge.js +1343 -0
- package/dist/channels/bridge_messaging_router.d.ts +8 -4
- package/dist/channels/bridge_messaging_router.js +15 -4
- package/dist/channels/telegram/telegram_messaging_port.d.ts +16 -0
- package/dist/channels/telegram/telegram_messaging_port.js +22 -2
- package/dist/channels/text_actions.d.ts +14 -0
- package/dist/channels/text_actions.js +48 -0
- package/dist/channels/weixin/weixin_channel_adapter.d.ts +3 -4
- package/dist/channels/weixin/weixin_channel_adapter.js +2 -2
- package/dist/channels/weixin/weixin_messaging_port.d.ts +16 -1
- package/dist/channels/weixin/weixin_messaging_port.js +34 -7
- package/dist/codex_app/activity.d.ts +62 -0
- package/dist/codex_app/activity.js +418 -0
- package/dist/codex_app/adapter.d.ts +2 -2
- package/dist/codex_app/adapter.js +55 -25
- package/dist/codex_app/backend_ui.d.ts +4 -0
- package/dist/codex_app/backend_ui.js +48 -0
- package/dist/codex_app/client.js +0 -12
- package/dist/controller/access.d.ts +1 -11
- package/dist/controller/access.js +1 -33
- package/dist/controller/activity.d.ts +1 -62
- package/dist/controller/activity.js +2 -418
- package/dist/controller/auth_errors.d.ts +11 -0
- package/dist/controller/auth_errors.js +124 -0
- package/dist/controller/auth_files.d.ts +13 -0
- package/dist/controller/auth_files.js +156 -0
- package/dist/controller/auth_presentation.d.ts +77 -0
- package/dist/controller/auth_presentation.js +697 -0
- package/dist/controller/auth_quota_service.d.ts +27 -0
- package/dist/controller/auth_quota_service.js +151 -0
- package/dist/controller/controller.d.ts +30 -56
- package/dist/controller/controller.js +181 -3497
- package/dist/controller/interactions.d.ts +74 -0
- package/dist/controller/interactions.js +560 -0
- package/dist/controller/local_usage_service.d.ts +19 -0
- package/dist/controller/local_usage_service.js +62 -0
- package/dist/controller/native_panel_service.d.ts +55 -0
- package/dist/controller/native_panel_service.js +214 -0
- package/dist/controller/native_panels.d.ts +61 -0
- package/dist/controller/native_panels.js +784 -0
- package/dist/controller/shared_helpers.d.ts +53 -0
- package/dist/controller/shared_helpers.js +399 -0
- package/dist/controller/state_types.d.ts +390 -0
- package/dist/controller/state_types.js +82 -0
- package/dist/controller/turn_rendering.d.ts +22 -0
- package/dist/controller/turn_rendering.js +288 -0
- package/dist/core/access.d.ts +11 -0
- package/dist/core/access.js +33 -0
- package/dist/core/attachment_files.d.ts +13 -0
- package/dist/core/attachment_files.js +180 -0
- package/dist/core/attachment_types.d.ts +22 -0
- package/dist/core/attachment_types.js +1 -0
- package/dist/core/attachments.d.ts +5 -3
- package/dist/core/attachments.js +2 -2
- package/dist/core/backend_registry.d.ts +22 -0
- package/dist/core/backend_registry.js +50 -0
- package/dist/core/backend_ui.d.ts +28 -0
- package/dist/core/backend_ui.js +1 -0
- package/dist/core/channel_events.d.ts +48 -0
- package/dist/core/channel_events.js +1 -0
- package/dist/core/channel_port.d.ts +52 -8
- package/dist/core/engine_spi.d.ts +19 -4
- package/dist/core/listener_scope.d.ts +11 -0
- package/dist/core/listener_scope.js +10 -0
- package/dist/core/message_text.d.ts +2 -0
- package/dist/core/message_text.js +28 -0
- package/dist/core/network_errors.d.ts +1 -0
- package/dist/core/network_errors.js +23 -0
- package/dist/core/orchestrator.d.ts +53 -24
- package/dist/core/orchestrator.js +855 -507
- package/dist/core/queued_engine_input.d.ts +12 -0
- package/dist/core/queued_engine_input.js +16 -0
- package/dist/core/runtime_supervisor.d.ts +19 -0
- package/dist/core/runtime_supervisor.js +45 -0
- package/dist/core/scope_operations.d.ts +7 -0
- package/dist/core/scope_operations.js +19 -0
- package/dist/core/stream_preview.js +21 -9
- package/dist/dsh/adapter.js +18 -5
- package/dist/dsh/backend.d.ts +7 -0
- package/dist/dsh/backend.js +21 -0
- package/dist/dsh/runtime.d.ts +0 -1
- package/dist/dsh/runtime.js +4 -13
- package/dist/dsh/ui.d.ts +8 -8
- package/dist/dsh/ui.js +1 -1
- package/dist/main.js +55 -43
- package/dist/opencode/adapter.js +28 -9
- package/dist/opencode/controller.js +1 -1
- package/dist/store/channel_inbox.d.ts +14 -0
- package/dist/store/channel_inbox.js +21 -0
- package/dist/store/database.d.ts +7 -3
- package/dist/store/database.js +7 -2
- package/dist/store/task_journal.d.ts +32 -0
- package/dist/store/task_journal.js +70 -0
- package/dist/telegram/api.d.ts +2 -2
- package/dist/telegram/api.js +10 -28
- package/dist/telegram/gateway.d.ts +13 -27
- package/dist/telegram/gateway.js +86 -43
- package/dist/telegram/media.d.ts +2 -34
- package/dist/telegram/media.js +1 -180
- package/dist/telegram/task_preview.d.ts +13 -0
- package/dist/telegram/task_preview.js +92 -0
- package/dist/telegram/task_result.d.ts +2 -0
- package/dist/telegram/task_result.js +12 -0
- package/dist/telegram/text.d.ts +1 -1
- package/dist/telegram/text.js +2 -27
- package/dist/types.d.ts +1 -1
- package/docs/release.md +13 -11
- package/docs/zh/architecture-review.md +106 -0
- package/docs/zh/release.md +13 -11
- package/docs/zh/telegram-experience.md +40 -0
- package/package.json +5 -4
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ChannelTextEvent } from '../core/channel_events.js';
|
|
2
|
+
import type { ChannelPort } from '../core/channel_port.js';
|
|
3
|
+
type LegacyPanelPort = ChannelPort & Partial<Pick<TelegramMessagingPort, 'sendRichHtml' | 'editHtml' | 'editRichHtml' | 'sendVoice' | 'clearInlineKeyboard' | 'sendDraft' | 'sendRichDraft' | 'sendRichMarkdownDraft'>>;
|
|
2
4
|
import type { InlineKeyboard, TelegramMessagingPort } from './telegram/telegram_messaging_port.js';
|
|
3
5
|
import type { WeixinMessagingPort } from './weixin/weixin_messaging_port.js';
|
|
4
6
|
/**
|
|
@@ -8,7 +10,8 @@ import type { WeixinMessagingPort } from './weixin/weixin_messaging_port.js';
|
|
|
8
10
|
export declare class BridgeMessagingRouter {
|
|
9
11
|
private readonly telegram;
|
|
10
12
|
private readonly weixin;
|
|
11
|
-
constructor(telegram:
|
|
13
|
+
constructor(telegram: LegacyPanelPort, weixin: WeixinMessagingPort | null);
|
|
14
|
+
resolveAction(event: ChannelTextEvent): import("../core/channel_events.js").ChannelCallbackEvent | null;
|
|
12
15
|
get hasWeixinTransport(): boolean;
|
|
13
16
|
private isWeixinScope;
|
|
14
17
|
canSendToScope(scopeId: string): boolean;
|
|
@@ -29,6 +32,7 @@ export declare class BridgeMessagingRouter {
|
|
|
29
32
|
sendRichDraft(scopeId: string, draftId: number, html: string, fallbackText: string): Promise<void>;
|
|
30
33
|
sendRichMarkdownDraft(scopeId: string, draftId: number, markdown: string, fallbackText: string): Promise<void>;
|
|
31
34
|
answerCallback(callbackQueryId: string, text: string): Promise<void>;
|
|
32
|
-
getFile(fileId: string):
|
|
33
|
-
downloadResolvedFile(remoteFilePath: string, destinationPath: string):
|
|
35
|
+
getFile(fileId: string): ReturnType<ChannelPort['getFile']>;
|
|
36
|
+
downloadResolvedFile(remoteFilePath: string, destinationPath: string): ReturnType<ChannelPort['downloadResolvedFile']>;
|
|
34
37
|
}
|
|
38
|
+
export {};
|
|
@@ -10,6 +10,7 @@ export class BridgeMessagingRouter {
|
|
|
10
10
|
this.telegram = telegram;
|
|
11
11
|
this.weixin = weixin;
|
|
12
12
|
}
|
|
13
|
+
resolveAction(event) { return this.isWeixinScope(event.scopeId) ? this.weixin?.resolveAction?.(event) ?? null : null; }
|
|
13
14
|
get hasWeixinTransport() {
|
|
14
15
|
return this.weixin !== null;
|
|
15
16
|
}
|
|
@@ -41,7 +42,7 @@ export class BridgeMessagingRouter {
|
|
|
41
42
|
if (this.isWeixinScope(scopeId)) {
|
|
42
43
|
return this.requireWeixinTransport(scopeId).sendHtml(scopeId, fallbackHtml, keyboard);
|
|
43
44
|
}
|
|
44
|
-
return this.telegram.sendRichHtml(scopeId, html, keyboard);
|
|
45
|
+
return this.telegram.sendRichHtml?.(scopeId, html, keyboard) ?? this.telegram.sendHtml(scopeId, fallbackHtml, keyboard);
|
|
45
46
|
}
|
|
46
47
|
sendRichMarkdown(scopeId, markdown, fallbackText, keyboard) {
|
|
47
48
|
if (this.isWeixinScope(scopeId)) {
|
|
@@ -53,6 +54,8 @@ export class BridgeMessagingRouter {
|
|
|
53
54
|
if (this.isWeixinScope(scopeId)) {
|
|
54
55
|
throw new Error(`Voice messages are not supported for Weixin scope ${scopeId}`);
|
|
55
56
|
}
|
|
57
|
+
if (!this.telegram.sendVoice)
|
|
58
|
+
throw new Error('Channel does not support sendVoice');
|
|
56
59
|
return this.telegram.sendVoice(scopeId, filename, contents, caption, contentType);
|
|
57
60
|
}
|
|
58
61
|
editPlain(scopeId, messageId, text, keyboard) {
|
|
@@ -65,13 +68,13 @@ export class BridgeMessagingRouter {
|
|
|
65
68
|
if (this.isWeixinScope(scopeId)) {
|
|
66
69
|
return this.requireWeixinTransport(scopeId).editHtml(scopeId, messageId, text, keyboard);
|
|
67
70
|
}
|
|
68
|
-
return this.telegram.editHtml(scopeId, messageId, text, keyboard);
|
|
71
|
+
return this.telegram.editHtml?.(scopeId, messageId, text, keyboard) ?? this.telegram.editRichMarkdown(scopeId, messageId, text, keyboard);
|
|
69
72
|
}
|
|
70
73
|
editRichHtml(scopeId, messageId, html, fallbackHtml, keyboard) {
|
|
71
74
|
if (this.isWeixinScope(scopeId)) {
|
|
72
75
|
return this.requireWeixinTransport(scopeId).editHtml(scopeId, messageId, fallbackHtml, keyboard);
|
|
73
76
|
}
|
|
74
|
-
return this.telegram.editRichHtml(scopeId, messageId, html, keyboard);
|
|
77
|
+
return this.telegram.editRichHtml?.(scopeId, messageId, html, keyboard) ?? this.editHtml(scopeId, messageId, fallbackHtml, keyboard);
|
|
75
78
|
}
|
|
76
79
|
editRichMarkdown(scopeId, messageId, markdown, fallbackText, keyboard) {
|
|
77
80
|
if (this.isWeixinScope(scopeId)) {
|
|
@@ -95,27 +98,35 @@ export class BridgeMessagingRouter {
|
|
|
95
98
|
if (this.isWeixinScope(scopeId)) {
|
|
96
99
|
return this.requireWeixinTransport(scopeId).clearInlineKeyboard(scopeId, messageId);
|
|
97
100
|
}
|
|
98
|
-
return this.telegram.clearInlineKeyboard(scopeId, messageId);
|
|
101
|
+
return this.telegram.clearInlineKeyboard?.(scopeId, messageId) ?? Promise.resolve();
|
|
99
102
|
}
|
|
100
103
|
sendDraft(scopeId, draftId, text) {
|
|
101
104
|
if (this.isWeixinScope(scopeId)) {
|
|
102
105
|
return this.requireWeixinTransport(scopeId).sendDraft(scopeId, draftId, text);
|
|
103
106
|
}
|
|
107
|
+
if (!this.telegram.sendDraft)
|
|
108
|
+
throw new Error('Channel does not support sendDraft');
|
|
104
109
|
return this.telegram.sendDraft(scopeId, draftId, text);
|
|
105
110
|
}
|
|
106
111
|
sendRichDraft(scopeId, draftId, html, fallbackText) {
|
|
107
112
|
if (this.isWeixinScope(scopeId)) {
|
|
108
113
|
return this.requireWeixinTransport(scopeId).sendDraft(scopeId, draftId, fallbackText);
|
|
109
114
|
}
|
|
115
|
+
if (!this.telegram.sendRichDraft)
|
|
116
|
+
throw new Error('Channel does not support sendRichDraft');
|
|
110
117
|
return this.telegram.sendRichDraft(scopeId, draftId, html);
|
|
111
118
|
}
|
|
112
119
|
sendRichMarkdownDraft(scopeId, draftId, markdown, fallbackText) {
|
|
113
120
|
if (this.isWeixinScope(scopeId)) {
|
|
114
121
|
return this.requireWeixinTransport(scopeId).sendDraft(scopeId, draftId, fallbackText);
|
|
115
122
|
}
|
|
123
|
+
if (!this.telegram.sendRichMarkdownDraft)
|
|
124
|
+
throw new Error('Channel does not support sendRichMarkdownDraft');
|
|
116
125
|
return this.telegram.sendRichMarkdownDraft(scopeId, draftId, markdown);
|
|
117
126
|
}
|
|
118
127
|
answerCallback(callbackQueryId, text) {
|
|
128
|
+
if (callbackQueryId.startsWith('text:'))
|
|
129
|
+
return this.weixin?.answerCallback(callbackQueryId, text) ?? Promise.resolve();
|
|
119
130
|
return this.telegram.answerCallback(callbackQueryId, text);
|
|
120
131
|
}
|
|
121
132
|
getFile(fileId) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { AppConfig } from '../../config.js';
|
|
2
|
+
import type { ChannelTextEvent } from '../../core/channel_events.js';
|
|
1
3
|
import type { ChannelPort } from '../../core/channel_port.js';
|
|
2
4
|
import type { TelegramGateway } from '../../telegram/gateway.js';
|
|
3
5
|
import type { TelegramRemoteFile } from '../../telegram/api.js';
|
|
@@ -10,7 +12,21 @@ export type InlineKeyboard = Array<Array<{
|
|
|
10
12
|
*/
|
|
11
13
|
export declare class TelegramMessagingPort implements ChannelPort {
|
|
12
14
|
private readonly gateway;
|
|
15
|
+
private readonly taskPreviews;
|
|
13
16
|
constructor(gateway: TelegramGateway);
|
|
17
|
+
beginTaskPreview(scopeId: string, taskId: string, text: string, reuseMessageId?: number): Promise<number>;
|
|
18
|
+
updateTaskPreview(scopeId: string, taskId: string, messageId: number, html: string): Promise<number>;
|
|
19
|
+
endTaskPreview(scopeId: string, taskId: string): Promise<void>;
|
|
20
|
+
readonly capabilities: {
|
|
21
|
+
editableMessages: boolean;
|
|
22
|
+
inlineActions: boolean;
|
|
23
|
+
maxMessageLength: number;
|
|
24
|
+
};
|
|
25
|
+
resolveIncoming(event: ChannelTextEvent, config: AppConfig, username?: string | null): import("../../telegram/addressing.js").TelegramAddressingDecision;
|
|
26
|
+
setScopeCommands(scopeId: string, commands: Array<{
|
|
27
|
+
command: string;
|
|
28
|
+
description: string;
|
|
29
|
+
}>): Promise<void>;
|
|
14
30
|
sendPlain(bridgeScopeId: string, text: string, inlineKeyboard?: InlineKeyboard): Promise<number>;
|
|
15
31
|
sendHtml(bridgeScopeId: string, text: string, inlineKeyboard?: InlineKeyboard): Promise<number>;
|
|
16
32
|
sendRichHtml(bridgeScopeId: string, html: string, inlineKeyboard?: InlineKeyboard): Promise<number>;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { TelegramTaskPreviews } from '../../telegram/task_preview.js';
|
|
2
|
+
import { telegramTaskResult } from '../../telegram/task_result.js';
|
|
3
|
+
import { parseCommand } from '../../controller/commands.js';
|
|
4
|
+
import { isDefaultTelegramScope, resolveTelegramAddressing } from '../../telegram/addressing.js';
|
|
1
5
|
import { parseTelegramTargetFromBridgeScope } from '../../core/bridge_scope.js';
|
|
2
6
|
import { telegramRichHtml, telegramRichMarkdown } from '../../telegram/rich.js';
|
|
3
7
|
/**
|
|
@@ -5,8 +9,24 @@ import { telegramRichHtml, telegramRichMarkdown } from '../../telegram/rich.js';
|
|
|
5
9
|
*/
|
|
6
10
|
export class TelegramMessagingPort {
|
|
7
11
|
gateway;
|
|
12
|
+
taskPreviews;
|
|
8
13
|
constructor(gateway) {
|
|
9
14
|
this.gateway = gateway;
|
|
15
|
+
this.taskPreviews = new TelegramTaskPreviews(gateway);
|
|
16
|
+
}
|
|
17
|
+
beginTaskPreview(scopeId, taskId, text, reuseMessageId = 0) { return this.taskPreviews.begin(scopeId, taskId, text, reuseMessageId); }
|
|
18
|
+
updateTaskPreview(scopeId, taskId, messageId, html) { return this.taskPreviews.update(scopeId, taskId, messageId, html); }
|
|
19
|
+
endTaskPreview(scopeId, taskId) { return this.taskPreviews.end(scopeId, taskId); }
|
|
20
|
+
capabilities = { editableMessages: true, inlineActions: true, maxMessageLength: 4000 };
|
|
21
|
+
resolveIncoming(event, config, username) {
|
|
22
|
+
return resolveTelegramAddressing({ text: event.text, attachmentsCount: event.attachments.length,
|
|
23
|
+
entities: event.entities, command: parseCommand(event.text), botUsername: username ?? null,
|
|
24
|
+
isDefaultTopic: isDefaultTelegramScope({ chatType: event.chatType, allowedChatId: config.tgAllowedChatId ?? null,
|
|
25
|
+
allowedTopicId: config.tgAllowedTopicId ?? null, topicId: event.topicId, requireExplicitGroupAddressing: false }), replyToBot: event.replyToBot });
|
|
26
|
+
}
|
|
27
|
+
async setScopeCommands(scopeId, commands) {
|
|
28
|
+
const target = parseTelegramTargetFromBridgeScope(scopeId);
|
|
29
|
+
await this.gateway.setChatCommands(target.chatId, commands);
|
|
10
30
|
}
|
|
11
31
|
async sendPlain(bridgeScopeId, text, inlineKeyboard) {
|
|
12
32
|
const target = parseTelegramTargetFromBridgeScope(bridgeScopeId);
|
|
@@ -22,7 +42,7 @@ export class TelegramMessagingPort {
|
|
|
22
42
|
}
|
|
23
43
|
async sendRichMarkdown(bridgeScopeId, markdown, inlineKeyboard) {
|
|
24
44
|
const target = parseTelegramTargetFromBridgeScope(bridgeScopeId);
|
|
25
|
-
return this.gateway.sendRichMessage(target.chatId,
|
|
45
|
+
return this.gateway.sendRichMessage(target.chatId, telegramTaskResult(markdown), inlineKeyboard, target.topicId);
|
|
26
46
|
}
|
|
27
47
|
async sendVoice(bridgeScopeId, filename, contents, caption, contentType) {
|
|
28
48
|
const target = parseTelegramTargetFromBridgeScope(bridgeScopeId);
|
|
@@ -42,7 +62,7 @@ export class TelegramMessagingPort {
|
|
|
42
62
|
}
|
|
43
63
|
async editRichMarkdown(bridgeScopeId, messageId, markdown, inlineKeyboard) {
|
|
44
64
|
const target = parseTelegramTargetFromBridgeScope(bridgeScopeId);
|
|
45
|
-
await this.gateway.editRichMessage(target.chatId, messageId,
|
|
65
|
+
await this.gateway.editRichMessage(target.chatId, messageId, telegramTaskResult(markdown), inlineKeyboard);
|
|
46
66
|
}
|
|
47
67
|
async deleteMessage(bridgeScopeId, messageId) {
|
|
48
68
|
const target = parseTelegramTargetFromBridgeScope(bridgeScopeId);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ChannelInlineKeyboard } from '../core/channel_port.js';
|
|
2
|
+
import type { ChannelTextEvent, ChannelCallbackEvent } from '../core/channel_events.js';
|
|
3
|
+
/** Scoped, expiring actions for channels without inline buttons. */
|
|
4
|
+
export declare class TextActions {
|
|
5
|
+
private readonly ttlMs;
|
|
6
|
+
private readonly maxActions;
|
|
7
|
+
private readonly actions;
|
|
8
|
+
constructor(ttlMs?: number, maxActions?: number);
|
|
9
|
+
render(scopeId: string, messageId: number, keyboard: ChannelInlineKeyboard): string;
|
|
10
|
+
resolve(event: ChannelTextEvent): ChannelCallbackEvent | null;
|
|
11
|
+
clearMessage(scopeId: string, messageId: number): void;
|
|
12
|
+
clear(): void;
|
|
13
|
+
private prune;
|
|
14
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
/** Scoped, expiring actions for channels without inline buttons. */
|
|
3
|
+
export class TextActions {
|
|
4
|
+
ttlMs;
|
|
5
|
+
maxActions;
|
|
6
|
+
actions = new Map();
|
|
7
|
+
constructor(ttlMs = 300_000, maxActions = 2000) {
|
|
8
|
+
this.ttlMs = ttlMs;
|
|
9
|
+
this.maxActions = maxActions;
|
|
10
|
+
}
|
|
11
|
+
render(scopeId, messageId, keyboard) {
|
|
12
|
+
this.prune();
|
|
13
|
+
return keyboard.flat().map(button => {
|
|
14
|
+
this.prune();
|
|
15
|
+
while (this.actions.size >= this.maxActions && this.actions.size)
|
|
16
|
+
this.actions.delete(this.actions.keys().next().value);
|
|
17
|
+
const code = randomBytes(6).toString('hex');
|
|
18
|
+
this.actions.set(code, { scopeId, data: button.callback_data, messageId, expires: Date.now() + this.ttlMs });
|
|
19
|
+
return `${button.text} /choose ${code}`;
|
|
20
|
+
}).join('\n');
|
|
21
|
+
}
|
|
22
|
+
resolve(event) {
|
|
23
|
+
this.prune();
|
|
24
|
+
const match = /^\/choose\s+([a-f0-9]+)\s*$/i.exec(event.text);
|
|
25
|
+
const code = match?.[1];
|
|
26
|
+
if (!code)
|
|
27
|
+
return null;
|
|
28
|
+
const action = this.actions.get(code);
|
|
29
|
+
if (!action || action.scopeId !== event.scopeId)
|
|
30
|
+
return null;
|
|
31
|
+
this.actions.delete(code);
|
|
32
|
+
return { scopeId: event.scopeId, chatId: event.chatId, topicId: event.topicId, userId: event.userId,
|
|
33
|
+
messageId: action.messageId, data: action.data, callbackQueryId: `text:${code}`, ...(event.languageCode ? { languageCode: event.languageCode } : {}) };
|
|
34
|
+
}
|
|
35
|
+
clearMessage(scopeId, messageId) {
|
|
36
|
+
for (const [code, action] of this.actions)
|
|
37
|
+
if (action.scopeId === scopeId && action.messageId === messageId)
|
|
38
|
+
this.actions.delete(code);
|
|
39
|
+
}
|
|
40
|
+
clear() { this.actions.clear(); }
|
|
41
|
+
prune() {
|
|
42
|
+
for (const [code, action] of this.actions)
|
|
43
|
+
if (action.expires <= Date.now())
|
|
44
|
+
this.actions.delete(code);
|
|
45
|
+
while (this.actions.size > this.maxActions)
|
|
46
|
+
this.actions.delete(this.actions.keys().next().value);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import type { AppConfig } from '../../config.js';
|
|
2
|
-
import type { BridgeSessionCore } from '../../controller/controller.js';
|
|
3
2
|
import type { Logger } from '../../logger.js';
|
|
4
3
|
import type { BridgeStore } from '../../store/database.js';
|
|
5
|
-
import type {
|
|
4
|
+
import type { ChannelTextEvent } from '../../core/channel_events.js';
|
|
6
5
|
export interface IWeixinBridgeCore {
|
|
7
|
-
dispatchInboundLikeTelegramText(event:
|
|
6
|
+
dispatchInboundLikeTelegramText(event: ChannelTextEvent): void;
|
|
8
7
|
}
|
|
9
8
|
export declare class WeixinChannelAdapter {
|
|
10
9
|
private readonly core;
|
|
@@ -13,7 +12,7 @@ export declare class WeixinChannelAdapter {
|
|
|
13
12
|
private readonly logger;
|
|
14
13
|
private readonly abort;
|
|
15
14
|
private loops;
|
|
16
|
-
constructor(core:
|
|
15
|
+
constructor(core: IWeixinBridgeCore, store: BridgeStore, config: AppConfig, logger: Logger);
|
|
17
16
|
start(): Promise<void>;
|
|
18
17
|
stop(): Promise<void>;
|
|
19
18
|
private listAccounts;
|
|
@@ -26,7 +26,7 @@ function isAllowedWeixinUser(config, fromUserId) {
|
|
|
26
26
|
}
|
|
27
27
|
return config.wxAllowedIlinkUserIds.includes(fromUserId);
|
|
28
28
|
}
|
|
29
|
-
function
|
|
29
|
+
function buildInboundEvent(params) {
|
|
30
30
|
return {
|
|
31
31
|
chatId: params.fromUserId,
|
|
32
32
|
topicId: null,
|
|
@@ -261,7 +261,7 @@ export class WeixinChannelAdapter {
|
|
|
261
261
|
textLen: trimmed.length,
|
|
262
262
|
attachments: attachments.length,
|
|
263
263
|
});
|
|
264
|
-
const event =
|
|
264
|
+
const event = buildInboundEvent({
|
|
265
265
|
scopeId,
|
|
266
266
|
fromUserId,
|
|
267
267
|
text: trimmed,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ChannelPort } from '../../core/channel_port.js';
|
|
2
|
+
import type { ChannelTextEvent } from '../../core/channel_events.js';
|
|
1
3
|
import type { BridgeStore } from '../../store/database.js';
|
|
2
4
|
import { getConfig, sendTyping } from './ilink/api.js';
|
|
3
5
|
import type { WeixinSavedAccount } from './account_store.js';
|
|
@@ -10,15 +12,28 @@ type WeixinTypingApi = {
|
|
|
10
12
|
* Weixin (iLink) outbound: addressed by `weixin:<accountId>:<peerUserId>` scope ids.
|
|
11
13
|
* Editing/deleting Telegram messages is approximated as new plain text sends or no-ops.
|
|
12
14
|
*/
|
|
13
|
-
export declare class WeixinMessagingPort {
|
|
15
|
+
export declare class WeixinMessagingPort implements ChannelPort {
|
|
14
16
|
private readonly store;
|
|
15
17
|
private readonly loadAccount;
|
|
16
18
|
private readonly typingApi;
|
|
19
|
+
readonly capabilities: {
|
|
20
|
+
editableMessages: boolean;
|
|
21
|
+
inlineActions: boolean;
|
|
22
|
+
maxMessageLength: number;
|
|
23
|
+
};
|
|
24
|
+
private readonly actions;
|
|
25
|
+
private readonly callbackScopes;
|
|
17
26
|
private nextSyntheticId;
|
|
18
27
|
constructor(store: BridgeStore, loadAccount: (accountId: string) => WeixinSavedAccount | null, typingApi?: WeixinTypingApi);
|
|
19
28
|
private allocMessageId;
|
|
20
29
|
sendPlain(scopeId: string, text: string, keyboard?: ChannelInlineKeyboard): Promise<number>;
|
|
21
30
|
sendHtml(scopeId: string, html: string, keyboard?: ChannelInlineKeyboard): Promise<number>;
|
|
31
|
+
sendRichMarkdown(scopeId: string, text: string, keyboard?: ChannelInlineKeyboard): Promise<number>;
|
|
32
|
+
editRichMarkdown(scopeId: string, messageId: number, text: string, keyboard?: ChannelInlineKeyboard): Promise<void>;
|
|
33
|
+
resolveAction(event: ChannelTextEvent): import("../../core/channel_events.js").ChannelCallbackEvent | null;
|
|
34
|
+
answerCallback(callbackId: string, text: string): Promise<void>;
|
|
35
|
+
getFile(): Promise<never>;
|
|
36
|
+
downloadResolvedFile(): Promise<never>;
|
|
22
37
|
editPlain(scopeId: string, _messageId: number, text: string, keyboard?: ChannelInlineKeyboard): Promise<void>;
|
|
23
38
|
editHtml(scopeId: string, messageId: number, html: string, keyboard?: ChannelInlineKeyboard): Promise<void>;
|
|
24
39
|
deleteMessage(scopeId: string, messageId: number): Promise<void>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TextActions } from '../text_actions.js';
|
|
1
2
|
import { parseWeixinBridgeScope } from '../../core/bridge_scope.js';
|
|
2
3
|
import { getConfig, sendTyping } from './ilink/api.js';
|
|
3
4
|
import { sendMessageWeixin } from './ilink/send.js';
|
|
@@ -22,6 +23,9 @@ export class WeixinMessagingPort {
|
|
|
22
23
|
store;
|
|
23
24
|
loadAccount;
|
|
24
25
|
typingApi;
|
|
26
|
+
capabilities = { editableMessages: false, inlineActions: false, maxMessageLength: 2000 };
|
|
27
|
+
actions = new TextActions();
|
|
28
|
+
callbackScopes = new Map();
|
|
25
29
|
nextSyntheticId = 1;
|
|
26
30
|
constructor(store, loadAccount, typingApi = DEFAULT_TYPING_API) {
|
|
27
31
|
this.store = store;
|
|
@@ -34,7 +38,9 @@ export class WeixinMessagingPort {
|
|
|
34
38
|
return id;
|
|
35
39
|
}
|
|
36
40
|
async sendPlain(scopeId, text, keyboard) {
|
|
37
|
-
|
|
41
|
+
const messageId = this.allocMessageId();
|
|
42
|
+
const choices = keyboard?.length ? this.actions.render(scopeId, messageId, keyboard) : '';
|
|
43
|
+
const rendered = choices ? `${text}\n\n${choices}` : text;
|
|
38
44
|
const parsed = parseWeixinBridgeScope(scopeId);
|
|
39
45
|
if (!parsed) {
|
|
40
46
|
throw new Error(`Invalid weixin scope: ${scopeId}`);
|
|
@@ -46,27 +52,49 @@ export class WeixinMessagingPort {
|
|
|
46
52
|
const contextToken = this.store.getWeixinContextToken(scopeId);
|
|
47
53
|
await sendMessageWeixin({
|
|
48
54
|
to: parsed.fromUserId,
|
|
49
|
-
text,
|
|
55
|
+
text: rendered,
|
|
50
56
|
opts: {
|
|
51
57
|
baseUrl: account.baseUrl,
|
|
52
58
|
token: account.botToken,
|
|
53
59
|
...(contextToken !== null && contextToken !== '' ? { contextToken } : {}),
|
|
54
60
|
},
|
|
55
61
|
});
|
|
56
|
-
return
|
|
62
|
+
return messageId;
|
|
57
63
|
}
|
|
58
64
|
async sendHtml(scopeId, html, keyboard) {
|
|
59
65
|
return this.sendPlain(scopeId, stripHtmlBasic(html), keyboard);
|
|
60
66
|
}
|
|
67
|
+
sendRichMarkdown(scopeId, text, keyboard) {
|
|
68
|
+
// Shared result summaries contain HTML fragments as well as Markdown.
|
|
69
|
+
return this.sendPlain(scopeId, stripHtmlBasic(text), keyboard);
|
|
70
|
+
}
|
|
71
|
+
async editRichMarkdown(scopeId, messageId, text, keyboard) {
|
|
72
|
+
this.actions.clearMessage(scopeId, messageId);
|
|
73
|
+
await this.sendRichMarkdown(scopeId, text, keyboard);
|
|
74
|
+
}
|
|
75
|
+
resolveAction(event) {
|
|
76
|
+
const action = this.actions.resolve(event);
|
|
77
|
+
if (action)
|
|
78
|
+
this.callbackScopes.set(action.callbackQueryId, event.scopeId);
|
|
79
|
+
return action;
|
|
80
|
+
}
|
|
81
|
+
async answerCallback(callbackId, text) {
|
|
82
|
+
const scopeId = this.callbackScopes.get(callbackId);
|
|
83
|
+
this.callbackScopes.delete(callbackId);
|
|
84
|
+
if (scopeId && text)
|
|
85
|
+
await this.sendPlain(scopeId, text);
|
|
86
|
+
}
|
|
87
|
+
async getFile() { throw new Error('Weixin attachments must be downloaded by the inbound channel'); }
|
|
88
|
+
async downloadResolvedFile() { throw new Error('Weixin attachments must be downloaded by the inbound channel'); }
|
|
61
89
|
async editPlain(scopeId, _messageId, text, keyboard) {
|
|
90
|
+
this.actions.clearMessage(scopeId, _messageId);
|
|
62
91
|
await this.sendPlain(scopeId, text, keyboard);
|
|
63
92
|
}
|
|
64
93
|
async editHtml(scopeId, messageId, html, keyboard) {
|
|
65
94
|
await this.sendHtml(scopeId, html, keyboard);
|
|
66
95
|
}
|
|
67
96
|
async deleteMessage(scopeId, messageId) {
|
|
68
|
-
|
|
69
|
-
void messageId;
|
|
97
|
+
this.actions.clearMessage(scopeId, messageId);
|
|
70
98
|
}
|
|
71
99
|
async sendTypingInScope(scopeId) {
|
|
72
100
|
const parsed = parseWeixinBridgeScope(scopeId);
|
|
@@ -104,8 +132,7 @@ export class WeixinMessagingPort {
|
|
|
104
132
|
}
|
|
105
133
|
}
|
|
106
134
|
async clearInlineKeyboard(scopeId, messageId) {
|
|
107
|
-
|
|
108
|
-
void messageId;
|
|
135
|
+
this.actions.clearMessage(scopeId, messageId);
|
|
109
136
|
}
|
|
110
137
|
async sendDraft(scopeId, _draftId, text) {
|
|
111
138
|
await this.sendPlain(scopeId, text);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { JsonRpcNotification } from './client.js';
|
|
2
|
+
export interface RawExecCommandEvent {
|
|
3
|
+
callId: string;
|
|
4
|
+
turnId: string;
|
|
5
|
+
command: string[];
|
|
6
|
+
cwd: string | null;
|
|
7
|
+
parsedCmd: any[];
|
|
8
|
+
}
|
|
9
|
+
export type TurnActivityState = 'thinking' | 'reading' | 'searching' | 'editing' | 'running_command' | 'approval_waiting' | 'completed' | 'interrupted';
|
|
10
|
+
export type TurnOutputKind = 'commentary' | 'final_answer' | 'tool_summary' | 'error';
|
|
11
|
+
export type TurnActivityEvent = {
|
|
12
|
+
kind: 'user_message';
|
|
13
|
+
turnId: string;
|
|
14
|
+
text: string;
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'agent_message_started';
|
|
17
|
+
turnId: string;
|
|
18
|
+
itemId: string;
|
|
19
|
+
phase: string | null;
|
|
20
|
+
outputKind: TurnOutputKind;
|
|
21
|
+
isPlan?: boolean;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'agent_message_delta';
|
|
24
|
+
turnId: string;
|
|
25
|
+
itemId: string;
|
|
26
|
+
delta: string;
|
|
27
|
+
outputKind: TurnOutputKind;
|
|
28
|
+
isPlan?: boolean;
|
|
29
|
+
} | {
|
|
30
|
+
kind: 'agent_message_completed';
|
|
31
|
+
turnId: string;
|
|
32
|
+
itemId: string;
|
|
33
|
+
phase: string | null;
|
|
34
|
+
text: string | null;
|
|
35
|
+
outputKind: TurnOutputKind;
|
|
36
|
+
isPlan?: boolean;
|
|
37
|
+
} | {
|
|
38
|
+
kind: 'reasoning_started';
|
|
39
|
+
turnId: string;
|
|
40
|
+
state: 'thinking';
|
|
41
|
+
} | {
|
|
42
|
+
kind: 'reasoning_completed';
|
|
43
|
+
turnId: string;
|
|
44
|
+
state: 'thinking';
|
|
45
|
+
} | {
|
|
46
|
+
kind: 'tool_started';
|
|
47
|
+
turnId: string;
|
|
48
|
+
exec: RawExecCommandEvent;
|
|
49
|
+
state: TurnActivityState;
|
|
50
|
+
} | {
|
|
51
|
+
kind: 'tool_completed';
|
|
52
|
+
turnId: string;
|
|
53
|
+
exec: RawExecCommandEvent;
|
|
54
|
+
state: TurnActivityState;
|
|
55
|
+
} | {
|
|
56
|
+
kind: 'turn_completed';
|
|
57
|
+
turnId: string;
|
|
58
|
+
state: 'completed' | 'interrupted';
|
|
59
|
+
};
|
|
60
|
+
export declare function normalizeTurnActivityEvent(notification: JsonRpcNotification): TurnActivityEvent | null;
|
|
61
|
+
export declare function classifyAgentOutput(phase: string | null, completed: boolean): TurnOutputKind;
|
|
62
|
+
export declare function inferToolActivityState(event: RawExecCommandEvent): TurnActivityState;
|