@foxden-app/foxclaw 0.5.65 → 0.5.67

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.67 - 2026-07-01
6
+
7
+ ### 中文
8
+ - 修复手动中断 Codex thread 后 Telegram 过程消息残留的问题:中断收尾现在也会折叠已有过程汇报,并按 `TELEGRAM_DELETE_TOOL_DETAILS_AFTER_FINAL` 删除已归档的工具/运行状态明细。
9
+ - 中断时即使没有 final answer,也会被视为已有持久化收尾消息,从而复用正常完成路径的聊天清理逻辑,减少未折叠、未删除的旧消息。
10
+
11
+ ### English
12
+ - Fixed stale Telegram progress messages after manually interrupting a Codex thread. Interrupted turn cleanup now also collapses existing progress updates and deletes archived tool/runtime detail messages according to `TELEGRAM_DELETE_TOOL_DETAILS_AFTER_FINAL`.
13
+ - Interrupted turns are now treated as having a persistent terminal message even when there is no final answer, allowing them to reuse the normal completion cleanup path.
14
+
15
+ ## 0.5.66 - 2026-06-29
16
+
17
+ ### 中文
18
+ - 优化 Codex 需要用户确认时的 Telegram 富文本展示:问题保持顶层编号,候选项改为缩进的二级编号列表,避免多问题确认卡片看起来像一整串扁平 `1/2/3`。
19
+ - RichMessage Markdown 渲染器现在支持嵌套有序/无序列表,类似安装选项、配置选择这类两层列表会渲染成真正的 Telegram 嵌套列表。
20
+
21
+ ### English
22
+ - 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.
23
+ - The RichMessage Markdown renderer now supports nested ordered and unordered lists, so setup and configuration prompts render as real Telegram nested lists.
24
+
5
25
  ## 0.5.65 - 2026-06-29
6
26
 
7
27
  ### 中文
@@ -3563,6 +3563,9 @@ export class BridgeSessionCore {
3563
3563
  }
3564
3564
  }
3565
3565
  hasObservedPersistentReply(active) {
3566
+ if (active.interruptRequested) {
3567
+ return true;
3568
+ }
3566
3569
  if ((active.finalText || '').trim()) {
3567
3570
  return true;
3568
3571
  }
@@ -11039,23 +11042,25 @@ function renderUserInputMessage(locale, record) {
11039
11042
  record.questions.forEach((question, index) => {
11040
11043
  lines.push('');
11041
11044
  const title = question.header || question.question || question.id;
11042
- lines.push(`${index + 1}. ${title}`);
11043
11045
  if (question.header && question.question) {
11044
- lines.push(question.question);
11046
+ lines.push(`${index + 1}. **${title}** ${question.question}`);
11047
+ }
11048
+ else {
11049
+ lines.push(`${index + 1}. **${title}**`);
11045
11050
  }
11046
11051
  if (question.isOther) {
11047
- lines.push(t(locale, 'user_input_other_hint'));
11052
+ lines.push(` - ${t(locale, 'user_input_other_hint')}`);
11048
11053
  }
11049
11054
  if (question.isSecret) {
11050
- lines.push(t(locale, 'user_input_secret_warning'));
11055
+ lines.push(` - ${t(locale, 'user_input_secret_warning')}`);
11051
11056
  }
11052
11057
  question.options.forEach((option, optionIndex) => {
11053
11058
  const description = option.description ? ` - ${option.description}` : '';
11054
- lines.push(`${optionIndex + 1}) ${option.label}${description}`);
11059
+ lines.push(` ${optionIndex + 1}. ${option.label}${description}`);
11055
11060
  });
11056
11061
  const answer = record.answers.get(question.id);
11057
11062
  if (answer) {
11058
- lines.push(t(locale, 'user_input_selected', { value: answer }));
11063
+ lines.push(` - ${t(locale, 'user_input_selected', { value: answer })}`);
11059
11064
  }
11060
11065
  });
11061
11066
  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.67",
4
4
  "description": "Foxden local execution claw for controlling Codex from trusted chat interfaces.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",