@foxden-app/foxclaw 0.5.39 → 0.5.41
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/channels/bridge_messaging_router.d.ts +3 -0
- package/dist/channels/bridge_messaging_router.js +18 -0
- package/dist/channels/telegram/telegram_messaging_port.d.ts +3 -0
- package/dist/channels/telegram/telegram_messaging_port.js +13 -1
- package/dist/controller/controller.d.ts +1 -0
- package/dist/controller/controller.js +45 -15
- package/dist/telegram/rich.d.ts +1 -0
- package/dist/telegram/rich.js +10 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable FoxClaw changes are listed here. Each release note is bilingual so GitHub Releases and the npm package are useful to both Chinese and English readers.
|
|
4
4
|
|
|
5
|
+
## 0.5.41 - 2026-06-18
|
|
6
|
+
|
|
7
|
+
### 中文
|
|
8
|
+
- 将 FoxClaw 的普通 Telegram 回复统一升级为 RichMessage Markdown 优先发送,覆盖短提示、usage、操作结果、升级开始/完成、错误通知和大多数诊断文本。
|
|
9
|
+
- 保持稳定降级:Telegram rich markdown 失败时自动回普通文本;`/status`、`/auth`、`/update` 阻塞诊断等结构化面板继续使用已验证的 RichMessage HTML 表格/列表/details。
|
|
10
|
+
|
|
11
|
+
### English
|
|
12
|
+
- Upgraded FoxClaw's normal Telegram replies to prefer RichMessage Markdown, covering short notices, usage text, action results, update start/completion messages, error notifications, and most diagnostic text.
|
|
13
|
+
- Stable fallback is preserved: Telegram rich markdown failures automatically return to plain text, while structured panels such as `/status`, `/auth`, and blocked `/update` diagnostics continue using the verified RichMessage HTML tables/lists/details path.
|
|
14
|
+
|
|
15
|
+
## 0.5.40 - 2026-06-18
|
|
16
|
+
|
|
17
|
+
### 中文
|
|
18
|
+
- Codex 普通输出完成后优先使用 Telegram 原生 RichMessage Markdown 发送/编辑,保留原始 Markdown 给 Telegram 解析,用于观察编号列表、复制文本和更多 Markdown 语法的真实客户端表现。
|
|
19
|
+
- 如果 Telegram 拒绝原生 Rich Markdown,FoxClaw 会自动退回现有 Markdown -> Rich HTML 转换;若 rich 编辑仍失败,则保留已经发送的普通文本,避免影响输出稳定性。Rich draft 也采用同样的 markdown 优先、HTML/纯文本降级路径。
|
|
20
|
+
|
|
21
|
+
### English
|
|
22
|
+
- Normal Codex output now prefers Telegram's native RichMessage Markdown when finalizing sent segments, preserving the original Markdown for Telegram to parse so ordered lists, copied text, and broader Markdown syntax can be observed in real clients.
|
|
23
|
+
- If Telegram rejects native Rich Markdown, FoxClaw automatically falls back to the existing Markdown-to-Rich-HTML renderer; if rich editing still fails, the already-sent plain text remains intact. Rich drafts use the same markdown-first path with HTML/plain fallback.
|
|
24
|
+
|
|
5
25
|
## 0.5.39 - 2026-06-17
|
|
6
26
|
|
|
7
27
|
### 中文
|
|
@@ -16,14 +16,17 @@ export declare class BridgeMessagingRouter {
|
|
|
16
16
|
sendPlain(scopeId: string, text: string, keyboard?: InlineKeyboard): Promise<number>;
|
|
17
17
|
sendHtml(scopeId: string, text: string, keyboard?: InlineKeyboard): Promise<number>;
|
|
18
18
|
sendRichHtml(scopeId: string, html: string, fallbackHtml: string, keyboard?: InlineKeyboard): Promise<number>;
|
|
19
|
+
sendRichMarkdown(scopeId: string, markdown: string, fallbackText: string, keyboard?: InlineKeyboard): Promise<number>;
|
|
19
20
|
editPlain(scopeId: string, messageId: number, text: string, keyboard?: InlineKeyboard): Promise<void>;
|
|
20
21
|
editHtml(scopeId: string, messageId: number, text: string, keyboard?: InlineKeyboard): Promise<void>;
|
|
21
22
|
editRichHtml(scopeId: string, messageId: number, html: string, fallbackHtml: string, keyboard?: InlineKeyboard): Promise<void>;
|
|
23
|
+
editRichMarkdown(scopeId: string, messageId: number, markdown: string, fallbackText: string, keyboard?: InlineKeyboard): Promise<void>;
|
|
22
24
|
deleteMessage(scopeId: string, messageId: number): Promise<void>;
|
|
23
25
|
sendTypingInScope(scopeId: string): Promise<void>;
|
|
24
26
|
clearInlineKeyboard(scopeId: string, messageId: number): Promise<void>;
|
|
25
27
|
sendDraft(scopeId: string, draftId: number, text: string): Promise<void>;
|
|
26
28
|
sendRichDraft(scopeId: string, draftId: number, html: string, fallbackText: string): Promise<void>;
|
|
29
|
+
sendRichMarkdownDraft(scopeId: string, draftId: number, markdown: string, fallbackText: string): Promise<void>;
|
|
27
30
|
answerCallback(callbackQueryId: string, text: string): Promise<void>;
|
|
28
31
|
getFile(fileId: string): Promise<TelegramRemoteFile>;
|
|
29
32
|
downloadResolvedFile(remoteFilePath: string, destinationPath: string): Promise<number>;
|
|
@@ -43,6 +43,12 @@ export class BridgeMessagingRouter {
|
|
|
43
43
|
}
|
|
44
44
|
return this.telegram.sendRichHtml(scopeId, html, keyboard);
|
|
45
45
|
}
|
|
46
|
+
sendRichMarkdown(scopeId, markdown, fallbackText, keyboard) {
|
|
47
|
+
if (this.isWeixinScope(scopeId)) {
|
|
48
|
+
return this.requireWeixinTransport(scopeId).sendPlain(scopeId, fallbackText, keyboard);
|
|
49
|
+
}
|
|
50
|
+
return this.telegram.sendRichMarkdown(scopeId, markdown, keyboard);
|
|
51
|
+
}
|
|
46
52
|
editPlain(scopeId, messageId, text, keyboard) {
|
|
47
53
|
if (this.isWeixinScope(scopeId)) {
|
|
48
54
|
return this.requireWeixinTransport(scopeId).editPlain(scopeId, messageId, text, keyboard);
|
|
@@ -61,6 +67,12 @@ export class BridgeMessagingRouter {
|
|
|
61
67
|
}
|
|
62
68
|
return this.telegram.editRichHtml(scopeId, messageId, html, keyboard);
|
|
63
69
|
}
|
|
70
|
+
editRichMarkdown(scopeId, messageId, markdown, fallbackText, keyboard) {
|
|
71
|
+
if (this.isWeixinScope(scopeId)) {
|
|
72
|
+
return this.requireWeixinTransport(scopeId).editPlain(scopeId, messageId, fallbackText, keyboard);
|
|
73
|
+
}
|
|
74
|
+
return this.telegram.editRichMarkdown(scopeId, messageId, markdown, keyboard);
|
|
75
|
+
}
|
|
64
76
|
deleteMessage(scopeId, messageId) {
|
|
65
77
|
if (this.isWeixinScope(scopeId)) {
|
|
66
78
|
return this.requireWeixinTransport(scopeId).deleteMessage(scopeId, messageId);
|
|
@@ -91,6 +103,12 @@ export class BridgeMessagingRouter {
|
|
|
91
103
|
}
|
|
92
104
|
return this.telegram.sendRichDraft(scopeId, draftId, html);
|
|
93
105
|
}
|
|
106
|
+
sendRichMarkdownDraft(scopeId, draftId, markdown, fallbackText) {
|
|
107
|
+
if (this.isWeixinScope(scopeId)) {
|
|
108
|
+
return this.requireWeixinTransport(scopeId).sendDraft(scopeId, draftId, fallbackText);
|
|
109
|
+
}
|
|
110
|
+
return this.telegram.sendRichMarkdownDraft(scopeId, draftId, markdown);
|
|
111
|
+
}
|
|
94
112
|
answerCallback(callbackQueryId, text) {
|
|
95
113
|
return this.telegram.answerCallback(callbackQueryId, text);
|
|
96
114
|
}
|
|
@@ -14,14 +14,17 @@ export declare class TelegramMessagingPort implements ChannelPort {
|
|
|
14
14
|
sendPlain(bridgeScopeId: string, text: string, inlineKeyboard?: InlineKeyboard): Promise<number>;
|
|
15
15
|
sendHtml(bridgeScopeId: string, text: string, inlineKeyboard?: InlineKeyboard): Promise<number>;
|
|
16
16
|
sendRichHtml(bridgeScopeId: string, html: string, inlineKeyboard?: InlineKeyboard): Promise<number>;
|
|
17
|
+
sendRichMarkdown(bridgeScopeId: string, markdown: string, inlineKeyboard?: InlineKeyboard): Promise<number>;
|
|
17
18
|
editPlain(bridgeScopeId: string, messageId: number, text: string, inlineKeyboard?: InlineKeyboard): Promise<void>;
|
|
18
19
|
editHtml(bridgeScopeId: string, messageId: number, text: string, inlineKeyboard?: InlineKeyboard): Promise<void>;
|
|
19
20
|
editRichHtml(bridgeScopeId: string, messageId: number, html: string, inlineKeyboard?: InlineKeyboard): Promise<void>;
|
|
21
|
+
editRichMarkdown(bridgeScopeId: string, messageId: number, markdown: string, inlineKeyboard?: InlineKeyboard): Promise<void>;
|
|
20
22
|
deleteMessage(bridgeScopeId: string, messageId: number): Promise<void>;
|
|
21
23
|
sendTypingInScope(bridgeScopeId: string): Promise<void>;
|
|
22
24
|
clearInlineKeyboard(bridgeScopeId: string, messageId: number): Promise<void>;
|
|
23
25
|
sendDraft(bridgeScopeId: string, draftId: number, text: string): Promise<void>;
|
|
24
26
|
sendRichDraft(bridgeScopeId: string, draftId: number, html: string): Promise<void>;
|
|
27
|
+
sendRichMarkdownDraft(bridgeScopeId: string, draftId: number, markdown: string): Promise<void>;
|
|
25
28
|
answerCallback(callbackQueryId: string, text: string): Promise<void>;
|
|
26
29
|
getFile(fileId: string): Promise<TelegramRemoteFile>;
|
|
27
30
|
downloadResolvedFile(remoteFilePath: string, destinationPath: string): Promise<number>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseTelegramTargetFromBridgeScope } from '../../core/bridge_scope.js';
|
|
2
|
-
import { telegramRichHtml } from '../../telegram/rich.js';
|
|
2
|
+
import { telegramRichHtml, telegramRichMarkdown } from '../../telegram/rich.js';
|
|
3
3
|
/**
|
|
4
4
|
* Telegram outbound operations addressed by bridge scope id (`telegram:…`).
|
|
5
5
|
*/
|
|
@@ -20,6 +20,10 @@ export class TelegramMessagingPort {
|
|
|
20
20
|
const target = parseTelegramTargetFromBridgeScope(bridgeScopeId);
|
|
21
21
|
return this.gateway.sendRichMessage(target.chatId, telegramRichHtml(html, { skipEntityDetection: true }), inlineKeyboard, target.topicId);
|
|
22
22
|
}
|
|
23
|
+
async sendRichMarkdown(bridgeScopeId, markdown, inlineKeyboard) {
|
|
24
|
+
const target = parseTelegramTargetFromBridgeScope(bridgeScopeId);
|
|
25
|
+
return this.gateway.sendRichMessage(target.chatId, telegramRichMarkdown(markdown, { skipEntityDetection: true }), inlineKeyboard, target.topicId);
|
|
26
|
+
}
|
|
23
27
|
async editPlain(bridgeScopeId, messageId, text, inlineKeyboard) {
|
|
24
28
|
const target = parseTelegramTargetFromBridgeScope(bridgeScopeId);
|
|
25
29
|
await this.gateway.editMessage(target.chatId, messageId, text, inlineKeyboard);
|
|
@@ -32,6 +36,10 @@ export class TelegramMessagingPort {
|
|
|
32
36
|
const target = parseTelegramTargetFromBridgeScope(bridgeScopeId);
|
|
33
37
|
await this.gateway.editRichMessage(target.chatId, messageId, telegramRichHtml(html, { skipEntityDetection: true }), inlineKeyboard);
|
|
34
38
|
}
|
|
39
|
+
async editRichMarkdown(bridgeScopeId, messageId, markdown, inlineKeyboard) {
|
|
40
|
+
const target = parseTelegramTargetFromBridgeScope(bridgeScopeId);
|
|
41
|
+
await this.gateway.editRichMessage(target.chatId, messageId, telegramRichMarkdown(markdown, { skipEntityDetection: true }), inlineKeyboard);
|
|
42
|
+
}
|
|
35
43
|
async deleteMessage(bridgeScopeId, messageId) {
|
|
36
44
|
const target = parseTelegramTargetFromBridgeScope(bridgeScopeId);
|
|
37
45
|
await this.gateway.deleteMessage(target.chatId, messageId);
|
|
@@ -52,6 +60,10 @@ export class TelegramMessagingPort {
|
|
|
52
60
|
const target = parseTelegramTargetFromBridgeScope(bridgeScopeId);
|
|
53
61
|
await this.gateway.sendRichMessageDraft(target.chatId, draftId, telegramRichHtml(html, { skipEntityDetection: true }), target.topicId);
|
|
54
62
|
}
|
|
63
|
+
async sendRichMarkdownDraft(bridgeScopeId, draftId, markdown) {
|
|
64
|
+
const target = parseTelegramTargetFromBridgeScope(bridgeScopeId);
|
|
65
|
+
await this.gateway.sendRichMessageDraft(target.chatId, draftId, telegramRichMarkdown(markdown, { skipEntityDetection: true }), target.topicId);
|
|
66
|
+
}
|
|
55
67
|
answerCallback(callbackQueryId, text) {
|
|
56
68
|
return this.gateway.answerCallback(callbackQueryId, text);
|
|
57
69
|
}
|
|
@@ -445,6 +445,7 @@ export declare class BridgeSessionCore {
|
|
|
445
445
|
private clearMessageButtons;
|
|
446
446
|
private sendDraft;
|
|
447
447
|
private sendRichDraft;
|
|
448
|
+
private sendRichMarkdownDraft;
|
|
448
449
|
private renderActiveStatus;
|
|
449
450
|
private dismissTurnPreview;
|
|
450
451
|
private ensureStatusMessage;
|
|
@@ -3361,7 +3361,7 @@ export class BridgeSessionCore {
|
|
|
3361
3361
|
writeRuntimeStatus(this.config.statusPath, status);
|
|
3362
3362
|
}
|
|
3363
3363
|
async sendMessage(scopeId, text, inlineKeyboard) {
|
|
3364
|
-
return this.
|
|
3364
|
+
return this.sendRichMarkdownMessage(scopeId, text, inlineKeyboard);
|
|
3365
3365
|
}
|
|
3366
3366
|
async sendHtmlMessage(scopeId, text, inlineKeyboard) {
|
|
3367
3367
|
return this.messaging.sendHtml(scopeId, text, inlineKeyboard);
|
|
@@ -3376,7 +3376,13 @@ export class BridgeSessionCore {
|
|
|
3376
3376
|
}
|
|
3377
3377
|
}
|
|
3378
3378
|
async sendRichMarkdownMessage(scopeId, text, inlineKeyboard) {
|
|
3379
|
-
|
|
3379
|
+
try {
|
|
3380
|
+
return await this.messaging.sendRichMarkdown(scopeId, text, text, inlineKeyboard);
|
|
3381
|
+
}
|
|
3382
|
+
catch (markdownError) {
|
|
3383
|
+
this.logger.warn('telegram.rich_markdown_send_failed', { scopeId, error: toErrorMeta(markdownError) });
|
|
3384
|
+
return this.sendRichHtmlMessage(scopeId, renderTelegramMarkdownRichHtml(text), escapeTelegramHtml(text), inlineKeyboard);
|
|
3385
|
+
}
|
|
3380
3386
|
}
|
|
3381
3387
|
async sendRichInternalMessage(scopeId, title, text, inlineKeyboard) {
|
|
3382
3388
|
if (scopeId.startsWith(BRIDGE_SCOPE_WEIXIN_PREFIX)) {
|
|
@@ -7772,6 +7778,15 @@ export class BridgeSessionCore {
|
|
|
7772
7778
|
async sendRichDraft(scopeId, draftId, html, fallbackText) {
|
|
7773
7779
|
await this.messaging.sendRichDraft(scopeId, draftId, html, fallbackText);
|
|
7774
7780
|
}
|
|
7781
|
+
async sendRichMarkdownDraft(scopeId, draftId, markdown, fallbackText) {
|
|
7782
|
+
try {
|
|
7783
|
+
await this.messaging.sendRichMarkdownDraft(scopeId, draftId, markdown, fallbackText);
|
|
7784
|
+
}
|
|
7785
|
+
catch (markdownError) {
|
|
7786
|
+
this.logger.warn('telegram.rich_markdown_draft_failed', { scopeId, error: toErrorMeta(markdownError) });
|
|
7787
|
+
await this.sendRichDraft(scopeId, draftId, renderTelegramMarkdownRichHtml(markdown), fallbackText);
|
|
7788
|
+
}
|
|
7789
|
+
}
|
|
7775
7790
|
renderActiveStatus(active) {
|
|
7776
7791
|
const locale = this.localeForChat(active.scopeId);
|
|
7777
7792
|
return renderActiveTurnStatus(locale, {
|
|
@@ -8021,7 +8036,7 @@ export class BridgeSessionCore {
|
|
|
8021
8036
|
await this.sendDraft(active.scopeId, active.draftId, draftText);
|
|
8022
8037
|
}
|
|
8023
8038
|
else {
|
|
8024
|
-
await this.
|
|
8039
|
+
await this.sendRichMarkdownDraft(active.scopeId, active.draftId, draftText, draftText);
|
|
8025
8040
|
}
|
|
8026
8041
|
active.draftText = draftText;
|
|
8027
8042
|
}
|
|
@@ -8158,28 +8173,43 @@ export class BridgeSessionCore {
|
|
|
8158
8173
|
if (!existing || !chunk.trim()) {
|
|
8159
8174
|
continue;
|
|
8160
8175
|
}
|
|
8161
|
-
|
|
8162
|
-
if (existing.richHtml === richHtml || existing.richFailedForText === chunk) {
|
|
8176
|
+
if (existing.richHtml === chunk || existing.richFailedForText === chunk) {
|
|
8163
8177
|
continue;
|
|
8164
8178
|
}
|
|
8165
8179
|
try {
|
|
8166
|
-
await this.messaging.
|
|
8167
|
-
existing.richHtml =
|
|
8180
|
+
await this.messaging.editRichMarkdown(active.scopeId, existing.messageId, chunk, chunk);
|
|
8181
|
+
existing.richHtml = chunk;
|
|
8168
8182
|
existing.richFailedForText = null;
|
|
8169
8183
|
}
|
|
8170
|
-
catch (
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
return;
|
|
8174
|
-
}
|
|
8175
|
-
existing.richFailedForText = chunk;
|
|
8176
|
-
this.logger.warn('telegram.stream_rich_edit_failed', {
|
|
8177
|
-
error: String(error),
|
|
8184
|
+
catch (markdownError) {
|
|
8185
|
+
this.logger.warn('telegram.stream_rich_markdown_edit_failed', {
|
|
8186
|
+
error: String(markdownError),
|
|
8178
8187
|
turnId: active.turnId,
|
|
8179
8188
|
itemId: segment.itemId,
|
|
8180
8189
|
messageId: existing.messageId,
|
|
8181
8190
|
chunkIndex: index,
|
|
8182
8191
|
});
|
|
8192
|
+
const richHtml = renderTelegramMarkdownRichHtml(chunk);
|
|
8193
|
+
try {
|
|
8194
|
+
await this.messaging.editRichHtml(active.scopeId, existing.messageId, richHtml, escapeTelegramHtml(chunk));
|
|
8195
|
+
existing.richHtml = richHtml;
|
|
8196
|
+
existing.richFailedForText = null;
|
|
8197
|
+
continue;
|
|
8198
|
+
}
|
|
8199
|
+
catch (error) {
|
|
8200
|
+
if (isTelegramMessageGone(error)) {
|
|
8201
|
+
segment.messages.splice(index);
|
|
8202
|
+
return;
|
|
8203
|
+
}
|
|
8204
|
+
existing.richFailedForText = chunk;
|
|
8205
|
+
this.logger.warn('telegram.stream_rich_edit_failed', {
|
|
8206
|
+
error: String(error),
|
|
8207
|
+
turnId: active.turnId,
|
|
8208
|
+
itemId: segment.itemId,
|
|
8209
|
+
messageId: existing.messageId,
|
|
8210
|
+
chunkIndex: index,
|
|
8211
|
+
});
|
|
8212
|
+
}
|
|
8183
8213
|
}
|
|
8184
8214
|
}
|
|
8185
8215
|
}
|
package/dist/telegram/rich.d.ts
CHANGED
|
@@ -11,3 +11,4 @@ export interface TelegramRichMessageOptions {
|
|
|
11
11
|
skipEntityDetection?: boolean;
|
|
12
12
|
}
|
|
13
13
|
export declare function telegramRichHtml(html: string, options?: TelegramRichMessageOptions): TelegramInputRichMessage;
|
|
14
|
+
export declare function telegramRichMarkdown(markdown: string, options?: TelegramRichMessageOptions): TelegramInputRichMessage;
|
package/dist/telegram/rich.js
CHANGED
|
@@ -10,3 +10,13 @@ export function telegramRichHtml(html, options = {}) {
|
|
|
10
10
|
}
|
|
11
11
|
return message;
|
|
12
12
|
}
|
|
13
|
+
export function telegramRichMarkdown(markdown, options = {}) {
|
|
14
|
+
const message = { markdown };
|
|
15
|
+
if (options.isRtl) {
|
|
16
|
+
message.is_rtl = true;
|
|
17
|
+
}
|
|
18
|
+
if (options.skipEntityDetection) {
|
|
19
|
+
message.skip_entity_detection = true;
|
|
20
|
+
}
|
|
21
|
+
return message;
|
|
22
|
+
}
|