@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 +22 -0
- package/dist/controller/controller.d.ts +1 -0
- package/dist/controller/controller.js +93 -7
- package/package.json +1 -1
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 =
|
|
6182
|
-
? '
|
|
6183
|
-
:
|
|
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
|
-
|
|
8182
|
+
this.recordArchivedMessageId(active, messageId);
|
|
8179
8183
|
}
|
|
8180
8184
|
}
|
|
8181
8185
|
catch (error) {
|
|
8182
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
}
|