@foxden-app/foxclaw 0.5.64 → 0.5.66
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/controller/controller.js +9 -6
- package/dist/telegram/rich_markdown.js +54 -22
- 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.66 - 2026-06-29
|
|
6
|
+
|
|
7
|
+
### 中文
|
|
8
|
+
- 优化 Codex 需要用户确认时的 Telegram 富文本展示:问题保持顶层编号,候选项改为缩进的二级编号列表,避免多问题确认卡片看起来像一整串扁平 `1/2/3`。
|
|
9
|
+
- RichMessage Markdown 渲染器现在支持嵌套有序/无序列表,类似安装选项、配置选择这类两层列表会渲染成真正的 Telegram 嵌套列表。
|
|
10
|
+
|
|
11
|
+
### English
|
|
12
|
+
- Improved Telegram RichMessage rendering for Codex user-input prompts. Questions stay as top-level numbered items, while choices are indented as second-level numbered lists, avoiding a flat sequence of confusing `1/2/3` items.
|
|
13
|
+
- The RichMessage Markdown renderer now supports nested ordered and unordered lists, so setup and configuration prompts render as real Telegram nested lists.
|
|
14
|
+
|
|
15
|
+
## 0.5.65 - 2026-06-29
|
|
16
|
+
|
|
17
|
+
### 中文
|
|
18
|
+
- 修复 Telegram 运行状态消息在网络/TLS 抖动下可能残留的问题:当 FoxClaw 编辑“正在运行命令”等 preview 消息失败并改发新状态消息时,旧 message id 现在会登记到最终清理列表。
|
|
19
|
+
- 保持“过程汇报”折叠消息的完整内容,不做自动裁剪;用户仍可通过 Telegram 展开、查看更多或弹窗查看全部过程。
|
|
20
|
+
|
|
21
|
+
### English
|
|
22
|
+
- Fixed stale Telegram runtime status messages after transient network/TLS edit failures. When FoxClaw fails to edit a "running command" preview and sends a replacement status message, the old message id is now tracked for final cleanup.
|
|
23
|
+
- Kept collapsed progress summaries complete rather than trimming them, preserving Telegram's expand, "show more", and full-view behavior.
|
|
24
|
+
|
|
5
25
|
## 0.5.64 - 2026-06-29
|
|
6
26
|
|
|
7
27
|
### 中文
|
|
@@ -8152,6 +8152,7 @@ export class BridgeSessionCore {
|
|
|
8152
8152
|
active.previewActive = false;
|
|
8153
8153
|
active.statusMessageText = null;
|
|
8154
8154
|
active.statusNeedsRebase = false;
|
|
8155
|
+
this.recordArchivedMessageId(active, active.previewMessageId);
|
|
8155
8156
|
this.store.removeActiveTurnPreview(active.turnId);
|
|
8156
8157
|
await this.ensureStatusMessage(active, text);
|
|
8157
8158
|
return;
|
|
@@ -11038,23 +11039,25 @@ function renderUserInputMessage(locale, record) {
|
|
|
11038
11039
|
record.questions.forEach((question, index) => {
|
|
11039
11040
|
lines.push('');
|
|
11040
11041
|
const title = question.header || question.question || question.id;
|
|
11041
|
-
lines.push(`${index + 1}. ${title}`);
|
|
11042
11042
|
if (question.header && question.question) {
|
|
11043
|
-
lines.push(question.question);
|
|
11043
|
+
lines.push(`${index + 1}. **${title}** ${question.question}`);
|
|
11044
|
+
}
|
|
11045
|
+
else {
|
|
11046
|
+
lines.push(`${index + 1}. **${title}**`);
|
|
11044
11047
|
}
|
|
11045
11048
|
if (question.isOther) {
|
|
11046
|
-
lines.push(t(locale, 'user_input_other_hint'));
|
|
11049
|
+
lines.push(` - ${t(locale, 'user_input_other_hint')}`);
|
|
11047
11050
|
}
|
|
11048
11051
|
if (question.isSecret) {
|
|
11049
|
-
lines.push(t(locale, 'user_input_secret_warning'));
|
|
11052
|
+
lines.push(` - ${t(locale, 'user_input_secret_warning')}`);
|
|
11050
11053
|
}
|
|
11051
11054
|
question.options.forEach((option, optionIndex) => {
|
|
11052
11055
|
const description = option.description ? ` - ${option.description}` : '';
|
|
11053
|
-
lines.push(
|
|
11056
|
+
lines.push(` ${optionIndex + 1}. ${option.label}${description}`);
|
|
11054
11057
|
});
|
|
11055
11058
|
const answer = record.answers.get(question.id);
|
|
11056
11059
|
if (answer) {
|
|
11057
|
-
lines.push(t(locale, 'user_input_selected', { value: answer }));
|
|
11060
|
+
lines.push(` - ${t(locale, 'user_input_selected', { value: answer })}`);
|
|
11058
11061
|
}
|
|
11059
11062
|
});
|
|
11060
11063
|
lines.push('');
|
|
@@ -31,22 +31,11 @@ export function renderTelegramMarkdownRichHtml(markdown) {
|
|
|
31
31
|
index += 1;
|
|
32
32
|
continue;
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
blocks.push(`<ul>${items.map(item => `<li>${renderInlineRichHtml(item)}</li>`).join('')}</ul>`);
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
if (/^\s*\d+[.)]\s+/.test(line)) {
|
|
44
|
-
const items = [];
|
|
45
|
-
while (index < lines.length && /^\s*\d+[.)]\s+/.test(lines[index])) {
|
|
46
|
-
items.push(stripListMarker(lines[index], true));
|
|
47
|
-
index += 1;
|
|
48
|
-
}
|
|
49
|
-
blocks.push(`<ol>${items.map(item => `<li>${renderInlineRichHtml(item)}</li>`).join('')}</ol>`);
|
|
34
|
+
const list = parseListMarker(line);
|
|
35
|
+
if (list) {
|
|
36
|
+
const rendered = consumeList(lines, index, list.indent, list.ordered);
|
|
37
|
+
blocks.push(rendered.html);
|
|
38
|
+
index = rendered.nextIndex;
|
|
50
39
|
continue;
|
|
51
40
|
}
|
|
52
41
|
if (/^\s*>\s?/.test(line)) {
|
|
@@ -64,8 +53,7 @@ export function renderTelegramMarkdownRichHtml(markdown) {
|
|
|
64
53
|
&& lines[index].trim()
|
|
65
54
|
&& !/^```/.test(lines[index])
|
|
66
55
|
&& !/^(#{1,4})\s+/.test(lines[index])
|
|
67
|
-
&&
|
|
68
|
-
&& !/^\s*\d+[.)]\s+/.test(lines[index])
|
|
56
|
+
&& !parseListMarker(lines[index])
|
|
69
57
|
&& !/^\s*>\s?/.test(lines[index])) {
|
|
70
58
|
paragraphLines.push(lines[index].trimEnd());
|
|
71
59
|
index += 1;
|
|
@@ -94,10 +82,54 @@ export function renderInlineRichHtml(markdown) {
|
|
|
94
82
|
html = html.replace(/\{\{FOXCLAW_RICH_CODE_(\d+)}}/g, (_match, tokenIndex) => tokens[Number(tokenIndex)] ?? '');
|
|
95
83
|
return html.replaceAll('\n', '<br>');
|
|
96
84
|
}
|
|
97
|
-
function
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
85
|
+
function consumeList(lines, startIndex, indent, ordered) {
|
|
86
|
+
const items = [];
|
|
87
|
+
let index = startIndex;
|
|
88
|
+
while (index < lines.length) {
|
|
89
|
+
const marker = parseListMarker(lines[index]);
|
|
90
|
+
if (!marker || marker.indent < indent) {
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
if (marker.indent > indent) {
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
if (marker.ordered !== ordered) {
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
let itemHtml = renderInlineRichHtml(marker.text);
|
|
100
|
+
index += 1;
|
|
101
|
+
while (index < lines.length) {
|
|
102
|
+
const nested = parseListMarker(lines[index]);
|
|
103
|
+
if (!nested || nested.indent <= indent) {
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
const rendered = consumeList(lines, index, nested.indent, nested.ordered);
|
|
107
|
+
itemHtml += rendered.html;
|
|
108
|
+
index = rendered.nextIndex;
|
|
109
|
+
}
|
|
110
|
+
items.push(`<li>${itemHtml}</li>`);
|
|
111
|
+
}
|
|
112
|
+
const tag = ordered ? 'ol' : 'ul';
|
|
113
|
+
return { html: `<${tag}>${items.join('')}</${tag}>`, nextIndex: index };
|
|
114
|
+
}
|
|
115
|
+
function parseListMarker(line) {
|
|
116
|
+
const ordered = line.match(/^(\s*)\d+[.)]\s+(.+)$/);
|
|
117
|
+
if (ordered) {
|
|
118
|
+
return {
|
|
119
|
+
indent: ordered[1].replaceAll('\t', ' ').length,
|
|
120
|
+
ordered: true,
|
|
121
|
+
text: ordered[2],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const unordered = line.match(/^(\s*)[-*+]\s+(.+)$/);
|
|
125
|
+
if (unordered) {
|
|
126
|
+
return {
|
|
127
|
+
indent: unordered[1].replaceAll('\t', ' ').length,
|
|
128
|
+
ordered: false,
|
|
129
|
+
text: unordered[2],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
101
133
|
}
|
|
102
134
|
function sanitizeCodeLanguage(language) {
|
|
103
135
|
const normalized = language.trim().replace(/[^A-Za-z0-9_+-]/g, '');
|