@foxden-app/foxclaw 0.5.3 → 0.5.4

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,16 @@
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.4 - 2026-06-04
6
+
7
+ ### 中文
8
+ - 修复 `/update` 遗留 `self-update.json` pending 后永久显示“升级已经在进行中”的问题:pending 超过 15 分钟会自动转为失败状态,Telegram 会回报失败,下一次 `/update` 可以重新发起。
9
+ - 这个补丁专门兜底 0.5.2 在 `KillMode=control-group` 下被杀掉的旧 updater,以及任何未来被外部中断、没有写完成状态的升级进程。
10
+
11
+ ### English
12
+ - Fixed `/update` getting permanently stuck on "update already running" after a leftover `self-update.json` pending state: pending updates now expire as failed after 15 minutes, Telegram reports the failure, and the next `/update` can start again.
13
+ - This specifically recovers from the 0.5.2 updater killed by `KillMode=control-group`, and from any future updater interruption that fails to write a terminal status.
14
+
5
15
  ## 0.5.3 - 2026-06-04
6
16
 
7
17
  ### 中文
package/dist/update.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { AppLocale } from './types.js';
2
+ export declare const SELF_UPDATE_PENDING_TIMEOUT_MS: number;
2
3
  export type SelfUpdateState = 'pending' | 'succeeded' | 'failed';
3
4
  export interface SelfUpdateStatus {
4
5
  state: SelfUpdateState;
@@ -31,6 +32,8 @@ interface CreateSelfUpdateRuntimeOptions {
31
32
  statusPath: string;
32
33
  logPath: string;
33
34
  codexCliBin?: string;
35
+ pendingTimeoutMs?: number;
36
+ now?: () => Date;
34
37
  }
35
38
  interface PerformSelfUpdateOptions {
36
39
  entryPoint: string;
package/dist/update.js CHANGED
@@ -5,6 +5,7 @@ import { spawn, spawnSync } from 'node:child_process';
5
5
  const PACKAGE_SPEC = '@foxden-app/foxclaw@latest';
6
6
  const CODEX_PACKAGE_SPEC = '@openai/codex@latest';
7
7
  const UPDATE_STATUS_FILENAME = 'self-update.json';
8
+ export const SELF_UPDATE_PENDING_TIMEOUT_MS = 15 * 60_000;
8
9
  export function selfUpdateStatusPath(statusPath) {
9
10
  return path.join(path.dirname(statusPath), UPDATE_STATUS_FILENAME);
10
11
  }
@@ -148,11 +149,33 @@ export function writeSelfUpdateStatus(statusFile, status) {
148
149
  fs.writeFileSync(temporaryFile, `${JSON.stringify(status, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
149
150
  fs.renameSync(temporaryFile, statusFile);
150
151
  }
152
+ function readNormalizedSelfUpdateStatus(statusFile, pendingTimeoutMs, now) {
153
+ const status = readSelfUpdateStatus(statusFile);
154
+ if (!status || status.state !== 'pending' || pendingTimeoutMs <= 0) {
155
+ return status;
156
+ }
157
+ const updatedAtMs = Date.parse(status.updatedAt);
158
+ const nowMs = now().getTime();
159
+ const isStale = Number.isNaN(updatedAtMs) || nowMs - updatedAtMs > pendingTimeoutMs;
160
+ if (!isStale) {
161
+ return status;
162
+ }
163
+ const failed = {
164
+ ...status,
165
+ state: 'failed',
166
+ error: `self-update timed out after ${Math.round(pendingTimeoutMs / 60_000)} minute(s) without writing a completion status; the previous updater may have been interrupted`,
167
+ updatedAt: new Date(nowMs).toISOString(),
168
+ };
169
+ writeSelfUpdateStatus(statusFile, failed);
170
+ return failed;
171
+ }
151
172
  export function createSelfUpdateRuntime(options) {
152
173
  const statusFile = selfUpdateStatusPath(options.statusPath);
174
+ const pendingTimeoutMs = options.pendingTimeoutMs ?? SELF_UPDATE_PENDING_TIMEOUT_MS;
175
+ const now = options.now ?? (() => new Date());
153
176
  return {
154
177
  async launch(scopeId, locale) {
155
- const current = readSelfUpdateStatus(statusFile);
178
+ const current = readNormalizedSelfUpdateStatus(statusFile, pendingTimeoutMs, now);
156
179
  if (current?.state === 'pending') {
157
180
  throw new Error('A FoxClaw update is already running.');
158
181
  }
@@ -219,7 +242,7 @@ export function createSelfUpdateRuntime(options) {
219
242
  }
220
243
  },
221
244
  async readStatus() {
222
- return readSelfUpdateStatus(statusFile);
245
+ return readNormalizedSelfUpdateStatus(statusFile, pendingTimeoutMs, now);
223
246
  },
224
247
  async clearStatus() {
225
248
  fs.rmSync(statusFile, { force: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxden-app/foxclaw",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "Foxden local execution claw for controlling Codex from trusted chat interfaces.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",