@foxden-app/foxclaw 0.3.13 → 0.3.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/README.md +4 -2
- package/README_EN.md +4 -2
- package/dist/controller/controller.d.ts +9 -1
- package/dist/controller/controller.js +80 -1
- package/dist/i18n.d.ts +14 -0
- package/dist/i18n.js +15 -0
- package/dist/main.js +27 -1
- package/dist/update.d.ts +49 -0
- package/dist/update.js +203 -0
- package/docs/install-for-beginners.md +3 -2
- package/docs/troubleshooting.md +1 -1
- package/docs/user-manual.md +4 -1
- package/docs/zh/install-for-beginners.md +3 -2
- package/docs/zh/troubleshooting.md +1 -1
- package/docs/zh/user-manual.md +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ DEFAULT_SANDBOX_MODE=workspace-write
|
|
|
84
84
|
|
|
85
85
|
配置文件默认在 `~/.foxclaw/.env`。想放别处的话设 `FOXCLAW_ENV=/path/to/.env`。
|
|
86
86
|
|
|
87
|
-
`foxclaw start`
|
|
87
|
+
`foxclaw start` 会自动检查环境并安装/重启后台服务。后续升级直接运行 `foxclaw update`,它会沿用当前的 npm/pnpm 全局安装方式,完成安装、自检和服务重启。
|
|
88
88
|
|
|
89
89
|
FoxClaw 只响应 `TG_ALLOWED_USER_ID` 的消息——把机器人拉进群不代表群里所有人都能用。
|
|
90
90
|
|
|
@@ -150,6 +150,7 @@ journalctl --user -u foxclaw.service -f
|
|
|
150
150
|
```bash
|
|
151
151
|
foxclaw status
|
|
152
152
|
foxclaw restart
|
|
153
|
+
foxclaw update
|
|
153
154
|
foxclaw stop
|
|
154
155
|
```
|
|
155
156
|
|
|
@@ -238,7 +239,7 @@ FoxClaw 会把 `codex app-server` 作为 detached 子进程启动,记录其 pi
|
|
|
238
239
|
- `/setup` — 统一设置面板
|
|
239
240
|
- `/fast <on|off|toggle>`
|
|
240
241
|
- `/active <steer|queue>`
|
|
241
|
-
- `/status`、`/account`、`/quota`
|
|
242
|
+
- `/status`、`/account`、`/quota`、`/update`
|
|
242
243
|
- `/quota_nudge <credits|usage_limit> confirm`
|
|
243
244
|
- `/login_device`、`/login_cancel [id]`、`/logout confirm`
|
|
244
245
|
- `/auth [list|use <n>|enable <n>|disable <n>|reload|add <name>]`
|
|
@@ -290,6 +291,7 @@ foxclaw doctor
|
|
|
290
291
|
foxclaw status
|
|
291
292
|
foxclaw start
|
|
292
293
|
foxclaw restart
|
|
294
|
+
foxclaw update
|
|
293
295
|
foxclaw stop
|
|
294
296
|
foxclaw uninstall-systemd
|
|
295
297
|
```
|
package/README_EN.md
CHANGED
|
@@ -84,7 +84,7 @@ DEFAULT_SANDBOX_MODE=workspace-write
|
|
|
84
84
|
|
|
85
85
|
The default config file is `~/.foxclaw/.env`. Set `FOXCLAW_ENV=/path/to/.env` if you want to keep it somewhere else.
|
|
86
86
|
|
|
87
|
-
`foxclaw start` runs checks and installs or restarts the background service.
|
|
87
|
+
`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, runs checks, and restarts the service.
|
|
88
88
|
|
|
89
89
|
FoxClaw accepts messages only from `TG_ALLOWED_USER_ID`. Putting the bot in a group does not make it available to every group member.
|
|
90
90
|
|
|
@@ -150,6 +150,7 @@ You can also use the wrapper commands:
|
|
|
150
150
|
```bash
|
|
151
151
|
foxclaw status
|
|
152
152
|
foxclaw restart
|
|
153
|
+
foxclaw update
|
|
153
154
|
foxclaw stop
|
|
154
155
|
```
|
|
155
156
|
|
|
@@ -238,7 +239,7 @@ No static Codex app-server port is required in normal installs.
|
|
|
238
239
|
- `/setup` — unified preference panel
|
|
239
240
|
- `/fast <on|off|toggle>`
|
|
240
241
|
- `/active <steer|queue>`
|
|
241
|
-
- `/status`, `/account`, `/quota`
|
|
242
|
+
- `/status`, `/account`, `/quota`, `/update`
|
|
242
243
|
- `/quota_nudge <credits|usage_limit> confirm`
|
|
243
244
|
- `/login_device`, `/login_cancel [id]`, `/logout confirm`
|
|
244
245
|
- `/auth [list|use <n>|enable <n>|disable <n>|reload|add <name>]`
|
|
@@ -290,6 +291,7 @@ foxclaw doctor
|
|
|
290
291
|
foxclaw status
|
|
291
292
|
foxclaw start
|
|
292
293
|
foxclaw restart
|
|
294
|
+
foxclaw update
|
|
293
295
|
foxclaw stop
|
|
294
296
|
foxclaw uninstall-systemd
|
|
295
297
|
```
|
|
@@ -5,12 +5,14 @@ import type { RuntimeStatus } from '../types.js';
|
|
|
5
5
|
import type { TelegramGateway, TelegramTextEvent } from '../telegram/gateway.js';
|
|
6
6
|
import { BridgeMessagingRouter } from '../channels/bridge_messaging_router.js';
|
|
7
7
|
import type { CodexAppClient } from '../codex_app/client.js';
|
|
8
|
+
import type { SelfUpdateRuntime } from '../update.js';
|
|
8
9
|
export declare class BridgeSessionCore {
|
|
9
10
|
private readonly config;
|
|
10
11
|
private readonly store;
|
|
11
12
|
private readonly logger;
|
|
12
13
|
private readonly bot;
|
|
13
14
|
private readonly app;
|
|
15
|
+
private readonly selfUpdater;
|
|
14
16
|
private activeTurns;
|
|
15
17
|
private activeTurnsByTurnId;
|
|
16
18
|
private observedThreadWatchers;
|
|
@@ -38,13 +40,14 @@ export declare class BridgeSessionCore {
|
|
|
38
40
|
private locks;
|
|
39
41
|
private approvalTimers;
|
|
40
42
|
private submittedUserInputTimers;
|
|
43
|
+
private selfUpdatePollTimer;
|
|
41
44
|
private attachedThreads;
|
|
42
45
|
private botUsername;
|
|
43
46
|
private lastError;
|
|
44
47
|
/** Last threads-panel pagination state per scope (Telegram inline nav + /open index alignment). */
|
|
45
48
|
private threadListPresentationState;
|
|
46
49
|
private readonly messaging;
|
|
47
|
-
constructor(config: AppConfig, store: BridgeStore, logger: Logger, bot: TelegramGateway, app: CodexAppClient, outbound: BridgeMessagingRouter);
|
|
50
|
+
constructor(config: AppConfig, store: BridgeStore, logger: Logger, bot: TelegramGateway, app: CodexAppClient, outbound: BridgeMessagingRouter, selfUpdater?: SelfUpdateRuntime | null);
|
|
48
51
|
/** Wire Telegram inbound events. Call before {@link startCodexApp}. */
|
|
49
52
|
registerTelegramInboundHandlers(): void;
|
|
50
53
|
/**
|
|
@@ -203,6 +206,11 @@ export declare class BridgeSessionCore {
|
|
|
203
206
|
private handleFastCommand;
|
|
204
207
|
private setCollaborationMode;
|
|
205
208
|
private handleAuthReloadCommand;
|
|
209
|
+
private handleSelfUpdateCommand;
|
|
210
|
+
private scheduleSelfUpdateStatusPoll;
|
|
211
|
+
private clearSelfUpdateStatusPoll;
|
|
212
|
+
private pollSelfUpdateStatus;
|
|
213
|
+
private formatSelfUpdateResult;
|
|
206
214
|
private handleAuthCommand;
|
|
207
215
|
private handleAuthUseCommand;
|
|
208
216
|
private handleAuthToggleCommand;
|
|
@@ -25,6 +25,7 @@ const OBSERVED_CLI_USER_LABEL = 'codex-cli-user';
|
|
|
25
25
|
const DEFAULT_COLLABORATION_MODE = 'default';
|
|
26
26
|
const CODEX_LOCAL_USAGE_CACHE_MS = 30_000;
|
|
27
27
|
const USER_INPUT_SUBMITTED_NOTICE_MS = 90_000;
|
|
28
|
+
const SELF_UPDATE_STATUS_POLL_MS = 1000;
|
|
28
29
|
const PLAN_IMPLEMENTATION_CODING_MESSAGE = 'Implement the plan.';
|
|
29
30
|
const PLAN_IMPLEMENTATION_CLEAR_CONTEXT_PREFIX = 'A previous agent produced the plan below to accomplish the user\'s task. Implement the plan in a fresh context. Treat the plan as the source of user intent, re-read files as needed, and carry the work through implementation and verification.';
|
|
30
31
|
const PINNED_HELP_COMMANDS = [
|
|
@@ -39,6 +40,7 @@ const DYNAMIC_HELP_COMMANDS = [
|
|
|
39
40
|
{ key: 'active', line: '/active <steer|queue>' },
|
|
40
41
|
{ key: 'account', line: '/account' },
|
|
41
42
|
{ key: 'quota', line: '/quota' },
|
|
43
|
+
{ key: 'update', line: '/update' },
|
|
42
44
|
{ key: 'login_device', line: '/login_device' },
|
|
43
45
|
{ key: 'threads_archived', line: '/threads archived [query]' },
|
|
44
46
|
{ key: 'open', line: '/open <n>' },
|
|
@@ -88,6 +90,7 @@ export class BridgeSessionCore {
|
|
|
88
90
|
logger;
|
|
89
91
|
bot;
|
|
90
92
|
app;
|
|
93
|
+
selfUpdater;
|
|
91
94
|
activeTurns = new Map();
|
|
92
95
|
activeTurnsByTurnId = new Map();
|
|
93
96
|
observedThreadWatchers = new Map();
|
|
@@ -115,18 +118,20 @@ export class BridgeSessionCore {
|
|
|
115
118
|
locks = new Map();
|
|
116
119
|
approvalTimers = new Map();
|
|
117
120
|
submittedUserInputTimers = new Map();
|
|
121
|
+
selfUpdatePollTimer = null;
|
|
118
122
|
attachedThreads = new Set();
|
|
119
123
|
botUsername = null;
|
|
120
124
|
lastError = null;
|
|
121
125
|
/** Last threads-panel pagination state per scope (Telegram inline nav + /open index alignment). */
|
|
122
126
|
threadListPresentationState = new Map();
|
|
123
127
|
messaging;
|
|
124
|
-
constructor(config, store, logger, bot, app, outbound) {
|
|
128
|
+
constructor(config, store, logger, bot, app, outbound, selfUpdater = null) {
|
|
125
129
|
this.config = config;
|
|
126
130
|
this.store = store;
|
|
127
131
|
this.logger = logger;
|
|
128
132
|
this.bot = bot;
|
|
129
133
|
this.app = app;
|
|
134
|
+
this.selfUpdater = selfUpdater;
|
|
130
135
|
this.messaging = outbound;
|
|
131
136
|
}
|
|
132
137
|
/** Wire Telegram inbound events. Call before {@link startCodexApp}. */
|
|
@@ -188,6 +193,7 @@ export class BridgeSessionCore {
|
|
|
188
193
|
await this.bot.start();
|
|
189
194
|
this.botUsername = this.bot.username;
|
|
190
195
|
this.updateStatus();
|
|
196
|
+
this.scheduleSelfUpdateStatusPoll(0);
|
|
191
197
|
}
|
|
192
198
|
/** Telegram-only default startup (single channel). */
|
|
193
199
|
async start() {
|
|
@@ -223,6 +229,7 @@ export class BridgeSessionCore {
|
|
|
223
229
|
clearTimeout(timer);
|
|
224
230
|
}
|
|
225
231
|
this.submittedUserInputTimers.clear();
|
|
232
|
+
this.clearSelfUpdateStatusPoll();
|
|
226
233
|
await this.app.stop({ terminateServer: false });
|
|
227
234
|
this.updateStatus();
|
|
228
235
|
}
|
|
@@ -382,6 +389,10 @@ export class BridgeSessionCore {
|
|
|
382
389
|
await this.handleQuotaCommand(scopeId, locale);
|
|
383
390
|
return;
|
|
384
391
|
}
|
|
392
|
+
case 'update': {
|
|
393
|
+
await this.handleSelfUpdateCommand(scopeId, locale);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
385
396
|
case 'quota_nudge': {
|
|
386
397
|
await this.handleQuotaNudgeCommand(scopeId, locale, args);
|
|
387
398
|
return;
|
|
@@ -3641,6 +3652,74 @@ export class BridgeSessionCore {
|
|
|
3641
3652
|
lines.push(...await this.buildCodexUsageStatusLines(locale));
|
|
3642
3653
|
await this.sendMessage(scopeId, lines.join('\n'));
|
|
3643
3654
|
}
|
|
3655
|
+
async handleSelfUpdateCommand(scopeId, locale) {
|
|
3656
|
+
if (!this.selfUpdater) {
|
|
3657
|
+
await this.sendMessage(scopeId, t(locale, 'update_unavailable'));
|
|
3658
|
+
return;
|
|
3659
|
+
}
|
|
3660
|
+
if (this.activeTurns.size > 0 || this.store.countPendingApprovals() > 0 || this.pendingUserInputs.size > 0 || this.pendingMcpElicitations.size > 0) {
|
|
3661
|
+
await this.sendMessage(scopeId, t(locale, 'update_blocked_active'));
|
|
3662
|
+
return;
|
|
3663
|
+
}
|
|
3664
|
+
const status = await this.selfUpdater.readStatus();
|
|
3665
|
+
if (status?.state === 'pending') {
|
|
3666
|
+
await this.sendMessage(scopeId, t(locale, 'update_already_running'));
|
|
3667
|
+
this.scheduleSelfUpdateStatusPoll();
|
|
3668
|
+
return;
|
|
3669
|
+
}
|
|
3670
|
+
if (status) {
|
|
3671
|
+
await this.selfUpdater.clearStatus();
|
|
3672
|
+
}
|
|
3673
|
+
await this.sendMessage(scopeId, t(locale, 'update_started'));
|
|
3674
|
+
try {
|
|
3675
|
+
await this.selfUpdater.launch(scopeId, locale);
|
|
3676
|
+
this.scheduleSelfUpdateStatusPoll();
|
|
3677
|
+
}
|
|
3678
|
+
catch (error) {
|
|
3679
|
+
await this.selfUpdater.clearStatus().catch(() => undefined);
|
|
3680
|
+
await this.sendMessage(scopeId, t(locale, 'update_failed', { error: formatUserError(error) }));
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
scheduleSelfUpdateStatusPoll(delay = SELF_UPDATE_STATUS_POLL_MS) {
|
|
3684
|
+
if (!this.selfUpdater || this.selfUpdatePollTimer) {
|
|
3685
|
+
return;
|
|
3686
|
+
}
|
|
3687
|
+
this.selfUpdatePollTimer = setTimeout(() => {
|
|
3688
|
+
this.selfUpdatePollTimer = null;
|
|
3689
|
+
void this.pollSelfUpdateStatus().catch((error) => {
|
|
3690
|
+
this.logger.error('self_update.poll_failed', { error: toErrorMeta(error) });
|
|
3691
|
+
this.scheduleSelfUpdateStatusPoll();
|
|
3692
|
+
});
|
|
3693
|
+
}, delay);
|
|
3694
|
+
}
|
|
3695
|
+
clearSelfUpdateStatusPoll() {
|
|
3696
|
+
if (!this.selfUpdatePollTimer) {
|
|
3697
|
+
return;
|
|
3698
|
+
}
|
|
3699
|
+
clearTimeout(this.selfUpdatePollTimer);
|
|
3700
|
+
this.selfUpdatePollTimer = null;
|
|
3701
|
+
}
|
|
3702
|
+
async pollSelfUpdateStatus() {
|
|
3703
|
+
const status = await this.selfUpdater?.readStatus();
|
|
3704
|
+
if (!status) {
|
|
3705
|
+
return;
|
|
3706
|
+
}
|
|
3707
|
+
if (status.state === 'pending') {
|
|
3708
|
+
this.scheduleSelfUpdateStatusPoll();
|
|
3709
|
+
return;
|
|
3710
|
+
}
|
|
3711
|
+
await this.sendMessage(status.scopeId, this.formatSelfUpdateResult(status));
|
|
3712
|
+
await this.selfUpdater?.clearStatus();
|
|
3713
|
+
}
|
|
3714
|
+
formatSelfUpdateResult(status) {
|
|
3715
|
+
if (status.state === 'succeeded') {
|
|
3716
|
+
return t(status.locale, 'update_succeeded', {
|
|
3717
|
+
from: status.fromVersion,
|
|
3718
|
+
to: status.toVersion ?? t(status.locale, 'unknown'),
|
|
3719
|
+
});
|
|
3720
|
+
}
|
|
3721
|
+
return t(status.locale, 'update_failed', { error: status.error ?? t(status.locale, 'unknown') });
|
|
3722
|
+
}
|
|
3644
3723
|
async handleAuthCommand(scopeId, locale, args) {
|
|
3645
3724
|
const action = args[0]?.toLowerCase() ?? 'list';
|
|
3646
3725
|
if (action === 'reload' || action === 'restart') {
|
package/dist/i18n.d.ts
CHANGED
|
@@ -46,6 +46,7 @@ declare const MESSAGES: {
|
|
|
46
46
|
readonly cmd_desc_fast: "Toggle Fast mode";
|
|
47
47
|
readonly cmd_desc_active: "Active-turn message behavior";
|
|
48
48
|
readonly cmd_desc_status: "Bridge status";
|
|
49
|
+
readonly cmd_desc_update: "Update and restart FoxClaw";
|
|
49
50
|
readonly cmd_desc_account: "Codex account";
|
|
50
51
|
readonly cmd_desc_quota: "Codex quota";
|
|
51
52
|
readonly cmd_desc_login_device: "ChatGPT device login";
|
|
@@ -115,6 +116,12 @@ declare const MESSAGES: {
|
|
|
115
116
|
readonly status_codex_local_usage_unavailable: "Codex local history: unavailable ({error})";
|
|
116
117
|
readonly status_codex_credits: "Codex credits: {value}";
|
|
117
118
|
readonly status_codex_limit_reached: "Codex limit: {value}";
|
|
119
|
+
readonly update_started: "FoxClaw update started. I will report here after installation, checks, and service restart complete.";
|
|
120
|
+
readonly update_succeeded: "FoxClaw updated and restarted: {from} -> {to}.";
|
|
121
|
+
readonly update_failed: "FoxClaw update failed: {error}\nRun foxclaw update in a terminal for details.";
|
|
122
|
+
readonly update_unavailable: "Self-update is unavailable in this runtime. Run foxclaw update in a terminal.";
|
|
123
|
+
readonly update_already_running: "A FoxClaw update is already running. I will report here when it finishes.";
|
|
124
|
+
readonly update_blocked_active: "Cannot update FoxClaw while a turn, approval, or question is active. Wait or use /interrupt first.";
|
|
118
125
|
readonly auth_reload_restarting: "Restarting Codex app-server to reload auth...";
|
|
119
126
|
readonly auth_reload_done: "Codex app-server restarted. Current auth has been reloaded.";
|
|
120
127
|
readonly auth_reload_blocked_active: "Cannot reload Codex auth while a turn, approval, or question is active. Wait or use /interrupt first.";
|
|
@@ -600,6 +607,7 @@ declare const MESSAGES: {
|
|
|
600
607
|
readonly cmd_desc_fast: "切换 Fast 模式";
|
|
601
608
|
readonly cmd_desc_active: "运行中新消息处理方式";
|
|
602
609
|
readonly cmd_desc_status: "查看桥接状态";
|
|
610
|
+
readonly cmd_desc_update: "升级并重启 FoxClaw";
|
|
603
611
|
readonly cmd_desc_account: "Codex 账号";
|
|
604
612
|
readonly cmd_desc_quota: "Codex 用量";
|
|
605
613
|
readonly cmd_desc_login_device: "ChatGPT 设备登录";
|
|
@@ -669,6 +677,12 @@ declare const MESSAGES: {
|
|
|
669
677
|
readonly status_codex_local_usage_unavailable: "Codex 本地历史:无法获取({error})";
|
|
670
678
|
readonly status_codex_credits: "Codex 额度:{value}";
|
|
671
679
|
readonly status_codex_limit_reached: "Codex 限制:{value}";
|
|
680
|
+
readonly update_started: "已开始升级 FoxClaw。安装、自检和服务重启完成后,我会在这里回报结果。";
|
|
681
|
+
readonly update_succeeded: "FoxClaw 已升级并重启:{from} -> {to}。";
|
|
682
|
+
readonly update_failed: "FoxClaw 升级失败:{error}\n请在终端运行 foxclaw update 查看详情。";
|
|
683
|
+
readonly update_unavailable: "当前运行方式不支持自升级,请在终端运行 foxclaw update。";
|
|
684
|
+
readonly update_already_running: "FoxClaw 升级已经在进行中,结束后我会在这里回报结果。";
|
|
685
|
+
readonly update_blocked_active: "当前有回复、审批或问题在进行中,不能升级 FoxClaw。请先等待,或使用 /interrupt。";
|
|
672
686
|
readonly auth_reload_restarting: "正在重启 Codex app-server 以重新读取 auth...";
|
|
673
687
|
readonly auth_reload_done: "Codex app-server 已重启,当前 auth 已重新读取。";
|
|
674
688
|
readonly auth_reload_blocked_active: "当前有回复、审批或问题在进行中,不能重载 Codex auth。请先等待,或使用 /interrupt。";
|
package/dist/i18n.js
CHANGED
|
@@ -44,6 +44,7 @@ const MESSAGES = {
|
|
|
44
44
|
cmd_desc_fast: 'Toggle Fast mode',
|
|
45
45
|
cmd_desc_active: 'Active-turn message behavior',
|
|
46
46
|
cmd_desc_status: 'Bridge status',
|
|
47
|
+
cmd_desc_update: 'Update and restart FoxClaw',
|
|
47
48
|
cmd_desc_account: 'Codex account',
|
|
48
49
|
cmd_desc_quota: 'Codex quota',
|
|
49
50
|
cmd_desc_login_device: 'ChatGPT device login',
|
|
@@ -113,6 +114,12 @@ const MESSAGES = {
|
|
|
113
114
|
status_codex_local_usage_unavailable: 'Codex local history: unavailable ({error})',
|
|
114
115
|
status_codex_credits: 'Codex credits: {value}',
|
|
115
116
|
status_codex_limit_reached: 'Codex limit: {value}',
|
|
117
|
+
update_started: 'FoxClaw update started. I will report here after installation, checks, and service restart complete.',
|
|
118
|
+
update_succeeded: 'FoxClaw updated and restarted: {from} -> {to}.',
|
|
119
|
+
update_failed: 'FoxClaw update failed: {error}\nRun foxclaw update in a terminal for details.',
|
|
120
|
+
update_unavailable: 'Self-update is unavailable in this runtime. Run foxclaw update in a terminal.',
|
|
121
|
+
update_already_running: 'A FoxClaw update is already running. I will report here when it finishes.',
|
|
122
|
+
update_blocked_active: 'Cannot update FoxClaw while a turn, approval, or question is active. Wait or use /interrupt first.',
|
|
116
123
|
auth_reload_restarting: 'Restarting Codex app-server to reload auth...',
|
|
117
124
|
auth_reload_done: 'Codex app-server restarted. Current auth has been reloaded.',
|
|
118
125
|
auth_reload_blocked_active: 'Cannot reload Codex auth while a turn, approval, or question is active. Wait or use /interrupt first.',
|
|
@@ -598,6 +605,7 @@ const MESSAGES = {
|
|
|
598
605
|
cmd_desc_fast: '切换 Fast 模式',
|
|
599
606
|
cmd_desc_active: '运行中新消息处理方式',
|
|
600
607
|
cmd_desc_status: '查看桥接状态',
|
|
608
|
+
cmd_desc_update: '升级并重启 FoxClaw',
|
|
601
609
|
cmd_desc_account: 'Codex 账号',
|
|
602
610
|
cmd_desc_quota: 'Codex 用量',
|
|
603
611
|
cmd_desc_login_device: 'ChatGPT 设备登录',
|
|
@@ -667,6 +675,12 @@ const MESSAGES = {
|
|
|
667
675
|
status_codex_local_usage_unavailable: 'Codex 本地历史:无法获取({error})',
|
|
668
676
|
status_codex_credits: 'Codex 额度:{value}',
|
|
669
677
|
status_codex_limit_reached: 'Codex 限制:{value}',
|
|
678
|
+
update_started: '已开始升级 FoxClaw。安装、自检和服务重启完成后,我会在这里回报结果。',
|
|
679
|
+
update_succeeded: 'FoxClaw 已升级并重启:{from} -> {to}。',
|
|
680
|
+
update_failed: 'FoxClaw 升级失败:{error}\n请在终端运行 foxclaw update 查看详情。',
|
|
681
|
+
update_unavailable: '当前运行方式不支持自升级,请在终端运行 foxclaw update。',
|
|
682
|
+
update_already_running: 'FoxClaw 升级已经在进行中,结束后我会在这里回报结果。',
|
|
683
|
+
update_blocked_active: '当前有回复、审批或问题在进行中,不能升级 FoxClaw。请先等待,或使用 /interrupt。',
|
|
670
684
|
auth_reload_restarting: '正在重启 Codex app-server 以重新读取 auth...',
|
|
671
685
|
auth_reload_done: 'Codex app-server 已重启,当前 auth 已重新读取。',
|
|
672
686
|
auth_reload_blocked_active: '当前有回复、审批或问题在进行中,不能重载 Codex auth。请先等待,或使用 /interrupt。',
|
|
@@ -1121,6 +1135,7 @@ export function getTelegramCommands(locale) {
|
|
|
1121
1135
|
{ command: 'help', description: t(locale, 'cmd_desc_help') },
|
|
1122
1136
|
{ command: 'setup', description: t(locale, 'cmd_desc_setup') },
|
|
1123
1137
|
{ command: 'status', description: t(locale, 'cmd_desc_status') },
|
|
1138
|
+
{ command: 'update', description: t(locale, 'cmd_desc_update') },
|
|
1124
1139
|
{ command: 'threads', description: t(locale, 'cmd_desc_threads') },
|
|
1125
1140
|
{ command: 'auth', description: t(locale, 'cmd_desc_auth') },
|
|
1126
1141
|
{ command: 'fast', description: t(locale, 'cmd_desc_fast') },
|
package/dist/main.js
CHANGED
|
@@ -9,6 +9,7 @@ import { APP_HOME, DEFAULT_ENV_PATH, DEFAULT_LOG_PATH, DEFAULT_STATUS_PATH, getL
|
|
|
9
9
|
import { acquireProcessLock, LockHeldError } from './lock.js';
|
|
10
10
|
import { readRuntimeStatus, writeRuntimeStatus } from './runtime.js';
|
|
11
11
|
import { refreshFoxclawExecStartDropIns, removeFoxclawExecStartDropIns } from './systemd.js';
|
|
12
|
+
import { createSelfUpdateRuntime, performSelfUpdate } from './update.js';
|
|
12
13
|
const rawCommand = process.argv[2];
|
|
13
14
|
const command = rawCommand || 'serve';
|
|
14
15
|
loadEnv();
|
|
@@ -53,6 +54,18 @@ async function main() {
|
|
|
53
54
|
startService('restart');
|
|
54
55
|
return;
|
|
55
56
|
}
|
|
57
|
+
if (command === 'update') {
|
|
58
|
+
requireNode24(command);
|
|
59
|
+
const notificationFile = readOptionValue('--notification-file');
|
|
60
|
+
const options = {
|
|
61
|
+
entryPoint,
|
|
62
|
+
nodePath: process.execPath,
|
|
63
|
+
version: readPackageVersion(),
|
|
64
|
+
...(notificationFile ? { notificationFile } : {}),
|
|
65
|
+
};
|
|
66
|
+
const outcome = performSelfUpdate(options);
|
|
67
|
+
process.exit(outcome.ok ? 0 : 1);
|
|
68
|
+
}
|
|
56
69
|
if (command === 'stop') {
|
|
57
70
|
stopService();
|
|
58
71
|
return;
|
|
@@ -116,6 +129,7 @@ Usage:
|
|
|
116
129
|
foxclaw doctor
|
|
117
130
|
foxclaw status
|
|
118
131
|
foxclaw start|restart|stop
|
|
132
|
+
foxclaw update
|
|
119
133
|
foxclaw install-systemd|uninstall-systemd
|
|
120
134
|
foxclaw install-launchd
|
|
121
135
|
foxclaw weixin-login [account-id]
|
|
@@ -152,7 +166,14 @@ async function runServeCli() {
|
|
|
152
166
|
? new WeixinMessagingPort(store, (id) => loadWeixinAccount(config.weixinAccountsDir, id))
|
|
153
167
|
: null;
|
|
154
168
|
const outbound = new BridgeMessagingRouter(telegramMessaging, weixinMessaging);
|
|
155
|
-
const
|
|
169
|
+
const selfUpdater = createSelfUpdateRuntime({
|
|
170
|
+
entryPoint,
|
|
171
|
+
nodePath: process.execPath,
|
|
172
|
+
version: readPackageVersion(),
|
|
173
|
+
statusPath: config.statusPath,
|
|
174
|
+
logPath: path.join(APP_HOME, 'logs', 'update.log'),
|
|
175
|
+
});
|
|
176
|
+
const core = new BridgeSessionCore(config, store, logger, bot, app, outbound, selfUpdater);
|
|
156
177
|
const telegram = new TelegramChannelAdapter(core);
|
|
157
178
|
if (config.wxEnabled) {
|
|
158
179
|
weixinAdapter = new WeixinChannelAdapter(core, store, config, logger);
|
|
@@ -436,6 +457,11 @@ function escapeRegExp(value) {
|
|
|
436
457
|
function editorCommand(envPath) {
|
|
437
458
|
return `${process.env.EDITOR?.trim() || '$EDITOR'} ${envPath}`;
|
|
438
459
|
}
|
|
460
|
+
function readOptionValue(option) {
|
|
461
|
+
const index = process.argv.indexOf(option);
|
|
462
|
+
const value = index >= 0 ? process.argv[index + 1] : undefined;
|
|
463
|
+
return value && !value.startsWith('--') ? value : undefined;
|
|
464
|
+
}
|
|
439
465
|
function startService(action) {
|
|
440
466
|
if (!runDoctorChecks()) {
|
|
441
467
|
console.error('');
|
package/dist/update.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { AppLocale } from './types.js';
|
|
2
|
+
export type SelfUpdateState = 'pending' | 'succeeded' | 'failed';
|
|
3
|
+
export interface SelfUpdateStatus {
|
|
4
|
+
state: SelfUpdateState;
|
|
5
|
+
scopeId: string;
|
|
6
|
+
locale: AppLocale;
|
|
7
|
+
fromVersion: string;
|
|
8
|
+
toVersion: string | null;
|
|
9
|
+
error: string | null;
|
|
10
|
+
updatedAt: string;
|
|
11
|
+
}
|
|
12
|
+
export interface SelfUpdateRuntime {
|
|
13
|
+
launch(scopeId: string, locale: AppLocale): Promise<void>;
|
|
14
|
+
readStatus(): Promise<SelfUpdateStatus | null>;
|
|
15
|
+
clearStatus(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export interface SelfUpdateInstaller {
|
|
18
|
+
manager: 'npm' | 'pnpm';
|
|
19
|
+
command: string;
|
|
20
|
+
installArgs: string[];
|
|
21
|
+
rootArgs: string[];
|
|
22
|
+
}
|
|
23
|
+
interface CreateSelfUpdateRuntimeOptions {
|
|
24
|
+
entryPoint: string;
|
|
25
|
+
nodePath: string;
|
|
26
|
+
version: string;
|
|
27
|
+
statusPath: string;
|
|
28
|
+
logPath: string;
|
|
29
|
+
}
|
|
30
|
+
interface PerformSelfUpdateOptions {
|
|
31
|
+
entryPoint: string;
|
|
32
|
+
nodePath: string;
|
|
33
|
+
version: string;
|
|
34
|
+
notificationFile?: string;
|
|
35
|
+
env?: NodeJS.ProcessEnv;
|
|
36
|
+
}
|
|
37
|
+
export interface SelfUpdateOutcome {
|
|
38
|
+
ok: boolean;
|
|
39
|
+
fromVersion: string;
|
|
40
|
+
toVersion: string | null;
|
|
41
|
+
error: string | null;
|
|
42
|
+
}
|
|
43
|
+
export declare function selfUpdateStatusPath(statusPath: string): string;
|
|
44
|
+
export declare function resolveSelfUpdateInstaller(entryPoint: string, nodePath?: string, exists?: (target: string) => boolean): SelfUpdateInstaller;
|
|
45
|
+
export declare function readSelfUpdateStatus(statusFile: string): SelfUpdateStatus | null;
|
|
46
|
+
export declare function writeSelfUpdateStatus(statusFile: string, status: SelfUpdateStatus): void;
|
|
47
|
+
export declare function createSelfUpdateRuntime(options: CreateSelfUpdateRuntimeOptions): SelfUpdateRuntime;
|
|
48
|
+
export declare function performSelfUpdate(options: PerformSelfUpdateOptions): SelfUpdateOutcome;
|
|
49
|
+
export {};
|
package/dist/update.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
5
|
+
const PACKAGE_SPEC = '@foxden-app/foxclaw@latest';
|
|
6
|
+
const UPDATE_STATUS_FILENAME = 'self-update.json';
|
|
7
|
+
export function selfUpdateStatusPath(statusPath) {
|
|
8
|
+
return path.join(path.dirname(statusPath), UPDATE_STATUS_FILENAME);
|
|
9
|
+
}
|
|
10
|
+
export function resolveSelfUpdateInstaller(entryPoint, nodePath = process.execPath, exists = fs.existsSync) {
|
|
11
|
+
const normalizedEntryPoint = entryPoint.replace(/\\/g, '/');
|
|
12
|
+
const globalMarker = '/global/';
|
|
13
|
+
const globalIndex = normalizedEntryPoint.indexOf(globalMarker);
|
|
14
|
+
if (globalIndex > 0 && normalizedEntryPoint.includes('/.pnpm/')) {
|
|
15
|
+
const pnpmHome = normalizedEntryPoint.slice(0, globalIndex);
|
|
16
|
+
const pnpmCommand = path.join(pnpmHome, process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm');
|
|
17
|
+
if (!exists(pnpmCommand)) {
|
|
18
|
+
throw new Error(`Current installation is managed by pnpm, but pnpm was not found at ${pnpmCommand}.`);
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
manager: 'pnpm',
|
|
22
|
+
command: pnpmCommand,
|
|
23
|
+
installArgs: ['add', '--global', PACKAGE_SPEC],
|
|
24
|
+
rootArgs: ['root', '--global'],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const adjacentNpm = path.join(path.dirname(nodePath), process.platform === 'win32' ? 'npm.cmd' : 'npm');
|
|
28
|
+
return {
|
|
29
|
+
manager: 'npm',
|
|
30
|
+
command: exists(adjacentNpm) ? adjacentNpm : (process.platform === 'win32' ? 'npm.cmd' : 'npm'),
|
|
31
|
+
installArgs: ['install', '--global', PACKAGE_SPEC],
|
|
32
|
+
rootArgs: ['root', '--global'],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function readSelfUpdateStatus(statusFile) {
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(fs.readFileSync(statusFile, 'utf8'));
|
|
38
|
+
if ((parsed.state !== 'pending' && parsed.state !== 'succeeded' && parsed.state !== 'failed')
|
|
39
|
+
|| typeof parsed.scopeId !== 'string'
|
|
40
|
+
|| (parsed.locale !== 'en' && parsed.locale !== 'zh')
|
|
41
|
+
|| typeof parsed.fromVersion !== 'string'
|
|
42
|
+
|| typeof parsed.updatedAt !== 'string') {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
state: parsed.state,
|
|
47
|
+
scopeId: parsed.scopeId,
|
|
48
|
+
locale: parsed.locale,
|
|
49
|
+
fromVersion: parsed.fromVersion,
|
|
50
|
+
toVersion: typeof parsed.toVersion === 'string' ? parsed.toVersion : null,
|
|
51
|
+
error: typeof parsed.error === 'string' ? parsed.error : null,
|
|
52
|
+
updatedAt: parsed.updatedAt,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function writeSelfUpdateStatus(statusFile, status) {
|
|
60
|
+
fs.mkdirSync(path.dirname(statusFile), { recursive: true });
|
|
61
|
+
const temporaryFile = `${statusFile}.${process.pid}.tmp`;
|
|
62
|
+
fs.writeFileSync(temporaryFile, `${JSON.stringify(status, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
63
|
+
fs.renameSync(temporaryFile, statusFile);
|
|
64
|
+
}
|
|
65
|
+
export function createSelfUpdateRuntime(options) {
|
|
66
|
+
const statusFile = selfUpdateStatusPath(options.statusPath);
|
|
67
|
+
return {
|
|
68
|
+
async launch(scopeId, locale) {
|
|
69
|
+
const current = readSelfUpdateStatus(statusFile);
|
|
70
|
+
if (current?.state === 'pending') {
|
|
71
|
+
throw new Error('A FoxClaw update is already running.');
|
|
72
|
+
}
|
|
73
|
+
writeSelfUpdateStatus(statusFile, {
|
|
74
|
+
state: 'pending',
|
|
75
|
+
scopeId,
|
|
76
|
+
locale,
|
|
77
|
+
fromVersion: options.version,
|
|
78
|
+
toVersion: null,
|
|
79
|
+
error: null,
|
|
80
|
+
updatedAt: new Date().toISOString(),
|
|
81
|
+
});
|
|
82
|
+
fs.mkdirSync(path.dirname(options.logPath), { recursive: true });
|
|
83
|
+
const logFd = fs.openSync(options.logPath, 'a', 0o600);
|
|
84
|
+
try {
|
|
85
|
+
const child = spawn(options.nodePath, [options.entryPoint, 'update', '--notification-file', statusFile], {
|
|
86
|
+
detached: true,
|
|
87
|
+
stdio: ['ignore', logFd, logFd],
|
|
88
|
+
env: process.env,
|
|
89
|
+
});
|
|
90
|
+
child.unref();
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
writeSelfUpdateStatus(statusFile, {
|
|
94
|
+
state: 'failed',
|
|
95
|
+
scopeId,
|
|
96
|
+
locale,
|
|
97
|
+
fromVersion: options.version,
|
|
98
|
+
toVersion: null,
|
|
99
|
+
error: formatError(error),
|
|
100
|
+
updatedAt: new Date().toISOString(),
|
|
101
|
+
});
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
fs.closeSync(logFd);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
async readStatus() {
|
|
109
|
+
return readSelfUpdateStatus(statusFile);
|
|
110
|
+
},
|
|
111
|
+
async clearStatus() {
|
|
112
|
+
fs.rmSync(statusFile, { force: true });
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export function performSelfUpdate(options) {
|
|
117
|
+
const env = options.env ?? process.env;
|
|
118
|
+
let toVersion = null;
|
|
119
|
+
try {
|
|
120
|
+
const installer = resolveSelfUpdateInstaller(options.entryPoint, options.nodePath);
|
|
121
|
+
console.log(`[UPDATE] Installing ${PACKAGE_SPEC} with ${installer.manager}...`);
|
|
122
|
+
runInherited(installer.command, installer.installArgs, env);
|
|
123
|
+
const updatedEntryPoint = resolveUpdatedEntryPoint(installer, env);
|
|
124
|
+
toVersion = readInstalledPackageVersion(updatedEntryPoint);
|
|
125
|
+
console.log('[UPDATE] Running checks and restarting the FoxClaw service...');
|
|
126
|
+
runInherited(options.nodePath, [updatedEntryPoint, 'start'], env);
|
|
127
|
+
completeNotification(options.notificationFile, 'succeeded', toVersion, null);
|
|
128
|
+
console.log(`[OK] FoxClaw updated and restarted: ${options.version} -> ${toVersion}`);
|
|
129
|
+
return {
|
|
130
|
+
ok: true,
|
|
131
|
+
fromVersion: options.version,
|
|
132
|
+
toVersion,
|
|
133
|
+
error: null,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
const message = formatError(error);
|
|
138
|
+
completeNotification(options.notificationFile, 'failed', toVersion, message);
|
|
139
|
+
console.error(`[FAIL] FoxClaw update failed: ${message}`);
|
|
140
|
+
return {
|
|
141
|
+
ok: false,
|
|
142
|
+
fromVersion: options.version,
|
|
143
|
+
toVersion,
|
|
144
|
+
error: message,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function runInherited(command, args, env) {
|
|
149
|
+
const result = spawnSync(command, args, { stdio: 'inherit', env });
|
|
150
|
+
if (result.error) {
|
|
151
|
+
throw result.error;
|
|
152
|
+
}
|
|
153
|
+
if (result.status !== 0) {
|
|
154
|
+
throw new Error(`${command} ${args.join(' ')} exited with status ${result.status ?? 'unknown'}.`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function resolveUpdatedEntryPoint(installer, env) {
|
|
158
|
+
const result = spawnSync(installer.command, installer.rootArgs, { encoding: 'utf8', env });
|
|
159
|
+
if (result.error) {
|
|
160
|
+
throw result.error;
|
|
161
|
+
}
|
|
162
|
+
if (result.status !== 0) {
|
|
163
|
+
throw new Error(`Could not locate the updated global package root using ${installer.manager}.`);
|
|
164
|
+
}
|
|
165
|
+
const globalRoot = result.stdout.trim().split(/\r?\n/).filter(Boolean).at(-1);
|
|
166
|
+
if (!globalRoot) {
|
|
167
|
+
throw new Error(`Could not locate the updated global package root using ${installer.manager}.`);
|
|
168
|
+
}
|
|
169
|
+
const updatedEntryPoint = path.join(globalRoot, '@foxden-app', 'foxclaw', 'dist', 'main.js');
|
|
170
|
+
if (!fs.existsSync(updatedEntryPoint)) {
|
|
171
|
+
throw new Error(`Updated FoxClaw entry point was not found at ${updatedEntryPoint}.`);
|
|
172
|
+
}
|
|
173
|
+
return updatedEntryPoint;
|
|
174
|
+
}
|
|
175
|
+
function readInstalledPackageVersion(updatedEntryPoint) {
|
|
176
|
+
try {
|
|
177
|
+
const packageFile = path.resolve(path.dirname(updatedEntryPoint), '..', 'package.json');
|
|
178
|
+
const pkg = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
|
|
179
|
+
return typeof pkg.version === 'string' && pkg.version.trim() ? pkg.version : 'unknown';
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return 'unknown';
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function completeNotification(notificationFile, state, toVersion, error) {
|
|
186
|
+
if (!notificationFile) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const pending = readSelfUpdateStatus(notificationFile);
|
|
190
|
+
if (!pending) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
writeSelfUpdateStatus(notificationFile, {
|
|
194
|
+
...pending,
|
|
195
|
+
state,
|
|
196
|
+
toVersion,
|
|
197
|
+
error,
|
|
198
|
+
updatedAt: new Date().toISOString(),
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function formatError(error) {
|
|
202
|
+
return error instanceof Error ? error.message : String(error);
|
|
203
|
+
}
|
|
@@ -277,10 +277,11 @@ foxclaw uninstall-systemd
|
|
|
277
277
|
Update FoxClaw later:
|
|
278
278
|
|
|
279
279
|
```bash
|
|
280
|
-
|
|
281
|
-
foxclaw start
|
|
280
|
+
foxclaw update
|
|
282
281
|
```
|
|
283
282
|
|
|
283
|
+
You can also send `/update` in an authorized Telegram chat. When no turn, approval, or question is active, it upgrades, checks, restarts the service, and reports the result after restart.
|
|
284
|
+
|
|
284
285
|
## Next Step
|
|
285
286
|
|
|
286
287
|
After the first install works, read the [User Manual](./user-manual.md) for `/help`, `/setup`, `/threads`, `/watch`, `/auth`, Codex login, and multi-account auth rotation.
|
package/docs/troubleshooting.md
CHANGED
|
@@ -223,7 +223,7 @@ Then run:
|
|
|
223
223
|
foxclaw restart
|
|
224
224
|
```
|
|
225
225
|
|
|
226
|
-
FoxClaw writes proxychains into the main service and removes stale FoxClaw `ExecStart` overrides, so later upgrades
|
|
226
|
+
FoxClaw writes proxychains into the main service and removes stale FoxClaw `ExecStart` overrides, so later upgrades only need `foxclaw update`.
|
|
227
227
|
|
|
228
228
|
## Service Starts With The Wrong Node Version
|
|
229
229
|
|
package/docs/user-manual.md
CHANGED
|
@@ -136,6 +136,8 @@ foxclaw start
|
|
|
136
136
|
foxclaw status
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
+
For later upgrades, run `foxclaw update`. It uses the npm or pnpm installation method currently managing FoxClaw, upgrades globally, runs checks, and restarts the background service.
|
|
140
|
+
|
|
139
141
|
Linux service logs:
|
|
140
142
|
|
|
141
143
|
```bash
|
|
@@ -226,11 +228,12 @@ For group messages:
|
|
|
226
228
|
|
|
227
229
|
Later commands are sorted by recent usage. Plain text, photos, and files continue the currently bound thread; if no thread is bound, FoxClaw creates one.
|
|
228
230
|
|
|
229
|
-
### 3.2 `/status`, `/account`, `/quota`
|
|
231
|
+
### 3.2 `/status`, `/account`, `/quota`, `/update`
|
|
230
232
|
|
|
231
233
|
- `/status`: FoxClaw, app-server, current thread binding, model, access, and Codex usage summary.
|
|
232
234
|
- `/account`: current Codex account.
|
|
233
235
|
- `/quota`: Codex usage and quota window.
|
|
236
|
+
- `/update`: upgrade FoxClaw, run checks, and restart the service; it refuses while a turn, approval, or question is active, then reports the result after restart.
|
|
234
237
|
|
|
235
238
|
### 3.3 `/config`, `/requirements`, `/provider`
|
|
236
239
|
|
|
@@ -275,10 +275,11 @@ foxclaw uninstall-systemd
|
|
|
275
275
|
以后升级 FoxClaw:
|
|
276
276
|
|
|
277
277
|
```bash
|
|
278
|
-
|
|
279
|
-
foxclaw start
|
|
278
|
+
foxclaw update
|
|
280
279
|
```
|
|
281
280
|
|
|
281
|
+
也可以在已授权的 Telegram 私聊里发送 `/update`。它会在当前没有运行中回复、审批或待确认问题时,完成升级、自检和服务重启,并在重启后回报结果。
|
|
282
|
+
|
|
282
283
|
如果 `~/.foxclaw/.env` 已经存在,`foxclaw init` 会先询问是否更新 Telegram 和工作目录相关字段,其它配置保持不变。
|
|
283
284
|
|
|
284
285
|
## 鸣谢
|
|
@@ -224,7 +224,7 @@ FOXCLAW_PROXYCHAINS_CONF=/home/wuya/.proxychains-rt.conf
|
|
|
224
224
|
foxclaw restart
|
|
225
225
|
```
|
|
226
226
|
|
|
227
|
-
FoxClaw 会把 proxychains 写进主 service,并清理旧的 FoxClaw `ExecStart`
|
|
227
|
+
FoxClaw 会把 proxychains 写进主 service,并清理旧的 FoxClaw `ExecStart` 覆盖,后续升级直接运行 `foxclaw update` 即可。
|
|
228
228
|
|
|
229
229
|
## 服务用了错误的 Node 版本
|
|
230
230
|
|
package/docs/zh/user-manual.md
CHANGED
|
@@ -136,6 +136,8 @@ foxclaw start
|
|
|
136
136
|
foxclaw status
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
+
后续升级只需运行 `foxclaw update`;它会使用当前安装 FoxClaw 的 npm 或 pnpm,全局升级后执行自检并重启后台服务。
|
|
140
|
+
|
|
139
141
|
Linux 查看服务日志:
|
|
140
142
|
|
|
141
143
|
```bash
|
|
@@ -226,11 +228,12 @@ TG_ALLOWED_TOPIC_ID=42
|
|
|
226
228
|
|
|
227
229
|
后面的命令会按你最近使用情况排序。直接发送普通文本、图片或文件时,FoxClaw 会继续当前绑定线程;如果没有绑定线程,会自动新建线程。
|
|
228
230
|
|
|
229
|
-
### 3.2 `/status`、`/account`、`/quota`
|
|
231
|
+
### 3.2 `/status`、`/account`、`/quota`、`/update`
|
|
230
232
|
|
|
231
233
|
- `/status`:查看 FoxClaw、app-server、当前绑定线程、模型、权限和 Codex 用量摘要。
|
|
232
234
|
- `/account`:查看当前 Codex 登录账号。
|
|
233
235
|
- `/quota`:查看 Codex 用量和额度窗口。
|
|
236
|
+
- `/update`:升级 FoxClaw、自检并重启服务;当前有运行中回复、审批或待确认问题时会拒绝执行,重启后会回报结果。
|
|
234
237
|
|
|
235
238
|
### 3.3 `/config`、`/requirements`、`/provider`
|
|
236
239
|
|