@foxden-app/foxclaw 0.4.12 → 0.4.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +12 -2
- package/CHANGELOG.md +24 -0
- package/README.md +5 -3
- package/README_EN.md +5 -3
- package/dist/auth/cross_node_sync.d.ts +112 -0
- package/dist/auth/cross_node_sync.js +682 -0
- package/dist/auth/mirror.d.ts +37 -1
- package/dist/auth/mirror.js +136 -3
- package/dist/config.d.ts +10 -0
- package/dist/config.js +14 -0
- package/dist/controller/controller.d.ts +21 -0
- package/dist/controller/controller.js +200 -9
- package/dist/i18n.d.ts +42 -2
- package/dist/i18n.js +42 -2
- package/dist/main.js +192 -8
- package/dist/telegram/api.d.ts +6 -0
- package/dist/telegram/api.js +50 -0
- package/dist/telegram/gateway.d.ts +9 -0
- package/dist/telegram/gateway.js +33 -2
- package/dist/types.d.ts +16 -0
- package/dist/update.d.ts +2 -0
- package/dist/update.js +44 -6
- package/docs/user-manual.md +40 -4
- package/docs/zh/user-manual.md +40 -4
- package/package.json +1 -1
- package/skills/foxclaw/SKILL.md +4 -2
package/.env.example
CHANGED
|
@@ -35,8 +35,18 @@ DEFAULT_SANDBOX_MODE=workspace-write
|
|
|
35
35
|
TELEGRAM_POLL_INTERVAL_MS=1200
|
|
36
36
|
TELEGRAM_PREVIEW_THROTTLE_MS=800
|
|
37
37
|
THREAD_LIST_LIMIT=10
|
|
38
|
-
CODEX_CLI_BIN=/absolute/path/to/codex
|
|
39
|
-
|
|
38
|
+
CODEX_CLI_BIN=/absolute/path/to/codex
|
|
39
|
+
|
|
40
|
+
# Optional cross-node Codex auth sync.
|
|
41
|
+
# Disabled by default. When enabled, FoxClaw uses Telegram Bot-to-Bot private
|
|
42
|
+
# messages to move encrypted auth bundles between nodes. Enable Bot-to-Bot
|
|
43
|
+
# Communication Mode for the participating bots in BotFather first.
|
|
44
|
+
# AUTH_SYNC_ENABLED=true
|
|
45
|
+
# AUTH_SYNC_KEY=<shared-key-with-at-least-32-bytes>
|
|
46
|
+
# AUTH_SYNC_PEERS=@other_node_bot,@third_node_bot
|
|
47
|
+
# AUTH_SYNC_CLUSTER_ID=my-codex-auth-pool
|
|
48
|
+
# AUTH_SYNC_NODE_ID=
|
|
49
|
+
|
|
40
50
|
# Optional: standard HTTP(S) proxy for Telegram and ChatGPT/Codex backend requests.
|
|
41
51
|
# Put these in the same env file that `foxclaw start` installs into systemd/launchd.
|
|
42
52
|
# FoxClaw passes them to the service and enables Node's env proxy support.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
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.4.14 - 2026-06-04
|
|
6
|
+
|
|
7
|
+
### 中文
|
|
8
|
+
- 新增可选跨节点 auth 同步:通过 Telegram Bot-to-Bot 私聊传输 AES-GCM 加密 auth 包,无需公网 IP 或 FRP。
|
|
9
|
+
- 跨节点同步支持双主动:本机验证刷新后主动 push,切换/重载前本机恢复失败时主动 pull peer 已持有的有效副本。
|
|
10
|
+
- `/auth sync status|test|push all` 可查看状态、测试 peer/密钥、手动推送全部已验证候选;远端导入必须等全局空闲并通过 usage 验证后才写盘。
|
|
11
|
+
- `/auth refresh all confirm` 在启用跨节点同步时会先申请跨节点刷新锁;任一 peer 忙碌、拒绝或无响应都会阻止 refresh token 轮换。
|
|
12
|
+
|
|
13
|
+
### English
|
|
14
|
+
- Added optional cross-node auth sync using AES-GCM encrypted auth bundles over Telegram Bot-to-Bot private messages, with no public IP or FRP required.
|
|
15
|
+
- Added dual-active sync behavior: locally validated refreshes are pushed, and auth switch/reload recovery can pull an already-held valid peer copy when local recovery fails.
|
|
16
|
+
- Added `/auth sync status|test|push all` for status, peer/key testing, and manual broadcast of verified candidates. Remote imports wait for global idleness and usage validation before writing files.
|
|
17
|
+
- When cross-node sync is enabled, `/auth refresh all confirm` requests a cross-node refresh lease first; any busy, denying, or non-responsive peer blocks refresh-token rotation.
|
|
18
|
+
|
|
19
|
+
## 0.4.13 - 2026-06-03
|
|
20
|
+
|
|
21
|
+
### 中文
|
|
22
|
+
- `/update` 完成回报新增 Codex CLI 版本变化,和 FoxClaw 版本变化并排行展示。
|
|
23
|
+
- `/auth` 文本列表继续保留额度窗口详情,候选按钮改为只显示两个剩余百分比数字,未知值用 `—`,减少窄屏按钮截断。
|
|
24
|
+
|
|
25
|
+
### English
|
|
26
|
+
- Added Codex CLI from/to version reporting to completed `/update` messages alongside the FoxClaw version change.
|
|
27
|
+
- Kept quota-window detail in `/auth` text rows, while compacting candidate buttons to two remaining-percent numbers with `—` for unknown values.
|
|
28
|
+
|
|
5
29
|
## 0.4.12 - 2026-06-02
|
|
6
30
|
|
|
7
31
|
### 中文
|
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ DEFAULT_SANDBOX_MODE=workspace-write
|
|
|
85
85
|
|
|
86
86
|
配置文件默认在 `~/.foxclaw/.env`。想放别处的话设 `FOXCLAW_ENV=/path/to/.env`。
|
|
87
87
|
|
|
88
|
-
`foxclaw start` 会自动检查环境并安装/重启后台服务。后续升级直接运行 `foxclaw update`,它会沿用当前的 npm/pnpm 全局安装方式,尝试同步升级 npm/pnpm 安装的 Codex CLI
|
|
88
|
+
`foxclaw start` 会自动检查环境并安装/重启后台服务。后续升级直接运行 `foxclaw update`,它会沿用当前的 npm/pnpm 全局安装方式,尝试同步升级 npm/pnpm 安装的 Codex CLI,再完成安装、自检和服务重启,并在聊天回报 FoxClaw 与 Codex CLI 的版本变化。
|
|
89
89
|
|
|
90
90
|
FoxClaw 只响应 `TG_ALLOWED_USER_ID` 的消息——把机器人拉进群不代表群里所有人都能用。
|
|
91
91
|
|
|
@@ -128,7 +128,7 @@ FoxClaw 的一大特色是自动多账号切换。当一个账号触发用量限
|
|
|
128
128
|
3. 用 `/auth` 查看所有候选账号状态。
|
|
129
129
|
4. 用 `/auth enable <n>` / `/auth disable <n>` 控制哪些账号参与自动轮换。
|
|
130
130
|
|
|
131
|
-
候选较多时,`/auth` 每页显示 8 个账号,并提供翻页、`全部 / 已启用 / 需关注` 筛选。也可以用 `/auth list <关键词>` 搜索文件名,或用 `/auth page <页码>`
|
|
131
|
+
候选较多时,`/auth` 每页显示 8 个账号,并提供翻页、`全部 / 已启用 / 需关注` 筛选。也可以用 `/auth list <关键词>` 搜索文件名,或用 `/auth page <页码>` 直接跳页。文本列表的额度窗口按 Codex 实际返回值展示,例如 `5h:20|7d:25` 或单月窗口 `30d:97`;按钮只显示紧凑的两个剩余数字,例如 `20|25`,未知值显示为 `—`。面板会省略候选名中重复的 `auth.json_` 前缀,磁盘文件名保持不变。
|
|
132
132
|
|
|
133
133
|
当 Codex 报告用量限制错误时,FoxClaw 会自动:
|
|
134
134
|
- 切换到下一个未失败的候选账号
|
|
@@ -211,6 +211,8 @@ TG_BOT_TOKEN=123456:token_a
|
|
|
211
211
|
|
|
212
212
|
FoxClaw 仍然只运行一个系统服务。默认情况下,它会为每个 bot 启动独立 `codex app-server` 和独立 `CODEX_HOME`。因此 A 私聊运行 turn 时,B 私聊仍可独立切换自己的 `/auth`。候选凭据由 FoxClaw 在登录或刷新在线验证后镜像同步;切换或重载前还会从其他 Codex home 恢复同账号较新凭据。各 bot 的当前选择互不影响。每个 bot 首次私聊发送 `/help` 和 `/status`;`/auth` 会标明正在操作的 bot runtime,`/status` 会列出全部 bot 的连接、runtime 类型、当前 auth 和活动 turn 摘要。
|
|
213
213
|
|
|
214
|
+
多台机器共享同一合法账号池时,可以启用可选跨节点 auth 同步:`AUTH_SYNC_ENABLED=true`、`AUTH_SYNC_KEY` 和 `AUTH_SYNC_PEERS=@peer_bot`。FoxClaw 会通过 Telegram Bot-to-Bot 私聊传输加密 auth 包;本机验证刷新后主动 push,发现本机候选失效时主动 pull peer 已持有的有效副本。跨节点恢复不会自动刷新 token,`/auth refresh all confirm` 会先申请跨节点刷新锁。
|
|
215
|
+
|
|
214
216
|
如果你需要一路 Telegram 与终端互通 session,把同一个 token 同时填入 `TG_BOT_TOKENS` 和 `TG_BOT_TOKEN`。这个 bot 使用默认 `CODEX_HOME`(未设置时通常是 `~/.codex`)和默认 auth,因此能看到终端 Codex 的本地线程;它不再享有隔离 runtime 的“互不影响”保证,切换 auth 会影响终端和其他默认 runtime。
|
|
215
217
|
|
|
216
218
|
**怎么找群组和话题 ID:**
|
|
@@ -260,7 +262,7 @@ FoxClaw 会把 `codex app-server` 作为 detached 子进程启动,记录其 pi
|
|
|
260
262
|
- `/status`、`/account`、`/quota`、`/update`
|
|
261
263
|
- `/quota_nudge <credits|usage_limit> confirm`
|
|
262
264
|
- `/login_device`、`/login_cancel [id]`、`/logout confirm`
|
|
263
|
-
- `/auth [list [关键词]|filter <all|enabled|attention>|page <页码>|use <n>|enable <n>|disable <n>|reload|refresh all [confirm]|add <name>]`
|
|
265
|
+
- `/auth [list [关键词]|filter <all|enabled|attention>|page <页码>|use <n>|enable <n>|disable <n>|reload|refresh all [confirm]|sync <status|test|push all>|add <name>]`
|
|
264
266
|
- `/threads [query]`、`/threads archived`、`/open <n>`
|
|
265
267
|
- `/goal [objective|pause|resume|done|budget <tokens|off>|clear confirm]`
|
|
266
268
|
- `/history [limit]`、`/files <query>`、`/remote`
|
package/README_EN.md
CHANGED
|
@@ -85,7 +85,7 @@ DEFAULT_SANDBOX_MODE=workspace-write
|
|
|
85
85
|
|
|
86
86
|
The default config file is `~/.foxclaw/.env`. Set `FOXCLAW_ENV=/path/to/.env` if you want to keep it somewhere else.
|
|
87
87
|
|
|
88
|
-
`foxclaw start` runs checks and installs or restarts the background service. For later upgrades, run `foxclaw update`; it preserves the current npm/pnpm global-install method, attempts to update an npm/pnpm-managed Codex CLI, runs checks,
|
|
88
|
+
`foxclaw start` runs checks and installs or restarts the background service. For later upgrades, run `foxclaw update`; it preserves the current npm/pnpm global-install method, attempts to update an npm/pnpm-managed Codex CLI, runs checks, restarts the service, and reports both FoxClaw and Codex CLI version changes in chat.
|
|
89
89
|
|
|
90
90
|
FoxClaw accepts messages only from `TG_ALLOWED_USER_ID`. Putting the bot in a group does not make it available to every group member.
|
|
91
91
|
|
|
@@ -128,7 +128,7 @@ Setup:
|
|
|
128
128
|
3. Use `/auth` to view all candidate account statuses.
|
|
129
129
|
4. Use `/auth enable <n>` / `/auth disable <n>` to control which accounts participate in auto-rotation.
|
|
130
130
|
|
|
131
|
-
For larger inventories, `/auth` shows 8 candidates per page with paging and `All / Enabled / Attention` filters. Use `/auth list <keyword>` to search filenames or `/auth page <n>` to jump directly.
|
|
131
|
+
For larger inventories, `/auth` shows 8 candidates per page with paging and `All / Enabled / Attention` filters. Use `/auth list <keyword>` to search filenames or `/auth page <n>` to jump directly. Candidate rows use the quota windows reported by Codex, for example `5h:20|7d:25` or a single monthly window such as `30d:97`; buttons use compact remaining numbers such as `20|25`, with `—` for unknown values. The panel omits the repeated `auth.json_` prefix from candidate labels without renaming files on disk.
|
|
132
132
|
|
|
133
133
|
When Codex reports a usage-limit error, FoxClaw automatically:
|
|
134
134
|
- Switches to the next non-failed candidate account
|
|
@@ -211,6 +211,8 @@ TG_BOT_TOKEN=123456:token_a
|
|
|
211
211
|
|
|
212
212
|
FoxClaw remains one system service. By default, it starts an independent `codex app-server` and `CODEX_HOME` for each bot. While bot A is running a turn, bot B can switch its own `/auth` selection. Candidate credentials are mirrored only after online-validated login or refresh; before switching or reloading, FoxClaw also restores a newer same-account credential from another Codex home when available. Current selections remain independent. Send `/help` and `/status` in a private chat with each bot after installation; `/auth` names the runtime being managed, and `/status` summarizes every bot's connection, runtime type, selected auth, and active turns.
|
|
213
213
|
|
|
214
|
+
When multiple machines share the same legally owned account pool, optional cross-node auth sync can be enabled with `AUTH_SYNC_ENABLED=true`, `AUTH_SYNC_KEY`, and `AUTH_SYNC_PEERS=@peer_bot`. FoxClaw sends encrypted auth bundles through Telegram Bot-to-Bot private messages. A locally verified refresh is pushed to peers, and a node with a bad local candidate can pull an already-held valid peer copy. Cross-node recovery never auto-refreshes tokens; `/auth refresh all confirm` first requests a cross-node refresh lease.
|
|
215
|
+
|
|
214
216
|
To keep one Telegram bot interoperable with terminal Codex sessions, put the same token in both `TG_BOT_TOKENS` and `TG_BOT_TOKEN`. That bot uses the default `CODEX_HOME` (usually `~/.codex` when unset) and default auth, so it can see local terminal sessions. It no longer has the isolated runtime guarantee: `/auth` switches also affect the terminal and other default runtimes.
|
|
215
217
|
|
|
216
218
|
**How to find group and topic IDs:**
|
|
@@ -260,7 +262,7 @@ No static Codex app-server port is required in normal installs.
|
|
|
260
262
|
- `/status`, `/account`, `/quota`, `/update`
|
|
261
263
|
- `/quota_nudge <credits|usage_limit> confirm`
|
|
262
264
|
- `/login_device`, `/login_cancel [id]`, `/logout confirm`
|
|
263
|
-
- `/auth [list [keyword]|filter <all|enabled|attention>|page <n>|use <n>|enable <n>|disable <n>|reload|refresh all [confirm]|add <name>]`
|
|
265
|
+
- `/auth [list [keyword]|filter <all|enabled|attention>|page <n>|use <n>|enable <n>|disable <n>|reload|refresh all [confirm]|sync <status|test|push all>|add <name>]`
|
|
264
266
|
- `/threads [query]`, `/threads archived`, `/open <n>`
|
|
265
267
|
- `/goal [objective|pause|resume|done|budget <tokens|off>|clear confirm]`
|
|
266
268
|
- `/history [limit]`, `/files <query>`, `/remote`
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { Logger } from '../logger.js';
|
|
2
|
+
import type { AuthMirrorCandidateRecord, AuthMirrorImportResult } from './mirror.js';
|
|
3
|
+
export interface AuthSyncConfig {
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
transport: 'telegram-private';
|
|
6
|
+
key: string | null;
|
|
7
|
+
peers: string[];
|
|
8
|
+
nodeId: string | null;
|
|
9
|
+
clusterId: string;
|
|
10
|
+
statePath: string;
|
|
11
|
+
tempDir: string;
|
|
12
|
+
}
|
|
13
|
+
export interface AuthSyncPeerIdentity {
|
|
14
|
+
userId: string;
|
|
15
|
+
username: string | null;
|
|
16
|
+
}
|
|
17
|
+
export interface AuthSyncStatus {
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
nodeId: string | null;
|
|
20
|
+
transport: 'telegram-private';
|
|
21
|
+
peers: string[];
|
|
22
|
+
pendingImports: number;
|
|
23
|
+
lastSentAt: string | null;
|
|
24
|
+
lastReceivedAt: string | null;
|
|
25
|
+
lastImportedAt: string | null;
|
|
26
|
+
lastImportCandidate: string | null;
|
|
27
|
+
lastPullAt: string | null;
|
|
28
|
+
lastPullCandidate: string | null;
|
|
29
|
+
lastError: string | null;
|
|
30
|
+
activeLeaseId: string | null;
|
|
31
|
+
}
|
|
32
|
+
export interface AuthSyncValidationResult {
|
|
33
|
+
ok: boolean;
|
|
34
|
+
reason?: string | null;
|
|
35
|
+
}
|
|
36
|
+
export interface AuthSyncImportCallbacks {
|
|
37
|
+
readLocalCandidate: (candidateName: string) => Promise<AuthMirrorCandidateRecord | null>;
|
|
38
|
+
listLocalCandidates: () => Promise<AuthMirrorCandidateRecord[]>;
|
|
39
|
+
validateCandidate: (candidateName: string, raw: string, expectedAccountId: string) => Promise<AuthSyncValidationResult>;
|
|
40
|
+
importCandidate: (candidateName: string, raw: string, source: {
|
|
41
|
+
nodeId: string;
|
|
42
|
+
label?: string | null;
|
|
43
|
+
}) => Promise<AuthMirrorImportResult>;
|
|
44
|
+
isIdle: () => boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface AuthSyncTransport {
|
|
47
|
+
send: (peer: string, envelope: string) => Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
export interface AuthSyncLeaseResult {
|
|
50
|
+
ok: boolean;
|
|
51
|
+
leaseId: string | null;
|
|
52
|
+
reason?: string | null;
|
|
53
|
+
}
|
|
54
|
+
export declare class CrossNodeAuthSync {
|
|
55
|
+
private readonly config;
|
|
56
|
+
private readonly logger;
|
|
57
|
+
private readonly transport;
|
|
58
|
+
private readonly callbacks;
|
|
59
|
+
private nodeId;
|
|
60
|
+
private key;
|
|
61
|
+
private readonly peers;
|
|
62
|
+
private readonly peerKeys;
|
|
63
|
+
private readonly pendingImports;
|
|
64
|
+
private readonly pendingPulls;
|
|
65
|
+
private readonly pendingLeases;
|
|
66
|
+
private seenNonces;
|
|
67
|
+
private timer;
|
|
68
|
+
private activeRemoteLease;
|
|
69
|
+
private activeLocalLease;
|
|
70
|
+
private state;
|
|
71
|
+
constructor(config: AuthSyncConfig, logger: Logger, transport: AuthSyncTransport, callbacks: AuthSyncImportCallbacks);
|
|
72
|
+
initialize(): Promise<void>;
|
|
73
|
+
start(): void;
|
|
74
|
+
stop(): void;
|
|
75
|
+
getStatus(): AuthSyncStatus;
|
|
76
|
+
isIdle(): boolean;
|
|
77
|
+
publishCandidate(candidateName: string): Promise<boolean>;
|
|
78
|
+
pushAll(): Promise<{
|
|
79
|
+
sent: number;
|
|
80
|
+
skipped: number;
|
|
81
|
+
}>;
|
|
82
|
+
publishDigest(): Promise<void>;
|
|
83
|
+
requestRecovery(candidateName: string, current?: {
|
|
84
|
+
accountId: string | null;
|
|
85
|
+
lastRefreshMs: number | null;
|
|
86
|
+
}): Promise<boolean>;
|
|
87
|
+
acquireRefreshLease(reason: string): Promise<AuthSyncLeaseResult>;
|
|
88
|
+
releaseRefreshLease(leaseId: string | null): Promise<void>;
|
|
89
|
+
testPeers(): Promise<{
|
|
90
|
+
sent: number;
|
|
91
|
+
}>;
|
|
92
|
+
handleIncomingEnvelope(rawEnvelope: string, peer: AuthSyncPeerIdentity): Promise<boolean>;
|
|
93
|
+
private handleMessage;
|
|
94
|
+
private handlePullRequest;
|
|
95
|
+
private handlePullResponse;
|
|
96
|
+
private handleDigest;
|
|
97
|
+
private handleLeaseRequest;
|
|
98
|
+
private handleLeaseReply;
|
|
99
|
+
private enqueueImport;
|
|
100
|
+
private processPendingImports;
|
|
101
|
+
private validateAndImport;
|
|
102
|
+
private sendToAll;
|
|
103
|
+
private sendToPeer;
|
|
104
|
+
private sealEnvelope;
|
|
105
|
+
private openEnvelope;
|
|
106
|
+
private isReady;
|
|
107
|
+
private isAllowedPeer;
|
|
108
|
+
private expireLeases;
|
|
109
|
+
private recordError;
|
|
110
|
+
private writeState;
|
|
111
|
+
}
|
|
112
|
+
export declare function readAccessTokenExpiresAtMs(raw: string): number | null;
|