@foxden-app/foxclaw 0.5.61 → 0.5.63

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,28 @@
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.63 - 2026-06-28
6
+
7
+ ### 中文
8
+ - 修复 auth 切换失败分类:`failed to fetch codex rate limits` 中如果同时包含 `401 Unauthorized` / `token_invalidated`,现在按登录失效处理并标记候选为 `needs_repair`,不再误判为额度耗尽。
9
+ - 自动轮转遇到这类失效候选时会把它标为需登录修复,然后继续尝试下一个可用候选。
10
+
11
+ ### English
12
+ - Fixed auth switch failure classification. `failed to fetch codex rate limits` errors that also contain `401 Unauthorized` / `token_invalidated` are now treated as invalid auth and mark the candidate `needs_repair`, instead of being mistaken for quota exhaustion.
13
+ - Auto-rotation now marks these invalid candidates for login repair and continues trying the next usable candidate.
14
+
15
+ ## 0.5.62 - 2026-06-22
16
+
17
+ ### 中文
18
+ - 修复超长工具操作明细无法折叠或最终删除的问题:当 Telegram 拒绝编辑超长归档消息时,FoxClaw 会降级为短摘要,并仍登记原消息用于最终回复后的清理。
19
+ - 工具归档 details 现在会截短单条命令行,避免长 shell 命令触发 `MESSAGE_TOO_LONG` 后反复重试。
20
+ - 归档操作明细的 message id 会在登记时同步持久化,降低网络切换或重启窗口中丢失最终清理目标的概率。
21
+
22
+ ### English
23
+ - Fixed oversized tool activity details failing to collapse or delete after the final reply. If Telegram rejects an oversized archive edit, FoxClaw now falls back to a short summary while still tracking the message for final cleanup.
24
+ - Tool archive details now truncate individual command lines to avoid repeated `MESSAGE_TOO_LONG` retries for long shell commands.
25
+ - Archived tool-detail message ids are persisted as soon as they are registered, reducing cleanup loss during network changes or restarts.
26
+
5
27
  ## 0.5.61 - 2026-06-21
6
28
 
7
29
  ### 中文
@@ -464,6 +464,7 @@ export declare class BridgeSessionCore {
464
464
  private ensureStatusMessage;
465
465
  private rebaseStatusMessage;
466
466
  private archiveStatusMessage;
467
+ private recordArchivedMessageId;
467
468
  private noteToolCommandStart;
468
469
  private noteToolCommandEnd;
469
470
  private scheduleToolBatchArchive;
@@ -28,6 +28,8 @@ import { renderActiveTurnStatus } from './status.js';
28
28
  import { writeRuntimeStatus } from '../runtime.js';
29
29
  const AUTH_DELETE_REASON_NEEDS_REPAIR = 'needs_repair';
30
30
  const RESTART_PREVIEW_RECOVERY_RETRY_DELAYS_MS = [3000, 7000, 15_000, 30_000, 45_000, 60_000, 60_000, 60_000];
31
+ const TOOL_ARCHIVE_MAX_LINES = 12;
32
+ const TOOL_ARCHIVE_LINE_LIMIT = 240;
31
33
  class UserFacingError extends Error {
32
34
  }
33
35
  const OBSERVED_THREAD_POLL_MS = 1500;
@@ -6178,9 +6180,11 @@ export class BridgeSessionCore {
6178
6180
  error: toErrorMeta(error),
6179
6181
  });
6180
6182
  }
6181
- const validationFailureKind = isCodexQuotaLimitError(validation.error)
6182
- ? 'quota_limited'
6183
- : 'auth_invalid';
6183
+ const validationFailureKind = isCodexAuthInvalidError(validation.error)
6184
+ ? 'auth_invalid'
6185
+ : isCodexQuotaLimitError(validation.error)
6186
+ ? 'quota_limited'
6187
+ : 'auth_invalid';
6184
6188
  const repairDisposition = validationFailureKind === 'auth_invalid'
6185
6189
  ? await this.markCodexAuthCandidateNeedsRepair(candidate.name)
6186
6190
  : { deleted: false, restarted: false };
@@ -8175,11 +8179,23 @@ export class BridgeSessionCore {
8175
8179
  messageId = await this.sendMessage(active.scopeId, content.text);
8176
8180
  }
8177
8181
  if (messageId !== null) {
8178
- active.archivedMessageIds.push(messageId);
8182
+ this.recordArchivedMessageId(active, messageId);
8179
8183
  }
8180
8184
  }
8181
8185
  catch (error) {
8182
- this.logger.warn('telegram.preview_archive_send_failed', { error: String(error), turnId: active.turnId });
8186
+ if (isTelegramMessageTooLong(error)) {
8187
+ try {
8188
+ const messageId = await this.sendMessage(active.scopeId, firstLine(content.text));
8189
+ this.recordArchivedMessageId(active, messageId);
8190
+ return true;
8191
+ }
8192
+ catch (fallbackError) {
8193
+ this.logger.warn('telegram.preview_archive_fallback_send_failed', { error: String(fallbackError), turnId: active.turnId });
8194
+ }
8195
+ }
8196
+ else {
8197
+ this.logger.warn('telegram.preview_archive_send_failed', { error: String(error), turnId: active.turnId });
8198
+ }
8183
8199
  this.scheduleRenderRetry(active);
8184
8200
  return false;
8185
8201
  }
@@ -8192,7 +8208,7 @@ export class BridgeSessionCore {
8192
8208
  else {
8193
8209
  await this.editMessage(active.scopeId, active.previewMessageId, content.text, []);
8194
8210
  }
8195
- active.archivedMessageIds.push(active.previewMessageId);
8211
+ this.recordArchivedMessageId(active, active.previewMessageId);
8196
8212
  }
8197
8213
  catch (error) {
8198
8214
  if (isTelegramMessageGone(error)) {
@@ -8202,6 +8218,37 @@ export class BridgeSessionCore {
8202
8218
  this.store.removeActiveTurnPreview(active.turnId);
8203
8219
  return this.archiveStatusMessage(active, content);
8204
8220
  }
8221
+ if (isTelegramMessageTooLong(error)) {
8222
+ try {
8223
+ await this.editMessage(active.scopeId, active.previewMessageId, firstLine(content.text), []);
8224
+ this.recordArchivedMessageId(active, active.previewMessageId);
8225
+ active.previewActive = false;
8226
+ active.statusMessageText = null;
8227
+ active.statusNeedsRebase = false;
8228
+ this.store.removeActiveTurnPreview(active.turnId);
8229
+ return true;
8230
+ }
8231
+ catch (fallbackError) {
8232
+ if (isTelegramMessageGone(fallbackError)) {
8233
+ active.previewActive = false;
8234
+ active.statusMessageText = null;
8235
+ active.statusNeedsRebase = false;
8236
+ this.store.removeActiveTurnPreview(active.turnId);
8237
+ return this.archiveStatusMessage(active, { text: firstLine(content.text), html: null });
8238
+ }
8239
+ this.logger.warn('telegram.preview_archive_fallback_failed', {
8240
+ error: String(fallbackError),
8241
+ turnId: active.turnId,
8242
+ messageId: active.previewMessageId,
8243
+ });
8244
+ this.recordArchivedMessageId(active, active.previewMessageId);
8245
+ active.previewActive = false;
8246
+ active.statusMessageText = null;
8247
+ active.statusNeedsRebase = false;
8248
+ this.store.removeActiveTurnPreview(active.turnId);
8249
+ return true;
8250
+ }
8251
+ }
8205
8252
  this.logger.warn('telegram.preview_archive_failed', {
8206
8253
  error: String(error),
8207
8254
  turnId: active.turnId,
@@ -8216,6 +8263,21 @@ export class BridgeSessionCore {
8216
8263
  this.store.removeActiveTurnPreview(active.turnId);
8217
8264
  return true;
8218
8265
  }
8266
+ recordArchivedMessageId(active, messageId) {
8267
+ if (!active.archivedMessageIds.includes(messageId)) {
8268
+ active.archivedMessageIds.push(messageId);
8269
+ }
8270
+ if (active.previewActive && active.previewMessageId > 0) {
8271
+ this.store.saveActiveTurnPreview({
8272
+ turnId: active.turnId,
8273
+ scopeId: active.scopeId,
8274
+ threadId: active.threadId,
8275
+ messageId: active.previewMessageId,
8276
+ isObserved: active.isObserved,
8277
+ archivedMessageIds: active.archivedMessageIds,
8278
+ });
8279
+ }
8280
+ }
8219
8281
  noteToolCommandStart(active, event) {
8220
8282
  if (!active.toolBatch) {
8221
8283
  active.toolBatch = createToolBatchState();
@@ -8652,9 +8714,12 @@ function renderArchivedToolBatchStatus(locale, counts, actionLines) {
8652
8714
  return { text, html: null };
8653
8715
  }
8654
8716
  const heading = formatToolBatchHeading(locale, counts, false);
8717
+ const detailLines = actionLines
8718
+ .slice(0, TOOL_ARCHIVE_MAX_LINES)
8719
+ .map(line => truncateInline(line, TOOL_ARCHIVE_LINE_LIMIT));
8655
8720
  const html = [
8656
8721
  telegramBold(heading),
8657
- telegramExpandableBlockquote(actionLines.slice(0, 12).join('\n')),
8722
+ telegramExpandableBlockquote(detailLines.join('\n')),
8658
8723
  ].join('\n');
8659
8724
  return { text, html };
8660
8725
  }
@@ -8803,6 +8868,9 @@ function truncateInline(value, limit) {
8803
8868
  }
8804
8869
  return `${value.slice(0, Math.max(0, limit - 1))}…`;
8805
8870
  }
8871
+ function firstLine(value) {
8872
+ return value.split(/\r?\n/, 1)[0]?.trim() || value.trim();
8873
+ }
8806
8874
  function parseReviewTarget(args) {
8807
8875
  if (args.length === 0) {
8808
8876
  return { type: 'uncommittedChanges' };
@@ -10645,6 +10713,9 @@ function classifyCodexAuthRotationError(params) {
10645
10713
  const code = stringOrNull(params?.error?.codexErrorInfo) ?? stringOrNull(params?.error?.code);
10646
10714
  const message = stringOrNull(params?.error?.message) ?? '';
10647
10715
  const text = `${code ?? ''}\n${message}\n${collected}`;
10716
+ if (isCodexAuthInvalidError(text)) {
10717
+ return 'auth_invalid';
10718
+ }
10648
10719
  if (isCodexQuotaLimitError(text)) {
10649
10720
  return 'quota_limited';
10650
10721
  }
@@ -10655,6 +10726,15 @@ function classifyCodexAuthRotationError(params) {
10655
10726
  ? 'auth_invalid'
10656
10727
  : null;
10657
10728
  }
10729
+ function isCodexAuthInvalidError(text) {
10730
+ return /token[_\s-]?invalidated/i.test(text)
10731
+ || /authentication token has been invalidated/i.test(text)
10732
+ || /\b401\s+unauthorized\b/i.test(text)
10733
+ || /\bunauthorized\b/i.test(text)
10734
+ || /\bnot authenticated\b/i.test(text)
10735
+ || /\bsign in again\b/i.test(text)
10736
+ || /\blog in again\b/i.test(text);
10737
+ }
10658
10738
  function isCodexQuotaLimitError(text) {
10659
10739
  return /usageLimitExceeded/i.test(text)
10660
10740
  || /you['’]?ve hit your usage limit/i.test(text)
@@ -11406,6 +11486,12 @@ function isTelegramMessageGone(error) {
11406
11486
  || message.includes('message to edit not found')
11407
11487
  || message.includes('message not found');
11408
11488
  }
11489
+ function isTelegramMessageTooLong(error) {
11490
+ const message = formatUserError(error).toLowerCase();
11491
+ return message.includes('message_too_long')
11492
+ || message.includes('message is too long')
11493
+ || message.includes('message too long');
11494
+ }
11409
11495
  function isFileMissingError(error) {
11410
11496
  return error instanceof Error && /enoent|no such file or directory/i.test(error.message);
11411
11497
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxden-app/foxclaw",
3
- "version": "0.5.61",
3
+ "version": "0.5.63",
4
4
  "description": "Foxden local execution claw for controlling Codex from trusted chat interfaces.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",