@foxden-app/foxclaw 0.5.23 → 0.5.24

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,16 @@
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.24 - 2026-06-09
6
+
7
+ ### 中文
8
+ - 后台主动 auth 刷新不再向私聊推送开始、跳过、完成或失败消息;最近一次刷新状态会写入 runtime status,并可在 `/status` 和 `/auth sync status` 主动查看。
9
+ - 本机 auth mirror 和跨节点 auth sync 的刷新/导入/同步摘要也默认静默,不再推送 `auth 刷新/同步汇总` 或候选已同步提示,减少后台维护对注意力的打扰。
10
+
11
+ ### English
12
+ - Background proactive auth refresh no longer pushes private start, skipped, completed, or failed messages. The latest refresh state is stored in runtime status and can be checked with `/status` and `/auth sync status`.
13
+ - Same-node auth mirroring and cross-node auth sync refresh/import/sync summaries are quiet by default, so background maintenance no longer pushes auth refresh/sync summaries or per-candidate synced notices.
14
+
5
15
  ## 0.5.23 - 2026-06-09
6
16
 
7
17
  ### 中文
@@ -6,6 +6,7 @@ import type { TelegramGateway, TelegramTextEvent } from '../telegram/gateway.js'
6
6
  import { BridgeMessagingRouter } from '../channels/bridge_messaging_router.js';
7
7
  import type { CodexAppClient } from '../codex_app/client.js';
8
8
  import type { SelfUpdateRuntime, SelfUpdateStatus } from '../update.js';
9
+ type AuthProactiveRefreshStatus = NonNullable<RuntimeStatus['authProactiveRefresh']>;
9
10
  export interface CoreCoordinator {
10
11
  canSelfUpdate?: () => boolean;
11
12
  authCandidateUpdated?: (runtimeId: string, candidateName: string) => Promise<void>;
@@ -41,6 +42,7 @@ export interface CoreCoordinator {
41
42
  weixinRuntime?: RuntimeStatus['weixinRuntime'];
42
43
  authMirror?: RuntimeStatus['authMirror'];
43
44
  authSync?: RuntimeStatus['authSync'];
45
+ authProactiveRefresh?: AuthProactiveRefreshStatus | null;
44
46
  lastUpdate?: SelfUpdateStatus | null;
45
47
  }>;
46
48
  selfUpdateCompleted?: (status: SelfUpdateStatus) => void;
@@ -89,7 +91,7 @@ export declare class BridgeSessionCore {
89
91
  private selfUpdatePollTimer;
90
92
  private proactiveAuthRefreshTimer;
91
93
  private proactiveAuthRefreshInProgress;
92
- private proactiveAuthRefreshStatusMessage;
94
+ private proactiveAuthRefreshStatus;
93
95
  private attachedThreads;
94
96
  private botUsername;
95
97
  private lastError;
@@ -290,8 +292,7 @@ export declare class BridgeSessionCore {
290
292
  private scheduleProactiveAuthRefresh;
291
293
  private clearProactiveAuthRefreshTimer;
292
294
  private runProactiveAuthRefresh;
293
- private proactiveAuthRefreshLocale;
294
- private notifyProactiveAuthRefresh;
295
+ private recordProactiveAuthRefreshStatus;
295
296
  private handleAuthCommand;
296
297
  private handleAuthSyncCommand;
297
298
  private runAuthSafeSyncAll;
@@ -139,7 +139,7 @@ export class BridgeSessionCore {
139
139
  selfUpdatePollTimer = null;
140
140
  proactiveAuthRefreshTimer = null;
141
141
  proactiveAuthRefreshInProgress = false;
142
- proactiveAuthRefreshStatusMessage = null;
142
+ proactiveAuthRefreshStatus = null;
143
143
  attachedThreads = new Set();
144
144
  botUsername = null;
145
145
  lastError = null;
@@ -284,6 +284,7 @@ export class BridgeSessionCore {
284
284
  telegram: this.ownsTelegramRuntime,
285
285
  weixin: Boolean(this.config.wxEnabled && this.messaging.hasWeixinTransport),
286
286
  },
287
+ authProactiveRefresh: this.proactiveAuthRefreshStatus,
287
288
  };
288
289
  }
289
290
  async handleText(event) {
@@ -478,6 +479,11 @@ export class BridgeSessionCore {
478
479
  }));
479
480
  }
480
481
  }
482
+ if (serviceStatus.authProactiveRefresh) {
483
+ lines.push(t(locale, 'status_auth_proactive_refresh', {
484
+ value: formatAuthProactiveRefreshStatus(locale, serviceStatus.authProactiveRefresh),
485
+ }));
486
+ }
481
487
  if (serviceStatus.lastUpdate) {
482
488
  lines.push(t(locale, 'status_last_update', {
483
489
  from: serviceStatus.lastUpdate.fromVersion,
@@ -4423,65 +4429,72 @@ export class BridgeSessionCore {
4423
4429
  return;
4424
4430
  }
4425
4431
  this.proactiveAuthRefreshInProgress = true;
4426
- const locale = this.proactiveAuthRefreshLocale();
4427
- await this.notifyProactiveAuthRefresh(locale, t(locale, 'auth_proactive_refresh_starting', {
4428
- value: dueCandidates.map(candidate => candidate.name).join(', '),
4429
- }));
4432
+ const startedAt = new Date().toISOString();
4433
+ const dueCandidateNames = dueCandidates.map(candidate => candidate.name);
4434
+ this.recordProactiveAuthRefreshStatus({
4435
+ state: 'running',
4436
+ startedAt,
4437
+ finishedAt: null,
4438
+ candidates: dueCandidateNames,
4439
+ refreshed: 0,
4440
+ skipped: 0,
4441
+ failed: 0,
4442
+ error: null,
4443
+ details: [],
4444
+ });
4430
4445
  let lease;
4431
4446
  try {
4432
- lease = await this.coordinator?.acquireAuthRefreshLease?.(`proactive auth refresh: ${dueCandidates.map(candidate => candidate.name).join(', ')}`);
4447
+ lease = await this.coordinator?.acquireAuthRefreshLease?.(`proactive auth refresh: ${dueCandidateNames.join(', ')}`);
4433
4448
  if (lease && !lease.ok) {
4434
4449
  this.logger.warn('codex.auth_proactive_refresh_lease_failed', { reason: lease.reason });
4435
- await this.notifyProactiveAuthRefresh(locale, t(locale, 'auth_proactive_refresh_lease_failed', {
4436
- error: lease.reason ?? t(locale, 'unknown'),
4437
- }), true);
4450
+ this.recordProactiveAuthRefreshStatus({
4451
+ state: 'lease_failed',
4452
+ startedAt,
4453
+ finishedAt: new Date().toISOString(),
4454
+ candidates: dueCandidateNames,
4455
+ refreshed: 0,
4456
+ skipped: 0,
4457
+ failed: 0,
4458
+ error: lease.reason ?? 'unknown',
4459
+ details: [],
4460
+ });
4438
4461
  return;
4439
4462
  }
4440
- const result = await this.refreshCodexAuthCandidates(new Set(dueCandidates.map(candidate => candidate.name)));
4441
- await this.notifyProactiveAuthRefresh(locale, formatAuthRefreshAllResult(locale, result, 'proactive'), true);
4463
+ const result = await this.refreshCodexAuthCandidates(new Set(dueCandidateNames));
4464
+ this.recordProactiveAuthRefreshStatus({
4465
+ state: 'completed',
4466
+ startedAt,
4467
+ finishedAt: new Date().toISOString(),
4468
+ candidates: dueCandidateNames,
4469
+ refreshed: result.refreshed.length,
4470
+ skipped: result.skipped.length,
4471
+ failed: result.failed.length,
4472
+ error: result.failed.length > 0 ? `${result.failed.length} failed` : null,
4473
+ details: result.failed.slice(0, 5).map(failure => `${failure.name}: ${failure.error}`),
4474
+ });
4475
+ }
4476
+ catch (error) {
4477
+ this.recordProactiveAuthRefreshStatus({
4478
+ state: 'failed',
4479
+ startedAt,
4480
+ finishedAt: new Date().toISOString(),
4481
+ candidates: dueCandidateNames,
4482
+ refreshed: 0,
4483
+ skipped: 0,
4484
+ failed: dueCandidateNames.length,
4485
+ error: formatUserError(error),
4486
+ details: [],
4487
+ });
4488
+ throw error;
4442
4489
  }
4443
4490
  finally {
4444
4491
  await this.coordinator?.releaseAuthRefreshLease?.(lease?.leaseId ?? null);
4445
4492
  this.proactiveAuthRefreshInProgress = false;
4446
4493
  }
4447
4494
  }
4448
- proactiveAuthRefreshLocale() {
4449
- const identity = this.bot.identity;
4450
- if (!identity)
4451
- return 'en';
4452
- const privateScope = this.store.getTelegramPrivateScope(identity);
4453
- if (!privateScope)
4454
- return 'en';
4455
- return this.localeForChat(privateScope.scopeId);
4456
- }
4457
- async notifyProactiveAuthRefresh(locale, message, final = false) {
4458
- const identity = this.bot.identity;
4459
- if (!identity)
4460
- return;
4461
- const privateScope = this.store.getTelegramPrivateScope(identity);
4462
- if (!privateScope)
4463
- return;
4464
- const previous = this.proactiveAuthRefreshStatusMessage;
4465
- if (previous?.chatId === privateScope.chatId) {
4466
- try {
4467
- await this.bot.editMessage(privateScope.chatId, previous.messageId, message, []);
4468
- if (final) {
4469
- this.proactiveAuthRefreshStatusMessage = null;
4470
- }
4471
- return;
4472
- }
4473
- catch (error) {
4474
- this.logger.warn('codex.auth_proactive_refresh_notify_edit_failed', { error: toErrorMeta(error) });
4475
- }
4476
- }
4477
- await this.bot.sendMessage(privateScope.chatId, message).then((messageId) => {
4478
- this.proactiveAuthRefreshStatusMessage = final ? null : { chatId: privateScope.chatId, messageId };
4479
- }).catch((error) => {
4480
- if (final) {
4481
- this.proactiveAuthRefreshStatusMessage = null;
4482
- }
4483
- this.logger.warn('codex.auth_proactive_refresh_notify_failed', { error: toErrorMeta(error) });
4484
- });
4495
+ recordProactiveAuthRefreshStatus(status) {
4496
+ this.proactiveAuthRefreshStatus = status;
4497
+ this.updateStatus();
4485
4498
  }
4486
4499
  async handleAuthCommand(scopeId, locale, args) {
4487
4500
  const action = args[0]?.toLowerCase() ?? 'list';
@@ -4532,7 +4545,7 @@ export class BridgeSessionCore {
4532
4545
  async handleAuthSyncCommand(scopeId, locale, args) {
4533
4546
  const action = args[0]?.toLowerCase() ?? 'status';
4534
4547
  if (action === 'status') {
4535
- await this.sendMessage(scopeId, formatAuthSyncStatus(locale, this.coordinator?.getAuthSyncStatus?.() ?? null));
4548
+ await this.sendMessage(scopeId, formatAuthSyncStatus(locale, this.coordinator?.getAuthSyncStatus?.() ?? null, this.getRuntimeStatus().authProactiveRefresh ?? null));
4536
4549
  return;
4537
4550
  }
4538
4551
  if (action === 'events') {
@@ -9180,7 +9193,7 @@ function formatAuthRefreshAllResult(locale, result, mode = 'manual') {
9180
9193
  }
9181
9194
  return lines.join('\n');
9182
9195
  }
9183
- function formatAuthSyncStatus(locale, status) {
9196
+ function formatAuthSyncStatus(locale, status, proactiveRefresh = null) {
9184
9197
  if (!status?.enabled) {
9185
9198
  return t(locale, 'auth_sync_disabled');
9186
9199
  }
@@ -9209,6 +9222,11 @@ function formatAuthSyncStatus(locale, status) {
9209
9222
  if (status.lastError) {
9210
9223
  lines.push(t(locale, 'auth_sync_status_error', { value: status.lastError }));
9211
9224
  }
9225
+ if (proactiveRefresh) {
9226
+ lines.push(t(locale, 'auth_sync_status_proactive_refresh', {
9227
+ value: formatAuthProactiveRefreshStatus(locale, proactiveRefresh),
9228
+ }));
9229
+ }
9212
9230
  if (status.peerStatuses?.length) {
9213
9231
  lines.push(t(locale, 'auth_sync_status_peer_activity'));
9214
9232
  for (const peer of status.peerStatuses.slice(0, 5)) {
@@ -9239,6 +9257,52 @@ function formatAuthSyncStatus(locale, status) {
9239
9257
  }
9240
9258
  return lines.join('\n');
9241
9259
  }
9260
+ function formatAuthProactiveRefreshStatus(locale, status) {
9261
+ const time = status.finishedAt ?? status.startedAt;
9262
+ const candidates = formatLimitedList(status.candidates, 3);
9263
+ const state = formatAuthProactiveRefreshState(locale, status.state);
9264
+ const result = locale === 'zh'
9265
+ ? `已刷新 ${status.refreshed},已跳过 ${status.skipped},失败 ${status.failed}`
9266
+ : `refreshed ${status.refreshed}, skipped ${status.skipped}, failed ${status.failed}`;
9267
+ const parts = [
9268
+ `${state} @ ${time}`,
9269
+ locale === 'zh' ? `候选 ${candidates}` : `candidates ${candidates}`,
9270
+ result,
9271
+ ];
9272
+ if (status.error) {
9273
+ parts.push(locale === 'zh'
9274
+ ? `原因 ${formatShortStatusError(status.error)}`
9275
+ : `reason ${formatShortStatusError(status.error)}`);
9276
+ }
9277
+ if (status.details.length > 0) {
9278
+ parts.push(locale === 'zh'
9279
+ ? `明细 ${formatLimitedList(status.details.map(formatShortStatusError), 2)}`
9280
+ : `details ${formatLimitedList(status.details.map(formatShortStatusError), 2)}`);
9281
+ }
9282
+ return parts.join('; ');
9283
+ }
9284
+ function formatAuthProactiveRefreshState(locale, state) {
9285
+ if (locale === 'zh') {
9286
+ switch (state) {
9287
+ case 'running': return '进行中';
9288
+ case 'completed': return '已完成';
9289
+ case 'lease_failed': return '锁未授予';
9290
+ case 'failed': return '失败';
9291
+ }
9292
+ }
9293
+ switch (state) {
9294
+ case 'running': return 'running';
9295
+ case 'completed': return 'completed';
9296
+ case 'lease_failed': return 'lease not granted';
9297
+ case 'failed': return 'failed';
9298
+ }
9299
+ }
9300
+ function formatLimitedList(values, limit) {
9301
+ if (values.length === 0)
9302
+ return '-';
9303
+ const shown = values.slice(0, limit).join(', ');
9304
+ return values.length > limit ? `${shown}, +${values.length - limit}` : shown;
9305
+ }
9242
9306
  function formatAuthSyncEvents(locale, status, filter) {
9243
9307
  if (!status?.enabled) {
9244
9308
  return t(locale, 'auth_sync_disabled');
package/dist/i18n.d.ts CHANGED
@@ -129,6 +129,7 @@ declare const MESSAGES: {
129
129
  readonly status_auth_sync: "Cross-node auth sync: node {node}, contact {contact}, peers {peers}, pending imports {pending}";
130
130
  readonly status_auth_sync_error: "Cross-node auth sync error: {value}";
131
131
  readonly status_auth_sync_candidate_failures: "Auth sync candidate failures: {value}";
132
+ readonly status_auth_proactive_refresh: "Recent proactive auth refresh: {value}";
132
133
  readonly status_last_update_none: "Last service update: none recorded";
133
134
  readonly status_last_update: "Last service update: {from} -> {to} at {time}";
134
135
  readonly status_last_codex_update: "Last Codex update: {value}";
@@ -255,6 +256,7 @@ declare const MESSAGES: {
255
256
  readonly auth_sync_status_pull: "Last pull: {value}";
256
257
  readonly auth_sync_status_lease: "Active refresh lease: {value}";
257
258
  readonly auth_sync_status_error: "Last error: {value}";
259
+ readonly auth_sync_status_proactive_refresh: "Recent proactive refresh: {value}";
258
260
  readonly auth_sync_status_candidate_failures: "Candidate failures:";
259
261
  readonly auth_sync_status_candidate_failure: "- {candidate}: {reason} (source {source}, peer {peer}, at {time})";
260
262
  readonly auth_sync_status_peer_activity: "Peer activity:";
@@ -823,6 +825,7 @@ declare const MESSAGES: {
823
825
  readonly status_auth_sync: "跨节点 auth 同步:节点 {node},联系人 {contact},peer {peers},待导入 {pending}";
824
826
  readonly status_auth_sync_error: "跨节点 auth 同步错误:{value}";
825
827
  readonly status_auth_sync_candidate_failures: "auth 同步候选失败:{value}";
828
+ readonly status_auth_proactive_refresh: "最近主动 auth 刷新:{value}";
826
829
  readonly status_last_update_none: "最近服务升级:暂无记录";
827
830
  readonly status_last_update: "最近服务升级:{from} -> {to}({time})";
828
831
  readonly status_last_codex_update: "最近 Codex 升级:{value}";
@@ -949,6 +952,7 @@ declare const MESSAGES: {
949
952
  readonly auth_sync_status_pull: "最近拉取:{value}";
950
953
  readonly auth_sync_status_lease: "当前刷新锁:{value}";
951
954
  readonly auth_sync_status_error: "最近错误:{value}";
955
+ readonly auth_sync_status_proactive_refresh: "最近主动刷新:{value}";
952
956
  readonly auth_sync_status_candidate_failures: "候选失败:";
953
957
  readonly auth_sync_status_candidate_failure: "- {candidate}:{reason}(来源 {source},peer {peer},时间 {time})";
954
958
  readonly auth_sync_status_peer_activity: "Peer 活动:";
package/dist/i18n.js CHANGED
@@ -127,6 +127,7 @@ const MESSAGES = {
127
127
  status_auth_sync: 'Cross-node auth sync: node {node}, contact {contact}, peers {peers}, pending imports {pending}',
128
128
  status_auth_sync_error: 'Cross-node auth sync error: {value}',
129
129
  status_auth_sync_candidate_failures: 'Auth sync candidate failures: {value}',
130
+ status_auth_proactive_refresh: 'Recent proactive auth refresh: {value}',
130
131
  status_last_update_none: 'Last service update: none recorded',
131
132
  status_last_update: 'Last service update: {from} -> {to} at {time}',
132
133
  status_last_codex_update: 'Last Codex update: {value}',
@@ -253,6 +254,7 @@ const MESSAGES = {
253
254
  auth_sync_status_pull: 'Last pull: {value}',
254
255
  auth_sync_status_lease: 'Active refresh lease: {value}',
255
256
  auth_sync_status_error: 'Last error: {value}',
257
+ auth_sync_status_proactive_refresh: 'Recent proactive refresh: {value}',
256
258
  auth_sync_status_candidate_failures: 'Candidate failures:',
257
259
  auth_sync_status_candidate_failure: '- {candidate}: {reason} (source {source}, peer {peer}, at {time})',
258
260
  auth_sync_status_peer_activity: 'Peer activity:',
@@ -821,6 +823,7 @@ const MESSAGES = {
821
823
  status_auth_sync: '跨节点 auth 同步:节点 {node},联系人 {contact},peer {peers},待导入 {pending}',
822
824
  status_auth_sync_error: '跨节点 auth 同步错误:{value}',
823
825
  status_auth_sync_candidate_failures: 'auth 同步候选失败:{value}',
826
+ status_auth_proactive_refresh: '最近主动 auth 刷新:{value}',
824
827
  status_last_update_none: '最近服务升级:暂无记录',
825
828
  status_last_update: '最近服务升级:{from} -> {to}({time})',
826
829
  status_last_codex_update: '最近 Codex 升级:{value}',
@@ -947,6 +950,7 @@ const MESSAGES = {
947
950
  auth_sync_status_pull: '最近拉取:{value}',
948
951
  auth_sync_status_lease: '当前刷新锁:{value}',
949
952
  auth_sync_status_error: '最近错误:{value}',
953
+ auth_sync_status_proactive_refresh: '最近主动刷新:{value}',
950
954
  auth_sync_status_candidate_failures: '候选失败:',
951
955
  auth_sync_status_candidate_failure: '- {candidate}:{reason}(来源 {source},peer {peer},时间 {time})',
952
956
  auth_sync_status_peer_activity: 'Peer 活动:',
package/dist/main.js CHANGED
@@ -255,6 +255,7 @@ async function runServeCli() {
255
255
  validate: async (context) => validateRefreshedAuthCandidate(runtime, context.candidateName),
256
256
  notify: createAuthMirrorNotifier(store, runtime.id, runtime.bot, authNotificationAggregator, {
257
257
  quietAuthPoolMode: () => config.authAutoDeleteNeedsRepair,
258
+ suppressBackgroundNotifications: () => true,
258
259
  }),
259
260
  })), logger, path.join(APP_HOME, 'runtime', 'auth-mirror.json'), {
260
261
  onSynced: async (event) => {
@@ -279,6 +280,7 @@ async function runServeCli() {
279
280
  const writeAggregateStatus = (running = true) => {
280
281
  const statuses = runtimes.map((runtime) => runtime.core.getRuntimeStatus());
281
282
  const weixinStatus = activeWeixinCore?.getRuntimeStatus() ?? null;
283
+ const authProactiveRefresh = newestAuthProactiveRefreshStatus([...statuses, ...(weixinStatus ? [weixinStatus] : [])]);
282
284
  const first = statuses[0] ?? null;
283
285
  writeRuntimeStatus(config.statusPath, {
284
286
  running,
@@ -316,6 +318,7 @@ async function runServeCli() {
316
318
  } : {}),
317
319
  authMirror: mirror.getStatus(),
318
320
  authSync: authSync?.getStatus() ?? null,
321
+ authProactiveRefresh,
319
322
  lastUpdate: lastSelfUpdate,
320
323
  });
321
324
  };
@@ -384,6 +387,10 @@ async function runServeCli() {
384
387
  } : {}),
385
388
  authMirror: mirror.getStatus(),
386
389
  authSync: authSync?.getStatus() ?? null,
390
+ authProactiveRefresh: newestAuthProactiveRefreshStatus([
391
+ ...runtimes.map(runtime => runtime.core.getRuntimeStatus()),
392
+ ...(activeWeixinCore ? [activeWeixinCore.getRuntimeStatus()] : []),
393
+ ]),
387
394
  lastUpdate: lastSelfUpdate,
388
395
  }),
389
396
  selfUpdateCompleted: (status) => {
@@ -444,6 +451,7 @@ async function runServeCli() {
444
451
  isIdle: authSyncLocalIdle,
445
452
  notify: createAuthSyncNotifier(store, authSyncTransportBot.bot, authNotificationAggregator, {
446
453
  quietAuthPoolMode: () => config.authAutoDeleteNeedsRepair,
454
+ suppressBackgroundNotifications: () => true,
447
455
  }),
448
456
  });
449
457
  await authSync.initialize();
@@ -604,6 +612,7 @@ async function runServeCli() {
604
612
  isIdle: singleAuthSyncLocalIdle,
605
613
  notify: createAuthSyncNotifier(store, bot, authNotificationAggregator, {
606
614
  quietAuthPoolMode: () => config.authAutoDeleteNeedsRepair,
615
+ suppressBackgroundNotifications: () => true,
607
616
  }),
608
617
  });
609
618
  await singleAuthSync.initialize();
@@ -676,6 +685,8 @@ async function runServeCli() {
676
685
  }
677
686
  function createAuthMirrorNotifier(store, botId, bot, aggregator, options = {}) {
678
687
  return async (event) => {
688
+ if (options.suppressBackgroundNotifications?.())
689
+ return;
679
690
  if (options.quietAuthPoolMode?.())
680
691
  return;
681
692
  const privateScope = store.getTelegramPrivateScope(botId);
@@ -711,6 +722,8 @@ function createAuthSyncNotifier(store, bot, aggregator, options = {}) {
711
722
  });
712
723
  };
713
724
  return async (event) => {
725
+ if (options.suppressBackgroundNotifications?.())
726
+ return;
714
727
  if (!bot.identity)
715
728
  return;
716
729
  const privateScope = store.getTelegramPrivateScope(bot.identity);
@@ -742,6 +755,18 @@ function formatAuthPoolSummary(locale, stats) {
742
755
  ? `auth 池:历史 ${stats.totalSeen},存活 ${stats.alive},因失效剔除 ${stats.deletedInvalid}。`
743
756
  : `Auth pool: total seen ${stats.totalSeen}, alive ${stats.alive}, invalid-deleted ${stats.deletedInvalid}.`;
744
757
  }
758
+ function newestAuthProactiveRefreshStatus(statuses) {
759
+ const entries = statuses
760
+ .map(status => status.authProactiveRefresh ?? null)
761
+ .filter((status) => status !== null);
762
+ if (entries.length === 0)
763
+ return null;
764
+ return entries.reduce((newest, status) => {
765
+ const newestTime = newest.finishedAt ?? newest.startedAt;
766
+ const statusTime = status.finishedAt ?? status.startedAt;
767
+ return Date.parse(statusTime) >= Date.parse(newestTime) ? status : newest;
768
+ });
769
+ }
745
770
  function isQuietAuthPoolNotification(event) {
746
771
  switch (event.kind) {
747
772
  case 'candidate_publish_started':
package/dist/types.d.ts CHANGED
@@ -452,6 +452,17 @@ export interface RuntimeStatus {
452
452
  detail: string | null;
453
453
  }>;
454
454
  } | null;
455
+ authProactiveRefresh?: {
456
+ state: 'running' | 'completed' | 'lease_failed' | 'failed';
457
+ startedAt: string;
458
+ finishedAt: string | null;
459
+ candidates: string[];
460
+ refreshed: number;
461
+ skipped: number;
462
+ failed: number;
463
+ error: string | null;
464
+ details: string[];
465
+ } | null;
455
466
  lastUpdate?: {
456
467
  state: string;
457
468
  fromVersion: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxden-app/foxclaw",
3
- "version": "0.5.23",
3
+ "version": "0.5.24",
4
4
  "description": "Foxden local execution claw for controlling Codex from trusted chat interfaces.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",