@foxden-app/foxclaw 0.5.29 → 0.5.31

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.31 - 2026-06-09
6
+
7
+ ### 中文
8
+ - 修复自动 auth 轮转:目标候选切换后如果在 account/usage 验证阶段失败,会把该候选加入本次失败集合并继续轮询后续候选,而不是恢复原 auth 后停止。
9
+ - 新增回归测试覆盖候选池中间账号验证失败时继续切到下一个可用账号并重试原请求。
10
+
11
+ ### English
12
+ - Fixed automatic auth rotation so a candidate that fails account/usage validation after switching is added to the current failure set and FoxClaw keeps polling later candidates instead of stopping after restoring the previous auth.
13
+ - Added regression coverage for continuing to the next usable account and retrying the original request when a middle candidate fails validation.
14
+
15
+ ## 0.5.30 - 2026-06-09
16
+
17
+ ### 中文
18
+ - 修正重启自动续接路径:自动恢复现在会先像用户手动发送“继续”一样 `resumeThread` 原 Codex thread,再在原 thread 上启动续接 turn。
19
+ - 如果原 thread 暂时无法恢复,FoxClaw 会保留状态卡并重试;不会静默切到 replacement thread,避免丢失原线程上下文。
20
+
21
+ ### English
22
+ - Fixed restart auto-resume to match the manual "continue" path: FoxClaw now resumes the original Codex thread first, then starts the continuation turn on that same thread.
23
+ - If the original thread cannot be resumed yet, FoxClaw keeps the status card and retries instead of silently switching to a replacement thread and losing context.
24
+
5
25
  ## 0.5.29 - 2026-06-09
6
26
 
7
27
  ### 中文
@@ -2565,9 +2565,7 @@ export class BridgeSessionCore {
2565
2565
  }
2566
2566
  this.logger.warn('codex.turn_thread_not_found', { scopeId, threadId: binding.threadId });
2567
2567
  const replacement = await this.createBinding(scopeId, binding.cwd ?? this.config.defaultCwd);
2568
- if (overrides.notifyMissingThread !== false) {
2569
- await this.sendMessage(scopeId, t(this.localeForChat(scopeId), 'current_thread_unavailable_continued', { threadId: replacement.threadId }));
2570
- }
2568
+ await this.sendMessage(scopeId, t(this.localeForChat(scopeId), 'current_thread_unavailable_continued', { threadId: replacement.threadId }));
2571
2569
  const nextSettings = this.store.getChatSettings(scopeId);
2572
2570
  const nextAccess = this.resolveEffectiveAccess(scopeId, nextSettings);
2573
2571
  const replacementCwd = replacement.cwd ?? this.config.defaultCwd;
@@ -5546,32 +5544,35 @@ export class BridgeSessionCore {
5546
5544
  await this.sendMessage(rotation.scopeId, formatCodexAuthPoolSummary(locale, this.store.getCodexAuthPoolStats()));
5547
5545
  }
5548
5546
  }
5549
- const selection = await this.selectNextCodexAuthCandidate(failedTargets);
5550
- if (!selection) {
5551
- await this.sendMessage(rotation.scopeId, t(locale, rotation.reasonKind === 'quota_limited' ? 'auth_quota_no_candidate' : 'auth_auto_no_candidate', {
5552
- error: formatShortStatusError(rotation.reason),
5553
- }));
5554
- return false;
5555
- }
5556
- const { candidate, fromLabel, toLabel } = selection;
5557
- const switchingKey = rotation.reasonKind === 'quota_limited'
5558
- ? (this.config.authAutoDeleteNeedsRepair ? 'auth_quota_switching_quiet' : 'auth_quota_switching')
5559
- : (this.config.authAutoDeleteNeedsRepair ? 'auth_auto_switching_quiet' : 'auth_auto_switching');
5560
- await this.sendMessage(rotation.scopeId, this.config.authAutoDeleteNeedsRepair
5561
- ? t(locale, switchingKey, { error: formatShortStatusError(rotation.reason) })
5562
- : t(locale, switchingKey, {
5563
- ...this.codexAuthSwitchParams(locale, fromLabel, toLabel),
5564
- error: formatShortStatusError(rotation.reason),
5565
- }));
5566
- const outcome = await this.switchCodexAuthAndRestart(rotation.scopeId, locale, candidate, true, true, rotation.reasonKind);
5567
- if (!outcome.ok) {
5547
+ while (true) {
5548
+ const selection = await this.selectNextCodexAuthCandidate(failedTargets);
5549
+ if (!selection) {
5550
+ await this.sendMessage(rotation.scopeId, t(locale, rotation.reasonKind === 'quota_limited' ? 'auth_quota_no_candidate' : 'auth_auto_no_candidate', {
5551
+ error: formatShortStatusError(rotation.reason),
5552
+ }));
5553
+ return false;
5554
+ }
5555
+ const { candidate, fromLabel, toLabel } = selection;
5556
+ const switchingKey = rotation.reasonKind === 'quota_limited'
5557
+ ? (this.config.authAutoDeleteNeedsRepair ? 'auth_quota_switching_quiet' : 'auth_quota_switching')
5558
+ : (this.config.authAutoDeleteNeedsRepair ? 'auth_auto_switching_quiet' : 'auth_auto_switching');
5559
+ await this.sendMessage(rotation.scopeId, this.config.authAutoDeleteNeedsRepair
5560
+ ? t(locale, switchingKey, { error: formatShortStatusError(rotation.reason) })
5561
+ : t(locale, switchingKey, {
5562
+ ...this.codexAuthSwitchParams(locale, fromLabel, toLabel),
5563
+ error: formatShortStatusError(rotation.reason),
5564
+ }));
5565
+ const outcome = await this.switchCodexAuthAndRestart(rotation.scopeId, locale, candidate, true, true, rotation.reasonKind);
5566
+ if (!outcome.ok) {
5567
+ failedTargets.add(candidate.path);
5568
+ continue;
5569
+ }
5570
+ if (rotation.retry) {
5571
+ await this.retryTurnAfterAuthRotation(rotation.scopeId, locale, rotation.retry);
5572
+ return true;
5573
+ }
5568
5574
  return false;
5569
5575
  }
5570
- if (rotation.retry) {
5571
- await this.retryTurnAfterAuthRotation(rotation.scopeId, locale, rotation.retry);
5572
- return true;
5573
- }
5574
- return false;
5575
5576
  }
5576
5577
  catch (error) {
5577
5578
  await this.handleAsyncError('codex.auth_rotation', error, rotation.scopeId);
@@ -7394,10 +7395,15 @@ export class BridgeSessionCore {
7394
7395
  }
7395
7396
  async resumeInterruptedTurnAfterRestart(preview, target) {
7396
7397
  const locale = this.localeForChat(preview.scopeId);
7397
- const binding = {
7398
- threadId: preview.threadId,
7399
- cwd: this.store.getBinding(preview.scopeId)?.cwd ?? this.config.defaultCwd,
7400
- };
7398
+ const storedBinding = this.store.getBinding(preview.scopeId);
7399
+ const binding = storedBinding?.threadId === preview.threadId
7400
+ ? storedBinding
7401
+ : {
7402
+ chatId: preview.scopeId,
7403
+ threadId: preview.threadId,
7404
+ cwd: storedBinding?.cwd ?? this.config.defaultCwd,
7405
+ updatedAt: Date.now(),
7406
+ };
7401
7407
  const input = [{
7402
7408
  type: 'text',
7403
7409
  text: t(locale, 'restart_auto_resume_prompt'),
@@ -7405,16 +7411,20 @@ export class BridgeSessionCore {
7405
7411
  }];
7406
7412
  try {
7407
7413
  await this.sendTyping(preview.scopeId);
7408
- const turnState = await this.startTurnWithRecovery(preview.scopeId, binding, input, {
7409
- notifyMissingThread: false,
7414
+ const readyBinding = await this.ensureThreadReady(preview.scopeId, binding, {
7415
+ recoverMissingThread: false,
7410
7416
  });
7417
+ const turnState = await this.startTurnWithRecovery(preview.scopeId, readyBinding, input, {
7418
+ recoverMissingThread: false,
7419
+ });
7420
+ const cwd = readyBinding.cwd ?? this.store.getBinding(preview.scopeId)?.cwd ?? this.config.defaultCwd;
7411
7421
  if (turnState.collaborationMode === 'plan') {
7412
7422
  this.store.setChatCollaborationMode(preview.scopeId, DEFAULT_COLLABORATION_MODE);
7413
7423
  }
7414
7424
  await this.registerActiveTurn(preview.scopeId, target.chatId, target.chatType, target.topicId, turnState.threadId, turnState.turnId, preview.messageId, {
7415
7425
  input,
7416
7426
  threadId: turnState.threadId,
7417
- cwd: binding.cwd,
7427
+ cwd,
7418
7428
  chatId: target.chatId,
7419
7429
  chatType: target.chatType,
7420
7430
  topicId: target.topicId,
package/dist/i18n.d.ts CHANGED
@@ -418,7 +418,7 @@ declare const MESSAGES: {
418
418
  readonly stale_preview_restarted: "The bridge restarted while turn {threadId} was still live. This status card is no longer live. Open the thread in Codex.app or send another message to continue.";
419
419
  readonly stale_preview_expired: "This live preview expired after the bridge stopped tracking the turn. If a final reply arrived, see below. Otherwise send the message again.";
420
420
  readonly stale_user_input_interrupted: "The bridge found a stale Codex input request for thread {threadId} with no recoverable Telegram question. It interrupted that stuck turn; send the message again to continue.";
421
- readonly restart_auto_resume_prompt: "FoxClaw restarted while the previous turn was running. Continue the interrupted work from where it left off. If the previous thread context is unavailable, inspect the current working directory, git status, service logs, and runtime state, then finish the user-facing summary.";
421
+ readonly restart_auto_resume_prompt: "FoxClaw restarted while the previous turn was running. Continue the interrupted work from where it left off.";
422
422
  readonly streaming_reply_below: "Streaming reply below...";
423
423
  readonly interrupt_requested_preview: "Interrupt requested...";
424
424
  readonly interrupt_requested_waiting: "Interrupt requested. Waiting for Codex to stop...";
@@ -1115,7 +1115,7 @@ declare const MESSAGES: {
1115
1115
  readonly stale_preview_restarted: "桥接在该线程 {threadId} 仍处于活动状态时重启了。这张状态卡已经不再实时更新。你可以去 Codex.app 打开线程,或重新发一条消息继续。";
1116
1116
  readonly stale_preview_expired: "之前的实时预览在桥接停止跟踪这轮回复后已经失效。如果下方已有最终回复,请以最终回复为准;否则请重新发送消息。";
1117
1117
  readonly stale_user_input_interrupted: "桥接发现线程 {threadId} 有一条已经无法恢复到 Telegram 的 Codex 输入请求,已中断这条卡住的回复。请重新发送消息继续。";
1118
- readonly restart_auto_resume_prompt: "FoxClaw 重启时上一轮回复仍在运行。请从中断处继续完成刚才的工作。如果上一线程上下文不可用,请检查当前工作目录、git 状态、服务日志和运行状态,然后完成面向用户的收尾总结。";
1118
+ readonly restart_auto_resume_prompt: "FoxClaw 重启时上一轮回复仍在运行。请从中断处继续完成刚才的工作。";
1119
1119
  readonly streaming_reply_below: "回复正在下方持续输出...";
1120
1120
  readonly interrupt_requested_preview: "已请求中断...";
1121
1121
  readonly interrupt_requested_waiting: "已请求中断,正在等待 Codex 停止...";
package/dist/i18n.js CHANGED
@@ -416,7 +416,7 @@ const MESSAGES = {
416
416
  stale_preview_restarted: 'The bridge restarted while turn {threadId} was still live. This status card is no longer live. Open the thread in Codex.app or send another message to continue.',
417
417
  stale_preview_expired: 'This live preview expired after the bridge stopped tracking the turn. If a final reply arrived, see below. Otherwise send the message again.',
418
418
  stale_user_input_interrupted: 'The bridge found a stale Codex input request for thread {threadId} with no recoverable Telegram question. It interrupted that stuck turn; send the message again to continue.',
419
- restart_auto_resume_prompt: 'FoxClaw restarted while the previous turn was running. Continue the interrupted work from where it left off. If the previous thread context is unavailable, inspect the current working directory, git status, service logs, and runtime state, then finish the user-facing summary.',
419
+ restart_auto_resume_prompt: 'FoxClaw restarted while the previous turn was running. Continue the interrupted work from where it left off.',
420
420
  streaming_reply_below: 'Streaming reply below...',
421
421
  interrupt_requested_preview: 'Interrupt requested...',
422
422
  interrupt_requested_waiting: 'Interrupt requested. Waiting for Codex to stop...',
@@ -1113,7 +1113,7 @@ const MESSAGES = {
1113
1113
  stale_preview_restarted: '桥接在该线程 {threadId} 仍处于活动状态时重启了。这张状态卡已经不再实时更新。你可以去 Codex.app 打开线程,或重新发一条消息继续。',
1114
1114
  stale_preview_expired: '之前的实时预览在桥接停止跟踪这轮回复后已经失效。如果下方已有最终回复,请以最终回复为准;否则请重新发送消息。',
1115
1115
  stale_user_input_interrupted: '桥接发现线程 {threadId} 有一条已经无法恢复到 Telegram 的 Codex 输入请求,已中断这条卡住的回复。请重新发送消息继续。',
1116
- restart_auto_resume_prompt: 'FoxClaw 重启时上一轮回复仍在运行。请从中断处继续完成刚才的工作。如果上一线程上下文不可用,请检查当前工作目录、git 状态、服务日志和运行状态,然后完成面向用户的收尾总结。',
1116
+ restart_auto_resume_prompt: 'FoxClaw 重启时上一轮回复仍在运行。请从中断处继续完成刚才的工作。',
1117
1117
  streaming_reply_below: '回复正在下方持续输出...',
1118
1118
  interrupt_requested_preview: '已请求中断...',
1119
1119
  interrupt_requested_waiting: '已请求中断,正在等待 Codex 停止...',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxden-app/foxclaw",
3
- "version": "0.5.29",
3
+ "version": "0.5.31",
4
4
  "description": "Foxden local execution claw for controlling Codex from trusted chat interfaces.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",