@foxden-app/foxclaw 0.5.44 → 0.5.45

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,18 @@
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.45 - 2026-06-18
6
+
7
+ ### 中文
8
+ - 将 `/update` 完成通知升级为 RichMessage 表格,分别展示 FoxClaw 版本与重启结果、Codex CLI 版本与升级状态、集群广播目标及真实发送 peer。
9
+ - 发布说明改为默认收起的 details,避免较长更新内容挤占聊天页面;RichMessage 不可用时仍保留清晰的 HTML 降级文本。
10
+ - 升级完成后会短暂等待 auth sync 广播事件,只展示本次升级对应的广播结果;未启用跨节点同步、无 peer 或广播仍在等待时会给出明确状态。
11
+
12
+ ### English
13
+ - Upgraded `/update` completion notifications to a RichMessage table with separate FoxClaw version/restart, Codex CLI version/state, and cluster broadcast target/peer results.
14
+ - Release notes now live in a collapsed details block to keep update notifications compact, with a clear HTML fallback when RichMessage is unavailable.
15
+ - The completion report briefly waits for the matching auth sync broadcast event and only shows results from the current update; disabled sync, no peers, and pending broadcasts are reported explicitly.
16
+
5
17
  ## 0.5.44 - 2026-06-18
6
18
 
7
19
  ### 中文
@@ -296,6 +296,7 @@ export declare class BridgeSessionCore {
296
296
  private clearSelfUpdateStatusPoll;
297
297
  private pollSelfUpdateStatus;
298
298
  private formatSelfUpdateResult;
299
+ private resolveSelfUpdateBroadcastSummary;
299
300
  private formatSelfUpdateReleaseNotes;
300
301
  private formatCodexUpdateResult;
301
302
  private scheduleProactiveAuthRefresh;
@@ -4585,21 +4585,84 @@ export class BridgeSessionCore {
4585
4585
  return;
4586
4586
  }
4587
4587
  this.coordinator?.selfUpdateCompleted?.(status);
4588
- await this.sendMessage(status.scopeId, this.formatSelfUpdateResult(status));
4588
+ const broadcast = await this.resolveSelfUpdateBroadcastSummary(status);
4589
+ const result = this.formatSelfUpdateResult(status, broadcast);
4590
+ await this.sendRichHtmlMessage(status.scopeId, result.html, result.fallbackHtml);
4589
4591
  await this.selfUpdater?.clearStatus();
4590
4592
  }
4591
- formatSelfUpdateResult(status) {
4593
+ formatSelfUpdateResult(status, broadcast) {
4592
4594
  const codexUpdateLine = this.formatCodexUpdateResult(status);
4593
4595
  const releaseNotes = this.formatSelfUpdateReleaseNotes(status);
4594
4596
  if (status.state === 'succeeded') {
4595
- const result = t(status.locale, 'update_succeeded', {
4597
+ const foxclawResult = t(status.locale, 'update_succeeded', {
4596
4598
  from: status.fromVersion,
4597
4599
  to: status.toVersion ?? t(status.locale, 'unknown'),
4598
4600
  });
4599
- return [result, releaseNotes, codexUpdateLine].filter(Boolean).join('\n');
4601
+ const broadcastLine = formatSelfUpdateBroadcastLine(status.locale, status.toVersion, broadcast);
4602
+ const rows = [
4603
+ ['FoxClaw', `${status.fromVersion} -> ${status.toVersion ?? t(status.locale, 'unknown')}`, status.locale === 'zh' ? '升级完成,服务已重启' : 'Updated; service restarted'],
4604
+ ['Codex CLI', formatSelfUpdateVersionTransition(status.codexFromVersion, status.codexToVersion, status.locale), formatCodexUpdateState(status)],
4605
+ [status.locale === 'zh' ? '集群广播' : 'Cluster broadcast', status.toVersion ?? t(status.locale, 'unknown'), broadcastLine],
4606
+ ];
4607
+ const notes = status.releaseNotes?.filter(note => note.trim()) ?? [];
4608
+ const notesHtml = notes.length > 0
4609
+ ? telegramDetails(status.locale === 'zh' ? `查看更新内容 · ${notes.length} 项` : `Release notes · ${notes.length} items`, `<ul>${notes.map(note => `<li>${escapeTelegramHtml(note)}</li>`).join('')}</ul>`)
4610
+ : '';
4611
+ const title = status.locale === 'zh' ? 'FoxClaw 升级完成' : 'FoxClaw update completed';
4612
+ const html = [
4613
+ `<h3>${escapeTelegramHtml(title)}</h3>`,
4614
+ renderTelegramTable(status.locale === 'zh' ? ['组件', '版本', '结果'] : ['Component', 'Version', 'Result'], rows),
4615
+ notesHtml,
4616
+ '<footer>FoxClaw · update</footer>',
4617
+ ].filter(Boolean).join('\n');
4618
+ const fallbackHtml = [
4619
+ telegramBold(title),
4620
+ escapeTelegramHtml(foxclawResult),
4621
+ escapeTelegramHtml(codexUpdateLine ?? (status.locale === 'zh' ? 'Codex CLI:未执行升级。' : 'Codex CLI: not updated.')),
4622
+ escapeTelegramHtml(broadcastLine),
4623
+ releaseNotes ? escapeTelegramHtml(releaseNotes) : '',
4624
+ ].filter(Boolean).join('\n');
4625
+ return { html, fallbackHtml };
4600
4626
  }
4601
4627
  const result = t(status.locale, 'update_failed', { error: status.error ?? t(status.locale, 'unknown') });
4602
- return codexUpdateLine ? `${result}\n${codexUpdateLine}` : result;
4628
+ const fallback = codexUpdateLine ? `${result}\n${codexUpdateLine}` : result;
4629
+ return {
4630
+ html: `<h3>${escapeTelegramHtml(status.locale === 'zh' ? 'FoxClaw 升级失败' : 'FoxClaw update failed')}</h3><p>${escapeTelegramHtml(fallback)}</p>`,
4631
+ fallbackHtml: escapeTelegramHtml(fallback),
4632
+ };
4633
+ }
4634
+ async resolveSelfUpdateBroadcastSummary(status) {
4635
+ if (status.state !== 'succeeded' || !status.toVersion || !this.coordinator?.getServiceStatus) {
4636
+ return { state: 'pending', sent: 0, peers: [] };
4637
+ }
4638
+ for (let attempt = 0; attempt < 5; attempt += 1) {
4639
+ const serviceStatus = await this.coordinator.getServiceStatus().catch(() => null);
4640
+ const authSync = serviceStatus?.authSync;
4641
+ if (!authSync?.enabled) {
4642
+ return { state: 'disabled', sent: 0, peers: [] };
4643
+ }
4644
+ const recentEvents = authSync.recentEvents ?? [];
4645
+ const updateCompletedAt = Date.parse(status.updatedAt);
4646
+ const broadcast = [...recentEvents].reverse().find(event => (event.kind === 'service.update.request'
4647
+ && event.stage === 'broadcast'
4648
+ && event.detail === `target=${status.toVersion}`
4649
+ && (!Number.isFinite(updateCompletedAt) || Date.parse(event.createdAt) >= updateCompletedAt)));
4650
+ if (broadcast) {
4651
+ const peers = recentEvents
4652
+ .filter(event => (event.kind === 'service.update.request'
4653
+ && event.stage === 'sent'
4654
+ && event.direction === 'out'
4655
+ && event.requestId === broadcast.requestId
4656
+ && Boolean(event.peer)))
4657
+ .map(event => event.peer)
4658
+ .filter((peer, index, all) => all.indexOf(peer) === index);
4659
+ return { state: 'sent', sent: peers.length, peers };
4660
+ }
4661
+ if (attempt < 4) {
4662
+ await new Promise(resolve => setTimeout(resolve, 500));
4663
+ }
4664
+ }
4665
+ return { state: 'pending', sent: 0, peers: [] };
4603
4666
  }
4604
4667
  formatSelfUpdateReleaseNotes(status) {
4605
4668
  const notes = status.releaseNotes?.filter(note => note.trim()) ?? [];
@@ -8896,6 +8959,45 @@ function formatRichInternalValue(value) {
8896
8959
  const escaped = escapeTelegramHtml(value);
8897
8960
  return escaped.replace(/`([^`]+)`/g, '<code>$1</code>');
8898
8961
  }
8962
+ function renderTelegramTable(headers, rows) {
8963
+ return [
8964
+ '<table bordered striped>',
8965
+ `<thead><tr>${headers.map(header => `<th>${escapeTelegramHtml(header)}</th>`).join('')}</tr></thead>`,
8966
+ `<tbody>${rows.map(row => `<tr>${row.map(cell => `<td>${escapeTelegramHtml(cell)}</td>`).join('')}</tr>`).join('')}</tbody>`,
8967
+ '</table>',
8968
+ ].join('');
8969
+ }
8970
+ function formatSelfUpdateVersionTransition(fromVersion, toVersion, locale) {
8971
+ if (!fromVersion && !toVersion) {
8972
+ return locale === 'zh' ? '未检测' : 'Not detected';
8973
+ }
8974
+ return `${fromVersion ?? '?'} -> ${toVersion ?? '?'}`;
8975
+ }
8976
+ function formatCodexUpdateState(status) {
8977
+ if (status.codexFromVersion && status.codexToVersion) {
8978
+ if (status.codexFromVersion === status.codexToVersion) {
8979
+ return status.locale === 'zh' ? '已是最新版本' : 'Already current';
8980
+ }
8981
+ return status.locale === 'zh' ? '升级完成' : 'Updated';
8982
+ }
8983
+ return status.codexUpdate ?? (status.locale === 'zh' ? '未执行升级' : 'Not updated');
8984
+ }
8985
+ function formatSelfUpdateBroadcastLine(locale, targetVersion, broadcast) {
8986
+ if (broadcast.state === 'disabled') {
8987
+ return locale === 'zh' ? '未启用跨节点同步' : 'Cross-node sync is disabled';
8988
+ }
8989
+ if (broadcast.state === 'pending') {
8990
+ return locale === 'zh'
8991
+ ? `目标 ${targetVersion ?? '?'},等待广播结果`
8992
+ : `Target ${targetVersion ?? '?'}; waiting for broadcast result`;
8993
+ }
8994
+ if (broadcast.peers.length === 0) {
8995
+ return locale === 'zh' ? '广播完成,无已配置 peer' : 'Broadcast completed; no configured peers';
8996
+ }
8997
+ return locale === 'zh'
8998
+ ? `已发送 ${broadcast.sent} 个 peer:${broadcast.peers.join('、')}`
8999
+ : `Sent to ${broadcast.sent} peers: ${broadcast.peers.join(', ')}`;
9000
+ }
8899
9001
  function clipRichMessageText(value, limit) {
8900
9002
  if (value.length <= limit) {
8901
9003
  return value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxden-app/foxclaw",
3
- "version": "0.5.44",
3
+ "version": "0.5.45",
4
4
  "description": "Foxden local execution claw for controlling Codex from trusted chat interfaces.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",