@foxden-app/foxclaw 0.5.65 → 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,16 @@
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
+
5
15
  ## 0.5.65 - 2026-06-29
6
16
 
7
17
  ### 中文
@@ -11039,23 +11039,25 @@ function renderUserInputMessage(locale, record) {
11039
11039
  record.questions.forEach((question, index) => {
11040
11040
  lines.push('');
11041
11041
  const title = question.header || question.question || question.id;
11042
- lines.push(`${index + 1}. ${title}`);
11043
11042
  if (question.header && question.question) {
11044
- lines.push(question.question);
11043
+ lines.push(`${index + 1}. **${title}** ${question.question}`);
11044
+ }
11045
+ else {
11046
+ lines.push(`${index + 1}. **${title}**`);
11045
11047
  }
11046
11048
  if (question.isOther) {
11047
- lines.push(t(locale, 'user_input_other_hint'));
11049
+ lines.push(` - ${t(locale, 'user_input_other_hint')}`);
11048
11050
  }
11049
11051
  if (question.isSecret) {
11050
- lines.push(t(locale, 'user_input_secret_warning'));
11052
+ lines.push(` - ${t(locale, 'user_input_secret_warning')}`);
11051
11053
  }
11052
11054
  question.options.forEach((option, optionIndex) => {
11053
11055
  const description = option.description ? ` - ${option.description}` : '';
11054
- lines.push(`${optionIndex + 1}) ${option.label}${description}`);
11056
+ lines.push(` ${optionIndex + 1}. ${option.label}${description}`);
11055
11057
  });
11056
11058
  const answer = record.answers.get(question.id);
11057
11059
  if (answer) {
11058
- lines.push(t(locale, 'user_input_selected', { value: answer }));
11060
+ lines.push(` - ${t(locale, 'user_input_selected', { value: answer })}`);
11059
11061
  }
11060
11062
  });
11061
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.65",
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",