@foxden-app/foxclaw 0.5.22 → 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 +20 -0
- package/dist/controller/controller.d.ts +4 -3
- package/dist/controller/controller.js +129 -56
- package/dist/i18n.d.ts +6 -0
- package/dist/i18n.js +6 -0
- package/dist/main.js +25 -0
- package/dist/types.d.ts +11 -0
- package/package.json +1 -1
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.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
|
+
|
|
15
|
+
## 0.5.23 - 2026-06-09
|
|
16
|
+
|
|
17
|
+
### 中文
|
|
18
|
+
- 补齐切换后验证路径:如果目标候选在验证 rate-limit usage 时返回 usage/rate/quota/billing/credits limit,FoxClaw 会恢复到原 auth,但不会把目标候选标记为 `?`,也不会触发自动剔除。
|
|
19
|
+
- 这让额度耗尽在自动轮换、手动切换和验证失败提示里都保持同一语义:它只是暂时没额度,不是凭据失效。
|
|
20
|
+
|
|
21
|
+
### English
|
|
22
|
+
- Completed the post-switch validation path: if the selected candidate returns a usage/rate/quota/billing/credits limit while validating rate-limit usage, FoxClaw restores the previous auth but does not mark the selected candidate `?` or auto-delete it.
|
|
23
|
+
- Quota exhaustion now has the same meaning across automatic rotation, manual switching, and validation failure messages: temporarily out of quota, not an invalid credential.
|
|
24
|
+
|
|
5
25
|
## 0.5.22 - 2026-06-09
|
|
6
26
|
|
|
7
27
|
### 中文
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
4427
|
-
|
|
4428
|
-
|
|
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: ${
|
|
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
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
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(
|
|
4441
|
-
|
|
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
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
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') {
|
|
@@ -5693,13 +5706,19 @@ export class BridgeSessionCore {
|
|
|
5693
5706
|
error: toErrorMeta(error),
|
|
5694
5707
|
});
|
|
5695
5708
|
}
|
|
5696
|
-
const
|
|
5709
|
+
const validationFailureKind = isCodexQuotaLimitError(validation.error)
|
|
5710
|
+
? 'quota_limited'
|
|
5711
|
+
: 'auth_invalid';
|
|
5712
|
+
const repairDisposition = validationFailureKind === 'auth_invalid'
|
|
5713
|
+
? await this.markCodexAuthCandidateNeedsRepair(candidate.name)
|
|
5714
|
+
: { deleted: false, restarted: false };
|
|
5697
5715
|
const outcome = {
|
|
5698
5716
|
...result,
|
|
5699
5717
|
ok: false,
|
|
5700
5718
|
candidateName: candidate.name,
|
|
5701
5719
|
recovered,
|
|
5702
5720
|
error: validation.error,
|
|
5721
|
+
validationFailureKind,
|
|
5703
5722
|
restoredPrevious,
|
|
5704
5723
|
autoDeleted: repairDisposition.deleted,
|
|
5705
5724
|
deleteRestarted: repairDisposition.restarted,
|
|
@@ -5718,6 +5737,7 @@ export class BridgeSessionCore {
|
|
|
5718
5737
|
candidateName: candidate.name,
|
|
5719
5738
|
recovered,
|
|
5720
5739
|
error: null,
|
|
5740
|
+
validationFailureKind: null,
|
|
5721
5741
|
restoredPrevious: false,
|
|
5722
5742
|
autoDeleted: false,
|
|
5723
5743
|
deleteRestarted: false,
|
|
@@ -5778,11 +5798,13 @@ export class BridgeSessionCore {
|
|
|
5778
5798
|
if (outcome.ok) {
|
|
5779
5799
|
return [];
|
|
5780
5800
|
}
|
|
5781
|
-
const validationKey = outcome.
|
|
5782
|
-
? '
|
|
5783
|
-
: outcome.autoDeleted
|
|
5784
|
-
? '
|
|
5785
|
-
:
|
|
5801
|
+
const validationKey = outcome.validationFailureKind === 'quota_limited'
|
|
5802
|
+
? 'auth_switch_validation_quota_limited'
|
|
5803
|
+
: outcome.autoDeleted && this.config.authAutoDeleteNeedsRepair
|
|
5804
|
+
? 'auth_switch_validation_auto_deleted_quiet'
|
|
5805
|
+
: outcome.autoDeleted
|
|
5806
|
+
? 'auth_switch_validation_auto_deleted'
|
|
5807
|
+
: 'auth_switch_validation_failed';
|
|
5786
5808
|
return [
|
|
5787
5809
|
t(locale, validationKey, {
|
|
5788
5810
|
value: outcome.candidateName,
|
|
@@ -9171,7 +9193,7 @@ function formatAuthRefreshAllResult(locale, result, mode = 'manual') {
|
|
|
9171
9193
|
}
|
|
9172
9194
|
return lines.join('\n');
|
|
9173
9195
|
}
|
|
9174
|
-
function formatAuthSyncStatus(locale, status) {
|
|
9196
|
+
function formatAuthSyncStatus(locale, status, proactiveRefresh = null) {
|
|
9175
9197
|
if (!status?.enabled) {
|
|
9176
9198
|
return t(locale, 'auth_sync_disabled');
|
|
9177
9199
|
}
|
|
@@ -9200,6 +9222,11 @@ function formatAuthSyncStatus(locale, status) {
|
|
|
9200
9222
|
if (status.lastError) {
|
|
9201
9223
|
lines.push(t(locale, 'auth_sync_status_error', { value: status.lastError }));
|
|
9202
9224
|
}
|
|
9225
|
+
if (proactiveRefresh) {
|
|
9226
|
+
lines.push(t(locale, 'auth_sync_status_proactive_refresh', {
|
|
9227
|
+
value: formatAuthProactiveRefreshStatus(locale, proactiveRefresh),
|
|
9228
|
+
}));
|
|
9229
|
+
}
|
|
9203
9230
|
if (status.peerStatuses?.length) {
|
|
9204
9231
|
lines.push(t(locale, 'auth_sync_status_peer_activity'));
|
|
9205
9232
|
for (const peer of status.peerStatuses.slice(0, 5)) {
|
|
@@ -9230,6 +9257,52 @@ function formatAuthSyncStatus(locale, status) {
|
|
|
9230
9257
|
}
|
|
9231
9258
|
return lines.join('\n');
|
|
9232
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
|
+
}
|
|
9233
9306
|
function formatAuthSyncEvents(locale, status, filter) {
|
|
9234
9307
|
if (!status?.enabled) {
|
|
9235
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}";
|
|
@@ -199,6 +200,7 @@ declare const MESSAGES: {
|
|
|
199
200
|
readonly auth_switch_validation_failed: "Selected auth failed validation: {error}. Marked {value} for login repair.";
|
|
200
201
|
readonly auth_switch_validation_auto_deleted: "Selected auth failed validation: {error}. Auto-deleted {value}.";
|
|
201
202
|
readonly auth_switch_validation_auto_deleted_quiet: "Selected auth failed validation and was auto-deleted.";
|
|
203
|
+
readonly auth_switch_validation_quota_limited: "Selected auth hit a Codex usage limit: {error}. It was not marked invalid.";
|
|
202
204
|
readonly auth_switch_validation_reverted: "Restored the previous auth after the failed switch.";
|
|
203
205
|
readonly auth_recovered_newer_candidate: "Recovered a newer same-account credential for {value} from another Codex home before reload.";
|
|
204
206
|
readonly auth_refresh_all_confirm_short: "Review refresh all risk first.";
|
|
@@ -254,6 +256,7 @@ declare const MESSAGES: {
|
|
|
254
256
|
readonly auth_sync_status_pull: "Last pull: {value}";
|
|
255
257
|
readonly auth_sync_status_lease: "Active refresh lease: {value}";
|
|
256
258
|
readonly auth_sync_status_error: "Last error: {value}";
|
|
259
|
+
readonly auth_sync_status_proactive_refresh: "Recent proactive refresh: {value}";
|
|
257
260
|
readonly auth_sync_status_candidate_failures: "Candidate failures:";
|
|
258
261
|
readonly auth_sync_status_candidate_failure: "- {candidate}: {reason} (source {source}, peer {peer}, at {time})";
|
|
259
262
|
readonly auth_sync_status_peer_activity: "Peer activity:";
|
|
@@ -822,6 +825,7 @@ declare const MESSAGES: {
|
|
|
822
825
|
readonly status_auth_sync: "跨节点 auth 同步:节点 {node},联系人 {contact},peer {peers},待导入 {pending}";
|
|
823
826
|
readonly status_auth_sync_error: "跨节点 auth 同步错误:{value}";
|
|
824
827
|
readonly status_auth_sync_candidate_failures: "auth 同步候选失败:{value}";
|
|
828
|
+
readonly status_auth_proactive_refresh: "最近主动 auth 刷新:{value}";
|
|
825
829
|
readonly status_last_update_none: "最近服务升级:暂无记录";
|
|
826
830
|
readonly status_last_update: "最近服务升级:{from} -> {to}({time})";
|
|
827
831
|
readonly status_last_codex_update: "最近 Codex 升级:{value}";
|
|
@@ -892,6 +896,7 @@ declare const MESSAGES: {
|
|
|
892
896
|
readonly auth_switch_validation_failed: "选中的 auth 验证失败:{error}。已将 {value} 标记为需要登录修复。";
|
|
893
897
|
readonly auth_switch_validation_auto_deleted: "选中的 auth 验证失败:{error}。已自动剔除 {value}。";
|
|
894
898
|
readonly auth_switch_validation_auto_deleted_quiet: "选中的 auth 验证失败,已自动剔除。";
|
|
899
|
+
readonly auth_switch_validation_quota_limited: "选中的 auth 已触发 Codex 额度限制:{error}。没有把它标记为失效。";
|
|
895
900
|
readonly auth_switch_validation_reverted: "已在切换失败后恢复到之前的 auth。";
|
|
896
901
|
readonly auth_recovered_newer_candidate: "重载前已从其他 Codex home 恢复 {value} 的同账号较新凭据。";
|
|
897
902
|
readonly auth_refresh_all_confirm_short: "请先确认刷新全部风险。";
|
|
@@ -947,6 +952,7 @@ declare const MESSAGES: {
|
|
|
947
952
|
readonly auth_sync_status_pull: "最近拉取:{value}";
|
|
948
953
|
readonly auth_sync_status_lease: "当前刷新锁:{value}";
|
|
949
954
|
readonly auth_sync_status_error: "最近错误:{value}";
|
|
955
|
+
readonly auth_sync_status_proactive_refresh: "最近主动刷新:{value}";
|
|
950
956
|
readonly auth_sync_status_candidate_failures: "候选失败:";
|
|
951
957
|
readonly auth_sync_status_candidate_failure: "- {candidate}:{reason}(来源 {source},peer {peer},时间 {time})";
|
|
952
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}',
|
|
@@ -197,6 +198,7 @@ const MESSAGES = {
|
|
|
197
198
|
auth_switch_validation_failed: 'Selected auth failed validation: {error}. Marked {value} for login repair.',
|
|
198
199
|
auth_switch_validation_auto_deleted: 'Selected auth failed validation: {error}. Auto-deleted {value}.',
|
|
199
200
|
auth_switch_validation_auto_deleted_quiet: 'Selected auth failed validation and was auto-deleted.',
|
|
201
|
+
auth_switch_validation_quota_limited: 'Selected auth hit a Codex usage limit: {error}. It was not marked invalid.',
|
|
200
202
|
auth_switch_validation_reverted: 'Restored the previous auth after the failed switch.',
|
|
201
203
|
auth_recovered_newer_candidate: 'Recovered a newer same-account credential for {value} from another Codex home before reload.',
|
|
202
204
|
auth_refresh_all_confirm_short: 'Review refresh all risk first.',
|
|
@@ -252,6 +254,7 @@ const MESSAGES = {
|
|
|
252
254
|
auth_sync_status_pull: 'Last pull: {value}',
|
|
253
255
|
auth_sync_status_lease: 'Active refresh lease: {value}',
|
|
254
256
|
auth_sync_status_error: 'Last error: {value}',
|
|
257
|
+
auth_sync_status_proactive_refresh: 'Recent proactive refresh: {value}',
|
|
255
258
|
auth_sync_status_candidate_failures: 'Candidate failures:',
|
|
256
259
|
auth_sync_status_candidate_failure: '- {candidate}: {reason} (source {source}, peer {peer}, at {time})',
|
|
257
260
|
auth_sync_status_peer_activity: 'Peer activity:',
|
|
@@ -820,6 +823,7 @@ const MESSAGES = {
|
|
|
820
823
|
status_auth_sync: '跨节点 auth 同步:节点 {node},联系人 {contact},peer {peers},待导入 {pending}',
|
|
821
824
|
status_auth_sync_error: '跨节点 auth 同步错误:{value}',
|
|
822
825
|
status_auth_sync_candidate_failures: 'auth 同步候选失败:{value}',
|
|
826
|
+
status_auth_proactive_refresh: '最近主动 auth 刷新:{value}',
|
|
823
827
|
status_last_update_none: '最近服务升级:暂无记录',
|
|
824
828
|
status_last_update: '最近服务升级:{from} -> {to}({time})',
|
|
825
829
|
status_last_codex_update: '最近 Codex 升级:{value}',
|
|
@@ -890,6 +894,7 @@ const MESSAGES = {
|
|
|
890
894
|
auth_switch_validation_failed: '选中的 auth 验证失败:{error}。已将 {value} 标记为需要登录修复。',
|
|
891
895
|
auth_switch_validation_auto_deleted: '选中的 auth 验证失败:{error}。已自动剔除 {value}。',
|
|
892
896
|
auth_switch_validation_auto_deleted_quiet: '选中的 auth 验证失败,已自动剔除。',
|
|
897
|
+
auth_switch_validation_quota_limited: '选中的 auth 已触发 Codex 额度限制:{error}。没有把它标记为失效。',
|
|
893
898
|
auth_switch_validation_reverted: '已在切换失败后恢复到之前的 auth。',
|
|
894
899
|
auth_recovered_newer_candidate: '重载前已从其他 Codex home 恢复 {value} 的同账号较新凭据。',
|
|
895
900
|
auth_refresh_all_confirm_short: '请先确认刷新全部风险。',
|
|
@@ -945,6 +950,7 @@ const MESSAGES = {
|
|
|
945
950
|
auth_sync_status_pull: '最近拉取:{value}',
|
|
946
951
|
auth_sync_status_lease: '当前刷新锁:{value}',
|
|
947
952
|
auth_sync_status_error: '最近错误:{value}',
|
|
953
|
+
auth_sync_status_proactive_refresh: '最近主动刷新:{value}',
|
|
948
954
|
auth_sync_status_candidate_failures: '候选失败:',
|
|
949
955
|
auth_sync_status_candidate_failure: '- {candidate}:{reason}(来源 {source},peer {peer},时间 {time})',
|
|
950
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;
|