@foxden-app/foxclaw 0.5.38 → 0.5.40
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 +4 -0
- package/dist/controller/controller.js +179 -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.40 - 2026-06-18
|
|
6
|
+
|
|
7
|
+
### 中文
|
|
8
|
+
- Codex 普通输出完成后优先使用 Telegram 原生 RichMessage Markdown 发送/编辑,保留原始 Markdown 给 Telegram 解析,用于观察编号列表、复制文本和更多 Markdown 语法的真实客户端表现。
|
|
9
|
+
- 如果 Telegram 拒绝原生 Rich Markdown,FoxClaw 会自动退回现有 Markdown -> Rich HTML 转换;若 rich 编辑仍失败,则保留已经发送的普通文本,避免影响输出稳定性。Rich draft 也采用同样的 markdown 优先、HTML/纯文本降级路径。
|
|
10
|
+
|
|
11
|
+
### English
|
|
12
|
+
- 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.
|
|
13
|
+
- 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.
|
|
14
|
+
|
|
15
|
+
## 0.5.39 - 2026-06-17
|
|
16
|
+
|
|
17
|
+
### 中文
|
|
18
|
+
- `/update` 被运行时活动或 auth 同步挡住时,Telegram 现在会用 RichMessage 展示具体阻塞清单,包括活跃 runtime、远端 auth 候选导入队列、最近收到时间、最近失败候选和失败原因。
|
|
19
|
+
- 阻塞提示明确说明“远端 auth 候选导入队列”不等于本机 auth 文件数量,方便识别其他节点残留旧 team 账号或删除广播未同步的问题。
|
|
20
|
+
|
|
21
|
+
### English
|
|
22
|
+
- `/update` now reports concrete blockers as a Telegram RichMessage when runtime work or auth sync prevents a chat-driven update, including active runtimes, remote auth import backlog, last receive time, recent failed candidate, and failure reason.
|
|
23
|
+
- The blocked-update message clarifies that remote auth candidate imports are not the same as local auth file count, making stale team accounts or missed delete broadcasts on another node easier to diagnose.
|
|
24
|
+
|
|
5
25
|
## 0.5.38 - 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
|
}
|
|
@@ -288,6 +288,9 @@ export declare class BridgeSessionCore {
|
|
|
288
288
|
private handleAuthReloadCommand;
|
|
289
289
|
private canRunGlobalAuthRefresh;
|
|
290
290
|
private handleSelfUpdateCommand;
|
|
291
|
+
private formatSelfUpdateBlockedMessage;
|
|
292
|
+
private collectSelfUpdateBlockers;
|
|
293
|
+
private readServiceStatusForUpdateBlockers;
|
|
291
294
|
private scheduleSelfUpdateStatusPoll;
|
|
292
295
|
private clearSelfUpdateStatusPoll;
|
|
293
296
|
private pollSelfUpdateStatus;
|
|
@@ -442,6 +445,7 @@ export declare class BridgeSessionCore {
|
|
|
442
445
|
private clearMessageButtons;
|
|
443
446
|
private sendDraft;
|
|
444
447
|
private sendRichDraft;
|
|
448
|
+
private sendRichMarkdownDraft;
|
|
445
449
|
private renderActiveStatus;
|
|
446
450
|
private dismissTurnPreview;
|
|
447
451
|
private ensureStatusMessage;
|
|
@@ -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)) {
|
|
@@ -4343,7 +4349,7 @@ export class BridgeSessionCore {
|
|
|
4343
4349
|
return;
|
|
4344
4350
|
}
|
|
4345
4351
|
if (!this.isIdleForServiceUpdate() || this.store.countPendingApprovals() > 0 || this.store.countPendingUserInputs() > 0 || (this.coordinator?.canSelfUpdate && !this.coordinator.canSelfUpdate())) {
|
|
4346
|
-
await this.
|
|
4352
|
+
await this.sendRichInternalMessage(scopeId, '/update', await this.formatSelfUpdateBlockedMessage(locale));
|
|
4347
4353
|
return;
|
|
4348
4354
|
}
|
|
4349
4355
|
const status = await this.selfUpdater.readStatus();
|
|
@@ -4365,6 +4371,140 @@ export class BridgeSessionCore {
|
|
|
4365
4371
|
await this.sendMessage(scopeId, t(locale, 'update_failed', { error: formatUserError(error) }));
|
|
4366
4372
|
}
|
|
4367
4373
|
}
|
|
4374
|
+
async formatSelfUpdateBlockedMessage(locale) {
|
|
4375
|
+
const blockers = await this.collectSelfUpdateBlockers(locale);
|
|
4376
|
+
if (locale === 'zh') {
|
|
4377
|
+
return [
|
|
4378
|
+
'现在不能从 Telegram 里升级 FoxClaw,因为后台仍有活动:',
|
|
4379
|
+
'',
|
|
4380
|
+
...(blockers.length > 0 ? blockers.map((blocker) => `- ${blocker}`) : [`- ${t(locale, 'update_blocked_active')}`]),
|
|
4381
|
+
'',
|
|
4382
|
+
'说明:远端 auth 候选导入队列不等于本机 auth 文件数量;它可能来自其他节点还没清理或没收到删除广播的旧 team 账号。',
|
|
4383
|
+
'处理:普通 Codex 回复可用 /interrupt;auth 同步队列请等待消化,或在远端清理旧候选后补发删除/安全同步。终端 foxclaw update 不受这个聊天保护限制。',
|
|
4384
|
+
].join('\n');
|
|
4385
|
+
}
|
|
4386
|
+
return [
|
|
4387
|
+
'FoxClaw cannot be updated from Telegram because background work is still active:',
|
|
4388
|
+
'',
|
|
4389
|
+
...(blockers.length > 0 ? blockers.map((blocker) => `- ${blocker}`) : [`- ${t(locale, 'update_blocked_active')}`]),
|
|
4390
|
+
'',
|
|
4391
|
+
'Note: remote auth import backlog is not the same as the number of local auth files; it can come from stale team accounts on another node.',
|
|
4392
|
+
'Action: use /interrupt for normal Codex turns; let auth sync drain, or clean the remote stale candidates and resend delete/safe-sync events. Terminal foxclaw update bypasses this chat guard.',
|
|
4393
|
+
].join('\n');
|
|
4394
|
+
}
|
|
4395
|
+
async collectSelfUpdateBlockers(locale) {
|
|
4396
|
+
const blockers = [];
|
|
4397
|
+
const pendingApprovals = this.pendingApprovalMessages.size + this.store.countPendingApprovals();
|
|
4398
|
+
const pendingUserInputs = this.pendingUserInputs.size + this.store.countPendingUserInputs();
|
|
4399
|
+
const localLabels = locale === 'zh'
|
|
4400
|
+
? {
|
|
4401
|
+
activeTurns: '当前 runtime 活跃回复',
|
|
4402
|
+
approvals: '待处理审批',
|
|
4403
|
+
userInputs: '待回答问题',
|
|
4404
|
+
mcp: '待处理 MCP 交互',
|
|
4405
|
+
logins: '登录流程',
|
|
4406
|
+
rotation: 'auth 自动轮换',
|
|
4407
|
+
refreshAll: 'auth 全量刷新',
|
|
4408
|
+
validation: '远端 auth 校验',
|
|
4409
|
+
turnStart: '回复启动中',
|
|
4410
|
+
otherRuntime: '其他 runtime 活跃',
|
|
4411
|
+
weixin: '微信 runtime 活跃回复',
|
|
4412
|
+
authQueue: '远端 auth 候选导入队列',
|
|
4413
|
+
received: '最近收到',
|
|
4414
|
+
imported: '最近导入',
|
|
4415
|
+
failed: '最近失败',
|
|
4416
|
+
lease: 'auth 同步租约',
|
|
4417
|
+
error: 'auth 同步错误',
|
|
4418
|
+
coordinator: '服务协调器报告仍忙',
|
|
4419
|
+
}
|
|
4420
|
+
: {
|
|
4421
|
+
activeTurns: 'Current runtime active turns',
|
|
4422
|
+
approvals: 'Pending approvals',
|
|
4423
|
+
userInputs: 'Pending questions',
|
|
4424
|
+
mcp: 'Pending MCP interactions',
|
|
4425
|
+
logins: 'Login flows',
|
|
4426
|
+
rotation: 'Auth rotation',
|
|
4427
|
+
refreshAll: 'Auth refresh-all',
|
|
4428
|
+
validation: 'Remote auth validation',
|
|
4429
|
+
turnStart: 'Turn startup',
|
|
4430
|
+
otherRuntime: 'Other runtime active turns',
|
|
4431
|
+
weixin: 'Weixin runtime active turns',
|
|
4432
|
+
authQueue: 'Remote auth candidate import queue',
|
|
4433
|
+
received: 'last received',
|
|
4434
|
+
imported: 'last imported',
|
|
4435
|
+
failed: 'recent failure',
|
|
4436
|
+
lease: 'Auth sync lease',
|
|
4437
|
+
error: 'Auth sync error',
|
|
4438
|
+
coordinator: 'Service coordinator reports busy',
|
|
4439
|
+
};
|
|
4440
|
+
if (this.activeTurns.size > 0)
|
|
4441
|
+
blockers.push(`${localLabels.activeTurns}: ${this.activeTurns.size}`);
|
|
4442
|
+
if (pendingApprovals > 0)
|
|
4443
|
+
blockers.push(`${localLabels.approvals}: ${pendingApprovals}`);
|
|
4444
|
+
if (pendingUserInputs > 0)
|
|
4445
|
+
blockers.push(`${localLabels.userInputs}: ${pendingUserInputs}`);
|
|
4446
|
+
if (this.pendingMcpElicitations.size > 0)
|
|
4447
|
+
blockers.push(`${localLabels.mcp}: ${this.pendingMcpElicitations.size}`);
|
|
4448
|
+
if (this.pendingLoginsByScope.size > 0)
|
|
4449
|
+
blockers.push(`${localLabels.logins}: ${this.pendingLoginsByScope.size}`);
|
|
4450
|
+
if (this.authRotationInProgress)
|
|
4451
|
+
blockers.push(localLabels.rotation);
|
|
4452
|
+
if (this.authRefreshAllInProgress)
|
|
4453
|
+
blockers.push(localLabels.refreshAll);
|
|
4454
|
+
if (this.externalAuthValidationInProgress)
|
|
4455
|
+
blockers.push(localLabels.validation);
|
|
4456
|
+
if (this.turnStartInProgress > 0)
|
|
4457
|
+
blockers.push(`${localLabels.turnStart}: ${this.turnStartInProgress}`);
|
|
4458
|
+
const serviceStatus = await this.readServiceStatusForUpdateBlockers();
|
|
4459
|
+
if (serviceStatus) {
|
|
4460
|
+
const activeBots = serviceStatus.bots
|
|
4461
|
+
.filter((runtime) => runtime.activeTurns > 0)
|
|
4462
|
+
.map((runtime) => `${runtime.username ? `@${runtime.username}` : runtime.id} ${runtime.activeTurns}`);
|
|
4463
|
+
if (activeBots.length > 0) {
|
|
4464
|
+
blockers.push(`${localLabels.otherRuntime}: ${activeBots.join(', ')}`);
|
|
4465
|
+
}
|
|
4466
|
+
if ((serviceStatus.weixinRuntime?.activeTurns ?? 0) > 0) {
|
|
4467
|
+
blockers.push(`${localLabels.weixin}: ${serviceStatus.weixinRuntime.activeTurns}`);
|
|
4468
|
+
}
|
|
4469
|
+
const authSync = serviceStatus.authSync;
|
|
4470
|
+
if (authSync?.enabled) {
|
|
4471
|
+
if (authSync.pendingImports > 0) {
|
|
4472
|
+
const details = [
|
|
4473
|
+
authSync.lastReceivedAt ? `${localLabels.received} ${authSync.lastReceivedAt}` : null,
|
|
4474
|
+
authSync.lastImportedAt ? `${localLabels.imported} ${authSync.lastImportCandidate ?? authSync.lastImportedAt}` : null,
|
|
4475
|
+
].filter(Boolean);
|
|
4476
|
+
blockers.push(`${localLabels.authQueue}: ${authSync.pendingImports}${details.length > 0 ? ` (${details.join('; ')})` : ''}`);
|
|
4477
|
+
}
|
|
4478
|
+
const latestFailure = authSync.candidateFailures?.[0] ?? null;
|
|
4479
|
+
if (latestFailure) {
|
|
4480
|
+
const source = latestFailure.sourceLabel ?? latestFailure.peer ?? latestFailure.sourceNodeId;
|
|
4481
|
+
blockers.push(`${localLabels.failed}: ${latestFailure.candidateName}: ${latestFailure.reason}${source ? ` (${source})` : ''}`);
|
|
4482
|
+
}
|
|
4483
|
+
if (authSync.activeLeaseId)
|
|
4484
|
+
blockers.push(`${localLabels.lease}: ${authSync.activeLeaseId}`);
|
|
4485
|
+
if (authSync.lastError)
|
|
4486
|
+
blockers.push(`${localLabels.error}: ${authSync.lastError}`);
|
|
4487
|
+
}
|
|
4488
|
+
}
|
|
4489
|
+
else if (this.coordinator?.canSelfUpdate && !this.coordinator.canSelfUpdate()) {
|
|
4490
|
+
blockers.push(localLabels.coordinator);
|
|
4491
|
+
}
|
|
4492
|
+
if (blockers.length === 0 && this.coordinator?.canSelfUpdate && !this.coordinator.canSelfUpdate()) {
|
|
4493
|
+
blockers.push(localLabels.coordinator);
|
|
4494
|
+
}
|
|
4495
|
+
return blockers;
|
|
4496
|
+
}
|
|
4497
|
+
async readServiceStatusForUpdateBlockers() {
|
|
4498
|
+
if (!this.coordinator?.getServiceStatus)
|
|
4499
|
+
return null;
|
|
4500
|
+
try {
|
|
4501
|
+
return await this.coordinator.getServiceStatus();
|
|
4502
|
+
}
|
|
4503
|
+
catch (error) {
|
|
4504
|
+
this.logger.warn('self_update.service_status_failed', { error: toErrorMeta(error) });
|
|
4505
|
+
return null;
|
|
4506
|
+
}
|
|
4507
|
+
}
|
|
4368
4508
|
scheduleSelfUpdateStatusPoll(delay = SELF_UPDATE_STATUS_POLL_MS) {
|
|
4369
4509
|
if (!this.selfUpdater || this.selfUpdatePollTimer) {
|
|
4370
4510
|
return;
|
|
@@ -7638,6 +7778,15 @@ export class BridgeSessionCore {
|
|
|
7638
7778
|
async sendRichDraft(scopeId, draftId, html, fallbackText) {
|
|
7639
7779
|
await this.messaging.sendRichDraft(scopeId, draftId, html, fallbackText);
|
|
7640
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
|
+
}
|
|
7641
7790
|
renderActiveStatus(active) {
|
|
7642
7791
|
const locale = this.localeForChat(active.scopeId);
|
|
7643
7792
|
return renderActiveTurnStatus(locale, {
|
|
@@ -7887,7 +8036,7 @@ export class BridgeSessionCore {
|
|
|
7887
8036
|
await this.sendDraft(active.scopeId, active.draftId, draftText);
|
|
7888
8037
|
}
|
|
7889
8038
|
else {
|
|
7890
|
-
await this.
|
|
8039
|
+
await this.sendRichMarkdownDraft(active.scopeId, active.draftId, draftText, draftText);
|
|
7891
8040
|
}
|
|
7892
8041
|
active.draftText = draftText;
|
|
7893
8042
|
}
|
|
@@ -8024,28 +8173,43 @@ export class BridgeSessionCore {
|
|
|
8024
8173
|
if (!existing || !chunk.trim()) {
|
|
8025
8174
|
continue;
|
|
8026
8175
|
}
|
|
8027
|
-
|
|
8028
|
-
if (existing.richHtml === richHtml || existing.richFailedForText === chunk) {
|
|
8176
|
+
if (existing.richHtml === chunk || existing.richFailedForText === chunk) {
|
|
8029
8177
|
continue;
|
|
8030
8178
|
}
|
|
8031
8179
|
try {
|
|
8032
|
-
await this.messaging.
|
|
8033
|
-
existing.richHtml =
|
|
8180
|
+
await this.messaging.editRichMarkdown(active.scopeId, existing.messageId, chunk, chunk);
|
|
8181
|
+
existing.richHtml = chunk;
|
|
8034
8182
|
existing.richFailedForText = null;
|
|
8035
8183
|
}
|
|
8036
|
-
catch (
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
return;
|
|
8040
|
-
}
|
|
8041
|
-
existing.richFailedForText = chunk;
|
|
8042
|
-
this.logger.warn('telegram.stream_rich_edit_failed', {
|
|
8043
|
-
error: String(error),
|
|
8184
|
+
catch (markdownError) {
|
|
8185
|
+
this.logger.warn('telegram.stream_rich_markdown_edit_failed', {
|
|
8186
|
+
error: String(markdownError),
|
|
8044
8187
|
turnId: active.turnId,
|
|
8045
8188
|
itemId: segment.itemId,
|
|
8046
8189
|
messageId: existing.messageId,
|
|
8047
8190
|
chunkIndex: index,
|
|
8048
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
|
+
}
|
|
8049
8213
|
}
|
|
8050
8214
|
}
|
|
8051
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
|
+
}
|