@foxden-app/foxclaw 0.5.9 → 0.5.11

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 CHANGED
@@ -2,6 +2,32 @@
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.11 - 2026-06-08
6
+
7
+ ### 中文
8
+ - `/auth` 面板的 `Bot runtime` 现在显示 Telegram bot id,例如 `@WuguiAI2_Bot (bot8949529424)`,便于和 `~/.foxclaw/codex/telegram/<botid>/home` 对应。
9
+ - `/auth` 面板新增“安全同步”按钮,并支持 `/auth sync safe`,可在全局空闲时安全打平本机多 bot auth,并把已校验的候选推送到跨节点 peer。
10
+ - 本机 auth mirror 新增全量安全同步路径:只传播通过既有在线校验的刷新候选,同时补齐 canonical 中已知、同账号且更新的 runtime 副本。
11
+
12
+ ### English
13
+ - The `/auth` panel now shows the Telegram bot id in `Bot runtime`, for example `@WuguiAI2_Bot (bot8949529424)`, making it easy to match the runtime with `~/.foxclaw/codex/telegram/<botid>/home`.
14
+ - Added a Safe sync button to the `/auth` panel, plus `/auth sync safe`, to flatten same-node multi-bot auth while globally idle and push validated candidates to cross-node peers.
15
+ - Added a full safe-sync path for the local auth mirror: it only propagates candidates that pass the existing online validation and fills runtime copies from newer same-account canonical candidates.
16
+
17
+ ## 0.5.10 - 2026-06-08
18
+
19
+ ### 中文
20
+ - Telegram 输入队列改为 SQLite 持久化 FIFO,FoxClaw 重启后不再丢失排队中的 Codex 请求,并在 `/status` 中显示排队数量。
21
+ - Telegram 图片和文件会先进入附件暂存区,支持媒体组归并、下一条文字自动带附件发给 Codex,以及“分析 / 清空”按钮操作。
22
+ - 引导式 Plan 会话现在会持久化并在重启后恢复,减少长任务或确认流程被服务重启打断的风险。
23
+ - `/update` 完成回报会从已安装包的 `CHANGELOG.md` 读取当前版本更新内容,让 Telegram 里直接看到这次升级改了什么。
24
+
25
+ ### English
26
+ - Replaced the in-memory Telegram prompt queue with a persisted SQLite FIFO so queued Codex requests survive FoxClaw restarts, and `/status` now reports queued turn count.
27
+ - Telegram photos and files are staged before dispatch, with media-group merging, next-message attachment consumption, and Analyze/Clear buttons.
28
+ - Guided Plan sessions are persisted and restored after restart, reducing interruption risk for long-running or confirmation-based flows.
29
+ - `/update` completion reports now read the installed package `CHANGELOG.md` entry for the target version so Telegram shows what changed in the upgrade.
30
+
5
31
  ## 0.5.9 - 2026-06-07
6
32
 
7
33
  ### 中文
package/README.md CHANGED
@@ -39,6 +39,7 @@ FoxClaw(狸爪)的目标很直接:让你用手机控制本机的 Codex,
39
39
  - 已经装好,想系统了解 `/help`、`/setup`、`/threads`、`/watch`、`/auth` 和账号轮转?看 [用户手册](./docs/zh/user-manual.md)。
40
40
  - 想把同一组合法 ChatGPT auth 候选同步到多台机器?看 [跨节点 auth 同步配置指南](./docs/zh/cross-node-auth-sync.md)。
41
41
  - 想了解每个版本改了什么?看 [更新日志](./CHANGELOG.md)。
42
+ - 维护者准备发版?看 [发布 runbook](./docs/zh/release.md)。
42
43
  - Git、Node、`.env` 都玩得转?直接往下看快速设置。
43
44
  - 卡住了?看 [故障排查](./docs/zh/troubleshooting.md)。
44
45
 
package/README_EN.md CHANGED
@@ -39,6 +39,7 @@ FoxClaw is more than message forwarding. It provides Telegram panels for Codex w
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
40
  - Want to sync the same legally owned ChatGPT auth candidate pool across multiple machines? Read the [Cross-Node Auth Sync Setup Guide](./docs/cross-node-auth-sync.md).
41
41
  - Want to see what changed in each release? Read the [Changelog](./CHANGELOG.md).
42
+ - Maintaining a release? Use the [Release Runbook](./docs/release.md).
42
43
  - Already comfortable with Git, Node, and `.env` files? Use the quick setup below.
43
44
  - Something failed? Check [Troubleshooting](./docs/troubleshooting.md).
44
45
 
@@ -52,6 +52,10 @@ export interface AuthMirrorSyncedEvent {
52
52
  status: AuthMirrorStatus;
53
53
  record: AuthMirrorCandidateRecord;
54
54
  }
55
+ export interface AuthMirrorSyncAllResult {
56
+ synced: number;
57
+ skipped: number;
58
+ }
55
59
  export interface AuthMirrorHooks {
56
60
  onSynced?: (event: AuthMirrorSyncedEvent) => Promise<void> | void;
57
61
  }
@@ -77,8 +81,10 @@ export declare class AuthCandidateMirror {
77
81
  readRuntimeCandidate(runtimeId: string, candidateName: string): Promise<AuthMirrorCandidateRecord | null>;
78
82
  listNewestCandidates(): Promise<AuthMirrorCandidateRecord[]>;
79
83
  syncRuntimeCandidate(runtimeId: string, candidateName: string): Promise<boolean>;
84
+ syncAllRuntimeCandidates(): Promise<AuthMirrorSyncAllResult>;
80
85
  recoverRuntimeCandidate(runtimeId: string, candidateName: string): Promise<AuthMirrorRecovery | null>;
81
86
  private scan;
87
+ private distributeCanonicalCandidates;
82
88
  importExternalCandidate(candidateName: string, raw: string, source: {
83
89
  nodeId: string;
84
90
  label?: string | null;
@@ -126,6 +126,28 @@ export class AuthCandidateMirror {
126
126
  return false;
127
127
  return this.withActivity(() => this.propagateValidatedCandidate(runtime, candidateName));
128
128
  }
129
+ async syncAllRuntimeCandidates() {
130
+ return this.withActivity(async () => {
131
+ let synced = 0;
132
+ let skipped = 0;
133
+ for (const runtime of this.runtimes) {
134
+ const names = await listAuthCandidateNames(runtime.authDir);
135
+ for (const name of names) {
136
+ if (await this.propagateValidatedCandidate(runtime, name)) {
137
+ synced += 1;
138
+ }
139
+ else {
140
+ skipped += 1;
141
+ }
142
+ }
143
+ }
144
+ const distributed = await this.distributeCanonicalCandidates();
145
+ return {
146
+ synced: synced + distributed.synced,
147
+ skipped: skipped + distributed.skipped,
148
+ };
149
+ });
150
+ }
129
151
  async recoverRuntimeCandidate(runtimeId, candidateName) {
130
152
  if (!isAuthCandidateName(candidateName))
131
153
  return null;
@@ -169,6 +191,33 @@ export class AuthCandidateMirror {
169
191
  }
170
192
  });
171
193
  }
194
+ async distributeCanonicalCandidates() {
195
+ let synced = 0;
196
+ let skipped = 0;
197
+ for (const name of await listAuthCandidateNames(this.canonicalDir)) {
198
+ const canonical = await readChatGptAuthRecord(path.join(this.canonicalDir, name));
199
+ if (!canonical) {
200
+ skipped += this.runtimes.length;
201
+ continue;
202
+ }
203
+ for (const runtime of this.runtimes) {
204
+ const destinationPath = path.join(runtime.authDir, name);
205
+ const destination = await readChatGptAuthRecord(destinationPath);
206
+ if (destination && destination.accountId !== canonical.accountId) {
207
+ skipped += 1;
208
+ this.logger.warn('auth.mirror.distribution_conflict', { runtimeId: runtime.id, name });
209
+ continue;
210
+ }
211
+ if (destination && destination.lastRefreshMs >= canonical.lastRefreshMs) {
212
+ skipped += 1;
213
+ continue;
214
+ }
215
+ await atomicWrite(destinationPath, canonical.raw);
216
+ synced += 1;
217
+ }
218
+ }
219
+ return { synced, skipped };
220
+ }
172
221
  async importExternalCandidate(candidateName, raw, source) {
173
222
  if (!isAuthCandidateName(candidateName)) {
174
223
  return { ok: false, imported: false, reason: 'invalid candidate name' };
@@ -19,6 +19,12 @@ export interface CoreCoordinator {
19
19
  }>;
20
20
  releaseAuthRefreshLease?: (leaseId: string | null) => Promise<void>;
21
21
  getAuthSyncStatus?: () => RuntimeStatus['authSync'];
22
+ authSyncSafeAll?: () => Promise<{
23
+ localSynced: number;
24
+ localSkipped: number;
25
+ sent: number;
26
+ skipped: number;
27
+ }>;
22
28
  authSyncPushAll?: () => Promise<{
23
29
  sent: number;
24
30
  skipped: number;
@@ -50,11 +56,9 @@ export declare class BridgeSessionCore {
50
56
  private activeTurns;
51
57
  private activeTurnsByTurnId;
52
58
  private observedThreadWatchers;
53
- private queuedPrompts;
54
59
  private pendingTurnErrors;
55
60
  private pendingUserInputs;
56
61
  private pendingMcpElicitations;
57
- private pendingPlanImplementations;
58
62
  private pendingLoginsByScope;
59
63
  private pendingLoginScopesById;
60
64
  private pendingAuthAddsByLoginId;
@@ -159,6 +163,8 @@ export declare class BridgeSessionCore {
159
163
  private maybeSendPlanImplementationPrompt;
160
164
  private findPendingPlanImplementation;
161
165
  private clearPlanImplementationPromptsForScope;
166
+ private pendingPlanImplementationFromSession;
167
+ private restoreGuidedPlanSessions;
162
168
  private hasPendingUserInputForTurn;
163
169
  private finalizeUserInputsForTurn;
164
170
  private handlePlanImplementationCallback;
@@ -173,8 +179,15 @@ export declare class BridgeSessionCore {
173
179
  private createBinding;
174
180
  private startTurnWithRecovery;
175
181
  private buildTurnInput;
182
+ private buildTurnInputFromStagedAttachments;
176
183
  private resolveServiceTierForTurn;
177
184
  private stageAttachments;
185
+ private handleTelegramAttachmentBatch;
186
+ private consumePendingAttachmentBatchWithText;
187
+ private handleAttachmentBatchCallback;
188
+ private consumePendingAttachmentBatch;
189
+ private startTurnFromPreparedAttachmentBatch;
190
+ private renderAttachmentBatchCard;
178
191
  private registerActiveTurn;
179
192
  private createActiveTurnState;
180
193
  getCurrentAuthLabel(): Promise<string | null>;
@@ -247,7 +260,11 @@ export declare class BridgeSessionCore {
247
260
  private handleActiveTurnInboundMessage;
248
261
  private queuePromptAfterActiveTurn;
249
262
  private steerActiveTurn;
263
+ private enqueuePreparedTurnInput;
264
+ private startBoundTurnFromQueuedInput;
250
265
  private startQueuedPromptIfPresent;
266
+ private recoverQueuedTurns;
267
+ private markQueuedTurnCompleted;
251
268
  private handleModeCommand;
252
269
  private handleActiveTurnMessageModeCommand;
253
270
  private handleGoalCommand;
@@ -263,6 +280,7 @@ export declare class BridgeSessionCore {
263
280
  private clearSelfUpdateStatusPoll;
264
281
  private pollSelfUpdateStatus;
265
282
  private formatSelfUpdateResult;
283
+ private formatSelfUpdateReleaseNotes;
266
284
  private formatCodexUpdateResult;
267
285
  private scheduleProactiveAuthRefresh;
268
286
  private clearProactiveAuthRefreshTimer;
@@ -271,6 +289,7 @@ export declare class BridgeSessionCore {
271
289
  private notifyProactiveAuthRefresh;
272
290
  private handleAuthCommand;
273
291
  private handleAuthSyncCommand;
292
+ private runAuthSafeSyncAll;
274
293
  private handleAuthRefreshAllCommand;
275
294
  private handleAuthUseCommand;
276
295
  private handleAuthToggleCommand;