@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.
Files changed (124) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/antigravity/account_ui.d.ts +34 -0
  3. package/dist/antigravity/account_ui.js +354 -0
  4. package/dist/antigravity/adapter.js +1 -1
  5. package/dist/antigravity/auth.d.ts +4 -1
  6. package/dist/antigravity/auth.js +17 -5
  7. package/dist/antigravity/controller.d.ts +3 -108
  8. package/dist/antigravity/controller.js +2 -2129
  9. package/dist/antigravity/conversation_ui.d.ts +56 -0
  10. package/dist/antigravity/conversation_ui.js +509 -0
  11. package/dist/antigravity/ui_callbacks.d.ts +6 -0
  12. package/dist/antigravity/ui_callbacks.js +6 -0
  13. package/dist/bridge/unified_bridge.d.ts +114 -0
  14. package/dist/bridge/unified_bridge.js +1343 -0
  15. package/dist/channels/bridge_messaging_router.d.ts +8 -4
  16. package/dist/channels/bridge_messaging_router.js +15 -4
  17. package/dist/channels/telegram/telegram_messaging_port.d.ts +16 -0
  18. package/dist/channels/telegram/telegram_messaging_port.js +22 -2
  19. package/dist/channels/text_actions.d.ts +14 -0
  20. package/dist/channels/text_actions.js +48 -0
  21. package/dist/channels/weixin/weixin_channel_adapter.d.ts +3 -4
  22. package/dist/channels/weixin/weixin_channel_adapter.js +2 -2
  23. package/dist/channels/weixin/weixin_messaging_port.d.ts +16 -1
  24. package/dist/channels/weixin/weixin_messaging_port.js +34 -7
  25. package/dist/codex_app/activity.d.ts +62 -0
  26. package/dist/codex_app/activity.js +418 -0
  27. package/dist/codex_app/adapter.d.ts +2 -2
  28. package/dist/codex_app/adapter.js +55 -25
  29. package/dist/codex_app/backend_ui.d.ts +4 -0
  30. package/dist/codex_app/backend_ui.js +48 -0
  31. package/dist/codex_app/client.js +0 -12
  32. package/dist/controller/access.d.ts +1 -11
  33. package/dist/controller/access.js +1 -33
  34. package/dist/controller/activity.d.ts +1 -62
  35. package/dist/controller/activity.js +2 -418
  36. package/dist/controller/auth_errors.d.ts +11 -0
  37. package/dist/controller/auth_errors.js +124 -0
  38. package/dist/controller/auth_files.d.ts +13 -0
  39. package/dist/controller/auth_files.js +156 -0
  40. package/dist/controller/auth_presentation.d.ts +77 -0
  41. package/dist/controller/auth_presentation.js +697 -0
  42. package/dist/controller/auth_quota_service.d.ts +27 -0
  43. package/dist/controller/auth_quota_service.js +151 -0
  44. package/dist/controller/controller.d.ts +30 -56
  45. package/dist/controller/controller.js +181 -3497
  46. package/dist/controller/interactions.d.ts +74 -0
  47. package/dist/controller/interactions.js +560 -0
  48. package/dist/controller/local_usage_service.d.ts +19 -0
  49. package/dist/controller/local_usage_service.js +62 -0
  50. package/dist/controller/native_panel_service.d.ts +55 -0
  51. package/dist/controller/native_panel_service.js +214 -0
  52. package/dist/controller/native_panels.d.ts +61 -0
  53. package/dist/controller/native_panels.js +784 -0
  54. package/dist/controller/shared_helpers.d.ts +53 -0
  55. package/dist/controller/shared_helpers.js +399 -0
  56. package/dist/controller/state_types.d.ts +390 -0
  57. package/dist/controller/state_types.js +82 -0
  58. package/dist/controller/turn_rendering.d.ts +22 -0
  59. package/dist/controller/turn_rendering.js +288 -0
  60. package/dist/core/access.d.ts +11 -0
  61. package/dist/core/access.js +33 -0
  62. package/dist/core/attachment_files.d.ts +13 -0
  63. package/dist/core/attachment_files.js +180 -0
  64. package/dist/core/attachment_types.d.ts +22 -0
  65. package/dist/core/attachment_types.js +1 -0
  66. package/dist/core/attachments.d.ts +5 -3
  67. package/dist/core/attachments.js +2 -2
  68. package/dist/core/backend_registry.d.ts +22 -0
  69. package/dist/core/backend_registry.js +50 -0
  70. package/dist/core/backend_ui.d.ts +28 -0
  71. package/dist/core/backend_ui.js +1 -0
  72. package/dist/core/channel_events.d.ts +48 -0
  73. package/dist/core/channel_events.js +1 -0
  74. package/dist/core/channel_port.d.ts +52 -8
  75. package/dist/core/engine_spi.d.ts +19 -4
  76. package/dist/core/listener_scope.d.ts +11 -0
  77. package/dist/core/listener_scope.js +10 -0
  78. package/dist/core/message_text.d.ts +2 -0
  79. package/dist/core/message_text.js +28 -0
  80. package/dist/core/network_errors.d.ts +1 -0
  81. package/dist/core/network_errors.js +23 -0
  82. package/dist/core/orchestrator.d.ts +53 -24
  83. package/dist/core/orchestrator.js +855 -507
  84. package/dist/core/queued_engine_input.d.ts +12 -0
  85. package/dist/core/queued_engine_input.js +16 -0
  86. package/dist/core/runtime_supervisor.d.ts +19 -0
  87. package/dist/core/runtime_supervisor.js +45 -0
  88. package/dist/core/scope_operations.d.ts +7 -0
  89. package/dist/core/scope_operations.js +19 -0
  90. package/dist/core/stream_preview.js +21 -9
  91. package/dist/dsh/adapter.js +18 -5
  92. package/dist/dsh/backend.d.ts +7 -0
  93. package/dist/dsh/backend.js +21 -0
  94. package/dist/dsh/runtime.d.ts +0 -1
  95. package/dist/dsh/runtime.js +4 -13
  96. package/dist/dsh/ui.d.ts +8 -8
  97. package/dist/dsh/ui.js +1 -1
  98. package/dist/main.js +55 -43
  99. package/dist/opencode/adapter.js +28 -9
  100. package/dist/opencode/controller.js +1 -1
  101. package/dist/store/channel_inbox.d.ts +14 -0
  102. package/dist/store/channel_inbox.js +21 -0
  103. package/dist/store/database.d.ts +7 -3
  104. package/dist/store/database.js +7 -2
  105. package/dist/store/task_journal.d.ts +32 -0
  106. package/dist/store/task_journal.js +70 -0
  107. package/dist/telegram/api.d.ts +2 -2
  108. package/dist/telegram/api.js +10 -28
  109. package/dist/telegram/gateway.d.ts +13 -27
  110. package/dist/telegram/gateway.js +86 -43
  111. package/dist/telegram/media.d.ts +2 -34
  112. package/dist/telegram/media.js +1 -180
  113. package/dist/telegram/task_preview.d.ts +13 -0
  114. package/dist/telegram/task_preview.js +92 -0
  115. package/dist/telegram/task_result.d.ts +2 -0
  116. package/dist/telegram/task_result.js +12 -0
  117. package/dist/telegram/text.d.ts +1 -1
  118. package/dist/telegram/text.js +2 -27
  119. package/dist/types.d.ts +1 -1
  120. package/docs/release.md +13 -11
  121. package/docs/zh/architecture-review.md +106 -0
  122. package/docs/zh/release.md +13 -11
  123. package/docs/zh/telegram-experience.md +40 -0
  124. package/package.json +5 -4
@@ -1,4 +1,6 @@
1
- import type { TelegramRemoteFile } from '../telegram/api.js';
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: TelegramMessagingPort, weixin: WeixinMessagingPort | null);
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): Promise<TelegramRemoteFile>;
33
- downloadResolvedFile(remoteFilePath: string, destinationPath: string): Promise<number>;
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, telegramRichMarkdown(markdown, { skipEntityDetection: true }), inlineKeyboard, target.topicId);
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, telegramRichMarkdown(markdown, { skipEntityDetection: true }), inlineKeyboard);
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 { TelegramTextEvent } from '../../telegram/gateway.js';
4
+ import type { ChannelTextEvent } from '../../core/channel_events.js';
6
5
  export interface IWeixinBridgeCore {
7
- dispatchInboundLikeTelegramText(event: TelegramTextEvent): void;
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: BridgeSessionCore | IWeixinBridgeCore, store: BridgeStore, config: AppConfig, logger: Logger);
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 buildTelegramShapedEvent(params) {
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 = buildTelegramShapedEvent({
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
- void keyboard;
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 this.allocMessageId();
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
- void scopeId;
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
- void scopeId;
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;