@foxden-app/foxclaw 0.5.35 → 0.5.37
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.js +137 -9
- package/dist/i18n.d.ts +2 -0
- package/dist/i18n.js +2 -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.37 - 2026-06-17
|
|
6
|
+
|
|
7
|
+
### 中文
|
|
8
|
+
- 精简 `/auth` RichMessage 表格的 `Quota A` / `Quota B` 单元格:只显示剩余额度百分比,例如 `26%`,窗口含义交给列和后续套餐/状态信息理解。
|
|
9
|
+
- 保持原始文本 fallback 和按钮文本不变,避免影响旧命令、微信复制路径和 auth 切换回调。
|
|
10
|
+
|
|
11
|
+
### English
|
|
12
|
+
- Simplified the `/auth` RichMessage `Quota A` / `Quota B` cells to show only the remaining percentage, such as `26%`, leaving the window context to the columns and following plan/status fields.
|
|
13
|
+
- Kept the plain-text fallback and button labels unchanged so legacy commands, Weixin copy-paste flows, and auth switching callbacks remain stable.
|
|
14
|
+
|
|
15
|
+
## 0.5.36 - 2026-06-17
|
|
16
|
+
|
|
17
|
+
### 中文
|
|
18
|
+
- `/auth` 的 Telegram RichMessage 候选区从旧的竖线分隔文本整理为横向表格,拆出额度窗口、auth 名称、当前标记、套餐、健康状态、最近刷新、过期时间和风险提示列。
|
|
19
|
+
- ChatGPT auth 候选会从 access token `exp` 读取过期时间,并以 UTC `YYYY-MM-DD HH:mmZ` 展示;拿不到过期字段时保持 `-`,原始文本 fallback 仍可展开查看。
|
|
20
|
+
|
|
21
|
+
### English
|
|
22
|
+
- Refined the Telegram RichMessage `/auth` candidate area from pipe-delimited text into a horizontally scrollable table with quota windows, auth name, current marker, plan, health, last refresh, expiry, and risk columns.
|
|
23
|
+
- ChatGPT auth candidates now read the access token `exp` value and display expiry as UTC `YYYY-MM-DD HH:mmZ`; candidates without an expiry field show `-`, with the expandable plain-text fallback preserved.
|
|
24
|
+
|
|
5
25
|
## 0.5.35 - 2026-06-17
|
|
6
26
|
|
|
7
27
|
### 中文
|
|
@@ -3,7 +3,8 @@ 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 { chatGptAuthMetadataMatchesCandidateName, parseChatGptAuthMetadata, readChatGptAuthMetadata, } from '../auth/mirror.js';
|
|
6
|
+
import { chatGptAuthMetadataMatchesCandidateName, parseChatGptAuthMetadata, readChatGptAuthRecord, readChatGptAuthMetadata, } from '../auth/mirror.js';
|
|
7
|
+
import { readAccessTokenExpiresAtMs } from '../auth/cross_node_sync.js';
|
|
7
8
|
import { parseCommand } from './commands.js';
|
|
8
9
|
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
10
|
import { clampServiceTierToModel, resolveFastTierForModel } from './service_tier.js';
|
|
@@ -6234,19 +6235,20 @@ export class BridgeSessionCore {
|
|
|
6234
6235
|
}
|
|
6235
6236
|
async readCodexAuthCandidateQuotaIdentities(candidates) {
|
|
6236
6237
|
const entries = await Promise.all(candidates.map(async (candidate) => {
|
|
6237
|
-
const
|
|
6238
|
-
const metadataMatchesName =
|
|
6239
|
-
? chatGptAuthMetadataMatchesCandidateName(candidate.name,
|
|
6238
|
+
const record = await readChatGptAuthRecord(candidate.path);
|
|
6239
|
+
const metadataMatchesName = record
|
|
6240
|
+
? chatGptAuthMetadataMatchesCandidateName(candidate.name, record)
|
|
6240
6241
|
: false;
|
|
6241
|
-
candidate.credentialKind =
|
|
6242
|
+
candidate.credentialKind = record && metadataMatchesName
|
|
6242
6243
|
? 'chatgpt'
|
|
6243
6244
|
: await isCodexApiKeyAuthCandidate(candidate.path)
|
|
6244
6245
|
? 'api-key'
|
|
6245
6246
|
: 'invalid';
|
|
6246
|
-
candidate.credentialLastRefreshMs =
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6247
|
+
candidate.credentialLastRefreshMs = record && metadataMatchesName ? record.lastRefreshMs : null;
|
|
6248
|
+
candidate.credentialExpiresAtMs = record && metadataMatchesName ? readAccessTokenExpiresAtMs(record.raw) : null;
|
|
6249
|
+
return [candidate.name, record && metadataMatchesName ? {
|
|
6250
|
+
accountId: record.accountId,
|
|
6251
|
+
quotaIdentityId: record.quotaIdentityId,
|
|
6250
6252
|
} : null];
|
|
6251
6253
|
}));
|
|
6252
6254
|
const quotaIdentities = new Map();
|
|
@@ -8479,6 +8481,19 @@ function splitRichInternalSections(text) {
|
|
|
8479
8481
|
return sections;
|
|
8480
8482
|
}
|
|
8481
8483
|
function formatRichInternalSection(lines) {
|
|
8484
|
+
const candidateRows = lines
|
|
8485
|
+
.map(parseRichAuthCandidateRow)
|
|
8486
|
+
.filter((row) => row !== null);
|
|
8487
|
+
if (candidateRows.length > 0) {
|
|
8488
|
+
const nonCandidateLines = lines.filter(line => parseRichAuthCandidateRow(line) === null);
|
|
8489
|
+
return [
|
|
8490
|
+
nonCandidateLines.length > 0 ? formatRichInternalSectionWithoutCandidates(nonCandidateLines) : '',
|
|
8491
|
+
formatRichAuthCandidateTable(candidateRows),
|
|
8492
|
+
].filter(Boolean).join('\n');
|
|
8493
|
+
}
|
|
8494
|
+
return formatRichInternalSectionWithoutCandidates(lines);
|
|
8495
|
+
}
|
|
8496
|
+
function formatRichInternalSectionWithoutCandidates(lines) {
|
|
8482
8497
|
const rows = lines
|
|
8483
8498
|
.map(parseRichInternalKeyValue)
|
|
8484
8499
|
.filter((row) => row !== null);
|
|
@@ -8495,6 +8510,107 @@ function formatRichInternalSection(lines) {
|
|
|
8495
8510
|
'</ul>',
|
|
8496
8511
|
].join('\n');
|
|
8497
8512
|
}
|
|
8513
|
+
function parseRichAuthCandidateRow(line) {
|
|
8514
|
+
const match = line.match(/^\s*(\d+)\.\s+(.+)$/);
|
|
8515
|
+
if (!match) {
|
|
8516
|
+
return null;
|
|
8517
|
+
}
|
|
8518
|
+
let body = match[2].trim();
|
|
8519
|
+
let status = '';
|
|
8520
|
+
const statusMatch = body.match(/^(.*?)\s+(\[[^\]]+\])$/);
|
|
8521
|
+
if (statusMatch) {
|
|
8522
|
+
body = statusMatch[1].trim();
|
|
8523
|
+
status = statusMatch[2].replace(/^\[|\]$/g, '');
|
|
8524
|
+
}
|
|
8525
|
+
const current = body.endsWith(' *');
|
|
8526
|
+
if (current) {
|
|
8527
|
+
body = body.slice(0, -2).trimEnd();
|
|
8528
|
+
}
|
|
8529
|
+
const parts = body.split('|');
|
|
8530
|
+
const name = parts.pop()?.trim() ?? '';
|
|
8531
|
+
if (!name) {
|
|
8532
|
+
return null;
|
|
8533
|
+
}
|
|
8534
|
+
const quotas = parts.map(formatRichAuthQuotaCell);
|
|
8535
|
+
const statusParts = splitRichAuthStatus(status);
|
|
8536
|
+
return {
|
|
8537
|
+
index: match[1],
|
|
8538
|
+
quotaA: quotas[0] ?? '-',
|
|
8539
|
+
quotaB: quotas[1] ?? '-',
|
|
8540
|
+
name,
|
|
8541
|
+
current,
|
|
8542
|
+
...statusParts,
|
|
8543
|
+
};
|
|
8544
|
+
}
|
|
8545
|
+
function formatRichAuthCandidateTable(rows) {
|
|
8546
|
+
return [
|
|
8547
|
+
'<table bordered striped>',
|
|
8548
|
+
'<tr><th>#</th><th>Quota A</th><th>Quota B</th><th>Auth</th><th>Current</th><th>Plan</th><th>Health</th><th>Last refresh</th><th>Expiry</th><th>Risk</th></tr>',
|
|
8549
|
+
...rows.map(row => [
|
|
8550
|
+
'<tr>',
|
|
8551
|
+
`<td>${escapeTelegramHtml(row.index)}</td>`,
|
|
8552
|
+
`<td>${escapeTelegramHtml(row.quotaA)}</td>`,
|
|
8553
|
+
`<td>${escapeTelegramHtml(row.quotaB)}</td>`,
|
|
8554
|
+
`<td>${escapeTelegramHtml(row.name)}</td>`,
|
|
8555
|
+
`<td>${row.current ? 'yes' : '-'}</td>`,
|
|
8556
|
+
`<td>${escapeTelegramHtml(row.plan)}</td>`,
|
|
8557
|
+
`<td>${escapeTelegramHtml(row.health)}</td>`,
|
|
8558
|
+
`<td>${escapeTelegramHtml(row.refresh)}</td>`,
|
|
8559
|
+
`<td>${escapeTelegramHtml(row.expiry)}</td>`,
|
|
8560
|
+
`<td>${escapeTelegramHtml(row.risk)}</td>`,
|
|
8561
|
+
'</tr>',
|
|
8562
|
+
].join('')),
|
|
8563
|
+
'</table>',
|
|
8564
|
+
].join('\n');
|
|
8565
|
+
}
|
|
8566
|
+
function splitRichAuthStatus(status) {
|
|
8567
|
+
const parts = status.split(' · ').map(part => part.trim()).filter(Boolean);
|
|
8568
|
+
const refreshIndex = parts.findIndex(part => /^(refreshed|刷新于)\b/i.test(part));
|
|
8569
|
+
const refresh = refreshIndex >= 0 ? parts.splice(refreshIndex, 1)[0] : '-';
|
|
8570
|
+
const expiryIndex = parts.findIndex(part => /^(expires|过期于)\b/i.test(part));
|
|
8571
|
+
const expiry = expiryIndex >= 0 ? parts.splice(expiryIndex, 1)[0] : '-';
|
|
8572
|
+
const plan = parts.length > 1 ? parts.shift() : '-';
|
|
8573
|
+
const health = parts.shift() ?? '-';
|
|
8574
|
+
return {
|
|
8575
|
+
plan,
|
|
8576
|
+
health,
|
|
8577
|
+
refresh,
|
|
8578
|
+
expiry,
|
|
8579
|
+
risk: formatRichAuthRisk(health),
|
|
8580
|
+
};
|
|
8581
|
+
}
|
|
8582
|
+
function formatRichAuthRisk(health) {
|
|
8583
|
+
if (/not recently refreshed|长期未刷新/i.test(health)) {
|
|
8584
|
+
return `stale >${CODEX_AUTH_STALE_CREDENTIAL_DAYS}d`;
|
|
8585
|
+
}
|
|
8586
|
+
if (/needs login repair|需要登录修复/i.test(health)) {
|
|
8587
|
+
return 'repair';
|
|
8588
|
+
}
|
|
8589
|
+
if (/quota exhausted|额度耗尽/i.test(health)) {
|
|
8590
|
+
return 'quota exhausted';
|
|
8591
|
+
}
|
|
8592
|
+
if (/invalid|无效/i.test(health)) {
|
|
8593
|
+
return 'invalid';
|
|
8594
|
+
}
|
|
8595
|
+
if (/\blow\b|偏低|低/i.test(health)) {
|
|
8596
|
+
return 'low quota';
|
|
8597
|
+
}
|
|
8598
|
+
if (/unknown|未知/i.test(health)) {
|
|
8599
|
+
return 'unknown';
|
|
8600
|
+
}
|
|
8601
|
+
return '-';
|
|
8602
|
+
}
|
|
8603
|
+
function formatRichAuthQuotaCell(value) {
|
|
8604
|
+
const trimmed = value.trim();
|
|
8605
|
+
if (!trimmed || trimmed === '--' || trimmed === '—') {
|
|
8606
|
+
return '-';
|
|
8607
|
+
}
|
|
8608
|
+
const [windowLabel, percent] = trimmed.split(':');
|
|
8609
|
+
if (!windowLabel || percent === undefined) {
|
|
8610
|
+
return trimmed;
|
|
8611
|
+
}
|
|
8612
|
+
return `${percent}%`;
|
|
8613
|
+
}
|
|
8498
8614
|
function parseRichInternalKeyValue(line) {
|
|
8499
8615
|
const separator = line.includes(':') ? ':' : ':';
|
|
8500
8616
|
const index = line.indexOf(separator);
|
|
@@ -9269,6 +9385,7 @@ async function listCodexAuthState(disabledNames = new Set(), candidateStates = n
|
|
|
9269
9385
|
mtimeMs: stat.mtimeMs,
|
|
9270
9386
|
credentialKind: 'invalid',
|
|
9271
9387
|
credentialLastRefreshMs: null,
|
|
9388
|
+
credentialExpiresAtMs: null,
|
|
9272
9389
|
quota: null,
|
|
9273
9390
|
});
|
|
9274
9391
|
}
|
|
@@ -9613,6 +9730,11 @@ function formatCodexAuthCandidateStatus(locale, candidate) {
|
|
|
9613
9730
|
value: formatCompactAge(locale, Date.now() - candidate.credentialLastRefreshMs),
|
|
9614
9731
|
}));
|
|
9615
9732
|
}
|
|
9733
|
+
if (candidate.credentialExpiresAtMs !== null) {
|
|
9734
|
+
details.push(t(locale, 'auth_candidate_expires_at', {
|
|
9735
|
+
value: formatUtcDateTime(candidate.credentialExpiresAtMs),
|
|
9736
|
+
}));
|
|
9737
|
+
}
|
|
9616
9738
|
return `[${details.join(' · ')}]`;
|
|
9617
9739
|
}
|
|
9618
9740
|
function formatCodexAuthFilter(locale, filter) {
|
|
@@ -10459,6 +10581,12 @@ function formatCompactAge(locale, ageMs) {
|
|
|
10459
10581
|
const roundedMinutes = Math.floor(minutes);
|
|
10460
10582
|
return locale === 'zh' ? `${roundedMinutes}分钟前` : `${roundedMinutes}m ago`;
|
|
10461
10583
|
}
|
|
10584
|
+
function formatUtcDateTime(timestampMs) {
|
|
10585
|
+
if (!Number.isFinite(timestampMs)) {
|
|
10586
|
+
return '-';
|
|
10587
|
+
}
|
|
10588
|
+
return `${new Date(timestampMs).toISOString().slice(0, 16).replace('T', ' ')}Z`;
|
|
10589
|
+
}
|
|
10462
10590
|
function formatUsagePercent(value) {
|
|
10463
10591
|
if (!Number.isFinite(value)) {
|
|
10464
10592
|
return '?';
|
package/dist/i18n.d.ts
CHANGED
|
@@ -173,6 +173,7 @@ declare const MESSAGES: {
|
|
|
173
173
|
readonly 'auth_candidate_health_api-key': "API key";
|
|
174
174
|
readonly auth_candidate_health_invalid: "invalid auth file";
|
|
175
175
|
readonly auth_candidate_last_refresh: "refreshed {value}";
|
|
176
|
+
readonly auth_candidate_expires_at: "expires {value}";
|
|
176
177
|
readonly auth_candidate_enabled: "Enabled auth candidate for auto-rotation: {value}";
|
|
177
178
|
readonly auth_candidate_disabled: "Disabled auth candidate for auto-rotation: {value}";
|
|
178
179
|
readonly auth_candidate_enabled_short: "Auth enabled";
|
|
@@ -871,6 +872,7 @@ declare const MESSAGES: {
|
|
|
871
872
|
readonly 'auth_candidate_health_api-key': "API key";
|
|
872
873
|
readonly auth_candidate_health_invalid: "auth 文件无效";
|
|
873
874
|
readonly auth_candidate_last_refresh: "刷新于 {value}";
|
|
875
|
+
readonly auth_candidate_expires_at: "过期于 {value}";
|
|
874
876
|
readonly auth_candidate_enabled: "已启用 auth 候选自动轮换:{value}";
|
|
875
877
|
readonly auth_candidate_disabled: "已禁用 auth 候选自动轮换:{value}";
|
|
876
878
|
readonly auth_candidate_enabled_short: "auth 已启用";
|
package/dist/i18n.js
CHANGED
|
@@ -171,6 +171,7 @@ const MESSAGES = {
|
|
|
171
171
|
'auth_candidate_health_api-key': 'API key',
|
|
172
172
|
auth_candidate_health_invalid: 'invalid auth file',
|
|
173
173
|
auth_candidate_last_refresh: 'refreshed {value}',
|
|
174
|
+
auth_candidate_expires_at: 'expires {value}',
|
|
174
175
|
auth_candidate_enabled: 'Enabled auth candidate for auto-rotation: {value}',
|
|
175
176
|
auth_candidate_disabled: 'Disabled auth candidate for auto-rotation: {value}',
|
|
176
177
|
auth_candidate_enabled_short: 'Auth enabled',
|
|
@@ -869,6 +870,7 @@ const MESSAGES = {
|
|
|
869
870
|
'auth_candidate_health_api-key': 'API key',
|
|
870
871
|
auth_candidate_health_invalid: 'auth 文件无效',
|
|
871
872
|
auth_candidate_last_refresh: '刷新于 {value}',
|
|
873
|
+
auth_candidate_expires_at: '过期于 {value}',
|
|
872
874
|
auth_candidate_enabled: '已启用 auth 候选自动轮换:{value}',
|
|
873
875
|
auth_candidate_disabled: '已禁用 auth 候选自动轮换:{value}',
|
|
874
876
|
auth_candidate_enabled_short: 'auth 已启用',
|