@foxden-app/foxclaw 0.4.12 → 0.4.14
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/.env.example +12 -2
- package/CHANGELOG.md +24 -0
- package/README.md +5 -3
- package/README_EN.md +5 -3
- package/dist/auth/cross_node_sync.d.ts +112 -0
- package/dist/auth/cross_node_sync.js +682 -0
- package/dist/auth/mirror.d.ts +37 -1
- package/dist/auth/mirror.js +136 -3
- package/dist/config.d.ts +10 -0
- package/dist/config.js +14 -0
- package/dist/controller/controller.d.ts +21 -0
- package/dist/controller/controller.js +200 -9
- package/dist/i18n.d.ts +42 -2
- package/dist/i18n.js +42 -2
- package/dist/main.js +192 -8
- package/dist/telegram/api.d.ts +6 -0
- package/dist/telegram/api.js +50 -0
- package/dist/telegram/gateway.d.ts +9 -0
- package/dist/telegram/gateway.js +33 -2
- package/dist/types.d.ts +16 -0
- package/dist/update.d.ts +2 -0
- package/dist/update.js +44 -6
- package/docs/user-manual.md +40 -4
- package/docs/zh/user-manual.md +40 -4
- package/package.json +1 -1
- package/skills/foxclaw/SKILL.md +4 -2
|
@@ -3,7 +3,7 @@ import fs from 'node:fs/promises';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { normalizeLocale, t } from '../i18n.js';
|
|
6
|
-
import { readChatGptAuthMetadata } from '../auth/mirror.js';
|
|
6
|
+
import { parseChatGptAuthMetadata, readChatGptAuthMetadata } from '../auth/mirror.js';
|
|
7
7
|
import { parseCommand } from './commands.js';
|
|
8
8
|
import { buildAccessSettingsKeyboard, buildModelSettingsKeyboard, buildSetupPanelKeyboard, buildThreadListKeyboard, buildThreadsKeyboard, clampEffortToModel, formatAccessPresetLabel, formatActiveTurnMessageModeLabel, formatAccessSettingsMessage, formatApprovalPolicyLabel, formatCollaborationModeLabel, formatModelSettingsMessage, formatSandboxModeLabel, formatServiceTierStatusLabel, formatSetupPanelMessage, formatThreadContextSummary, formatThreadsMessage, formatWeixinAccessCopyPaste, formatWeixinModelCopyPaste, formatWeixinThreadsCopyPaste, formatWeixinWhereNavCopyPaste, formatWhereMessage, normalizeRequestedEffort, resolveCurrentModel, resolveActiveTurnMessageMode, resolveRequestedModel, } from './presentation.js';
|
|
9
9
|
import { clampServiceTierToModel, resolveFastTierForModel } from './service_tier.js';
|
|
@@ -427,14 +427,25 @@ export class BridgeSessionCore {
|
|
|
427
427
|
time: serviceStatus.authMirror.syncedAt,
|
|
428
428
|
})
|
|
429
429
|
: t(locale, 'status_auth_mirror_none'));
|
|
430
|
+
if (serviceStatus.authSync?.enabled) {
|
|
431
|
+
lines.push(t(locale, 'status_auth_sync', {
|
|
432
|
+
node: serviceStatus.authSync.nodeId ?? t(locale, 'unknown'),
|
|
433
|
+
peers: serviceStatus.authSync.peers.length,
|
|
434
|
+
pending: serviceStatus.authSync.pendingImports,
|
|
435
|
+
}));
|
|
436
|
+
if (serviceStatus.authSync.lastError) {
|
|
437
|
+
lines.push(t(locale, 'status_auth_sync_error', { value: serviceStatus.authSync.lastError }));
|
|
438
|
+
}
|
|
439
|
+
}
|
|
430
440
|
if (serviceStatus.lastUpdate) {
|
|
431
441
|
lines.push(t(locale, 'status_last_update', {
|
|
432
442
|
from: serviceStatus.lastUpdate.fromVersion,
|
|
433
443
|
to: serviceStatus.lastUpdate.toVersion ?? t(locale, 'unknown'),
|
|
434
444
|
time: serviceStatus.lastUpdate.updatedAt,
|
|
435
445
|
}));
|
|
436
|
-
|
|
437
|
-
|
|
446
|
+
const codexUpdateLine = this.formatCodexUpdateResult(serviceStatus.lastUpdate);
|
|
447
|
+
if (codexUpdateLine) {
|
|
448
|
+
lines.push(t(locale, 'status_last_codex_update', { value: codexUpdateLine }));
|
|
438
449
|
}
|
|
439
450
|
}
|
|
440
451
|
else {
|
|
@@ -2618,6 +2629,55 @@ export class BridgeSessionCore {
|
|
|
2618
2629
|
async getCurrentAuthLabel() {
|
|
2619
2630
|
return (await this.listCodexAuthState()).currentLabel;
|
|
2620
2631
|
}
|
|
2632
|
+
async validateExternalCodexAuthCandidate(candidateName, rawAuth, expectedAccountId) {
|
|
2633
|
+
if (!this.isIdleForServiceUpdate()) {
|
|
2634
|
+
return { ok: false, reason: 'runtime is not idle' };
|
|
2635
|
+
}
|
|
2636
|
+
const metadata = parseChatGptAuthMetadata(rawAuth);
|
|
2637
|
+
if (!metadata || metadata.accountId !== expectedAccountId) {
|
|
2638
|
+
return { ok: false, reason: 'remote auth account id mismatch' };
|
|
2639
|
+
}
|
|
2640
|
+
const state = await this.listCodexAuthState();
|
|
2641
|
+
const existing = state.candidates.find(candidate => candidate.name === candidateName) ?? null;
|
|
2642
|
+
if (existing) {
|
|
2643
|
+
const existingMetadata = await readChatGptAuthMetadata(existing.path);
|
|
2644
|
+
if (existingMetadata && existingMetadata.accountId !== expectedAccountId) {
|
|
2645
|
+
return { ok: false, reason: 'same candidate belongs to a different account' };
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
const authStat = await fs.lstat(state.authPath).catch(() => null);
|
|
2649
|
+
const originalRegularAuth = authStat?.isFile()
|
|
2650
|
+
? await fs.readFile(state.authPath, 'utf8').catch(() => null)
|
|
2651
|
+
: null;
|
|
2652
|
+
const tempPath = path.join(state.authDir, `.auth-sync-validate-${process.pid}-${Date.now()}.json`);
|
|
2653
|
+
try {
|
|
2654
|
+
await fs.writeFile(tempPath, rawAuth, { encoding: 'utf8', mode: 0o600 });
|
|
2655
|
+
await pointCodexAuthAtTarget(state.authDir, state.authPath, tempPath);
|
|
2656
|
+
this.pendingTurnErrors.clear();
|
|
2657
|
+
this.attachedThreads.clear();
|
|
2658
|
+
await this.app.restart();
|
|
2659
|
+
const account = await this.app.readAccount(false);
|
|
2660
|
+
const rateLimits = await this.app.readAccountRateLimits();
|
|
2661
|
+
if (!account || !rateLimits || !selectCodexRateLimitSnapshot(rateLimits)) {
|
|
2662
|
+
return { ok: false, reason: 'Codex did not validate ChatGPT usage for remote auth' };
|
|
2663
|
+
}
|
|
2664
|
+
return { ok: true };
|
|
2665
|
+
}
|
|
2666
|
+
catch (error) {
|
|
2667
|
+
return { ok: false, reason: formatUserError(error) };
|
|
2668
|
+
}
|
|
2669
|
+
finally {
|
|
2670
|
+
await restoreCodexAuthTarget(state.authDir, state.authPath, state.currentTargetPath, originalRegularAuth).catch((error) => {
|
|
2671
|
+
this.logger.warn('codex.auth_sync_restore_failed', { error: toErrorMeta(error) });
|
|
2672
|
+
});
|
|
2673
|
+
await fs.rm(tempPath, { force: true }).catch(() => undefined);
|
|
2674
|
+
this.pendingTurnErrors.clear();
|
|
2675
|
+
this.attachedThreads.clear();
|
|
2676
|
+
await this.app.restart().catch((error) => {
|
|
2677
|
+
this.logger.warn('codex.auth_sync_restart_restore_failed', { error: toErrorMeta(error) });
|
|
2678
|
+
});
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2621
2681
|
isIdleForServiceUpdate() {
|
|
2622
2682
|
return this.activeTurns.size === 0
|
|
2623
2683
|
&& this.pendingApprovalMessages.size === 0
|
|
@@ -3871,18 +3931,32 @@ export class BridgeSessionCore {
|
|
|
3871
3931
|
await this.selfUpdater?.clearStatus();
|
|
3872
3932
|
}
|
|
3873
3933
|
formatSelfUpdateResult(status) {
|
|
3934
|
+
const codexUpdateLine = this.formatCodexUpdateResult(status);
|
|
3874
3935
|
if (status.state === 'succeeded') {
|
|
3875
3936
|
const result = t(status.locale, 'update_succeeded', {
|
|
3876
3937
|
from: status.fromVersion,
|
|
3877
3938
|
to: status.toVersion ?? t(status.locale, 'unknown'),
|
|
3878
3939
|
});
|
|
3879
|
-
return
|
|
3940
|
+
return codexUpdateLine ? `${result}\n${codexUpdateLine}` : result;
|
|
3880
3941
|
}
|
|
3881
3942
|
const result = t(status.locale, 'update_failed', { error: status.error ?? t(status.locale, 'unknown') });
|
|
3882
|
-
return
|
|
3943
|
+
return codexUpdateLine ? `${result}\n${codexUpdateLine}` : result;
|
|
3944
|
+
}
|
|
3945
|
+
formatCodexUpdateResult(status) {
|
|
3946
|
+
if (status.codexFromVersion || status.codexToVersion) {
|
|
3947
|
+
return t(status.locale, 'update_codex_result', {
|
|
3948
|
+
from: status.codexFromVersion ?? t(status.locale, 'unknown'),
|
|
3949
|
+
to: status.codexToVersion ?? t(status.locale, 'unknown'),
|
|
3950
|
+
});
|
|
3951
|
+
}
|
|
3952
|
+
return status.codexUpdate ?? null;
|
|
3883
3953
|
}
|
|
3884
3954
|
async handleAuthCommand(scopeId, locale, args) {
|
|
3885
3955
|
const action = args[0]?.toLowerCase() ?? 'list';
|
|
3956
|
+
if (action === 'sync') {
|
|
3957
|
+
await this.handleAuthSyncCommand(scopeId, locale, args.slice(1));
|
|
3958
|
+
return;
|
|
3959
|
+
}
|
|
3886
3960
|
if (action === 'reload' || action === 'restart') {
|
|
3887
3961
|
await this.handleAuthReloadCommand(scopeId, locale);
|
|
3888
3962
|
return;
|
|
@@ -3923,6 +3997,39 @@ export class BridgeSessionCore {
|
|
|
3923
3997
|
const messageId = await this.sendMessage(scopeId, renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(scopeId) !== null, record), authChoiceKeyboard(locale, record));
|
|
3924
3998
|
record.messageId = messageId;
|
|
3925
3999
|
}
|
|
4000
|
+
async handleAuthSyncCommand(scopeId, locale, args) {
|
|
4001
|
+
const action = args[0]?.toLowerCase() ?? 'status';
|
|
4002
|
+
if (action === 'status') {
|
|
4003
|
+
await this.sendMessage(scopeId, formatAuthSyncStatus(locale, this.coordinator?.getAuthSyncStatus?.() ?? null));
|
|
4004
|
+
return;
|
|
4005
|
+
}
|
|
4006
|
+
if (action === 'test') {
|
|
4007
|
+
const result = await this.coordinator?.authSyncTest?.();
|
|
4008
|
+
if (!result) {
|
|
4009
|
+
await this.sendMessage(scopeId, t(locale, 'auth_sync_disabled'));
|
|
4010
|
+
return;
|
|
4011
|
+
}
|
|
4012
|
+
await this.sendMessage(scopeId, t(locale, 'auth_sync_test_sent', { count: result.sent }));
|
|
4013
|
+
return;
|
|
4014
|
+
}
|
|
4015
|
+
if (action === 'push' && args[1]?.toLowerCase() === 'all') {
|
|
4016
|
+
if (!this.canRunGlobalAuthRefresh()) {
|
|
4017
|
+
await this.sendMessage(scopeId, t(locale, 'auth_sync_push_blocked_active'));
|
|
4018
|
+
return;
|
|
4019
|
+
}
|
|
4020
|
+
const result = await this.coordinator?.authSyncPushAll?.();
|
|
4021
|
+
if (!result) {
|
|
4022
|
+
await this.sendMessage(scopeId, t(locale, 'auth_sync_disabled'));
|
|
4023
|
+
return;
|
|
4024
|
+
}
|
|
4025
|
+
await this.sendMessage(scopeId, t(locale, 'auth_sync_push_done', {
|
|
4026
|
+
sent: result.sent,
|
|
4027
|
+
skipped: result.skipped,
|
|
4028
|
+
}));
|
|
4029
|
+
return;
|
|
4030
|
+
}
|
|
4031
|
+
await this.sendMessage(scopeId, t(locale, 'usage_auth_sync'));
|
|
4032
|
+
}
|
|
3926
4033
|
async handleAuthRefreshAllCommand(scopeId, locale, confirmed = false) {
|
|
3927
4034
|
if (!this.canRunGlobalAuthRefresh()) {
|
|
3928
4035
|
await this.sendMessage(scopeId, t(locale, 'auth_refresh_all_blocked_active'));
|
|
@@ -3938,7 +4045,18 @@ export class BridgeSessionCore {
|
|
|
3938
4045
|
return;
|
|
3939
4046
|
}
|
|
3940
4047
|
await this.sendMessage(scopeId, t(locale, 'auth_refresh_all_starting'));
|
|
3941
|
-
const
|
|
4048
|
+
const lease = await this.coordinator?.acquireAuthRefreshLease?.('auth refresh all');
|
|
4049
|
+
if (lease && !lease.ok) {
|
|
4050
|
+
await this.sendMessage(scopeId, t(locale, 'auth_refresh_all_lease_failed', { error: lease.reason ?? t(locale, 'unknown') }));
|
|
4051
|
+
return;
|
|
4052
|
+
}
|
|
4053
|
+
let result;
|
|
4054
|
+
try {
|
|
4055
|
+
result = await this.refreshAllCodexAuthCandidates();
|
|
4056
|
+
}
|
|
4057
|
+
finally {
|
|
4058
|
+
await this.coordinator?.releaseAuthRefreshLease?.(lease?.leaseId ?? null);
|
|
4059
|
+
}
|
|
3942
4060
|
const state = await this.listCodexAuthState();
|
|
3943
4061
|
await this.applySharedCodexAuthQuotaSnapshots(state);
|
|
3944
4062
|
const record = createPendingAuthChoiceList(scopeId, state.candidates);
|
|
@@ -4449,7 +4567,20 @@ export class BridgeSessionCore {
|
|
|
4449
4567
|
if (record.messageId !== null) {
|
|
4450
4568
|
await this.editMessage(event.scopeId, record.messageId, t(locale, 'auth_refresh_all_starting'), []);
|
|
4451
4569
|
}
|
|
4452
|
-
const
|
|
4570
|
+
const lease = await this.coordinator?.acquireAuthRefreshLease?.('auth refresh all');
|
|
4571
|
+
if (lease && !lease.ok) {
|
|
4572
|
+
if (record.messageId !== null) {
|
|
4573
|
+
await this.editMessage(event.scopeId, record.messageId, t(locale, 'auth_refresh_all_lease_failed', { error: lease.reason ?? t(locale, 'unknown') }), authChoiceKeyboard(locale, record));
|
|
4574
|
+
}
|
|
4575
|
+
return;
|
|
4576
|
+
}
|
|
4577
|
+
let result;
|
|
4578
|
+
try {
|
|
4579
|
+
result = await this.refreshAllCodexAuthCandidates();
|
|
4580
|
+
}
|
|
4581
|
+
finally {
|
|
4582
|
+
await this.coordinator?.releaseAuthRefreshLease?.(lease?.leaseId ?? null);
|
|
4583
|
+
}
|
|
4453
4584
|
const state = await this.listCodexAuthState();
|
|
4454
4585
|
await this.applySharedCodexAuthQuotaSnapshots(state);
|
|
4455
4586
|
record.candidates = state.candidates;
|
|
@@ -4567,8 +4698,27 @@ export class BridgeSessionCore {
|
|
|
4567
4698
|
this.authRotationInProgress = true;
|
|
4568
4699
|
try {
|
|
4569
4700
|
const failedTargets = rotation.retry?.failedAuthTargets ?? this.authRotationFailedTargets;
|
|
4570
|
-
const selection = await this.selectNextCodexAuthCandidate(failedTargets);
|
|
4571
4701
|
const locale = this.localeForChat(rotation.scopeId);
|
|
4702
|
+
const current = (await this.listCodexAuthState()).candidates.find(candidate => candidate.isCurrent) ?? null;
|
|
4703
|
+
if (current) {
|
|
4704
|
+
const recoveredCurrent = await this.recoverCodexAuthCandidate(current.name);
|
|
4705
|
+
if (recoveredCurrent) {
|
|
4706
|
+
await this.sendMessage(rotation.scopeId, t(locale, 'auth_auto_recovered_current', {
|
|
4707
|
+
value: current.name,
|
|
4708
|
+
error: formatShortStatusError(rotation.reason),
|
|
4709
|
+
}));
|
|
4710
|
+
this.pendingTurnErrors.clear();
|
|
4711
|
+
this.attachedThreads.clear();
|
|
4712
|
+
await this.app.restart();
|
|
4713
|
+
await this.syncCodexAuthCandidate(current.name);
|
|
4714
|
+
if (rotation.retry) {
|
|
4715
|
+
await this.retryTurnAfterAuthRotation(rotation.scopeId, locale, rotation.retry);
|
|
4716
|
+
return true;
|
|
4717
|
+
}
|
|
4718
|
+
return false;
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
const selection = await this.selectNextCodexAuthCandidate(failedTargets);
|
|
4572
4722
|
if (!selection) {
|
|
4573
4723
|
await this.sendMessage(rotation.scopeId, t(locale, 'auth_auto_no_candidate', {
|
|
4574
4724
|
error: formatShortStatusError(rotation.reason),
|
|
@@ -7834,7 +7984,7 @@ function authChoiceKeyboard(locale, record) {
|
|
|
7834
7984
|
const page = codexAuthListPage(record.candidates, record);
|
|
7835
7985
|
const rows = page.visible.map(({ candidate, index }) => [
|
|
7836
7986
|
{
|
|
7837
|
-
text: clipButtonText(`${candidate.isCurrent ? '✅ ' : '🔐 '}${
|
|
7987
|
+
text: clipButtonText(`${candidate.isCurrent ? '✅ ' : '🔐 '}${formatAuthQuotaButtonPrefix(candidate.quota)}|${formatCodexAuthCandidateDisplayName(candidate.name)}${candidate.disabled ? ' · off' : ''}`),
|
|
7838
7988
|
callback_data: `auth:${record.localId}:${index}`,
|
|
7839
7989
|
},
|
|
7840
7990
|
{
|
|
@@ -7960,6 +8110,36 @@ function formatAuthRefreshAllResult(locale, result) {
|
|
|
7960
8110
|
}
|
|
7961
8111
|
return lines.join('\n');
|
|
7962
8112
|
}
|
|
8113
|
+
function formatAuthSyncStatus(locale, status) {
|
|
8114
|
+
if (!status?.enabled) {
|
|
8115
|
+
return t(locale, 'auth_sync_disabled');
|
|
8116
|
+
}
|
|
8117
|
+
const lines = [
|
|
8118
|
+
t(locale, 'auth_sync_status_title'),
|
|
8119
|
+
t(locale, 'auth_sync_status_node', { value: status.nodeId ?? t(locale, 'unknown') }),
|
|
8120
|
+
t(locale, 'auth_sync_status_peers', { value: status.peers.length === 0 ? t(locale, 'none') : status.peers.join(', ') }),
|
|
8121
|
+
t(locale, 'auth_sync_status_pending', { value: status.pendingImports }),
|
|
8122
|
+
t(locale, 'auth_sync_status_sent', { value: status.lastSentAt ?? t(locale, 'none') }),
|
|
8123
|
+
t(locale, 'auth_sync_status_received', { value: status.lastReceivedAt ?? t(locale, 'none') }),
|
|
8124
|
+
t(locale, 'auth_sync_status_imported', {
|
|
8125
|
+
value: status.lastImportedAt
|
|
8126
|
+
? `${status.lastImportCandidate ?? t(locale, 'unknown')} @ ${status.lastImportedAt}`
|
|
8127
|
+
: t(locale, 'none'),
|
|
8128
|
+
}),
|
|
8129
|
+
t(locale, 'auth_sync_status_pull', {
|
|
8130
|
+
value: status.lastPullAt
|
|
8131
|
+
? `${status.lastPullCandidate ?? t(locale, 'unknown')} @ ${status.lastPullAt}`
|
|
8132
|
+
: t(locale, 'none'),
|
|
8133
|
+
}),
|
|
8134
|
+
];
|
|
8135
|
+
if (status.activeLeaseId) {
|
|
8136
|
+
lines.push(t(locale, 'auth_sync_status_lease', { value: status.activeLeaseId }));
|
|
8137
|
+
}
|
|
8138
|
+
if (status.lastError) {
|
|
8139
|
+
lines.push(t(locale, 'auth_sync_status_error', { value: status.lastError }));
|
|
8140
|
+
}
|
|
8141
|
+
return lines.join('\n');
|
|
8142
|
+
}
|
|
7963
8143
|
function normalizeHelpUsageKey(name) {
|
|
7964
8144
|
const normalized = name.toLowerCase();
|
|
7965
8145
|
switch (normalized) {
|
|
@@ -8576,6 +8756,17 @@ function formatAuthQuotaPrefix(locale, snapshot) {
|
|
|
8576
8756
|
.map(([duration, remaining, fallback]) => (`${formatCompactRateLimitWindowLabel(locale, duration, fallback)}:${remaining === null ? '--' : formatUsagePercent(remaining)}`));
|
|
8577
8757
|
return values.length > 0 ? values.join('|') : '--';
|
|
8578
8758
|
}
|
|
8759
|
+
function formatAuthQuotaButtonPrefix(snapshot) {
|
|
8760
|
+
return [
|
|
8761
|
+
snapshot?.primaryRemainingPercent ?? null,
|
|
8762
|
+
snapshot?.secondaryRemainingPercent ?? null,
|
|
8763
|
+
].map(formatAuthQuotaButtonValue).join('|');
|
|
8764
|
+
}
|
|
8765
|
+
function formatAuthQuotaButtonValue(value) {
|
|
8766
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
8767
|
+
? formatUsagePercent(value)
|
|
8768
|
+
: '—';
|
|
8769
|
+
}
|
|
8579
8770
|
function isCodexAuthQuotaSnapshot(value) {
|
|
8580
8771
|
if (!value || typeof value !== 'object') {
|
|
8581
8772
|
return false;
|
package/dist/i18n.d.ts
CHANGED
|
@@ -125,11 +125,14 @@ declare const MESSAGES: {
|
|
|
125
125
|
readonly status_runtime_weixin: "- Weixin default runtime: connected {connected}, active turns {turns}";
|
|
126
126
|
readonly status_auth_mirror_none: "Last auth mirror: none recorded";
|
|
127
127
|
readonly status_auth_mirror_synced: "Last auth mirror: {candidate} from {source} at {time}";
|
|
128
|
+
readonly status_auth_sync: "Cross-node auth sync: node {node}, peers {peers}, pending imports {pending}";
|
|
129
|
+
readonly status_auth_sync_error: "Cross-node auth sync error: {value}";
|
|
128
130
|
readonly status_last_update_none: "Last service update: none recorded";
|
|
129
131
|
readonly status_last_update: "Last service update: {from} -> {to} at {time}";
|
|
130
132
|
readonly status_last_codex_update: "Last Codex update: {value}";
|
|
131
133
|
readonly update_started: "FoxClaw update started. I will report here after installation, checks, and service restart complete.";
|
|
132
134
|
readonly update_succeeded: "FoxClaw updated and restarted: {from} -> {to}.";
|
|
135
|
+
readonly update_codex_result: "Codex CLI: {from} -> {to}.";
|
|
133
136
|
readonly update_failed: "FoxClaw update failed: {error}\nRun foxclaw update in a terminal for details.";
|
|
134
137
|
readonly update_unavailable: "Self-update is unavailable in this runtime. Run foxclaw update in a terminal.";
|
|
135
138
|
readonly update_already_running: "A FoxClaw update is already running. I will report here when it finishes.";
|
|
@@ -137,7 +140,8 @@ declare const MESSAGES: {
|
|
|
137
140
|
readonly auth_reload_restarting: "Restarting Codex app-server to reload auth...";
|
|
138
141
|
readonly auth_reload_done: "Codex app-server restarted. Current auth has been reloaded.";
|
|
139
142
|
readonly auth_reload_blocked_active: "Cannot reload Codex auth while a turn, approval, or question is active. Wait or use /interrupt first.";
|
|
140
|
-
readonly usage_auth: "Usage: /auth [list [keyword]|filter <all|enabled|attention>|page <n>|use <n>|enable <n>|disable <n>|reload|refresh all [confirm]|add <name>]";
|
|
143
|
+
readonly usage_auth: "Usage: /auth [list [keyword]|filter <all|enabled|attention>|page <n>|use <n>|enable <n>|disable <n>|reload|refresh all [confirm]|sync <status|test|push all>|add <name>]";
|
|
144
|
+
readonly usage_auth_sync: "Usage: /auth sync <status|test|push all>";
|
|
141
145
|
readonly usage_auth_add: "Usage: /auth add <name>. Use letters, numbers, dot, dash, or underscore.";
|
|
142
146
|
readonly auth_list_title: "Codex auth files:";
|
|
143
147
|
readonly auth_bot: "Bot runtime: {value}";
|
|
@@ -179,11 +183,13 @@ declare const MESSAGES: {
|
|
|
179
183
|
readonly auth_refresh_all_cancelled: "Refresh all cancelled.";
|
|
180
184
|
readonly auth_refresh_all_starting: "Refreshing all ChatGPT auth candidates. Every runtime must stay idle...";
|
|
181
185
|
readonly auth_refresh_all_blocked_active: "Cannot refresh all auth candidates while any runtime, approval, input, login, or auth mirror write is active. Wait or use /interrupt first.";
|
|
186
|
+
readonly auth_refresh_all_lease_failed: "Cross-node refresh lock was not granted: {error}";
|
|
182
187
|
readonly auth_refresh_all_done: "Auth refresh all complete: {refreshed} refreshed, {skipped} skipped, {failed} failed.";
|
|
183
188
|
readonly auth_refresh_all_refreshed: "Refreshed: {value}";
|
|
184
189
|
readonly auth_refresh_all_skipped: "Skipped non-ChatGPT/invalid candidates: {value}";
|
|
185
190
|
readonly auth_refresh_all_failed: "Failed: {value}";
|
|
186
191
|
readonly auth_auto_switching: "Codex auth problem detected ({error}). Switching: {from} -> {to}...";
|
|
192
|
+
readonly auth_auto_recovered_current: "Codex auth problem detected ({error}). Recovered a newer same-account credential for {value} and restarted Codex app-server.";
|
|
187
193
|
readonly auth_auto_done: "Auto-switched Codex auth: {from} -> {to}.";
|
|
188
194
|
readonly auth_auto_retrying: "Retrying the same request with the new auth...";
|
|
189
195
|
readonly auth_auto_retry_thread_missing: "Auth was switched, but the original thread is no longer available ({threadId}). Retry was stopped to avoid creating duplicate sessions.";
|
|
@@ -196,6 +202,20 @@ declare const MESSAGES: {
|
|
|
196
202
|
readonly auth_add_cancelled: "New auth login cancelled. Restored previous auth.";
|
|
197
203
|
readonly auth_add_reverted: "Restored previous auth.";
|
|
198
204
|
readonly auth_add_missing_file: "Login completed, but the new auth file was not created: {value}";
|
|
205
|
+
readonly auth_sync_disabled: "Cross-node auth sync is disabled.";
|
|
206
|
+
readonly auth_sync_test_sent: "Auth sync test ping sent to {count} peer(s).";
|
|
207
|
+
readonly auth_sync_push_blocked_active: "Cannot push auth sync while any runtime, approval, input, login, or auth mirror write is active.";
|
|
208
|
+
readonly auth_sync_push_done: "Auth sync push complete: sent {sent}, skipped {skipped}.";
|
|
209
|
+
readonly auth_sync_status_title: "Cross-node auth sync:";
|
|
210
|
+
readonly auth_sync_status_node: "Node: {value}";
|
|
211
|
+
readonly auth_sync_status_peers: "Peers: {value}";
|
|
212
|
+
readonly auth_sync_status_pending: "Pending imports: {value}";
|
|
213
|
+
readonly auth_sync_status_sent: "Last sent: {value}";
|
|
214
|
+
readonly auth_sync_status_received: "Last received: {value}";
|
|
215
|
+
readonly auth_sync_status_imported: "Last import: {value}";
|
|
216
|
+
readonly auth_sync_status_pull: "Last pull: {value}";
|
|
217
|
+
readonly auth_sync_status_lease: "Active refresh lease: {value}";
|
|
218
|
+
readonly auth_sync_status_error: "Last error: {value}";
|
|
199
219
|
readonly button_login_device: "🔑 Login";
|
|
200
220
|
readonly button_auth_reload: "🔄 Reload auth";
|
|
201
221
|
readonly button_auth_refresh_all_confirm: "⚠️ Accept risk & refresh";
|
|
@@ -730,11 +750,14 @@ declare const MESSAGES: {
|
|
|
730
750
|
readonly status_runtime_weixin: "- 微信默认运行时:连接 {connected},进行中回复 {turns}";
|
|
731
751
|
readonly status_auth_mirror_none: "最近 auth 镜像:暂无记录";
|
|
732
752
|
readonly status_auth_mirror_synced: "最近 auth 镜像:{candidate} 由 {source} 于 {time} 同步";
|
|
753
|
+
readonly status_auth_sync: "跨节点 auth 同步:节点 {node},peer {peers},待导入 {pending}";
|
|
754
|
+
readonly status_auth_sync_error: "跨节点 auth 同步错误:{value}";
|
|
733
755
|
readonly status_last_update_none: "最近服务升级:暂无记录";
|
|
734
756
|
readonly status_last_update: "最近服务升级:{from} -> {to}({time})";
|
|
735
757
|
readonly status_last_codex_update: "最近 Codex 升级:{value}";
|
|
736
758
|
readonly update_started: "已开始升级 FoxClaw。安装、自检和服务重启完成后,我会在这里回报结果。";
|
|
737
759
|
readonly update_succeeded: "FoxClaw 已升级并重启:{from} -> {to}。";
|
|
760
|
+
readonly update_codex_result: "Codex CLI:{from} -> {to}。";
|
|
738
761
|
readonly update_failed: "FoxClaw 升级失败:{error}\n请在终端运行 foxclaw update 查看详情。";
|
|
739
762
|
readonly update_unavailable: "当前运行方式不支持自升级,请在终端运行 foxclaw update。";
|
|
740
763
|
readonly update_already_running: "FoxClaw 升级已经在进行中,结束后我会在这里回报结果。";
|
|
@@ -742,7 +765,8 @@ declare const MESSAGES: {
|
|
|
742
765
|
readonly auth_reload_restarting: "正在重启 Codex app-server 以重新读取 auth...";
|
|
743
766
|
readonly auth_reload_done: "Codex app-server 已重启,当前 auth 已重新读取。";
|
|
744
767
|
readonly auth_reload_blocked_active: "当前有回复、审批或问题在进行中,不能重载 Codex auth。请先等待,或使用 /interrupt。";
|
|
745
|
-
readonly usage_auth: "用法:/auth [list [关键词]|filter <all|enabled|attention>|page <页码>|use <编号>|enable <编号>|disable <编号>|reload|refresh all [confirm]|add <名称>]";
|
|
768
|
+
readonly usage_auth: "用法:/auth [list [关键词]|filter <all|enabled|attention>|page <页码>|use <编号>|enable <编号>|disable <编号>|reload|refresh all [confirm]|sync <status|test|push all>|add <名称>]";
|
|
769
|
+
readonly usage_auth_sync: "用法:/auth sync <status|test|push all>";
|
|
746
770
|
readonly usage_auth_add: "用法:/auth add <名称>。名称只能包含字母、数字、点、短横线或下划线。";
|
|
747
771
|
readonly auth_list_title: "Codex auth 文件:";
|
|
748
772
|
readonly auth_bot: "Bot runtime:{value}";
|
|
@@ -784,11 +808,13 @@ declare const MESSAGES: {
|
|
|
784
808
|
readonly auth_refresh_all_cancelled: "已取消刷新全部。";
|
|
785
809
|
readonly auth_refresh_all_starting: "正在刷新全部 ChatGPT auth 候选。执行期间所有 runtime 必须保持空闲...";
|
|
786
810
|
readonly auth_refresh_all_blocked_active: "当前有任一 runtime、审批、待输入、登录或 auth 镜像写入在进行中,不能刷新全部 auth。请先等待,或使用 /interrupt。";
|
|
811
|
+
readonly auth_refresh_all_lease_failed: "跨节点刷新锁未授予:{error}";
|
|
787
812
|
readonly auth_refresh_all_done: "全部 auth 刷新完成:已刷新 {refreshed},已跳过 {skipped},失败 {failed}。";
|
|
788
813
|
readonly auth_refresh_all_refreshed: "已刷新:{value}";
|
|
789
814
|
readonly auth_refresh_all_skipped: "已跳过非 ChatGPT/无效候选:{value}";
|
|
790
815
|
readonly auth_refresh_all_failed: "失败:{value}";
|
|
791
816
|
readonly auth_auto_switching: "检测到 Codex auth 问题({error}),正在切换:{from} -> {to}...";
|
|
817
|
+
readonly auth_auto_recovered_current: "检测到 Codex auth 问题({error}),已为 {value} 恢复同账号较新凭据并重启 Codex app-server。";
|
|
792
818
|
readonly auth_auto_done: "已自动切换 Codex auth:{from} -> {to}。";
|
|
793
819
|
readonly auth_auto_retrying: "正在用新的 auth 重试同一条请求...";
|
|
794
820
|
readonly auth_auto_retry_thread_missing: "Auth 已切换,但原线程已经不可用({threadId})。已停止重试,避免创建重复 session。";
|
|
@@ -801,6 +827,20 @@ declare const MESSAGES: {
|
|
|
801
827
|
readonly auth_add_cancelled: "新 auth 登录已取消,已恢复之前的 auth。";
|
|
802
828
|
readonly auth_add_reverted: "已恢复之前的 auth。";
|
|
803
829
|
readonly auth_add_missing_file: "登录已完成,但没有创建新的 auth 文件:{value}";
|
|
830
|
+
readonly auth_sync_disabled: "跨节点 auth 同步未启用。";
|
|
831
|
+
readonly auth_sync_test_sent: "已向 {count} 个 peer 发送 auth sync 测试 ping。";
|
|
832
|
+
readonly auth_sync_push_blocked_active: "当前有任一 runtime、审批、待输入、登录或 auth 镜像写入在进行中,不能推送 auth 同步。";
|
|
833
|
+
readonly auth_sync_push_done: "auth 同步推送完成:已发送 {sent},已跳过 {skipped}。";
|
|
834
|
+
readonly auth_sync_status_title: "跨节点 auth 同步:";
|
|
835
|
+
readonly auth_sync_status_node: "节点:{value}";
|
|
836
|
+
readonly auth_sync_status_peers: "Peer:{value}";
|
|
837
|
+
readonly auth_sync_status_pending: "待导入:{value}";
|
|
838
|
+
readonly auth_sync_status_sent: "最近发送:{value}";
|
|
839
|
+
readonly auth_sync_status_received: "最近接收:{value}";
|
|
840
|
+
readonly auth_sync_status_imported: "最近导入:{value}";
|
|
841
|
+
readonly auth_sync_status_pull: "最近拉取:{value}";
|
|
842
|
+
readonly auth_sync_status_lease: "当前刷新锁:{value}";
|
|
843
|
+
readonly auth_sync_status_error: "最近错误:{value}";
|
|
804
844
|
readonly button_login_device: "🔑 设备登录";
|
|
805
845
|
readonly button_auth_reload: "🔄 重载 auth";
|
|
806
846
|
readonly button_auth_refresh_all_confirm: "⚠️ 接受风险并刷新";
|
package/dist/i18n.js
CHANGED
|
@@ -123,11 +123,14 @@ const MESSAGES = {
|
|
|
123
123
|
status_runtime_weixin: '- Weixin default runtime: connected {connected}, active turns {turns}',
|
|
124
124
|
status_auth_mirror_none: 'Last auth mirror: none recorded',
|
|
125
125
|
status_auth_mirror_synced: 'Last auth mirror: {candidate} from {source} at {time}',
|
|
126
|
+
status_auth_sync: 'Cross-node auth sync: node {node}, peers {peers}, pending imports {pending}',
|
|
127
|
+
status_auth_sync_error: 'Cross-node auth sync error: {value}',
|
|
126
128
|
status_last_update_none: 'Last service update: none recorded',
|
|
127
129
|
status_last_update: 'Last service update: {from} -> {to} at {time}',
|
|
128
130
|
status_last_codex_update: 'Last Codex update: {value}',
|
|
129
131
|
update_started: 'FoxClaw update started. I will report here after installation, checks, and service restart complete.',
|
|
130
132
|
update_succeeded: 'FoxClaw updated and restarted: {from} -> {to}.',
|
|
133
|
+
update_codex_result: 'Codex CLI: {from} -> {to}.',
|
|
131
134
|
update_failed: 'FoxClaw update failed: {error}\nRun foxclaw update in a terminal for details.',
|
|
132
135
|
update_unavailable: 'Self-update is unavailable in this runtime. Run foxclaw update in a terminal.',
|
|
133
136
|
update_already_running: 'A FoxClaw update is already running. I will report here when it finishes.',
|
|
@@ -135,7 +138,8 @@ const MESSAGES = {
|
|
|
135
138
|
auth_reload_restarting: 'Restarting Codex app-server to reload auth...',
|
|
136
139
|
auth_reload_done: 'Codex app-server restarted. Current auth has been reloaded.',
|
|
137
140
|
auth_reload_blocked_active: 'Cannot reload Codex auth while a turn, approval, or question is active. Wait or use /interrupt first.',
|
|
138
|
-
usage_auth: 'Usage: /auth [list [keyword]|filter <all|enabled|attention>|page <n>|use <n>|enable <n>|disable <n>|reload|refresh all [confirm]|add <name>]',
|
|
141
|
+
usage_auth: 'Usage: /auth [list [keyword]|filter <all|enabled|attention>|page <n>|use <n>|enable <n>|disable <n>|reload|refresh all [confirm]|sync <status|test|push all>|add <name>]',
|
|
142
|
+
usage_auth_sync: 'Usage: /auth sync <status|test|push all>',
|
|
139
143
|
usage_auth_add: 'Usage: /auth add <name>. Use letters, numbers, dot, dash, or underscore.',
|
|
140
144
|
auth_list_title: 'Codex auth files:',
|
|
141
145
|
auth_bot: 'Bot runtime: {value}',
|
|
@@ -177,11 +181,13 @@ const MESSAGES = {
|
|
|
177
181
|
auth_refresh_all_cancelled: 'Refresh all cancelled.',
|
|
178
182
|
auth_refresh_all_starting: 'Refreshing all ChatGPT auth candidates. Every runtime must stay idle...',
|
|
179
183
|
auth_refresh_all_blocked_active: 'Cannot refresh all auth candidates while any runtime, approval, input, login, or auth mirror write is active. Wait or use /interrupt first.',
|
|
184
|
+
auth_refresh_all_lease_failed: 'Cross-node refresh lock was not granted: {error}',
|
|
180
185
|
auth_refresh_all_done: 'Auth refresh all complete: {refreshed} refreshed, {skipped} skipped, {failed} failed.',
|
|
181
186
|
auth_refresh_all_refreshed: 'Refreshed: {value}',
|
|
182
187
|
auth_refresh_all_skipped: 'Skipped non-ChatGPT/invalid candidates: {value}',
|
|
183
188
|
auth_refresh_all_failed: 'Failed: {value}',
|
|
184
189
|
auth_auto_switching: 'Codex auth problem detected ({error}). Switching: {from} -> {to}...',
|
|
190
|
+
auth_auto_recovered_current: 'Codex auth problem detected ({error}). Recovered a newer same-account credential for {value} and restarted Codex app-server.',
|
|
185
191
|
auth_auto_done: 'Auto-switched Codex auth: {from} -> {to}.',
|
|
186
192
|
auth_auto_retrying: 'Retrying the same request with the new auth...',
|
|
187
193
|
auth_auto_retry_thread_missing: 'Auth was switched, but the original thread is no longer available ({threadId}). Retry was stopped to avoid creating duplicate sessions.',
|
|
@@ -194,6 +200,20 @@ const MESSAGES = {
|
|
|
194
200
|
auth_add_cancelled: 'New auth login cancelled. Restored previous auth.',
|
|
195
201
|
auth_add_reverted: 'Restored previous auth.',
|
|
196
202
|
auth_add_missing_file: 'Login completed, but the new auth file was not created: {value}',
|
|
203
|
+
auth_sync_disabled: 'Cross-node auth sync is disabled.',
|
|
204
|
+
auth_sync_test_sent: 'Auth sync test ping sent to {count} peer(s).',
|
|
205
|
+
auth_sync_push_blocked_active: 'Cannot push auth sync while any runtime, approval, input, login, or auth mirror write is active.',
|
|
206
|
+
auth_sync_push_done: 'Auth sync push complete: sent {sent}, skipped {skipped}.',
|
|
207
|
+
auth_sync_status_title: 'Cross-node auth sync:',
|
|
208
|
+
auth_sync_status_node: 'Node: {value}',
|
|
209
|
+
auth_sync_status_peers: 'Peers: {value}',
|
|
210
|
+
auth_sync_status_pending: 'Pending imports: {value}',
|
|
211
|
+
auth_sync_status_sent: 'Last sent: {value}',
|
|
212
|
+
auth_sync_status_received: 'Last received: {value}',
|
|
213
|
+
auth_sync_status_imported: 'Last import: {value}',
|
|
214
|
+
auth_sync_status_pull: 'Last pull: {value}',
|
|
215
|
+
auth_sync_status_lease: 'Active refresh lease: {value}',
|
|
216
|
+
auth_sync_status_error: 'Last error: {value}',
|
|
197
217
|
button_login_device: '🔑 Login',
|
|
198
218
|
button_auth_reload: '🔄 Reload auth',
|
|
199
219
|
button_auth_refresh_all_confirm: '⚠️ Accept risk & refresh',
|
|
@@ -728,11 +748,14 @@ const MESSAGES = {
|
|
|
728
748
|
status_runtime_weixin: '- 微信默认运行时:连接 {connected},进行中回复 {turns}',
|
|
729
749
|
status_auth_mirror_none: '最近 auth 镜像:暂无记录',
|
|
730
750
|
status_auth_mirror_synced: '最近 auth 镜像:{candidate} 由 {source} 于 {time} 同步',
|
|
751
|
+
status_auth_sync: '跨节点 auth 同步:节点 {node},peer {peers},待导入 {pending}',
|
|
752
|
+
status_auth_sync_error: '跨节点 auth 同步错误:{value}',
|
|
731
753
|
status_last_update_none: '最近服务升级:暂无记录',
|
|
732
754
|
status_last_update: '最近服务升级:{from} -> {to}({time})',
|
|
733
755
|
status_last_codex_update: '最近 Codex 升级:{value}',
|
|
734
756
|
update_started: '已开始升级 FoxClaw。安装、自检和服务重启完成后,我会在这里回报结果。',
|
|
735
757
|
update_succeeded: 'FoxClaw 已升级并重启:{from} -> {to}。',
|
|
758
|
+
update_codex_result: 'Codex CLI:{from} -> {to}。',
|
|
736
759
|
update_failed: 'FoxClaw 升级失败:{error}\n请在终端运行 foxclaw update 查看详情。',
|
|
737
760
|
update_unavailable: '当前运行方式不支持自升级,请在终端运行 foxclaw update。',
|
|
738
761
|
update_already_running: 'FoxClaw 升级已经在进行中,结束后我会在这里回报结果。',
|
|
@@ -740,7 +763,8 @@ const MESSAGES = {
|
|
|
740
763
|
auth_reload_restarting: '正在重启 Codex app-server 以重新读取 auth...',
|
|
741
764
|
auth_reload_done: 'Codex app-server 已重启,当前 auth 已重新读取。',
|
|
742
765
|
auth_reload_blocked_active: '当前有回复、审批或问题在进行中,不能重载 Codex auth。请先等待,或使用 /interrupt。',
|
|
743
|
-
usage_auth: '用法:/auth [list [关键词]|filter <all|enabled|attention>|page <页码>|use <编号>|enable <编号>|disable <编号>|reload|refresh all [confirm]|add <名称>]',
|
|
766
|
+
usage_auth: '用法:/auth [list [关键词]|filter <all|enabled|attention>|page <页码>|use <编号>|enable <编号>|disable <编号>|reload|refresh all [confirm]|sync <status|test|push all>|add <名称>]',
|
|
767
|
+
usage_auth_sync: '用法:/auth sync <status|test|push all>',
|
|
744
768
|
usage_auth_add: '用法:/auth add <名称>。名称只能包含字母、数字、点、短横线或下划线。',
|
|
745
769
|
auth_list_title: 'Codex auth 文件:',
|
|
746
770
|
auth_bot: 'Bot runtime:{value}',
|
|
@@ -782,11 +806,13 @@ const MESSAGES = {
|
|
|
782
806
|
auth_refresh_all_cancelled: '已取消刷新全部。',
|
|
783
807
|
auth_refresh_all_starting: '正在刷新全部 ChatGPT auth 候选。执行期间所有 runtime 必须保持空闲...',
|
|
784
808
|
auth_refresh_all_blocked_active: '当前有任一 runtime、审批、待输入、登录或 auth 镜像写入在进行中,不能刷新全部 auth。请先等待,或使用 /interrupt。',
|
|
809
|
+
auth_refresh_all_lease_failed: '跨节点刷新锁未授予:{error}',
|
|
785
810
|
auth_refresh_all_done: '全部 auth 刷新完成:已刷新 {refreshed},已跳过 {skipped},失败 {failed}。',
|
|
786
811
|
auth_refresh_all_refreshed: '已刷新:{value}',
|
|
787
812
|
auth_refresh_all_skipped: '已跳过非 ChatGPT/无效候选:{value}',
|
|
788
813
|
auth_refresh_all_failed: '失败:{value}',
|
|
789
814
|
auth_auto_switching: '检测到 Codex auth 问题({error}),正在切换:{from} -> {to}...',
|
|
815
|
+
auth_auto_recovered_current: '检测到 Codex auth 问题({error}),已为 {value} 恢复同账号较新凭据并重启 Codex app-server。',
|
|
790
816
|
auth_auto_done: '已自动切换 Codex auth:{from} -> {to}。',
|
|
791
817
|
auth_auto_retrying: '正在用新的 auth 重试同一条请求...',
|
|
792
818
|
auth_auto_retry_thread_missing: 'Auth 已切换,但原线程已经不可用({threadId})。已停止重试,避免创建重复 session。',
|
|
@@ -799,6 +825,20 @@ const MESSAGES = {
|
|
|
799
825
|
auth_add_cancelled: '新 auth 登录已取消,已恢复之前的 auth。',
|
|
800
826
|
auth_add_reverted: '已恢复之前的 auth。',
|
|
801
827
|
auth_add_missing_file: '登录已完成,但没有创建新的 auth 文件:{value}',
|
|
828
|
+
auth_sync_disabled: '跨节点 auth 同步未启用。',
|
|
829
|
+
auth_sync_test_sent: '已向 {count} 个 peer 发送 auth sync 测试 ping。',
|
|
830
|
+
auth_sync_push_blocked_active: '当前有任一 runtime、审批、待输入、登录或 auth 镜像写入在进行中,不能推送 auth 同步。',
|
|
831
|
+
auth_sync_push_done: 'auth 同步推送完成:已发送 {sent},已跳过 {skipped}。',
|
|
832
|
+
auth_sync_status_title: '跨节点 auth 同步:',
|
|
833
|
+
auth_sync_status_node: '节点:{value}',
|
|
834
|
+
auth_sync_status_peers: 'Peer:{value}',
|
|
835
|
+
auth_sync_status_pending: '待导入:{value}',
|
|
836
|
+
auth_sync_status_sent: '最近发送:{value}',
|
|
837
|
+
auth_sync_status_received: '最近接收:{value}',
|
|
838
|
+
auth_sync_status_imported: '最近导入:{value}',
|
|
839
|
+
auth_sync_status_pull: '最近拉取:{value}',
|
|
840
|
+
auth_sync_status_lease: '当前刷新锁:{value}',
|
|
841
|
+
auth_sync_status_error: '最近错误:{value}',
|
|
802
842
|
button_login_device: '🔑 设备登录',
|
|
803
843
|
button_auth_reload: '🔄 重载 auth',
|
|
804
844
|
button_auth_refresh_all_confirm: '⚠️ 接受风险并刷新',
|