@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 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(`${optionIndex + 1}) ${option.label}${description}`);
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
- if (/^\s*[-*+]\s+/.test(line)) {
35
- const items = [];
36
- while (index < lines.length && /^\s*[-*+]\s+/.test(lines[index])) {
37
- items.push(stripListMarker(lines[index], false));
38
- index += 1;
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
- && !/^\s*[-*+]\s+/.test(lines[index])
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 stripListMarker(line, ordered) {
98
- return ordered
99
- ? line.replace(/^\s*\d+[.)]\s+/, '')
100
- : line.replace(/^\s*[-*+]\s+/, '');
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, '');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxden-app/foxclaw",
3
- "version": "0.5.64",
3
+ "version": "0.5.66",
4
4
  "description": "Foxden local execution claw for controlling Codex from trusted chat interfaces.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",