@foxden-app/foxclaw 0.4.5 → 0.4.7
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 +195 -0
- package/README.md +2 -1
- package/README_EN.md +2 -1
- package/dist/codex_app/client.d.ts +1 -1
- package/dist/codex_app/client.js +2 -2
- package/dist/controller/controller.d.ts +5 -0
- package/dist/controller/controller.js +185 -8
- package/dist/i18n.d.ts +16 -2
- package/dist/i18n.js +16 -2
- package/docs/user-manual.md +4 -3
- package/docs/zh/user-manual.md +4 -3
- package/package.json +2 -1
- package/skills/foxclaw/SKILL.md +6 -0
- package/skills/npm-publish/SKILL.md +2 -0
- package/skills/npm-publish/agents/openai.yaml +2 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
## 0.4.7 - 2026-06-02
|
|
6
|
+
|
|
7
|
+
### 中文
|
|
8
|
+
- `/auth` 面板新增“刷新全部”按钮,并支持 `/auth refresh all`。
|
|
9
|
+
- 刷新全部仅在所有 Telegram runtime、微信 runtime、审批、待输入、登录流程和 auth 镜像写入都空闲时执行。
|
|
10
|
+
- 执行时逐个切换 ChatGPT 候选,调用 Codex `account/read refreshToken=true` 主动刷新,使用 usage 接口验证后再镜像同步,并在结束后恢复原当前 auth、刷新面板摘要。
|
|
11
|
+
- 更新 FoxClaw/npm 发布 skill:用户要求收尾或发布时,校验通过后自动 commit、push、publish,只在真实外部阻塞时再等待用户。
|
|
12
|
+
|
|
13
|
+
### English
|
|
14
|
+
- Added a Refresh all button to the `/auth` panel and the `/auth refresh all` command.
|
|
15
|
+
- Refresh all runs only when every Telegram runtime, the Weixin runtime, approvals, inputs, login flows, and auth mirror writes are idle.
|
|
16
|
+
- It visits each ChatGPT candidate, calls Codex `account/read refreshToken=true`, validates through the usage endpoint before mirroring, restores the original current auth, and refreshes the panel summary.
|
|
17
|
+
- Updated the FoxClaw/npm publish skills so release wrap-up proceeds through commit, push, and publish after validation, pausing only for real external blockers.
|
|
18
|
+
|
|
19
|
+
## 0.4.6 - 2026-06-01
|
|
20
|
+
|
|
21
|
+
### 中文
|
|
22
|
+
- 新增公开 `CHANGELOG.md`,让 GitHub 项目首页和 npm 包都能直接看到版本更新记录。
|
|
23
|
+
- README 增加更新日志入口,并将 `CHANGELOG.md` 纳入 npm 发布包。
|
|
24
|
+
- 发布 workflow 增加 GitHub Release 创建/更新步骤,后续 tag 发布会自动带上对应版本说明。
|
|
25
|
+
|
|
26
|
+
### English
|
|
27
|
+
- Added a public `CHANGELOG.md` so release notes are visible from GitHub and the npm package.
|
|
28
|
+
- Linked the changelog from the README and included it in the npm package contents.
|
|
29
|
+
- Added GitHub Release creation/update to the publish workflow so future version tags carry release notes automatically.
|
|
30
|
+
|
|
31
|
+
## 0.4.5 - 2026-06-01
|
|
32
|
+
|
|
33
|
+
### 中文
|
|
34
|
+
- `/auth` 面板点击切换账号后会刷新原消息并保留按钮,方便连续切换多个候选账号。
|
|
35
|
+
- 回归测试覆盖同一个面板上连续切换的行为。
|
|
36
|
+
|
|
37
|
+
### English
|
|
38
|
+
- The `/auth` panel now refreshes the original message and keeps its buttons after switching accounts, making consecutive switches practical.
|
|
39
|
+
- Added regression coverage for repeated switches from the same panel.
|
|
40
|
+
|
|
41
|
+
## 0.4.4 - 2026-06-01
|
|
42
|
+
|
|
43
|
+
### 中文
|
|
44
|
+
- 多 bot 模式下,切换或重载认证前会从其他 bot home 和规范凭据目录恢复同账号的较新可用候选。
|
|
45
|
+
- 认证同步前增加在线 usage 验证,避免只看 `last_refresh` 时传播已经失效的凭据。
|
|
46
|
+
- 登录/刷新成功后继续将同账号新凭据镜像到其他 runtime,解决某个 bot 仍拿到旧 401 凭据的问题。
|
|
47
|
+
|
|
48
|
+
### English
|
|
49
|
+
- In multi-bot mode, auth switch/reload now recovers a newer same-account candidate from other bot homes or the canonical auth directory before restart.
|
|
50
|
+
- Added online usage validation before syncing refreshed credentials, avoiding propagation based only on `last_refresh`.
|
|
51
|
+
- Successful login/refresh still mirrors the validated same-account credential to other runtimes, fixing stale 401 credentials in sibling bots.
|
|
52
|
+
|
|
53
|
+
## 0.4.3 - 2026-05-29
|
|
54
|
+
|
|
55
|
+
### 中文
|
|
56
|
+
- `/auth` 在多 bot 模式下会按 ChatGPT account id 汇总各 runtime 最近掌握的额度快照。
|
|
57
|
+
- 不同 bot 使用不同 auth 时,面板可以互补展示更完整的 5 小时/7 天额度信息。
|
|
58
|
+
|
|
59
|
+
### English
|
|
60
|
+
- `/auth` now merges recent quota snapshots by ChatGPT account id across runtimes in multi-bot mode.
|
|
61
|
+
- When different bots use different auth candidates, the panel can present a more complete 5-hour/7-day quota view.
|
|
62
|
+
|
|
63
|
+
## 0.4.2 - 2026-05-28
|
|
64
|
+
|
|
65
|
+
### 中文
|
|
66
|
+
- 支持把 `TG_BOT_TOKENS` 中的一个 bot 标记为默认/终端共享 runtime。
|
|
67
|
+
- 当 `TG_BOT_TOKEN` 精确匹配复数 token 中的一个值时,该 bot 使用终端默认 `CODEX_HOME` 和默认 auth,其他 bot 继续隔离。
|
|
68
|
+
- 文档补充共享 runtime 的配置方式和注意事项。
|
|
69
|
+
|
|
70
|
+
### English
|
|
71
|
+
- Added support for marking one `TG_BOT_TOKENS` bot as the default/shared-terminal runtime.
|
|
72
|
+
- When `TG_BOT_TOKEN` exactly matches one token in the multi-token list, that bot uses the terminal/default `CODEX_HOME` and auth while the others stay isolated.
|
|
73
|
+
- Updated docs with shared-runtime setup guidance.
|
|
74
|
+
|
|
75
|
+
## 0.4.1 - 2026-05-28
|
|
76
|
+
|
|
77
|
+
### 中文
|
|
78
|
+
- 认证候选刷新后,镜像同步前会校验同账号和可用性,降低错误凭据扩散风险。
|
|
79
|
+
- 增加认证刷新验证相关测试。
|
|
80
|
+
|
|
81
|
+
### English
|
|
82
|
+
- Refreshed auth candidates are validated for same-account usability before mirroring, reducing the risk of spreading bad credentials.
|
|
83
|
+
- Added tests for refreshed auth validation.
|
|
84
|
+
|
|
85
|
+
## 0.4.0 - 2026-05-27
|
|
86
|
+
|
|
87
|
+
### 中文
|
|
88
|
+
- 完善多 Telegram bot 隔离 runtime 的状态展示、验收路径和文档。
|
|
89
|
+
- 补充多 bot auth 镜像、独立 `CODEX_HOME`、群聊点名规则和全局 `/update` 空闲检查说明。
|
|
90
|
+
|
|
91
|
+
### English
|
|
92
|
+
- Completed status reporting, validation paths, and docs for isolated multi-Telegram-bot runtimes.
|
|
93
|
+
- Documented multi-bot auth mirroring, independent `CODEX_HOME`, group addressing rules, and global `/update` idle checks.
|
|
94
|
+
|
|
95
|
+
## 0.3.19 - 2026-05-27
|
|
96
|
+
|
|
97
|
+
### 中文
|
|
98
|
+
- 支持一个 FoxClaw 服务同时运行多个 Telegram bot,每个 bot 拥有独立 Codex app-server、独立 `CODEX_HOME`、独立会话和独立当前 auth。
|
|
99
|
+
- Telegram scope 加入 bot namespace,避免私聊、群聊、按钮回调和附件处理串线。
|
|
100
|
+
- 新增认证候选镜像协调器,仅同步经过校验的同账号候选文件,不共享 Codex sessions。
|
|
101
|
+
- `/update` 改为全服务空闲检查,并会尝试同步升级 npm/pnpm 管理的 Codex CLI。
|
|
102
|
+
|
|
103
|
+
### English
|
|
104
|
+
- Added support for running multiple Telegram bots in one FoxClaw service, with independent Codex app-servers, `CODEX_HOME` directories, sessions, and active auth selections.
|
|
105
|
+
- Added bot namespaces to Telegram scopes so private chats, groups, callbacks, and media handling do not cross runtimes.
|
|
106
|
+
- Added an auth candidate mirror coordinator that syncs validated same-account candidates without sharing Codex sessions.
|
|
107
|
+
- Made `/update` a global idle-checked operation and added npm/pnpm-managed Codex CLI update attempts.
|
|
108
|
+
|
|
109
|
+
## 0.3.18 - 2026-05-27
|
|
110
|
+
|
|
111
|
+
### 中文
|
|
112
|
+
- 修正 Codex 本地答复吞吐统计口径。
|
|
113
|
+
|
|
114
|
+
### English
|
|
115
|
+
- Corrected local Codex response throughput metrics.
|
|
116
|
+
|
|
117
|
+
## 0.3.17 - 2026-05-26
|
|
118
|
+
|
|
119
|
+
### 中文
|
|
120
|
+
- 修复 GitHub 发布流程的 checkout 鉴权失败。
|
|
121
|
+
|
|
122
|
+
### English
|
|
123
|
+
- Fixed checkout authentication failure in the GitHub release workflow.
|
|
124
|
+
|
|
125
|
+
## 0.3.16 - 2026-05-26
|
|
126
|
+
|
|
127
|
+
### 中文
|
|
128
|
+
- 缓存 `/status` 本地用量统计快照,减少状态页等待时间并优化响应。
|
|
129
|
+
|
|
130
|
+
### English
|
|
131
|
+
- Cached local usage statistics for `/status`, reducing wait time and improving responsiveness.
|
|
132
|
+
|
|
133
|
+
## 0.3.15 - 2026-05-26
|
|
134
|
+
|
|
135
|
+
### 中文
|
|
136
|
+
- 修复 pnpm 全局安装下 `/update` 对 Codex CLI 真实安装路径的识别。
|
|
137
|
+
|
|
138
|
+
### English
|
|
139
|
+
- Fixed `/update` detection of the real Codex CLI install path under pnpm global installs.
|
|
140
|
+
|
|
141
|
+
## 0.3.14 - 2026-05-26
|
|
142
|
+
|
|
143
|
+
### 中文
|
|
144
|
+
- 新增 FoxClaw 一键自升级和聊天内 `/update` 命令。
|
|
145
|
+
|
|
146
|
+
### English
|
|
147
|
+
- Added one-command FoxClaw self-update and the chat `/update` command.
|
|
148
|
+
|
|
149
|
+
## 0.3.13 - 2026-05-26
|
|
150
|
+
|
|
151
|
+
### 中文
|
|
152
|
+
- 新增 Codex 输出速度展示和线程上下文回显。
|
|
153
|
+
|
|
154
|
+
### English
|
|
155
|
+
- Added Codex output throughput display and thread context echoes.
|
|
156
|
+
|
|
157
|
+
## 0.3.12 - 2026-05-26
|
|
158
|
+
|
|
159
|
+
### 中文
|
|
160
|
+
- 简化 FoxClaw 代理服务配置。
|
|
161
|
+
|
|
162
|
+
### English
|
|
163
|
+
- Simplified FoxClaw proxy service configuration.
|
|
164
|
+
|
|
165
|
+
## 0.3.11 - 2026-05-26
|
|
166
|
+
|
|
167
|
+
### 中文
|
|
168
|
+
- 修复 systemd 安装时覆盖旧版本路径的问题。
|
|
169
|
+
|
|
170
|
+
### English
|
|
171
|
+
- Fixed systemd install behavior that could overwrite an older version path incorrectly.
|
|
172
|
+
|
|
173
|
+
## 0.3.10 - 2026-05-26
|
|
174
|
+
|
|
175
|
+
### 中文
|
|
176
|
+
- 支持 npm 发布令牌回退,用于 trusted publishing 不可用时的补发路径。
|
|
177
|
+
|
|
178
|
+
### English
|
|
179
|
+
- Added npm token fallback support for manual recovery when trusted publishing is unavailable.
|
|
180
|
+
|
|
181
|
+
## 0.1.1 - 2026-03-06
|
|
182
|
+
|
|
183
|
+
### 中文
|
|
184
|
+
- 显式解析 Codex 二进制路径,提升运行时定位可靠性。
|
|
185
|
+
|
|
186
|
+
### English
|
|
187
|
+
- Resolved the Codex binary path explicitly for more reliable runtime startup.
|
|
188
|
+
|
|
189
|
+
## 0.1.0 - 2026-03-06
|
|
190
|
+
|
|
191
|
+
### 中文
|
|
192
|
+
- 初始化 Telegram Codex app bridge。
|
|
193
|
+
|
|
194
|
+
### English
|
|
195
|
+
- Bootstrapped the Telegram Codex app bridge.
|
package/README.md
CHANGED
|
@@ -37,6 +37,7 @@ FoxClaw(狸爪)的目标很直接:让你用手机控制本机的 Codex,
|
|
|
37
37
|
- 手头有 Codex、OpenClaw、QwenPaw、Hermes、OpenCode、Kimi CLI 之类能跑 shell 的 agent?推荐走 [Agent 辅助安装](./docs/zh/agent-assisted-install.md)。
|
|
38
38
|
- 对 Node、Telegram 机器人、Codex CLI 不太熟?看 [新手安装指南](./docs/zh/install-for-beginners.md)。
|
|
39
39
|
- 已经装好,想系统了解 `/help`、`/setup`、`/threads`、`/watch`、`/auth` 和账号轮转?看 [用户手册](./docs/zh/user-manual.md)。
|
|
40
|
+
- 想了解每个版本改了什么?看 [更新日志](./CHANGELOG.md)。
|
|
40
41
|
- Git、Node、`.env` 都玩得转?直接往下看快速设置。
|
|
41
42
|
- 卡住了?看 [故障排查](./docs/zh/troubleshooting.md)。
|
|
42
43
|
|
|
@@ -257,7 +258,7 @@ FoxClaw 会把 `codex app-server` 作为 detached 子进程启动,记录其 pi
|
|
|
257
258
|
- `/status`、`/account`、`/quota`、`/update`
|
|
258
259
|
- `/quota_nudge <credits|usage_limit> confirm`
|
|
259
260
|
- `/login_device`、`/login_cancel [id]`、`/logout confirm`
|
|
260
|
-
- `/auth [list|use <n>|enable <n>|disable <n>|reload|add <name>]`
|
|
261
|
+
- `/auth [list|use <n>|enable <n>|disable <n>|reload|refresh all|add <name>]`
|
|
261
262
|
- `/threads [query]`、`/threads archived`、`/open <n>`
|
|
262
263
|
- `/goal [objective|pause|resume|done|budget <tokens|off>|clear confirm]`
|
|
263
264
|
- `/history [limit]`、`/files <query>`、`/remote`
|
package/README_EN.md
CHANGED
|
@@ -37,6 +37,7 @@ FoxClaw is more than message forwarding. It provides Telegram panels for Codex w
|
|
|
37
37
|
- Already have a shell-capable agent such as Codex, OpenClaw, QwenPaw, Hermes, OpenCode, or Kimi CLI? Use the [Agent-Assisted Install](./docs/agent-assisted-install.md) first. This is the recommended path.
|
|
38
38
|
- New to Node, Telegram bots, or Codex CLI? Use the [Beginner Install Guide](./docs/install-for-beginners.md).
|
|
39
39
|
- Already installed and want the full command guide for `/help`, `/setup`, `/threads`, `/watch`, `/auth`, and auth rotation? Read the [User Manual](./docs/user-manual.md).
|
|
40
|
+
- Want to see what changed in each release? Read the [Changelog](./CHANGELOG.md).
|
|
40
41
|
- Already comfortable with Git, Node, and `.env` files? Use the quick setup below.
|
|
41
42
|
- Something failed? Check [Troubleshooting](./docs/troubleshooting.md).
|
|
42
43
|
|
|
@@ -257,7 +258,7 @@ No static Codex app-server port is required in normal installs.
|
|
|
257
258
|
- `/status`, `/account`, `/quota`, `/update`
|
|
258
259
|
- `/quota_nudge <credits|usage_limit> confirm`
|
|
259
260
|
- `/login_device`, `/login_cancel [id]`, `/logout confirm`
|
|
260
|
-
- `/auth [list|use <n>|enable <n>|disable <n>|reload|add <name>]`
|
|
261
|
+
- `/auth [list|use <n>|enable <n>|disable <n>|reload|refresh all|add <name>]`
|
|
261
262
|
- `/threads [query]`, `/threads archived`, `/open <n>`
|
|
262
263
|
- `/goal [objective|pause|resume|done|budget <tokens|off>|clear confirm]`
|
|
263
264
|
- `/history [limit]`, `/files <query>`, `/remote`
|
|
@@ -125,7 +125,7 @@ export declare class CodexAppClient extends EventEmitter {
|
|
|
125
125
|
reviewThreadId: string;
|
|
126
126
|
}>;
|
|
127
127
|
listModels(): Promise<ModelInfo[]>;
|
|
128
|
-
readAccount(): Promise<CodexAccountInfo | null>;
|
|
128
|
+
readAccount(refreshToken?: boolean): Promise<CodexAccountInfo | null>;
|
|
129
129
|
startDeviceLogin(): Promise<CodexLoginDeviceCode>;
|
|
130
130
|
cancelLogin(loginId: string): Promise<void>;
|
|
131
131
|
logoutAccount(): Promise<void>;
|
package/dist/codex_app/client.js
CHANGED
|
@@ -277,8 +277,8 @@ export class CodexAppClient extends EventEmitter {
|
|
|
277
277
|
} while (cursor);
|
|
278
278
|
return models;
|
|
279
279
|
}
|
|
280
|
-
async readAccount() {
|
|
281
|
-
const result = await this.request('account/read', { refreshToken
|
|
280
|
+
async readAccount(refreshToken = false) {
|
|
281
|
+
const result = await this.request('account/read', { refreshToken });
|
|
282
282
|
const account = result?.account;
|
|
283
283
|
if (!account || typeof account !== 'object') {
|
|
284
284
|
return null;
|
|
@@ -45,6 +45,7 @@ export declare class BridgeSessionCore {
|
|
|
45
45
|
private pendingApprovalMessages;
|
|
46
46
|
private pendingAuthRotation;
|
|
47
47
|
private authRotationInProgress;
|
|
48
|
+
private authRefreshAllInProgress;
|
|
48
49
|
private authRotationFailedTargets;
|
|
49
50
|
private localUsageCache;
|
|
50
51
|
private localUsageCacheLoaded;
|
|
@@ -231,12 +232,14 @@ export declare class BridgeSessionCore {
|
|
|
231
232
|
private handleFastCommand;
|
|
232
233
|
private setCollaborationMode;
|
|
233
234
|
private handleAuthReloadCommand;
|
|
235
|
+
private canRunGlobalAuthRefresh;
|
|
234
236
|
private handleSelfUpdateCommand;
|
|
235
237
|
private scheduleSelfUpdateStatusPoll;
|
|
236
238
|
private clearSelfUpdateStatusPoll;
|
|
237
239
|
private pollSelfUpdateStatus;
|
|
238
240
|
private formatSelfUpdateResult;
|
|
239
241
|
private handleAuthCommand;
|
|
242
|
+
private handleAuthRefreshAllCommand;
|
|
240
243
|
private handleAuthUseCommand;
|
|
241
244
|
private handleAuthToggleCommand;
|
|
242
245
|
private findLatestAuthChoiceList;
|
|
@@ -288,6 +291,7 @@ export declare class BridgeSessionCore {
|
|
|
288
291
|
private switchCodexAuthAndRestart;
|
|
289
292
|
private recoverCodexAuthCandidate;
|
|
290
293
|
private syncCodexAuthCandidate;
|
|
294
|
+
private refreshAllCodexAuthCandidates;
|
|
291
295
|
private buildNativeCollaborationMode;
|
|
292
296
|
private buildCodexUsageStatusLines;
|
|
293
297
|
private buildCodexLocalUsageStatusLines;
|
|
@@ -299,6 +303,7 @@ export declare class BridgeSessionCore {
|
|
|
299
303
|
private codexLocalUsageSnapshotPath;
|
|
300
304
|
private refreshCurrentCodexAuthQuota;
|
|
301
305
|
private applySharedCodexAuthQuotaSnapshots;
|
|
306
|
+
private recordCodexAuthQuotaSnapshot;
|
|
302
307
|
private readCodexAuthCandidateAccountIds;
|
|
303
308
|
private codexAuthQuotaSnapshotMatchesAccount;
|
|
304
309
|
private readCodexAuthQuotaSnapshots;
|
|
@@ -113,6 +113,7 @@ export class BridgeSessionCore {
|
|
|
113
113
|
pendingApprovalMessages = new Map();
|
|
114
114
|
pendingAuthRotation = null;
|
|
115
115
|
authRotationInProgress = false;
|
|
116
|
+
authRefreshAllInProgress = false;
|
|
116
117
|
authRotationFailedTargets = new Set();
|
|
117
118
|
localUsageCache = null;
|
|
118
119
|
localUsageCacheLoaded = false;
|
|
@@ -232,6 +233,7 @@ export class BridgeSessionCore {
|
|
|
232
233
|
this.recentCommandUsageByScope.clear();
|
|
233
234
|
this.commandUsageSequence = 0;
|
|
234
235
|
this.pendingAuthRotation = null;
|
|
236
|
+
this.authRefreshAllInProgress = false;
|
|
235
237
|
this.clearObservedThreadWatchers();
|
|
236
238
|
this.releaseActiveTurnsForBridgeShutdown();
|
|
237
239
|
this.bot.stop();
|
|
@@ -1008,7 +1010,7 @@ export class BridgeSessionCore {
|
|
|
1008
1010
|
await this.handleAuthToggleCallback(event, authToggleMatch[1], Number.parseInt(authToggleMatch[2], 10), locale);
|
|
1009
1011
|
return;
|
|
1010
1012
|
}
|
|
1011
|
-
const authActionMatch = /^auth:([a-f0-9]+):(login_device|reload)$/.exec(event.data);
|
|
1013
|
+
const authActionMatch = /^auth:([a-f0-9]+):(login_device|reload|refresh_all)$/.exec(event.data);
|
|
1012
1014
|
if (authActionMatch) {
|
|
1013
1015
|
await this.handleAuthPanelActionCallback(event, authActionMatch[1], authActionMatch[2], locale);
|
|
1014
1016
|
return;
|
|
@@ -2604,7 +2606,8 @@ export class BridgeSessionCore {
|
|
|
2604
2606
|
&& this.pendingUserInputs.size === 0
|
|
2605
2607
|
&& this.pendingMcpElicitations.size === 0
|
|
2606
2608
|
&& this.pendingLoginsByScope.size === 0
|
|
2607
|
-
&& !this.authRotationInProgress
|
|
2609
|
+
&& !this.authRotationInProgress
|
|
2610
|
+
&& !this.authRefreshAllInProgress;
|
|
2608
2611
|
}
|
|
2609
2612
|
hasLocalBlockingActivity() {
|
|
2610
2613
|
return !this.isIdleForServiceUpdate();
|
|
@@ -3779,6 +3782,12 @@ export class BridgeSessionCore {
|
|
|
3779
3782
|
lines.push(...await this.buildCodexUsageStatusLines(locale));
|
|
3780
3783
|
await this.sendMessage(scopeId, lines.join('\n'));
|
|
3781
3784
|
}
|
|
3785
|
+
canRunGlobalAuthRefresh() {
|
|
3786
|
+
return this.isIdleForServiceUpdate()
|
|
3787
|
+
&& this.store.countPendingApprovals() === 0
|
|
3788
|
+
&& this.store.countPendingUserInputs() === 0
|
|
3789
|
+
&& (!this.coordinator?.canSelfUpdate || this.coordinator.canSelfUpdate());
|
|
3790
|
+
}
|
|
3782
3791
|
async handleSelfUpdateCommand(scopeId, locale) {
|
|
3783
3792
|
if (!this.selfUpdater) {
|
|
3784
3793
|
await this.sendMessage(scopeId, t(locale, 'update_unavailable'));
|
|
@@ -3860,6 +3869,18 @@ export class BridgeSessionCore {
|
|
|
3860
3869
|
await this.handleAuthReloadCommand(scopeId, locale);
|
|
3861
3870
|
return;
|
|
3862
3871
|
}
|
|
3872
|
+
if (action === 'refresh') {
|
|
3873
|
+
if (args[1]?.toLowerCase() === 'all') {
|
|
3874
|
+
await this.handleAuthRefreshAllCommand(scopeId, locale);
|
|
3875
|
+
return;
|
|
3876
|
+
}
|
|
3877
|
+
await this.sendMessage(scopeId, t(locale, 'usage_auth'));
|
|
3878
|
+
return;
|
|
3879
|
+
}
|
|
3880
|
+
if (action === 'refresh_all') {
|
|
3881
|
+
await this.handleAuthRefreshAllCommand(scopeId, locale);
|
|
3882
|
+
return;
|
|
3883
|
+
}
|
|
3863
3884
|
if (action === 'use') {
|
|
3864
3885
|
await this.handleAuthUseCommand(scopeId, locale, args.slice(1));
|
|
3865
3886
|
return;
|
|
@@ -3889,6 +3910,26 @@ export class BridgeSessionCore {
|
|
|
3889
3910
|
const messageId = await this.sendMessage(scopeId, renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(scopeId) !== null), authChoiceKeyboard(locale, record));
|
|
3890
3911
|
record.messageId = messageId;
|
|
3891
3912
|
}
|
|
3913
|
+
async handleAuthRefreshAllCommand(scopeId, locale) {
|
|
3914
|
+
if (!this.canRunGlobalAuthRefresh()) {
|
|
3915
|
+
await this.sendMessage(scopeId, t(locale, 'auth_refresh_all_blocked_active'));
|
|
3916
|
+
return;
|
|
3917
|
+
}
|
|
3918
|
+
await this.sendMessage(scopeId, t(locale, 'auth_refresh_all_starting'));
|
|
3919
|
+
const result = await this.refreshAllCodexAuthCandidates();
|
|
3920
|
+
const state = await this.listCodexAuthState();
|
|
3921
|
+
await this.applySharedCodexAuthQuotaSnapshots(state);
|
|
3922
|
+
const record = {
|
|
3923
|
+
localId: crypto.randomBytes(8).toString('hex'),
|
|
3924
|
+
chatId: scopeId,
|
|
3925
|
+
messageId: null,
|
|
3926
|
+
candidates: state.candidates,
|
|
3927
|
+
createdAt: Date.now(),
|
|
3928
|
+
};
|
|
3929
|
+
this.pendingAuthChoiceLists.set(record.localId, record);
|
|
3930
|
+
const messageId = await this.sendMessage(scopeId, `${formatAuthRefreshAllResult(locale, result)}\n\n${renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(scopeId) !== null)}`, authChoiceKeyboard(locale, record));
|
|
3931
|
+
record.messageId = messageId;
|
|
3932
|
+
}
|
|
3892
3933
|
async handleAuthUseCommand(scopeId, locale, args) {
|
|
3893
3934
|
if (this.hasLocalBlockingActivity()) {
|
|
3894
3935
|
await this.sendMessage(scopeId, t(locale, 'auth_reload_blocked_active'));
|
|
@@ -4361,6 +4402,25 @@ export class BridgeSessionCore {
|
|
|
4361
4402
|
await this.handleLoginDeviceCommand(event.scopeId, locale);
|
|
4362
4403
|
return;
|
|
4363
4404
|
}
|
|
4405
|
+
if (action === 'refresh_all') {
|
|
4406
|
+
if (!this.canRunGlobalAuthRefresh()) {
|
|
4407
|
+
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'auth_refresh_all_blocked_active'));
|
|
4408
|
+
return;
|
|
4409
|
+
}
|
|
4410
|
+
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'auth_refresh_all_starting'));
|
|
4411
|
+
if (record.messageId !== null) {
|
|
4412
|
+
await this.editMessage(event.scopeId, record.messageId, t(locale, 'auth_refresh_all_starting'), []);
|
|
4413
|
+
}
|
|
4414
|
+
const result = await this.refreshAllCodexAuthCandidates();
|
|
4415
|
+
const state = await this.listCodexAuthState();
|
|
4416
|
+
await this.applySharedCodexAuthQuotaSnapshots(state);
|
|
4417
|
+
record.candidates = state.candidates;
|
|
4418
|
+
record.createdAt = Date.now();
|
|
4419
|
+
if (record.messageId !== null) {
|
|
4420
|
+
await this.editMessage(event.scopeId, record.messageId, `${formatAuthRefreshAllResult(locale, result)}\n\n${renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null)}`, authChoiceKeyboard(locale, record));
|
|
4421
|
+
}
|
|
4422
|
+
return;
|
|
4423
|
+
}
|
|
4364
4424
|
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'auth_reload_restarting'));
|
|
4365
4425
|
await this.handleAuthReloadCommand(event.scopeId, locale);
|
|
4366
4426
|
}
|
|
@@ -4603,6 +4663,71 @@ export class BridgeSessionCore {
|
|
|
4603
4663
|
});
|
|
4604
4664
|
}
|
|
4605
4665
|
}
|
|
4666
|
+
async refreshAllCodexAuthCandidates() {
|
|
4667
|
+
if (this.authRefreshAllInProgress) {
|
|
4668
|
+
throw new UserFacingError('Auth refresh all is already running.');
|
|
4669
|
+
}
|
|
4670
|
+
const initialState = await this.listCodexAuthState();
|
|
4671
|
+
const result = { refreshed: [], skipped: [], failed: [] };
|
|
4672
|
+
if (initialState.candidates.length === 0) {
|
|
4673
|
+
return result;
|
|
4674
|
+
}
|
|
4675
|
+
const authStat = await fs.lstat(initialState.authPath).catch(() => null);
|
|
4676
|
+
const originalRegularAuth = authStat?.isFile()
|
|
4677
|
+
? await fs.readFile(initialState.authPath, 'utf8').catch(() => null)
|
|
4678
|
+
: null;
|
|
4679
|
+
let changedAuthTarget = false;
|
|
4680
|
+
this.authRefreshAllInProgress = true;
|
|
4681
|
+
try {
|
|
4682
|
+
for (const candidate of initialState.candidates) {
|
|
4683
|
+
const before = await readChatGptAuthMetadata(candidate.path);
|
|
4684
|
+
if (!before) {
|
|
4685
|
+
result.skipped.push(candidate.name);
|
|
4686
|
+
continue;
|
|
4687
|
+
}
|
|
4688
|
+
try {
|
|
4689
|
+
await pointCodexAuthAtTarget(initialState.authDir, initialState.authPath, candidate.path);
|
|
4690
|
+
changedAuthTarget = true;
|
|
4691
|
+
this.pendingTurnErrors.clear();
|
|
4692
|
+
this.attachedThreads.clear();
|
|
4693
|
+
await this.app.restart();
|
|
4694
|
+
await this.app.readAccount(true);
|
|
4695
|
+
const rateLimits = await this.app.readAccountRateLimits();
|
|
4696
|
+
const snapshot = selectCodexRateLimitSnapshot(rateLimits);
|
|
4697
|
+
if (!snapshot) {
|
|
4698
|
+
throw new Error('Codex did not return ChatGPT rate limits after refresh');
|
|
4699
|
+
}
|
|
4700
|
+
const after = await readChatGptAuthMetadata(candidate.path);
|
|
4701
|
+
if (!after || after.accountId !== before.accountId) {
|
|
4702
|
+
throw new Error('refreshed auth account id did not match the original candidate');
|
|
4703
|
+
}
|
|
4704
|
+
if (after.lastRefreshMs <= before.lastRefreshMs) {
|
|
4705
|
+
throw new Error('Codex did not advance last_refresh');
|
|
4706
|
+
}
|
|
4707
|
+
await this.recordCodexAuthQuotaSnapshot(candidate.name, after.accountId, snapshot);
|
|
4708
|
+
await this.syncCodexAuthCandidate(candidate.name);
|
|
4709
|
+
result.refreshed.push(candidate.name);
|
|
4710
|
+
}
|
|
4711
|
+
catch (error) {
|
|
4712
|
+
result.failed.push({ name: candidate.name, error: formatUserError(error) });
|
|
4713
|
+
}
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
finally {
|
|
4717
|
+
try {
|
|
4718
|
+
if (changedAuthTarget) {
|
|
4719
|
+
await restoreCodexAuthTarget(initialState.authDir, initialState.authPath, initialState.currentTargetPath, originalRegularAuth);
|
|
4720
|
+
this.pendingTurnErrors.clear();
|
|
4721
|
+
this.attachedThreads.clear();
|
|
4722
|
+
await this.app.restart();
|
|
4723
|
+
}
|
|
4724
|
+
}
|
|
4725
|
+
finally {
|
|
4726
|
+
this.authRefreshAllInProgress = false;
|
|
4727
|
+
}
|
|
4728
|
+
}
|
|
4729
|
+
return result;
|
|
4730
|
+
}
|
|
4606
4731
|
async buildNativeCollaborationMode(settings, cwd, modeOverride) {
|
|
4607
4732
|
const mode = resolveCollaborationMode(modeOverride === undefined ? settings?.collaborationMode ?? null : modeOverride);
|
|
4608
4733
|
try {
|
|
@@ -4807,11 +4932,7 @@ export class BridgeSessionCore {
|
|
|
4807
4932
|
}
|
|
4808
4933
|
const quota = authQuotaSnapshotFromRateLimit(snapshot, metadata?.accountId ?? null);
|
|
4809
4934
|
candidate.quota = quota;
|
|
4810
|
-
this.
|
|
4811
|
-
if (metadata?.accountId) {
|
|
4812
|
-
this.store.setCodexAuthQuotaSnapshot(this.authRuntimeId(), candidate.name, metadata.accountId, quota);
|
|
4813
|
-
}
|
|
4814
|
-
await this.writeCodexAuthQuotaSnapshots();
|
|
4935
|
+
await this.recordCodexAuthQuotaSnapshot(candidate.name, metadata?.accountId ?? null, snapshot);
|
|
4815
4936
|
await this.applySharedCodexAuthQuotaSnapshots(state);
|
|
4816
4937
|
await this.syncCodexAuthCandidate(candidate.name);
|
|
4817
4938
|
}
|
|
@@ -4840,6 +4961,14 @@ export class BridgeSessionCore {
|
|
|
4840
4961
|
candidate.quota = mergeCodexAuthQuotaSnapshots(candidate.quota, snapshotsByAccount.get(accountId) ?? null);
|
|
4841
4962
|
}
|
|
4842
4963
|
}
|
|
4964
|
+
async recordCodexAuthQuotaSnapshot(candidateName, accountId, snapshot) {
|
|
4965
|
+
const quota = authQuotaSnapshotFromRateLimit(snapshot, accountId);
|
|
4966
|
+
this.authQuotaSnapshots[candidateName] = quota;
|
|
4967
|
+
if (accountId) {
|
|
4968
|
+
this.store.setCodexAuthQuotaSnapshot(this.authRuntimeId(), candidateName, accountId, quota);
|
|
4969
|
+
}
|
|
4970
|
+
await this.writeCodexAuthQuotaSnapshots();
|
|
4971
|
+
}
|
|
4843
4972
|
async readCodexAuthCandidateAccountIds(candidates) {
|
|
4844
4973
|
const entries = await Promise.all(candidates.map(async (candidate) => {
|
|
4845
4974
|
const metadata = await readChatGptAuthMetadata(candidate.path);
|
|
@@ -7455,6 +7584,30 @@ async function pointCodexAuthAtTarget(authDir, authPath, targetPath) {
|
|
|
7455
7584
|
throw error;
|
|
7456
7585
|
}
|
|
7457
7586
|
}
|
|
7587
|
+
async function restoreCodexAuthTarget(authDir, authPath, targetPath, regularAuthContents) {
|
|
7588
|
+
if (regularAuthContents !== null) {
|
|
7589
|
+
await fs.mkdir(authDir, { recursive: true });
|
|
7590
|
+
const temporary = path.join(authDir, `.auth.json.${process.pid}.${Date.now()}.restore`);
|
|
7591
|
+
try {
|
|
7592
|
+
await fs.writeFile(temporary, regularAuthContents, { mode: 0o600 });
|
|
7593
|
+
await fs.rename(temporary, authPath);
|
|
7594
|
+
}
|
|
7595
|
+
catch (error) {
|
|
7596
|
+
await fs.unlink(temporary).catch(() => undefined);
|
|
7597
|
+
throw error;
|
|
7598
|
+
}
|
|
7599
|
+
return;
|
|
7600
|
+
}
|
|
7601
|
+
if (targetPath) {
|
|
7602
|
+
await pointCodexAuthAtTarget(authDir, authPath, targetPath);
|
|
7603
|
+
return;
|
|
7604
|
+
}
|
|
7605
|
+
await fs.unlink(authPath).catch((error) => {
|
|
7606
|
+
if (!isFileMissingError(error)) {
|
|
7607
|
+
throw error;
|
|
7608
|
+
}
|
|
7609
|
+
});
|
|
7610
|
+
}
|
|
7458
7611
|
async function switchCodexAuth(targetPath, explicitAuthDir = null) {
|
|
7459
7612
|
const state = await listCodexAuthState(new Set(), explicitAuthDir);
|
|
7460
7613
|
const candidate = state.candidates.find(entry => entry.path === targetPath);
|
|
@@ -7513,9 +7666,33 @@ function authChoiceKeyboard(locale, record) {
|
|
|
7513
7666
|
{ text: t(locale, 'button_permissions'), callback_data: 'nav:permissions' },
|
|
7514
7667
|
{ text: t(locale, 'button_login_device'), callback_data: `auth:${record.localId}:login_device` },
|
|
7515
7668
|
]);
|
|
7516
|
-
rows.push([
|
|
7669
|
+
rows.push([
|
|
7670
|
+
{ text: t(locale, 'button_auth_reload'), callback_data: `auth:${record.localId}:reload` },
|
|
7671
|
+
{ text: t(locale, 'button_auth_refresh_all'), callback_data: `auth:${record.localId}:refresh_all` },
|
|
7672
|
+
]);
|
|
7517
7673
|
return rows;
|
|
7518
7674
|
}
|
|
7675
|
+
function formatAuthRefreshAllResult(locale, result) {
|
|
7676
|
+
const lines = [t(locale, 'auth_refresh_all_done', {
|
|
7677
|
+
refreshed: String(result.refreshed.length),
|
|
7678
|
+
skipped: String(result.skipped.length),
|
|
7679
|
+
failed: String(result.failed.length),
|
|
7680
|
+
})];
|
|
7681
|
+
if (result.refreshed.length > 0) {
|
|
7682
|
+
lines.push(t(locale, 'auth_refresh_all_refreshed', { value: result.refreshed.join(', ') }));
|
|
7683
|
+
}
|
|
7684
|
+
if (result.skipped.length > 0) {
|
|
7685
|
+
lines.push(t(locale, 'auth_refresh_all_skipped', { value: result.skipped.join(', ') }));
|
|
7686
|
+
}
|
|
7687
|
+
if (result.failed.length > 0) {
|
|
7688
|
+
const details = result.failed
|
|
7689
|
+
.slice(0, 5)
|
|
7690
|
+
.map((failure) => `${failure.name}: ${failure.error}`)
|
|
7691
|
+
.join('; ');
|
|
7692
|
+
lines.push(t(locale, 'auth_refresh_all_failed', { value: details }));
|
|
7693
|
+
}
|
|
7694
|
+
return lines.join('\n');
|
|
7695
|
+
}
|
|
7519
7696
|
function normalizeHelpUsageKey(name) {
|
|
7520
7697
|
const normalized = name.toLowerCase();
|
|
7521
7698
|
switch (normalized) {
|
package/dist/i18n.d.ts
CHANGED
|
@@ -137,7 +137,7 @@ declare const MESSAGES: {
|
|
|
137
137
|
readonly auth_reload_restarting: "Restarting Codex app-server to reload auth...";
|
|
138
138
|
readonly auth_reload_done: "Codex app-server restarted. Current auth has been reloaded.";
|
|
139
139
|
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|use <n>|enable <n>|disable <n>|reload|add <name>]";
|
|
140
|
+
readonly usage_auth: "Usage: /auth [list|use <n>|enable <n>|disable <n>|reload|refresh all|add <name>]";
|
|
141
141
|
readonly usage_auth_add: "Usage: /auth add <name>. Use letters, numbers, dot, dash, or underscore.";
|
|
142
142
|
readonly auth_list_title: "Codex auth files:";
|
|
143
143
|
readonly auth_bot: "Bot runtime: {value}";
|
|
@@ -158,6 +158,12 @@ declare const MESSAGES: {
|
|
|
158
158
|
readonly auth_switching: "Switching Codex auth: {from} -> {to}...";
|
|
159
159
|
readonly auth_switch_done: "Codex auth switched: {from} -> {to}.";
|
|
160
160
|
readonly auth_recovered_newer_candidate: "Recovered a newer same-account credential for {value} from another Codex home before reload.";
|
|
161
|
+
readonly auth_refresh_all_starting: "Refreshing all ChatGPT auth candidates. Every runtime must stay idle...";
|
|
162
|
+
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.";
|
|
163
|
+
readonly auth_refresh_all_done: "Auth refresh all complete: {refreshed} refreshed, {skipped} skipped, {failed} failed.";
|
|
164
|
+
readonly auth_refresh_all_refreshed: "Refreshed: {value}";
|
|
165
|
+
readonly auth_refresh_all_skipped: "Skipped non-ChatGPT/invalid candidates: {value}";
|
|
166
|
+
readonly auth_refresh_all_failed: "Failed: {value}";
|
|
161
167
|
readonly auth_auto_switching: "Codex auth problem detected ({error}). Switching: {from} -> {to}...";
|
|
162
168
|
readonly auth_auto_done: "Auto-switched Codex auth: {from} -> {to}.";
|
|
163
169
|
readonly auth_auto_retrying: "Retrying the same request with the new auth...";
|
|
@@ -173,6 +179,7 @@ declare const MESSAGES: {
|
|
|
173
179
|
readonly auth_add_missing_file: "Login completed, but the new auth file was not created: {value}";
|
|
174
180
|
readonly button_login_device: "🔑 Login";
|
|
175
181
|
readonly button_auth_reload: "🔄 Reload auth";
|
|
182
|
+
readonly button_auth_refresh_all: "🔁 Refresh all";
|
|
176
183
|
readonly button_auth_enable: "✅";
|
|
177
184
|
readonly button_auth_disable: "⏸️";
|
|
178
185
|
readonly another_turn_running: "Another turn is already running. Use /interrupt, /takeover, /queue, or wait.";
|
|
@@ -713,7 +720,7 @@ declare const MESSAGES: {
|
|
|
713
720
|
readonly auth_reload_restarting: "正在重启 Codex app-server 以重新读取 auth...";
|
|
714
721
|
readonly auth_reload_done: "Codex app-server 已重启,当前 auth 已重新读取。";
|
|
715
722
|
readonly auth_reload_blocked_active: "当前有回复、审批或问题在进行中,不能重载 Codex auth。请先等待,或使用 /interrupt。";
|
|
716
|
-
readonly usage_auth: "用法:/auth [list|use <编号>|enable <编号>|disable <编号>|reload|add <名称>]";
|
|
723
|
+
readonly usage_auth: "用法:/auth [list|use <编号>|enable <编号>|disable <编号>|reload|refresh all|add <名称>]";
|
|
717
724
|
readonly usage_auth_add: "用法:/auth add <名称>。名称只能包含字母、数字、点、短横线或下划线。";
|
|
718
725
|
readonly auth_list_title: "Codex auth 文件:";
|
|
719
726
|
readonly auth_bot: "Bot runtime:{value}";
|
|
@@ -734,6 +741,12 @@ declare const MESSAGES: {
|
|
|
734
741
|
readonly auth_switching: "正在切换 Codex auth:{from} -> {to}...";
|
|
735
742
|
readonly auth_switch_done: "Codex auth 已切换:{from} -> {to}。";
|
|
736
743
|
readonly auth_recovered_newer_candidate: "重载前已从其他 Codex home 恢复 {value} 的同账号较新凭据。";
|
|
744
|
+
readonly auth_refresh_all_starting: "正在刷新全部 ChatGPT auth 候选。执行期间所有 runtime 必须保持空闲...";
|
|
745
|
+
readonly auth_refresh_all_blocked_active: "当前有任一 runtime、审批、待输入、登录或 auth 镜像写入在进行中,不能刷新全部 auth。请先等待,或使用 /interrupt。";
|
|
746
|
+
readonly auth_refresh_all_done: "全部 auth 刷新完成:已刷新 {refreshed},已跳过 {skipped},失败 {failed}。";
|
|
747
|
+
readonly auth_refresh_all_refreshed: "已刷新:{value}";
|
|
748
|
+
readonly auth_refresh_all_skipped: "已跳过非 ChatGPT/无效候选:{value}";
|
|
749
|
+
readonly auth_refresh_all_failed: "失败:{value}";
|
|
737
750
|
readonly auth_auto_switching: "检测到 Codex auth 问题({error}),正在切换:{from} -> {to}...";
|
|
738
751
|
readonly auth_auto_done: "已自动切换 Codex auth:{from} -> {to}。";
|
|
739
752
|
readonly auth_auto_retrying: "正在用新的 auth 重试同一条请求...";
|
|
@@ -749,6 +762,7 @@ declare const MESSAGES: {
|
|
|
749
762
|
readonly auth_add_missing_file: "登录已完成,但没有创建新的 auth 文件:{value}";
|
|
750
763
|
readonly button_login_device: "🔑 设备登录";
|
|
751
764
|
readonly button_auth_reload: "🔄 重载 auth";
|
|
765
|
+
readonly button_auth_refresh_all: "🔁 刷新全部";
|
|
752
766
|
readonly button_auth_enable: "✅";
|
|
753
767
|
readonly button_auth_disable: "⏸️";
|
|
754
768
|
readonly another_turn_running: "已经有一个回复在进行中。请先等待,或使用 /interrupt、/takeover、/queue。";
|
package/dist/i18n.js
CHANGED
|
@@ -135,7 +135,7 @@ const MESSAGES = {
|
|
|
135
135
|
auth_reload_restarting: 'Restarting Codex app-server to reload auth...',
|
|
136
136
|
auth_reload_done: 'Codex app-server restarted. Current auth has been reloaded.',
|
|
137
137
|
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|use <n>|enable <n>|disable <n>|reload|add <name>]',
|
|
138
|
+
usage_auth: 'Usage: /auth [list|use <n>|enable <n>|disable <n>|reload|refresh all|add <name>]',
|
|
139
139
|
usage_auth_add: 'Usage: /auth add <name>. Use letters, numbers, dot, dash, or underscore.',
|
|
140
140
|
auth_list_title: 'Codex auth files:',
|
|
141
141
|
auth_bot: 'Bot runtime: {value}',
|
|
@@ -156,6 +156,12 @@ const MESSAGES = {
|
|
|
156
156
|
auth_switching: 'Switching Codex auth: {from} -> {to}...',
|
|
157
157
|
auth_switch_done: 'Codex auth switched: {from} -> {to}.',
|
|
158
158
|
auth_recovered_newer_candidate: 'Recovered a newer same-account credential for {value} from another Codex home before reload.',
|
|
159
|
+
auth_refresh_all_starting: 'Refreshing all ChatGPT auth candidates. Every runtime must stay idle...',
|
|
160
|
+
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.',
|
|
161
|
+
auth_refresh_all_done: 'Auth refresh all complete: {refreshed} refreshed, {skipped} skipped, {failed} failed.',
|
|
162
|
+
auth_refresh_all_refreshed: 'Refreshed: {value}',
|
|
163
|
+
auth_refresh_all_skipped: 'Skipped non-ChatGPT/invalid candidates: {value}',
|
|
164
|
+
auth_refresh_all_failed: 'Failed: {value}',
|
|
159
165
|
auth_auto_switching: 'Codex auth problem detected ({error}). Switching: {from} -> {to}...',
|
|
160
166
|
auth_auto_done: 'Auto-switched Codex auth: {from} -> {to}.',
|
|
161
167
|
auth_auto_retrying: 'Retrying the same request with the new auth...',
|
|
@@ -171,6 +177,7 @@ const MESSAGES = {
|
|
|
171
177
|
auth_add_missing_file: 'Login completed, but the new auth file was not created: {value}',
|
|
172
178
|
button_login_device: '🔑 Login',
|
|
173
179
|
button_auth_reload: '🔄 Reload auth',
|
|
180
|
+
button_auth_refresh_all: '🔁 Refresh all',
|
|
174
181
|
button_auth_enable: '✅',
|
|
175
182
|
button_auth_disable: '⏸️',
|
|
176
183
|
another_turn_running: 'Another turn is already running. Use /interrupt, /takeover, /queue, or wait.',
|
|
@@ -711,7 +718,7 @@ const MESSAGES = {
|
|
|
711
718
|
auth_reload_restarting: '正在重启 Codex app-server 以重新读取 auth...',
|
|
712
719
|
auth_reload_done: 'Codex app-server 已重启,当前 auth 已重新读取。',
|
|
713
720
|
auth_reload_blocked_active: '当前有回复、审批或问题在进行中,不能重载 Codex auth。请先等待,或使用 /interrupt。',
|
|
714
|
-
usage_auth: '用法:/auth [list|use <编号>|enable <编号>|disable <编号>|reload|add <名称>]',
|
|
721
|
+
usage_auth: '用法:/auth [list|use <编号>|enable <编号>|disable <编号>|reload|refresh all|add <名称>]',
|
|
715
722
|
usage_auth_add: '用法:/auth add <名称>。名称只能包含字母、数字、点、短横线或下划线。',
|
|
716
723
|
auth_list_title: 'Codex auth 文件:',
|
|
717
724
|
auth_bot: 'Bot runtime:{value}',
|
|
@@ -732,6 +739,12 @@ const MESSAGES = {
|
|
|
732
739
|
auth_switching: '正在切换 Codex auth:{from} -> {to}...',
|
|
733
740
|
auth_switch_done: 'Codex auth 已切换:{from} -> {to}。',
|
|
734
741
|
auth_recovered_newer_candidate: '重载前已从其他 Codex home 恢复 {value} 的同账号较新凭据。',
|
|
742
|
+
auth_refresh_all_starting: '正在刷新全部 ChatGPT auth 候选。执行期间所有 runtime 必须保持空闲...',
|
|
743
|
+
auth_refresh_all_blocked_active: '当前有任一 runtime、审批、待输入、登录或 auth 镜像写入在进行中,不能刷新全部 auth。请先等待,或使用 /interrupt。',
|
|
744
|
+
auth_refresh_all_done: '全部 auth 刷新完成:已刷新 {refreshed},已跳过 {skipped},失败 {failed}。',
|
|
745
|
+
auth_refresh_all_refreshed: '已刷新:{value}',
|
|
746
|
+
auth_refresh_all_skipped: '已跳过非 ChatGPT/无效候选:{value}',
|
|
747
|
+
auth_refresh_all_failed: '失败:{value}',
|
|
735
748
|
auth_auto_switching: '检测到 Codex auth 问题({error}),正在切换:{from} -> {to}...',
|
|
736
749
|
auth_auto_done: '已自动切换 Codex auth:{from} -> {to}。',
|
|
737
750
|
auth_auto_retrying: '正在用新的 auth 重试同一条请求...',
|
|
@@ -747,6 +760,7 @@ const MESSAGES = {
|
|
|
747
760
|
auth_add_missing_file: '登录已完成,但没有创建新的 auth 文件:{value}',
|
|
748
761
|
button_login_device: '🔑 设备登录',
|
|
749
762
|
button_auth_reload: '🔄 重载 auth',
|
|
763
|
+
button_auth_refresh_all: '🔁 刷新全部',
|
|
750
764
|
button_auth_enable: '✅',
|
|
751
765
|
button_auth_disable: '⏸️',
|
|
752
766
|
another_turn_running: '已经有一个回复在进行中。请先等待,或使用 /interrupt、/takeover、/queue。',
|
package/docs/user-manual.md
CHANGED
|
@@ -389,7 +389,7 @@ If the login is cancelled or fails, FoxClaw tries to restore the previous auth t
|
|
|
389
389
|
|
|
390
390
|
### 6.3 The `/auth` Panel
|
|
391
391
|
|
|
392
|
-
`/auth` lists candidate accounts, the current account, and the auth directory. It also provides buttons for switching, disabling, login, and
|
|
392
|
+
`/auth` lists candidate accounts, the current account, and the auth directory. It also provides buttons for switching, disabling, login, reload, and Refresh all. In multi-bot mode the panel names the `@botname` runtime being managed, because private chats, groups, and topics on one bot share that bot's current auth. The `5h|7d` numbers before each filename are the last recorded remaining percentages for the two quota windows; the current auth is refreshed when the panel opens, while other candidates are not switched merely to query quota. When multiple bot runtimes have recently used the same ChatGPT account, FoxClaw combines their cached quota snapshots by verified account ID, so one bot's `/auth` panel can show quota information learned by another bot without mixing different accounts.
|
|
393
393
|
|
|
394
394
|
Approximation:
|
|
395
395
|
|
|
@@ -405,10 +405,10 @@ Quota remaining: 5h|7d|auth
|
|
|
405
405
|
[✅ 20|25|auth.json_personal] [✅]
|
|
406
406
|
[🔐 --|--|auth.json_team] [✅]
|
|
407
407
|
[🛡️ Access] [🔑 Login]
|
|
408
|
-
[🔄 Reload auth]
|
|
408
|
+
[🔄 Reload auth] [🔁 Refresh all]
|
|
409
409
|
```
|
|
410
410
|
|
|
411
|
-
The right-side `✅` / `⏸️` button shows the current state. Tapping it toggles enabled/disabled, and the refreshed list shows the new state. Tapping a candidate switches auth, restarts that runtime, and refreshes the same panel with its buttons intact so you can switch again immediately. `--|--` means no quota snapshot has been observed for that candidate yet.
|
|
411
|
+
The right-side `✅` / `⏸️` button shows the current state. Tapping it toggles enabled/disabled, and the refreshed list shows the new state. Tapping a candidate switches auth, restarts that runtime, and refreshes the same panel with its buttons intact so you can switch again immediately. `Refresh all` is a maintenance action: it is allowed only when every Telegram runtime, the Weixin runtime, approvals, inputs, logins, and auth mirroring are idle. It then visits every ChatGPT candidate in the panel, asks Codex to force-refresh tokens with `account/read refreshToken=true`, verifies the result through the usage endpoint, mirrors successful candidates, restores the original current auth, and refreshes the panel with a summary. `--|--` means no quota snapshot has been observed for that candidate yet.
|
|
412
412
|
|
|
413
413
|
Equivalent commands:
|
|
414
414
|
|
|
@@ -417,6 +417,7 @@ Equivalent commands:
|
|
|
417
417
|
- `/auth enable <n>`: let candidate n participate in auto-rotation.
|
|
418
418
|
- `/auth disable <n>`: skip candidate n during auto-rotation.
|
|
419
419
|
- `/auth reload` or `/auth_reload`: restart app-server and reload the current `auth.json`.
|
|
420
|
+
- `/auth refresh all`: same as the panel's Refresh all button.
|
|
420
421
|
|
|
421
422
|
If the requesting bot runtime has active turns, pending approvals, pending user inputs, or MCP elicitations, FoxClaw refuses manual auth switching to avoid changing accounts mid-request; another idle bot is unaffected.
|
|
422
423
|
|
package/docs/zh/user-manual.md
CHANGED
|
@@ -389,7 +389,7 @@ cp -L ~/.codex/auth.json ~/.codex/auth.json_personal
|
|
|
389
389
|
|
|
390
390
|
### 6.3 `/auth` 面板
|
|
391
391
|
|
|
392
|
-
`/auth` 会列出候选账号、当前账号和 auth
|
|
392
|
+
`/auth` 会列出候选账号、当前账号和 auth 目录,并提供按钮切换、禁用、登录、重载和刷新全部。多 bot 模式中,面板顶部还会显示当前正在管理的 `@botname`,因为该 bot 内的私聊、群聊和话题共享同一个当前 auth。每个候选名前的 `5h|7d` 数字表示上次记录到的两个额度窗口剩余百分比;当前 auth 会在打开面板时刷新,其他候选不会为了查询额度被自动切换。如果多个 bot runtime 最近使用过同一个 ChatGPT 账号,FoxClaw 会按已验证的账号 ID 合并它们缓存到的额度快照,因此一个 bot 的 `/auth` 面板可以显示另一个 bot 掌握到的额度信息,同时不会把不同账号混在一起。
|
|
393
393
|
|
|
394
394
|
示意:
|
|
395
395
|
|
|
@@ -405,10 +405,10 @@ Candidates: 2
|
|
|
405
405
|
[✅ 20|25|auth.json_personal] [✅]
|
|
406
406
|
[🔐 --|--|auth.json_team] [✅]
|
|
407
407
|
[🛡️ Access] [🔑 设备登录]
|
|
408
|
-
[🔄 Reload auth]
|
|
408
|
+
[🔄 Reload auth] [🔁 刷新全部]
|
|
409
409
|
```
|
|
410
410
|
|
|
411
|
-
右侧 `✅` / `⏸️` 表示当前状态。点一下会切换启用/禁用,列表刷新后图标会随状态变化。点击候选会切换 auth、重启对应 runtime
|
|
411
|
+
右侧 `✅` / `⏸️` 表示当前状态。点一下会切换启用/禁用,列表刷新后图标会随状态变化。点击候选会切换 auth、重启对应 runtime,并在原消息上刷新面板且保留按钮,因此可以立即连续切换。`刷新全部` 是维护操作:只有所有 Telegram runtime、微信 runtime、审批、待输入、登录流程和 auth 镜像写入都空闲时才允许执行。它会逐个访问面板里的 ChatGPT 候选,让 Codex 通过 `account/read refreshToken=true` 强制刷新 token,再用 usage 接口验证,成功后镜像到其他 bot home,最后恢复原本的当前 auth 并刷新面板摘要。`--|--` 表示该候选还没有额度历史快照。
|
|
412
412
|
|
|
413
413
|
命令等价用法:
|
|
414
414
|
|
|
@@ -417,6 +417,7 @@ Candidates: 2
|
|
|
417
417
|
- `/auth enable <n>`:让第 n 个候选参与自动轮转。
|
|
418
418
|
- `/auth disable <n>`:禁用第 n 个候选,自动轮转会跳过它。
|
|
419
419
|
- `/auth reload` 或 `/auth_reload`:重启 app-server,重新加载当前 `auth.json`。
|
|
420
|
+
- `/auth refresh all`:等同于面板上的“刷新全部”按钮。
|
|
420
421
|
|
|
421
422
|
切换 auth 时,如果当前 bot runtime 还有活跃 turn、待审批、待用户输入或 MCP elicitation,FoxClaw 会先拒绝切换,避免中途换号破坏正在进行的请求;另一个空闲 bot 不受影响。
|
|
422
423
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foxden-app/foxclaw",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "Foxden local execution claw for controlling Codex from trusted chat interfaces.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
".env.example",
|
|
16
16
|
"README.md",
|
|
17
17
|
"README_EN.md",
|
|
18
|
+
"CHANGELOG.md",
|
|
18
19
|
"LICENSE"
|
|
19
20
|
],
|
|
20
21
|
"private": false,
|
package/skills/foxclaw/SKILL.md
CHANGED
|
@@ -67,6 +67,7 @@ Telegram behavior:
|
|
|
67
67
|
- when `TG_BOT_TOKENS` contains multiple bots, one FoxClaw service starts independent Codex app-servers and auth selections per bot
|
|
68
68
|
- isolated Telegram runtimes use independent `CODEX_HOME` directories and file-backed credentials; they do not share Codex sessions
|
|
69
69
|
- refreshed ChatGPT candidates are mirrored only after online usage-endpoint validation; before auth switch or reload, a runtime restores any newer same-account credential found in another Codex home
|
|
70
|
+
- `/auth` includes a Refresh all panel button; it is a global-idle maintenance action that force-refreshes every ChatGPT candidate through Codex `account/read refreshToken=true`, validates usage, mirrors successful candidates, and restores the original current auth
|
|
70
71
|
- if `TG_BOT_TOKEN` is also set to one exact token from `TG_BOT_TOKENS`, that bot uses the default/shared-terminal runtime instead of an isolated Telegram home
|
|
71
72
|
- if Weixin is enabled alongside multiple Telegram bots, it remains on the default Codex runtime instead of borrowing a Telegram bot runtime
|
|
72
73
|
- in a group with multiple configured bots, address a bot by mention, reply, or suffixed command such as `/status@botname`
|
|
@@ -173,6 +174,8 @@ Do not describe the setup as "done" until this smoke test has either passed or b
|
|
|
173
174
|
|
|
174
175
|
Use this checklist when the user asks for standard closing actions, release wrap-up, local install updates, npm publish, or says things like "收尾动作", "中文 commit msg", "中英双语 commit msg", "push", "npm publish", or "本地安装更新".
|
|
175
176
|
|
|
177
|
+
When the user says development is complete, asks to "收尾", or asks to push/publish, treat that as authorization to finish the whole release path without asking for another confirmation. After validation passes, commit the scoped changes, push the branch, publish through the configured release flow, and verify the published result. Stop and ask only when a real external decision is required, such as npm/GitHub web auth, missing credentials, failed checks, merge conflicts, an already-published target version that requires choosing a new version, or unrelated local changes that would be staged.
|
|
178
|
+
|
|
176
179
|
1. Inspect scope before staging:
|
|
177
180
|
- `git status -sb`
|
|
178
181
|
- `git diff --stat`
|
|
@@ -188,6 +191,7 @@ Use this checklist when the user asks for standard closing actions, release wrap
|
|
|
188
191
|
- For this repo, prefer a bilingual one-line subject in the form `中文 | English`.
|
|
189
192
|
- Put Chinese first, keep the English half semantically equivalent, and keep both halves concise.
|
|
190
193
|
- For release commits, use the same format, for example `发布 0.4.0:支持多机器人隔离 | Release 0.4.0: support multi-bot isolation`.
|
|
194
|
+
- Update `CHANGELOG.md` for any published version, using bilingual Chinese and English notes.
|
|
191
195
|
- Never stage unrelated local changes.
|
|
192
196
|
5. Push the current branch after a successful commit:
|
|
193
197
|
- `git push origin <branch>`
|
|
@@ -202,12 +206,14 @@ Use this checklist when the user asks for standard closing actions, release wrap
|
|
|
202
206
|
- `/update` now attempts to update globally npm/pnpm-managed Codex CLI installations and refuses to restart while any configured Telegram runtime, enabled Weixin default runtime, or auth mirror write is busy.
|
|
203
207
|
7. Publish to npm when requested:
|
|
204
208
|
- Prefer GitHub Actions trusted publishing via `.github/workflows/publish.yml`: bump and commit the package version, push `main`, then push a matching `v<version>` tag. The tag version must match `package.json`.
|
|
209
|
+
- Ensure the matching changelog section exists before pushing the version tag; the publish workflow uses it to create or update the GitHub Release.
|
|
205
210
|
- Treat `workflow_dispatch` only as a retry path from an existing matching release tag; do not manually run publishing from `main`.
|
|
206
211
|
- Configure npmjs.com trusted publishing for `foxden-app/foxclaw` and workflow `publish.yml`; do not store npm tokens if OIDC trusted publishing is available.
|
|
207
212
|
- Temporary fallback: store an npm automation/bypass-2FA token as the GitHub Actions secret `NPM_TOKEN`. Never print the token or commit it.
|
|
208
213
|
- Check `npm whoami` and `npm view @foxden-app/foxclaw version`.
|
|
209
214
|
- If the target version is already published, bump with `npm version patch --no-git-tag-version` before validation and commit.
|
|
210
215
|
- If a workflow fails before `npm publish`, inspect that failed GitHub Actions step before attributing it to npm trusted publishing.
|
|
216
|
+
- After publishing, verify both `npm view @foxden-app/foxclaw version` and the GitHub Release page for the pushed tag.
|
|
211
217
|
- Manual fallback only: run `BROWSER=true npm publish` in a TTY.
|
|
212
218
|
- If npm returns `ENEEDAUTH`, report that npm publish is blocked by registry login and leave the package unreported as published.
|
|
213
219
|
- Never print npm tokens or `.npmrc` auth values.
|
|
@@ -7,6 +7,8 @@ description: Publish npm packages safely, especially packages that require npm 2
|
|
|
7
7
|
|
|
8
8
|
Use this skill to publish an npm package from a repo. Prefer CI trusted publishing when the repository has a publish workflow. Prefer the normal no-2FA publish path first only when CI publishing is not available. Fall back to npm web-auth only when npm explicitly prompts for it.
|
|
9
9
|
|
|
10
|
+
If the user asks to release, publish, "收尾", "push publish", or otherwise finish a completed package change, proceed through verification, commit, push, publish, and post-publish verification without asking for a second confirmation. Pause only for real blockers: missing credentials, failed checks, unrelated changes that would be staged, merge conflicts, an already-published target version that needs a version choice, or npm/GitHub web-auth that requires the user to click/confirm an external page.
|
|
11
|
+
|
|
10
12
|
## Release Checklist
|
|
11
13
|
|
|
12
14
|
1. Sync first when the user asks for it:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "NPM Publish"
|
|
3
|
-
short_description: "
|
|
4
|
-
default_prompt: "Publish this npm package
|
|
3
|
+
short_description: "Verify, push, and publish npm packages"
|
|
4
|
+
default_prompt: "Publish this npm package: verify it, commit and push scoped release changes, run the configured publish flow, and only pause for real auth or failure blockers."
|