@foxden-app/foxclaw 0.5.2 → 0.5.3
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 +10 -0
- package/dist/update.d.ts +17 -0
- package/dist/update.js +85 -5
- package/package.json +1 -1
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.3 - 2026-06-04
|
|
6
|
+
|
|
7
|
+
### 中文
|
|
8
|
+
- 修复 0.5.2 中 `/update` 完成回报丢失的问题:Linux 自升级子进程现在通过独立的 user systemd transient service 运行,不再留在 `foxclaw.service` control group 里,因此 `KillMode=control-group` 重启服务时不会提前杀掉 updater。
|
|
9
|
+
- 保留 `KillMode=control-group` 的 app-server 清理能力,同时让升级进程能在服务重启后继续写入完成状态,由新服务启动后的轮询发送 Telegram 成功/失败回报。
|
|
10
|
+
|
|
11
|
+
### English
|
|
12
|
+
- Fixed missing `/update` completion reports in 0.5.2: on Linux the self-update worker now runs in a separate user systemd transient service instead of the `foxclaw.service` control group, so `KillMode=control-group` no longer kills the updater during service restart.
|
|
13
|
+
- Kept `KillMode=control-group` app-server cleanup while allowing the updater to write its final status after restart; the newly started service polls that status and sends the Telegram success/failure report.
|
|
14
|
+
|
|
5
15
|
## 0.5.2 - 2026-06-04
|
|
6
16
|
|
|
7
17
|
### 中文
|
package/dist/update.d.ts
CHANGED
|
@@ -40,6 +40,12 @@ interface PerformSelfUpdateOptions {
|
|
|
40
40
|
codexCliBin?: string;
|
|
41
41
|
env?: NodeJS.ProcessEnv;
|
|
42
42
|
}
|
|
43
|
+
export interface SelfUpdateLaunchCommand {
|
|
44
|
+
command: string;
|
|
45
|
+
args: string[];
|
|
46
|
+
env: NodeJS.ProcessEnv;
|
|
47
|
+
viaSystemdRun: boolean;
|
|
48
|
+
}
|
|
43
49
|
export interface SelfUpdateOutcome {
|
|
44
50
|
ok: boolean;
|
|
45
51
|
fromVersion: string;
|
|
@@ -53,5 +59,16 @@ export declare function resolveCodexUpdateInstaller(codexCliBin: string, nodePat
|
|
|
53
59
|
export declare function readSelfUpdateStatus(statusFile: string): SelfUpdateStatus | null;
|
|
54
60
|
export declare function writeSelfUpdateStatus(statusFile: string, status: SelfUpdateStatus): void;
|
|
55
61
|
export declare function createSelfUpdateRuntime(options: CreateSelfUpdateRuntimeOptions): SelfUpdateRuntime;
|
|
62
|
+
export declare function buildSelfUpdateLaunchCommand(options: {
|
|
63
|
+
entryPoint: string;
|
|
64
|
+
nodePath: string;
|
|
65
|
+
statusFile: string;
|
|
66
|
+
logPath: string;
|
|
67
|
+
codexCliBin?: string;
|
|
68
|
+
env?: NodeJS.ProcessEnv;
|
|
69
|
+
platform?: NodeJS.Platform;
|
|
70
|
+
systemdRunPath?: string | null;
|
|
71
|
+
unitName?: string;
|
|
72
|
+
}): SelfUpdateLaunchCommand;
|
|
56
73
|
export declare function performSelfUpdate(options: PerformSelfUpdateOptions): SelfUpdateOutcome;
|
|
57
74
|
export {};
|
package/dist/update.js
CHANGED
|
@@ -171,12 +171,33 @@ export function createSelfUpdateRuntime(options) {
|
|
|
171
171
|
fs.mkdirSync(path.dirname(options.logPath), { recursive: true });
|
|
172
172
|
const logFd = fs.openSync(options.logPath, 'a', 0o600);
|
|
173
173
|
try {
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
174
|
+
const launch = buildSelfUpdateLaunchCommand({
|
|
175
|
+
entryPoint: options.entryPoint,
|
|
176
|
+
nodePath: options.nodePath,
|
|
177
|
+
statusFile,
|
|
178
|
+
logPath: options.logPath,
|
|
179
|
+
...(options.codexCliBin ? { codexCliBin: options.codexCliBin } : {}),
|
|
178
180
|
});
|
|
179
|
-
|
|
181
|
+
if (launch.viaSystemdRun) {
|
|
182
|
+
const result = spawnSync(launch.command, launch.args, {
|
|
183
|
+
stdio: ['ignore', logFd, logFd],
|
|
184
|
+
env: launch.env,
|
|
185
|
+
});
|
|
186
|
+
if (result.error) {
|
|
187
|
+
throw result.error;
|
|
188
|
+
}
|
|
189
|
+
if (result.status !== 0) {
|
|
190
|
+
throw new Error(`${launch.command} ${launch.args.join(' ')} exited with status ${result.status ?? 'unknown'}.`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
const child = spawn(launch.command, launch.args, {
|
|
195
|
+
detached: true,
|
|
196
|
+
stdio: ['ignore', logFd, logFd],
|
|
197
|
+
env: launch.env,
|
|
198
|
+
});
|
|
199
|
+
child.unref();
|
|
200
|
+
}
|
|
180
201
|
}
|
|
181
202
|
catch (error) {
|
|
182
203
|
writeSelfUpdateStatus(statusFile, {
|
|
@@ -205,6 +226,40 @@ export function createSelfUpdateRuntime(options) {
|
|
|
205
226
|
},
|
|
206
227
|
};
|
|
207
228
|
}
|
|
229
|
+
export function buildSelfUpdateLaunchCommand(options) {
|
|
230
|
+
const env = options.codexCliBin
|
|
231
|
+
? { ...(options.env ?? process.env), CODEX_CLI_BIN: options.codexCliBin }
|
|
232
|
+
: { ...(options.env ?? process.env) };
|
|
233
|
+
const updateArgs = [options.entryPoint, 'update', '--notification-file', options.statusFile];
|
|
234
|
+
const platform = options.platform ?? process.platform;
|
|
235
|
+
const systemdRunPath = options.systemdRunPath === undefined
|
|
236
|
+
? resolveCommand('systemd-run', env)
|
|
237
|
+
: options.systemdRunPath;
|
|
238
|
+
if (platform === 'linux' && systemdRunPath) {
|
|
239
|
+
const unitName = options.unitName ?? `foxclaw-update-${process.pid}-${Date.now()}`;
|
|
240
|
+
return {
|
|
241
|
+
command: systemdRunPath,
|
|
242
|
+
args: [
|
|
243
|
+
'--user',
|
|
244
|
+
'--collect',
|
|
245
|
+
`--unit=${unitName}`,
|
|
246
|
+
`--property=StandardOutput=append:${options.logPath}`,
|
|
247
|
+
`--property=StandardError=append:${options.logPath}`,
|
|
248
|
+
...systemdSetEnvArgs(env),
|
|
249
|
+
options.nodePath,
|
|
250
|
+
...updateArgs,
|
|
251
|
+
],
|
|
252
|
+
env,
|
|
253
|
+
viaSystemdRun: true,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
command: options.nodePath,
|
|
258
|
+
args: updateArgs,
|
|
259
|
+
env,
|
|
260
|
+
viaSystemdRun: false,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
208
263
|
export function performSelfUpdate(options) {
|
|
209
264
|
const env = options.env ?? process.env;
|
|
210
265
|
let toVersion = null;
|
|
@@ -295,6 +350,31 @@ function executableCandidates(commandName, nodePath, env, preferred = []) {
|
|
|
295
350
|
...(env.PATH || '').split(path.delimiter).filter(Boolean).map((dir) => path.join(dir, commandName)),
|
|
296
351
|
].filter((candidate, index, all) => candidate && all.indexOf(candidate) === index);
|
|
297
352
|
}
|
|
353
|
+
function resolveCommand(commandName, env) {
|
|
354
|
+
for (const directory of (env.PATH || '').split(path.delimiter).filter(Boolean)) {
|
|
355
|
+
const candidate = path.join(directory, commandName);
|
|
356
|
+
if (fs.existsSync(candidate)) {
|
|
357
|
+
return candidate;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
for (const fallback of ['/usr/bin', '/bin', '/usr/local/bin']) {
|
|
361
|
+
const candidate = path.join(fallback, commandName);
|
|
362
|
+
if (fs.existsSync(candidate)) {
|
|
363
|
+
return candidate;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
function systemdSetEnvArgs(env) {
|
|
369
|
+
return Object.entries(env)
|
|
370
|
+
.filter((entry) => {
|
|
371
|
+
const [key, value] = entry;
|
|
372
|
+
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(key)
|
|
373
|
+
&& typeof value === 'string'
|
|
374
|
+
&& !value.includes('\0');
|
|
375
|
+
})
|
|
376
|
+
.map(([key, value]) => `--setenv=${key}=${value}`);
|
|
377
|
+
}
|
|
298
378
|
function buildInstallerEnv(entryPoint, installer, env) {
|
|
299
379
|
const pnpmHome = installer.manager === 'pnpm'
|
|
300
380
|
? installer.pnpmHome ?? inferPnpmHomeFromEntryPoint(entryPoint)
|