@foxden-app/foxclaw 0.4.13 → 0.4.15
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 +3 -1
- package/README_EN.md +3 -1
- 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 +20 -0
- package/dist/controller/controller.js +173 -4
- package/dist/i18n.d.ts +40 -2
- package/dist/i18n.js +40 -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 +14 -0
- package/docs/user-manual.md +36 -0
- package/docs/zh/user-manual.md +36 -0
- package/package.json +1 -1
- package/skills/foxclaw/SKILL.md +15 -7
- package/skills/foxclaw/agents/openai.yaml +1 -1
- package/skills/npm-publish/SKILL.md +2 -0
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.15 - 2026-06-04
|
|
6
|
+
|
|
7
|
+
### 中文
|
|
8
|
+
- 更新 FoxClaw 收尾 skill:发布前固定检查设计文档、中文/英文使用手册和对外公开文档。
|
|
9
|
+
- 涉及 Telegram 设置的变更,使用手册必须写清 `@BotFather` 操作步骤;npm 发布 skill 也会遵守该项目文档门槛。
|
|
10
|
+
|
|
11
|
+
### English
|
|
12
|
+
- Updated the FoxClaw wrap-up skill to require design docs, Chinese/English user manuals, and public-facing docs before release.
|
|
13
|
+
- Telegram setup changes must document the exact `@BotFather` steps in the manuals; the npm publish skill now honors this project documentation gate too.
|
|
14
|
+
|
|
15
|
+
## 0.4.14 - 2026-06-04
|
|
16
|
+
|
|
17
|
+
### 中文
|
|
18
|
+
- 新增可选跨节点 auth 同步:通过 Telegram Bot-to-Bot 私聊传输 AES-GCM 加密 auth 包,无需公网 IP 或 FRP。
|
|
19
|
+
- 跨节点同步支持双主动:本机验证刷新后主动 push,切换/重载前本机恢复失败时主动 pull peer 已持有的有效副本。
|
|
20
|
+
- `/auth sync status|test|push all` 可查看状态、测试 peer/密钥、手动推送全部已验证候选;远端导入必须等全局空闲并通过 usage 验证后才写盘。
|
|
21
|
+
- `/auth refresh all confirm` 在启用跨节点同步时会先申请跨节点刷新锁;任一 peer 忙碌、拒绝或无响应都会阻止 refresh token 轮换。
|
|
22
|
+
|
|
23
|
+
### English
|
|
24
|
+
- 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.
|
|
25
|
+
- 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.
|
|
26
|
+
- 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.
|
|
27
|
+
- 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.
|
|
28
|
+
|
|
5
29
|
## 0.4.13 - 2026-06-03
|
|
6
30
|
|
|
7
31
|
### 中文
|
package/README.md
CHANGED
|
@@ -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
|
@@ -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;
|