@foxden-app/foxclaw 0.5.20 → 0.5.21
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 +4 -0
- package/CHANGELOG.md +14 -0
- package/README.md +1 -1
- package/dist/auth/cross_node_sync.d.ts +55 -0
- package/dist/auth/cross_node_sync.js +199 -2
- package/dist/config.d.ts +2 -0
- package/dist/config.js +2 -0
- package/dist/controller/controller.d.ts +5 -1
- package/dist/controller/controller.js +177 -15
- package/dist/i18n.d.ts +26 -0
- package/dist/i18n.js +26 -0
- package/dist/main.js +141 -7
- package/dist/store/database.d.ts +9 -0
- package/dist/store/database.js +87 -0
- package/docs/cross-node-auth-sync.md +4 -0
- package/docs/user-manual.md +3 -0
- package/docs/zh/cross-node-auth-sync.md +4 -0
- package/docs/zh/user-manual.md +3 -0
- package/package.json +1 -1
|
@@ -19,6 +19,7 @@ import { diffObservedTurn, findLatestTurn, findLiveTurn } from './observer.js';
|
|
|
19
19
|
import { applySessionLog, bootstrapSessionLog, splitJsonlChunk, } from './session_observer.js';
|
|
20
20
|
import { renderActiveTurnStatus } from './status.js';
|
|
21
21
|
import { writeRuntimeStatus } from '../runtime.js';
|
|
22
|
+
const AUTH_DELETE_REASON_NEEDS_REPAIR = 'needs_repair';
|
|
22
23
|
class UserFacingError extends Error {
|
|
23
24
|
}
|
|
24
25
|
const OBSERVED_THREAD_POLL_MS = 1500;
|
|
@@ -432,6 +433,7 @@ export class BridgeSessionCore {
|
|
|
432
433
|
t(locale, 'status_pending_user_inputs', { value: this.store.countPendingUserInputs() }),
|
|
433
434
|
t(locale, 'status_queued_turns', { value: this.store.countQueuedTurnInputs(scopeId) }),
|
|
434
435
|
t(locale, 'status_active_turns', { value: this.activeTurns.size }),
|
|
436
|
+
formatCodexAuthPoolSummary(locale, this.store.getCodexAuthPoolStats()),
|
|
435
437
|
];
|
|
436
438
|
if (serviceStatus) {
|
|
437
439
|
lines.push('', t(locale, 'status_runtime_overview'));
|
|
@@ -755,7 +757,7 @@ export class BridgeSessionCore {
|
|
|
755
757
|
return;
|
|
756
758
|
}
|
|
757
759
|
case 'config': {
|
|
758
|
-
await this.handleConfigCommand(scopeId, locale);
|
|
760
|
+
await this.handleConfigCommand(scopeId, locale, args);
|
|
759
761
|
return;
|
|
760
762
|
}
|
|
761
763
|
case 'requirements': {
|
|
@@ -1053,6 +1055,11 @@ export class BridgeSessionCore {
|
|
|
1053
1055
|
await this.handleSetupCallback(event, setupMatch[1], setupMatch[2], locale);
|
|
1054
1056
|
return;
|
|
1055
1057
|
}
|
|
1058
|
+
const configMatch = /^config:auth_auto_delete:(on|off)$/.exec(event.data);
|
|
1059
|
+
if (configMatch) {
|
|
1060
|
+
await this.handleConfigToggleCallback(event, configMatch[1] === 'on', locale);
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1056
1063
|
const settingsMatch = /^settings:(model|effort|access):(.+)$/.exec(event.data);
|
|
1057
1064
|
if (settingsMatch) {
|
|
1058
1065
|
await this.handleSettingsCallback(event, settingsMatch[1], settingsMatch[2], locale);
|
|
@@ -2931,6 +2938,19 @@ export class BridgeSessionCore {
|
|
|
2931
2938
|
async getCurrentAuthLabel() {
|
|
2932
2939
|
return (await this.listCodexAuthState()).currentLabel;
|
|
2933
2940
|
}
|
|
2941
|
+
async handleExternalCodexAuthCandidateDeleted(candidateName, reason = null) {
|
|
2942
|
+
this.store.deleteCodexAuthCandidate(candidateName);
|
|
2943
|
+
if (isInvalidCodexAuthDeleteReason(reason)) {
|
|
2944
|
+
this.store.recordCodexAuthCandidateInvalidDelete(candidateName, reason);
|
|
2945
|
+
}
|
|
2946
|
+
else {
|
|
2947
|
+
this.store.recordCodexAuthCandidateRemoved(candidateName, reason);
|
|
2948
|
+
}
|
|
2949
|
+
this.authRotationFailedTargets.delete(path.join(this.resolveAuthDir(), candidateName));
|
|
2950
|
+
this.pendingTurnErrors.clear();
|
|
2951
|
+
this.attachedThreads.clear();
|
|
2952
|
+
await this.app.restart();
|
|
2953
|
+
}
|
|
2934
2954
|
async validateExternalCodexAuthCandidate(candidateName, rawAuth, expectedAccountId) {
|
|
2935
2955
|
if (this.externalAuthValidationInProgress) {
|
|
2936
2956
|
return { ok: false, reason: 'runtime is not idle' };
|
|
@@ -4761,13 +4781,13 @@ export class BridgeSessionCore {
|
|
|
4761
4781
|
throw error;
|
|
4762
4782
|
}
|
|
4763
4783
|
}
|
|
4764
|
-
async deleteCodexAuthCandidate(candidate) {
|
|
4784
|
+
async deleteCodexAuthCandidate(candidate, reason = null) {
|
|
4765
4785
|
const wasCurrent = candidate.isCurrent;
|
|
4766
4786
|
const authDir = this.resolveAuthDir();
|
|
4767
4787
|
const authPath = path.join(authDir, 'auth.json');
|
|
4768
4788
|
let deletedByCoordinator = false;
|
|
4769
4789
|
try {
|
|
4770
|
-
await this.coordinator?.authCandidateDeleted?.(this.authRuntimeId(), candidate.name);
|
|
4790
|
+
await this.coordinator?.authCandidateDeleted?.(this.authRuntimeId(), candidate.name, reason);
|
|
4771
4791
|
deletedByCoordinator = Boolean(this.coordinator?.authCandidateDeleted);
|
|
4772
4792
|
}
|
|
4773
4793
|
catch (error) {
|
|
@@ -4784,6 +4804,12 @@ export class BridgeSessionCore {
|
|
|
4784
4804
|
}
|
|
4785
4805
|
}
|
|
4786
4806
|
this.store.deleteCodexAuthCandidate(candidate.name);
|
|
4807
|
+
if (isInvalidCodexAuthDeleteReason(reason)) {
|
|
4808
|
+
this.store.recordCodexAuthCandidateInvalidDelete(candidate.name, reason);
|
|
4809
|
+
}
|
|
4810
|
+
else {
|
|
4811
|
+
this.store.recordCodexAuthCandidateRemoved(candidate.name, reason);
|
|
4812
|
+
}
|
|
4787
4813
|
this.authRotationFailedTargets.delete(candidate.path);
|
|
4788
4814
|
const snapshots = await this.readCodexAuthQuotaSnapshots();
|
|
4789
4815
|
if (Object.prototype.hasOwnProperty.call(snapshots, candidate.name)) {
|
|
@@ -5092,10 +5118,58 @@ export class BridgeSessionCore {
|
|
|
5092
5118
|
const features = await this.app.listExperimentalFeatures();
|
|
5093
5119
|
await this.sendMessage(scopeId, formatFeaturesMessage(locale, features));
|
|
5094
5120
|
}
|
|
5095
|
-
async handleConfigCommand(scopeId, locale) {
|
|
5121
|
+
async handleConfigCommand(scopeId, locale, args = []) {
|
|
5122
|
+
const action = args[0]?.toLowerCase() ?? '';
|
|
5123
|
+
if (['auth_auto_delete', 'auth-auto-delete', 'auto_delete_needs_repair', 'auto-delete-needs-repair'].includes(action)) {
|
|
5124
|
+
const enabled = parseConfigBooleanArg(args[1]);
|
|
5125
|
+
if (enabled === null) {
|
|
5126
|
+
await this.sendMessage(scopeId, t(locale, 'config_auth_auto_delete_usage'));
|
|
5127
|
+
return;
|
|
5128
|
+
}
|
|
5129
|
+
const update = await this.setAuthAutoDeleteNeedsRepair(enabled);
|
|
5130
|
+
const binding = this.store.getBinding(scopeId);
|
|
5131
|
+
const result = await this.app.readConfig(binding?.cwd ?? this.config.defaultCwd, true);
|
|
5132
|
+
await this.sendMessage(scopeId, `${this.formatConfigToggleUpdate(locale, update)}\n\n${formatConfigMessage(locale, result, this.config, this.store.getCodexAuthPoolStats())}`, configKeyboard(locale, this.config));
|
|
5133
|
+
return;
|
|
5134
|
+
}
|
|
5096
5135
|
const binding = this.store.getBinding(scopeId);
|
|
5097
5136
|
const result = await this.app.readConfig(binding?.cwd ?? this.config.defaultCwd, true);
|
|
5098
|
-
await this.sendMessage(scopeId, formatConfigMessage(locale, result));
|
|
5137
|
+
await this.sendMessage(scopeId, formatConfigMessage(locale, result, this.config, this.store.getCodexAuthPoolStats()), configKeyboard(locale, this.config));
|
|
5138
|
+
}
|
|
5139
|
+
async handleConfigToggleCallback(event, enabled, locale) {
|
|
5140
|
+
const update = await this.setAuthAutoDeleteNeedsRepair(enabled);
|
|
5141
|
+
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'decision_recorded'));
|
|
5142
|
+
const binding = this.store.getBinding(event.scopeId);
|
|
5143
|
+
const result = await this.app.readConfig(binding?.cwd ?? this.config.defaultCwd, true);
|
|
5144
|
+
const message = `${this.formatConfigToggleUpdate(locale, update)}\n\n${formatConfigMessage(locale, result, this.config, this.store.getCodexAuthPoolStats())}`;
|
|
5145
|
+
if (event.messageId !== null) {
|
|
5146
|
+
await this.editMessage(event.scopeId, event.messageId, message, configKeyboard(locale, this.config));
|
|
5147
|
+
}
|
|
5148
|
+
else {
|
|
5149
|
+
await this.sendMessage(event.scopeId, message, configKeyboard(locale, this.config));
|
|
5150
|
+
}
|
|
5151
|
+
}
|
|
5152
|
+
async setAuthAutoDeleteNeedsRepair(enabled) {
|
|
5153
|
+
this.config.authAutoDeleteNeedsRepair = enabled;
|
|
5154
|
+
const envPath = this.config.envPath;
|
|
5155
|
+
if (!envPath) {
|
|
5156
|
+
return { enabled, envPath: null, envUpdated: false, envError: null };
|
|
5157
|
+
}
|
|
5158
|
+
try {
|
|
5159
|
+
await writeEnvBoolean(envPath, 'AUTH_AUTO_DELETE_NEEDS_REPAIR', enabled);
|
|
5160
|
+
return { enabled, envPath, envUpdated: true, envError: null };
|
|
5161
|
+
}
|
|
5162
|
+
catch (error) {
|
|
5163
|
+
this.logger.warn('config.env_update_failed', { key: 'AUTH_AUTO_DELETE_NEEDS_REPAIR', envPath, error: toErrorMeta(error) });
|
|
5164
|
+
return { enabled, envPath, envUpdated: false, envError: formatUserError(error) };
|
|
5165
|
+
}
|
|
5166
|
+
}
|
|
5167
|
+
formatConfigToggleUpdate(locale, update) {
|
|
5168
|
+
const lines = [t(locale, 'config_auth_auto_delete_updated', { value: t(locale, update.enabled ? 'yes' : 'no') })];
|
|
5169
|
+
if (update.envError) {
|
|
5170
|
+
lines.push(t(locale, 'config_env_update_failed', { value: update.envPath ?? t(locale, 'unknown'), error: update.envError }));
|
|
5171
|
+
}
|
|
5172
|
+
return lines.join('\n');
|
|
5099
5173
|
}
|
|
5100
5174
|
async handleRequirementsCommand(scopeId, locale) {
|
|
5101
5175
|
const requirements = await this.app.readConfigRequirements();
|
|
@@ -5447,7 +5521,10 @@ export class BridgeSessionCore {
|
|
|
5447
5521
|
}
|
|
5448
5522
|
return false;
|
|
5449
5523
|
}
|
|
5450
|
-
this.markCodexAuthCandidateNeedsRepair(current.name);
|
|
5524
|
+
const disposition = await this.markCodexAuthCandidateNeedsRepair(current.name);
|
|
5525
|
+
if (disposition.deleted) {
|
|
5526
|
+
await this.sendMessage(rotation.scopeId, formatCodexAuthPoolSummary(locale, this.store.getCodexAuthPoolStats()));
|
|
5527
|
+
}
|
|
5451
5528
|
}
|
|
5452
5529
|
const selection = await this.selectNextCodexAuthCandidate(failedTargets);
|
|
5453
5530
|
if (!selection) {
|
|
@@ -5457,10 +5534,12 @@ export class BridgeSessionCore {
|
|
|
5457
5534
|
return false;
|
|
5458
5535
|
}
|
|
5459
5536
|
const { candidate, fromLabel, toLabel } = selection;
|
|
5460
|
-
await this.sendMessage(rotation.scopeId,
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5537
|
+
await this.sendMessage(rotation.scopeId, this.config.authAutoDeleteNeedsRepair
|
|
5538
|
+
? t(locale, 'auth_auto_switching_quiet', { error: formatShortStatusError(rotation.reason) })
|
|
5539
|
+
: t(locale, 'auth_auto_switching', {
|
|
5540
|
+
...this.codexAuthSwitchParams(locale, fromLabel, toLabel),
|
|
5541
|
+
error: formatShortStatusError(rotation.reason),
|
|
5542
|
+
}));
|
|
5464
5543
|
const outcome = await this.switchCodexAuthAndRestart(rotation.scopeId, locale, candidate, true);
|
|
5465
5544
|
if (!outcome.ok) {
|
|
5466
5545
|
return false;
|
|
@@ -5548,6 +5627,7 @@ export class BridgeSessionCore {
|
|
|
5548
5627
|
}
|
|
5549
5628
|
async listCodexAuthState() {
|
|
5550
5629
|
const state = await listCodexAuthState(this.store.listDisabledCodexAuthCandidateNames(this.authRuntimeId()), this.store.listCodexAuthCandidateStates(this.authRuntimeId()), this.resolveAuthDir());
|
|
5630
|
+
this.store.recordCodexAuthPoolInventory(state.candidates.map(candidate => candidate.name));
|
|
5551
5631
|
const snapshots = await this.readCodexAuthQuotaSnapshots();
|
|
5552
5632
|
const candidateQuotaIdentities = await this.readCodexAuthCandidateQuotaIdentities(state.candidates);
|
|
5553
5633
|
state.candidates.forEach((candidate) => {
|
|
@@ -5594,7 +5674,6 @@ export class BridgeSessionCore {
|
|
|
5594
5674
|
await this.app.restart();
|
|
5595
5675
|
const validation = await this.validateCurrentCodexAuthCandidate(candidate);
|
|
5596
5676
|
if (!validation.ok) {
|
|
5597
|
-
this.markCodexAuthCandidateNeedsRepair(candidate.name);
|
|
5598
5677
|
let restoredPrevious = false;
|
|
5599
5678
|
try {
|
|
5600
5679
|
await restoreCodexAuthTarget(initialState.authDir, initialState.authPath, initialState.currentTargetPath, originalRegularAuth);
|
|
@@ -5609,6 +5688,7 @@ export class BridgeSessionCore {
|
|
|
5609
5688
|
error: toErrorMeta(error),
|
|
5610
5689
|
});
|
|
5611
5690
|
}
|
|
5691
|
+
const repairDisposition = await this.markCodexAuthCandidateNeedsRepair(candidate.name);
|
|
5612
5692
|
const outcome = {
|
|
5613
5693
|
...result,
|
|
5614
5694
|
ok: false,
|
|
@@ -5616,6 +5696,8 @@ export class BridgeSessionCore {
|
|
|
5616
5696
|
recovered,
|
|
5617
5697
|
error: validation.error,
|
|
5618
5698
|
restoredPrevious,
|
|
5699
|
+
autoDeleted: repairDisposition.deleted,
|
|
5700
|
+
deleteRestarted: repairDisposition.restarted,
|
|
5619
5701
|
};
|
|
5620
5702
|
if (sendResult) {
|
|
5621
5703
|
const lines = this.formatAuthSwitchValidationLines(locale, outcome);
|
|
@@ -5632,11 +5714,15 @@ export class BridgeSessionCore {
|
|
|
5632
5714
|
recovered,
|
|
5633
5715
|
error: null,
|
|
5634
5716
|
restoredPrevious: false,
|
|
5717
|
+
autoDeleted: false,
|
|
5718
|
+
deleteRestarted: false,
|
|
5635
5719
|
};
|
|
5636
5720
|
if (!sendResult) {
|
|
5637
5721
|
return outcome;
|
|
5638
5722
|
}
|
|
5639
|
-
const lines = [
|
|
5723
|
+
const lines = [automatic && this.config.authAutoDeleteNeedsRepair
|
|
5724
|
+
? t(locale, 'auth_auto_done_quiet')
|
|
5725
|
+
: t(locale, automatic ? 'auth_auto_done' : 'auth_switch_done', this.codexAuthSwitchParams(locale, result.fromLabel, result.toLabel))];
|
|
5640
5726
|
if (recovered) {
|
|
5641
5727
|
lines.push(t(locale, 'auth_recovered_newer_candidate', { value: candidate.name }));
|
|
5642
5728
|
}
|
|
@@ -5685,12 +5771,19 @@ export class BridgeSessionCore {
|
|
|
5685
5771
|
if (outcome.ok) {
|
|
5686
5772
|
return [];
|
|
5687
5773
|
}
|
|
5774
|
+
const validationKey = outcome.autoDeleted && this.config.authAutoDeleteNeedsRepair
|
|
5775
|
+
? 'auth_switch_validation_auto_deleted_quiet'
|
|
5776
|
+
: outcome.autoDeleted
|
|
5777
|
+
? 'auth_switch_validation_auto_deleted'
|
|
5778
|
+
: 'auth_switch_validation_failed';
|
|
5688
5779
|
return [
|
|
5689
|
-
t(locale,
|
|
5780
|
+
t(locale, validationKey, {
|
|
5690
5781
|
value: outcome.candidateName,
|
|
5691
5782
|
error: outcome.error ?? t(locale, 'unknown'),
|
|
5692
5783
|
}),
|
|
5693
5784
|
outcome.restoredPrevious ? t(locale, 'auth_switch_validation_reverted') : '',
|
|
5785
|
+
outcome.deleteRestarted ? t(locale, 'auth_delete_current_restarted') : '',
|
|
5786
|
+
outcome.autoDeleted ? formatCodexAuthPoolSummary(locale, this.store.getCodexAuthPoolStats()) : '',
|
|
5694
5787
|
].filter(Boolean);
|
|
5695
5788
|
}
|
|
5696
5789
|
async recoverCodexAuthCandidate(candidateName, options = { crossNode: true }) {
|
|
@@ -5709,8 +5802,23 @@ export class BridgeSessionCore {
|
|
|
5709
5802
|
return false;
|
|
5710
5803
|
}
|
|
5711
5804
|
}
|
|
5712
|
-
markCodexAuthCandidateNeedsRepair(candidateName) {
|
|
5805
|
+
async markCodexAuthCandidateNeedsRepair(candidateName) {
|
|
5806
|
+
if (this.config.authAutoDeleteNeedsRepair) {
|
|
5807
|
+
const candidate = (await this.listCodexAuthState()).candidates.find(entry => entry.name === candidateName) ?? null;
|
|
5808
|
+
if (!candidate) {
|
|
5809
|
+
this.store.deleteCodexAuthCandidate(candidateName);
|
|
5810
|
+
this.store.recordCodexAuthCandidateInvalidDelete(candidateName, AUTH_DELETE_REASON_NEEDS_REPAIR);
|
|
5811
|
+
return { deleted: true, restarted: false };
|
|
5812
|
+
}
|
|
5813
|
+
const restarted = await this.deleteCodexAuthCandidate(candidate, AUTH_DELETE_REASON_NEEDS_REPAIR);
|
|
5814
|
+
this.logger.warn('codex.auth_candidate_auto_deleted', {
|
|
5815
|
+
candidate: candidateName,
|
|
5816
|
+
runtimeId: this.authRuntimeId(),
|
|
5817
|
+
});
|
|
5818
|
+
return { deleted: true, restarted };
|
|
5819
|
+
}
|
|
5713
5820
|
this.store.setCodexAuthCandidateState(candidateName, 'needs_repair');
|
|
5821
|
+
return { deleted: false, restarted: false };
|
|
5714
5822
|
}
|
|
5715
5823
|
markCodexAuthCandidateActive(candidateName) {
|
|
5716
5824
|
this.store.setCodexAuthCandidateState(candidateName, 'active');
|
|
@@ -8078,7 +8186,7 @@ function formatFeaturesMessage(locale, features) {
|
|
|
8078
8186
|
}
|
|
8079
8187
|
return lines.join('\n');
|
|
8080
8188
|
}
|
|
8081
|
-
function formatConfigMessage(locale, result) {
|
|
8189
|
+
function formatConfigMessage(locale, result, appConfig, authPoolStats) {
|
|
8082
8190
|
const config = result.config && typeof result.config === 'object' ? result.config : {};
|
|
8083
8191
|
const layers = Array.isArray(result.layers) ? result.layers : [];
|
|
8084
8192
|
const keys = ['model', 'model_provider', 'approval_policy', 'sandbox_mode', 'web_search', 'service_tier', 'profile', 'review_model'];
|
|
@@ -8088,8 +8196,62 @@ function formatConfigMessage(locale, result) {
|
|
|
8088
8196
|
lines.push(`${key}: ${value === null || value === undefined ? '-' : formatConfigValue(value)}`);
|
|
8089
8197
|
}
|
|
8090
8198
|
lines.push(t(locale, 'config_layers', { count: layers.length }));
|
|
8199
|
+
lines.push('');
|
|
8200
|
+
lines.push(t(locale, 'config_foxclaw_title'));
|
|
8201
|
+
lines.push(t(locale, 'config_auth_auto_delete_needs_repair', {
|
|
8202
|
+
value: t(locale, appConfig.authAutoDeleteNeedsRepair ? 'yes' : 'no'),
|
|
8203
|
+
}));
|
|
8204
|
+
lines.push(`AUTH_AUTO_DELETE_NEEDS_REPAIR=${appConfig.authAutoDeleteNeedsRepair ? 'true' : 'false'}`);
|
|
8205
|
+
lines.push(formatCodexAuthPoolSummary(locale, authPoolStats));
|
|
8091
8206
|
return lines.join('\n');
|
|
8092
8207
|
}
|
|
8208
|
+
function formatCodexAuthPoolSummary(locale, stats) {
|
|
8209
|
+
return t(locale, 'auth_pool_summary', {
|
|
8210
|
+
total: stats.totalSeen,
|
|
8211
|
+
alive: stats.alive,
|
|
8212
|
+
deleted: stats.deletedInvalid,
|
|
8213
|
+
});
|
|
8214
|
+
}
|
|
8215
|
+
function isInvalidCodexAuthDeleteReason(reason) {
|
|
8216
|
+
return reason === AUTH_DELETE_REASON_NEEDS_REPAIR;
|
|
8217
|
+
}
|
|
8218
|
+
function configKeyboard(locale, appConfig) {
|
|
8219
|
+
const enabled = appConfig.authAutoDeleteNeedsRepair;
|
|
8220
|
+
return [[{
|
|
8221
|
+
text: t(locale, enabled ? 'button_config_auth_auto_delete_off' : 'button_config_auth_auto_delete_on'),
|
|
8222
|
+
callback_data: `config:auth_auto_delete:${enabled ? 'off' : 'on'}`,
|
|
8223
|
+
}]];
|
|
8224
|
+
}
|
|
8225
|
+
function parseConfigBooleanArg(value) {
|
|
8226
|
+
const normalized = value?.trim().toLowerCase();
|
|
8227
|
+
if (!normalized)
|
|
8228
|
+
return null;
|
|
8229
|
+
if (['1', 'true', 'yes', 'on', 'enable', 'enabled'].includes(normalized))
|
|
8230
|
+
return true;
|
|
8231
|
+
if (['0', 'false', 'no', 'off', 'disable', 'disabled'].includes(normalized))
|
|
8232
|
+
return false;
|
|
8233
|
+
return null;
|
|
8234
|
+
}
|
|
8235
|
+
async function writeEnvBoolean(envPath, key, enabled) {
|
|
8236
|
+
await fs.mkdir(path.dirname(envPath), { recursive: true });
|
|
8237
|
+
const nextLine = `${key}=${enabled ? 'true' : 'false'}`;
|
|
8238
|
+
let contents = '';
|
|
8239
|
+
try {
|
|
8240
|
+
contents = await fs.readFile(envPath, 'utf8');
|
|
8241
|
+
}
|
|
8242
|
+
catch {
|
|
8243
|
+
await fs.writeFile(envPath, `${nextLine}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
8244
|
+
return;
|
|
8245
|
+
}
|
|
8246
|
+
const pattern = new RegExp(`(^|\\n)[ \\t#]*${escapeRegExp(key)}\\s*=.*(?=\\r?\\n|$)`);
|
|
8247
|
+
const nextContents = pattern.test(contents)
|
|
8248
|
+
? contents.replace(pattern, (_match, prefix) => `${prefix}${nextLine}`)
|
|
8249
|
+
: `${contents}${contents.endsWith('\n') || contents.length === 0 ? '' : '\n'}${nextLine}\n`;
|
|
8250
|
+
await fs.writeFile(envPath, nextContents, { encoding: 'utf8', mode: 0o600 });
|
|
8251
|
+
}
|
|
8252
|
+
function escapeRegExp(value) {
|
|
8253
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
8254
|
+
}
|
|
8093
8255
|
function formatRequirementsMessage(locale, requirements) {
|
|
8094
8256
|
const lines = [t(locale, 'requirements_title')];
|
|
8095
8257
|
if (!requirements) {
|
package/dist/i18n.d.ts
CHANGED
|
@@ -187,6 +187,8 @@ declare const MESSAGES: {
|
|
|
187
187
|
readonly auth_repair_reverted: "Restored previous auth. The candidate still needs repair.";
|
|
188
188
|
readonly auth_candidate_deleted: "Deleted auth candidate: {value}";
|
|
189
189
|
readonly auth_candidate_deleted_short: "Auth deleted";
|
|
190
|
+
readonly auth_candidate_auto_deleted: "Auto-deleted unrecoverable auth candidate: {value}";
|
|
191
|
+
readonly auth_pool_summary: "Auth pool: total seen {total}, alive {alive}, invalid-deleted {deleted}.";
|
|
190
192
|
readonly auth_delete_current_restarted: "The deleted candidate was current, so Codex app-server was restarted without it.";
|
|
191
193
|
readonly auth_no_candidates: "No auth candidates found. Expected files like auth.json_personal in the auth dir.";
|
|
192
194
|
readonly auth_choice_expired: "This auth list is no longer active";
|
|
@@ -195,6 +197,8 @@ declare const MESSAGES: {
|
|
|
195
197
|
readonly auth_switching: "Switching Codex auth: {from} -> {to}...";
|
|
196
198
|
readonly auth_switch_done: "Codex auth switched: {from} -> {to}.";
|
|
197
199
|
readonly auth_switch_validation_failed: "Selected auth failed validation: {error}. Marked {value} for login repair.";
|
|
200
|
+
readonly auth_switch_validation_auto_deleted: "Selected auth failed validation: {error}. Auto-deleted {value}.";
|
|
201
|
+
readonly auth_switch_validation_auto_deleted_quiet: "Selected auth failed validation and was auto-deleted.";
|
|
198
202
|
readonly auth_switch_validation_reverted: "Restored the previous auth after the failed switch.";
|
|
199
203
|
readonly auth_recovered_newer_candidate: "Recovered a newer same-account credential for {value} from another Codex home before reload.";
|
|
200
204
|
readonly auth_refresh_all_confirm_short: "Review refresh all risk first.";
|
|
@@ -211,8 +215,10 @@ declare const MESSAGES: {
|
|
|
211
215
|
readonly auth_refresh_all_skipped: "Skipped non-ChatGPT/invalid candidates: {value}";
|
|
212
216
|
readonly auth_refresh_all_failed: "Failed: {value}";
|
|
213
217
|
readonly auth_auto_switching: "Codex auth problem detected ({error}). Switching: {from} -> {to}...";
|
|
218
|
+
readonly auth_auto_switching_quiet: "Codex auth problem detected ({error}). Selecting another maintained candidate...";
|
|
214
219
|
readonly auth_auto_recovered_current: "Codex auth problem detected ({error}). Recovered a newer same-account credential for {value} and restarted Codex app-server.";
|
|
215
220
|
readonly auth_auto_done: "Auto-switched Codex auth: {from} -> {to}.";
|
|
221
|
+
readonly auth_auto_done_quiet: "Codex auth problem handled with another maintained candidate.";
|
|
216
222
|
readonly auth_auto_retrying: "Retrying the same request with the new auth...";
|
|
217
223
|
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.";
|
|
218
224
|
readonly auth_auto_no_candidate: "Codex auth problem detected ({error}), but no unused auth candidate is available.";
|
|
@@ -263,6 +269,8 @@ declare const MESSAGES: {
|
|
|
263
269
|
readonly button_auth_filter_all: "All";
|
|
264
270
|
readonly button_auth_filter_enabled: "Enabled";
|
|
265
271
|
readonly button_auth_filter_attention: "Attention";
|
|
272
|
+
readonly button_config_auth_auto_delete_on: "Auto-delete on";
|
|
273
|
+
readonly button_config_auth_auto_delete_off: "Auto-delete off";
|
|
266
274
|
readonly another_turn_running: "Another turn is already running. Use /interrupt, /takeover, /queue, or wait.";
|
|
267
275
|
readonly working: "Working...";
|
|
268
276
|
readonly usage_open: "Usage: /open <n>";
|
|
@@ -593,6 +601,11 @@ declare const MESSAGES: {
|
|
|
593
601
|
readonly features_empty: "No features found.";
|
|
594
602
|
readonly config_title: "Config summary";
|
|
595
603
|
readonly config_layers: "Config layers: {count}";
|
|
604
|
+
readonly config_foxclaw_title: "FoxClaw runtime config";
|
|
605
|
+
readonly config_auth_auto_delete_needs_repair: "Auto-delete unrecoverable auth candidates: {value}";
|
|
606
|
+
readonly config_auth_auto_delete_updated: "Auto-delete unrecoverable auth candidates set to: {value}";
|
|
607
|
+
readonly config_auth_auto_delete_usage: "Usage: /config auth_auto_delete <on|off>";
|
|
608
|
+
readonly config_env_update_failed: "Runtime setting changed, but updating {value} failed: {error}";
|
|
596
609
|
readonly requirements_title: "Config requirements";
|
|
597
610
|
readonly requirements_empty: "No config requirements are configured.";
|
|
598
611
|
readonly provider_title: "Model provider capabilities";
|
|
@@ -862,6 +875,8 @@ declare const MESSAGES: {
|
|
|
862
875
|
readonly auth_repair_reverted: "已恢复之前的 auth。该候选仍需要修复。";
|
|
863
876
|
readonly auth_candidate_deleted: "已删除 auth 候选:{value}";
|
|
864
877
|
readonly auth_candidate_deleted_short: "auth 已删除";
|
|
878
|
+
readonly auth_candidate_auto_deleted: "已自动剔除无法恢复的 auth 候选:{value}";
|
|
879
|
+
readonly auth_pool_summary: "auth 池:历史 {total},存活 {alive},因失效剔除 {deleted}。";
|
|
865
880
|
readonly auth_delete_current_restarted: "被删除的候选是当前 auth,已在删除后重启 Codex app-server。";
|
|
866
881
|
readonly auth_no_candidates: "没有找到 auth 候选文件。请在 auth 目录中放置类似 auth.json_personal 的文件。";
|
|
867
882
|
readonly auth_choice_expired: "这个 auth 列表已经不再有效";
|
|
@@ -870,6 +885,8 @@ declare const MESSAGES: {
|
|
|
870
885
|
readonly auth_switching: "正在切换 Codex auth:{from} -> {to}...";
|
|
871
886
|
readonly auth_switch_done: "Codex auth 已切换:{from} -> {to}。";
|
|
872
887
|
readonly auth_switch_validation_failed: "选中的 auth 验证失败:{error}。已将 {value} 标记为需要登录修复。";
|
|
888
|
+
readonly auth_switch_validation_auto_deleted: "选中的 auth 验证失败:{error}。已自动剔除 {value}。";
|
|
889
|
+
readonly auth_switch_validation_auto_deleted_quiet: "选中的 auth 验证失败,已自动剔除。";
|
|
873
890
|
readonly auth_switch_validation_reverted: "已在切换失败后恢复到之前的 auth。";
|
|
874
891
|
readonly auth_recovered_newer_candidate: "重载前已从其他 Codex home 恢复 {value} 的同账号较新凭据。";
|
|
875
892
|
readonly auth_refresh_all_confirm_short: "请先确认刷新全部风险。";
|
|
@@ -886,8 +903,10 @@ declare const MESSAGES: {
|
|
|
886
903
|
readonly auth_refresh_all_skipped: "已跳过非 ChatGPT/无效候选:{value}";
|
|
887
904
|
readonly auth_refresh_all_failed: "失败:{value}";
|
|
888
905
|
readonly auth_auto_switching: "检测到 Codex auth 问题({error}),正在切换:{from} -> {to}...";
|
|
906
|
+
readonly auth_auto_switching_quiet: "检测到 Codex auth 问题({error}),正在选择另一个维护中的候选...";
|
|
889
907
|
readonly auth_auto_recovered_current: "检测到 Codex auth 问题({error}),已为 {value} 恢复同账号较新凭据并重启 Codex app-server。";
|
|
890
908
|
readonly auth_auto_done: "已自动切换 Codex auth:{from} -> {to}。";
|
|
909
|
+
readonly auth_auto_done_quiet: "Codex auth 问题已用另一个维护中的候选处理。";
|
|
891
910
|
readonly auth_auto_retrying: "正在用新的 auth 重试同一条请求...";
|
|
892
911
|
readonly auth_auto_retry_thread_missing: "Auth 已切换,但原线程已经不可用({threadId})。已停止重试,避免创建重复 session。";
|
|
893
912
|
readonly auth_auto_no_candidate: "检测到 Codex auth 问题({error}),但没有可用的未失败候选 auth。";
|
|
@@ -938,6 +957,8 @@ declare const MESSAGES: {
|
|
|
938
957
|
readonly button_auth_filter_all: "全部";
|
|
939
958
|
readonly button_auth_filter_enabled: "已启用";
|
|
940
959
|
readonly button_auth_filter_attention: "需关注";
|
|
960
|
+
readonly button_config_auth_auto_delete_on: "开启自动剔除";
|
|
961
|
+
readonly button_config_auth_auto_delete_off: "关闭自动剔除";
|
|
941
962
|
readonly another_turn_running: "已经有一个回复在进行中。请先等待,或使用 /interrupt、/takeover、/queue。";
|
|
942
963
|
readonly working: "处理中...";
|
|
943
964
|
readonly usage_open: "用法:/open <编号>";
|
|
@@ -1268,6 +1289,11 @@ declare const MESSAGES: {
|
|
|
1268
1289
|
readonly features_empty: "没有找到功能开关。";
|
|
1269
1290
|
readonly config_title: "配置摘要";
|
|
1270
1291
|
readonly config_layers: "配置层数:{count}";
|
|
1292
|
+
readonly config_foxclaw_title: "FoxClaw 运行时配置";
|
|
1293
|
+
readonly config_auth_auto_delete_needs_repair: "自动剔除无法恢复的 auth 候选:{value}";
|
|
1294
|
+
readonly config_auth_auto_delete_updated: "自动剔除无法恢复的 auth 候选已设置为:{value}";
|
|
1295
|
+
readonly config_auth_auto_delete_usage: "用法:/config auth_auto_delete <on|off>";
|
|
1296
|
+
readonly config_env_update_failed: "运行时设置已改变,但更新 {value} 失败:{error}";
|
|
1271
1297
|
readonly requirements_title: "配置要求";
|
|
1272
1298
|
readonly requirements_empty: "当前没有配置要求。";
|
|
1273
1299
|
readonly provider_title: "模型供应商能力";
|
package/dist/i18n.js
CHANGED
|
@@ -185,6 +185,8 @@ const MESSAGES = {
|
|
|
185
185
|
auth_repair_reverted: 'Restored previous auth. The candidate still needs repair.',
|
|
186
186
|
auth_candidate_deleted: 'Deleted auth candidate: {value}',
|
|
187
187
|
auth_candidate_deleted_short: 'Auth deleted',
|
|
188
|
+
auth_candidate_auto_deleted: 'Auto-deleted unrecoverable auth candidate: {value}',
|
|
189
|
+
auth_pool_summary: 'Auth pool: total seen {total}, alive {alive}, invalid-deleted {deleted}.',
|
|
188
190
|
auth_delete_current_restarted: 'The deleted candidate was current, so Codex app-server was restarted without it.',
|
|
189
191
|
auth_no_candidates: 'No auth candidates found. Expected files like auth.json_personal in the auth dir.',
|
|
190
192
|
auth_choice_expired: 'This auth list is no longer active',
|
|
@@ -193,6 +195,8 @@ const MESSAGES = {
|
|
|
193
195
|
auth_switching: 'Switching Codex auth: {from} -> {to}...',
|
|
194
196
|
auth_switch_done: 'Codex auth switched: {from} -> {to}.',
|
|
195
197
|
auth_switch_validation_failed: 'Selected auth failed validation: {error}. Marked {value} for login repair.',
|
|
198
|
+
auth_switch_validation_auto_deleted: 'Selected auth failed validation: {error}. Auto-deleted {value}.',
|
|
199
|
+
auth_switch_validation_auto_deleted_quiet: 'Selected auth failed validation and was auto-deleted.',
|
|
196
200
|
auth_switch_validation_reverted: 'Restored the previous auth after the failed switch.',
|
|
197
201
|
auth_recovered_newer_candidate: 'Recovered a newer same-account credential for {value} from another Codex home before reload.',
|
|
198
202
|
auth_refresh_all_confirm_short: 'Review refresh all risk first.',
|
|
@@ -209,8 +213,10 @@ const MESSAGES = {
|
|
|
209
213
|
auth_refresh_all_skipped: 'Skipped non-ChatGPT/invalid candidates: {value}',
|
|
210
214
|
auth_refresh_all_failed: 'Failed: {value}',
|
|
211
215
|
auth_auto_switching: 'Codex auth problem detected ({error}). Switching: {from} -> {to}...',
|
|
216
|
+
auth_auto_switching_quiet: 'Codex auth problem detected ({error}). Selecting another maintained candidate...',
|
|
212
217
|
auth_auto_recovered_current: 'Codex auth problem detected ({error}). Recovered a newer same-account credential for {value} and restarted Codex app-server.',
|
|
213
218
|
auth_auto_done: 'Auto-switched Codex auth: {from} -> {to}.',
|
|
219
|
+
auth_auto_done_quiet: 'Codex auth problem handled with another maintained candidate.',
|
|
214
220
|
auth_auto_retrying: 'Retrying the same request with the new auth...',
|
|
215
221
|
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.',
|
|
216
222
|
auth_auto_no_candidate: 'Codex auth problem detected ({error}), but no unused auth candidate is available.',
|
|
@@ -261,6 +267,8 @@ const MESSAGES = {
|
|
|
261
267
|
button_auth_filter_all: 'All',
|
|
262
268
|
button_auth_filter_enabled: 'Enabled',
|
|
263
269
|
button_auth_filter_attention: 'Attention',
|
|
270
|
+
button_config_auth_auto_delete_on: 'Auto-delete on',
|
|
271
|
+
button_config_auth_auto_delete_off: 'Auto-delete off',
|
|
264
272
|
another_turn_running: 'Another turn is already running. Use /interrupt, /takeover, /queue, or wait.',
|
|
265
273
|
working: 'Working...',
|
|
266
274
|
usage_open: 'Usage: /open <n>',
|
|
@@ -591,6 +599,11 @@ const MESSAGES = {
|
|
|
591
599
|
features_empty: 'No features found.',
|
|
592
600
|
config_title: 'Config summary',
|
|
593
601
|
config_layers: 'Config layers: {count}',
|
|
602
|
+
config_foxclaw_title: 'FoxClaw runtime config',
|
|
603
|
+
config_auth_auto_delete_needs_repair: 'Auto-delete unrecoverable auth candidates: {value}',
|
|
604
|
+
config_auth_auto_delete_updated: 'Auto-delete unrecoverable auth candidates set to: {value}',
|
|
605
|
+
config_auth_auto_delete_usage: 'Usage: /config auth_auto_delete <on|off>',
|
|
606
|
+
config_env_update_failed: 'Runtime setting changed, but updating {value} failed: {error}',
|
|
594
607
|
requirements_title: 'Config requirements',
|
|
595
608
|
requirements_empty: 'No config requirements are configured.',
|
|
596
609
|
provider_title: 'Model provider capabilities',
|
|
@@ -860,6 +873,8 @@ const MESSAGES = {
|
|
|
860
873
|
auth_repair_reverted: '已恢复之前的 auth。该候选仍需要修复。',
|
|
861
874
|
auth_candidate_deleted: '已删除 auth 候选:{value}',
|
|
862
875
|
auth_candidate_deleted_short: 'auth 已删除',
|
|
876
|
+
auth_candidate_auto_deleted: '已自动剔除无法恢复的 auth 候选:{value}',
|
|
877
|
+
auth_pool_summary: 'auth 池:历史 {total},存活 {alive},因失效剔除 {deleted}。',
|
|
863
878
|
auth_delete_current_restarted: '被删除的候选是当前 auth,已在删除后重启 Codex app-server。',
|
|
864
879
|
auth_no_candidates: '没有找到 auth 候选文件。请在 auth 目录中放置类似 auth.json_personal 的文件。',
|
|
865
880
|
auth_choice_expired: '这个 auth 列表已经不再有效',
|
|
@@ -868,6 +883,8 @@ const MESSAGES = {
|
|
|
868
883
|
auth_switching: '正在切换 Codex auth:{from} -> {to}...',
|
|
869
884
|
auth_switch_done: 'Codex auth 已切换:{from} -> {to}。',
|
|
870
885
|
auth_switch_validation_failed: '选中的 auth 验证失败:{error}。已将 {value} 标记为需要登录修复。',
|
|
886
|
+
auth_switch_validation_auto_deleted: '选中的 auth 验证失败:{error}。已自动剔除 {value}。',
|
|
887
|
+
auth_switch_validation_auto_deleted_quiet: '选中的 auth 验证失败,已自动剔除。',
|
|
871
888
|
auth_switch_validation_reverted: '已在切换失败后恢复到之前的 auth。',
|
|
872
889
|
auth_recovered_newer_candidate: '重载前已从其他 Codex home 恢复 {value} 的同账号较新凭据。',
|
|
873
890
|
auth_refresh_all_confirm_short: '请先确认刷新全部风险。',
|
|
@@ -884,8 +901,10 @@ const MESSAGES = {
|
|
|
884
901
|
auth_refresh_all_skipped: '已跳过非 ChatGPT/无效候选:{value}',
|
|
885
902
|
auth_refresh_all_failed: '失败:{value}',
|
|
886
903
|
auth_auto_switching: '检测到 Codex auth 问题({error}),正在切换:{from} -> {to}...',
|
|
904
|
+
auth_auto_switching_quiet: '检测到 Codex auth 问题({error}),正在选择另一个维护中的候选...',
|
|
887
905
|
auth_auto_recovered_current: '检测到 Codex auth 问题({error}),已为 {value} 恢复同账号较新凭据并重启 Codex app-server。',
|
|
888
906
|
auth_auto_done: '已自动切换 Codex auth:{from} -> {to}。',
|
|
907
|
+
auth_auto_done_quiet: 'Codex auth 问题已用另一个维护中的候选处理。',
|
|
889
908
|
auth_auto_retrying: '正在用新的 auth 重试同一条请求...',
|
|
890
909
|
auth_auto_retry_thread_missing: 'Auth 已切换,但原线程已经不可用({threadId})。已停止重试,避免创建重复 session。',
|
|
891
910
|
auth_auto_no_candidate: '检测到 Codex auth 问题({error}),但没有可用的未失败候选 auth。',
|
|
@@ -936,6 +955,8 @@ const MESSAGES = {
|
|
|
936
955
|
button_auth_filter_all: '全部',
|
|
937
956
|
button_auth_filter_enabled: '已启用',
|
|
938
957
|
button_auth_filter_attention: '需关注',
|
|
958
|
+
button_config_auth_auto_delete_on: '开启自动剔除',
|
|
959
|
+
button_config_auth_auto_delete_off: '关闭自动剔除',
|
|
939
960
|
another_turn_running: '已经有一个回复在进行中。请先等待,或使用 /interrupt、/takeover、/queue。',
|
|
940
961
|
working: '处理中...',
|
|
941
962
|
usage_open: '用法:/open <编号>',
|
|
@@ -1266,6 +1287,11 @@ const MESSAGES = {
|
|
|
1266
1287
|
features_empty: '没有找到功能开关。',
|
|
1267
1288
|
config_title: '配置摘要',
|
|
1268
1289
|
config_layers: '配置层数:{count}',
|
|
1290
|
+
config_foxclaw_title: 'FoxClaw 运行时配置',
|
|
1291
|
+
config_auth_auto_delete_needs_repair: '自动剔除无法恢复的 auth 候选:{value}',
|
|
1292
|
+
config_auth_auto_delete_updated: '自动剔除无法恢复的 auth 候选已设置为:{value}',
|
|
1293
|
+
config_auth_auto_delete_usage: '用法:/config auth_auto_delete <on|off>',
|
|
1294
|
+
config_env_update_failed: '运行时设置已改变,但更新 {value} 失败:{error}',
|
|
1269
1295
|
requirements_title: '配置要求',
|
|
1270
1296
|
requirements_empty: '当前没有配置要求。',
|
|
1271
1297
|
provider_title: '模型供应商能力',
|