@foxden-app/foxclaw 0.3.18 → 0.4.0

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.
Files changed (39) hide show
  1. package/.env.example +8 -3
  2. package/README.md +17 -7
  3. package/README_EN.md +17 -7
  4. package/dist/auth/mirror.d.ts +38 -0
  5. package/dist/auth/mirror.js +259 -0
  6. package/dist/codex_app/client.d.ts +4 -1
  7. package/dist/codex_app/client.js +23 -4
  8. package/dist/config.d.ts +7 -0
  9. package/dist/config.js +18 -1
  10. package/dist/controller/controller.d.ts +24 -2
  11. package/dist/controller/controller.js +161 -35
  12. package/dist/core/bridge_scope.d.ts +5 -2
  13. package/dist/core/bridge_scope.js +7 -3
  14. package/dist/i18n.d.ts +20 -2
  15. package/dist/i18n.js +20 -2
  16. package/dist/main.js +198 -10
  17. package/dist/store/database.d.ts +4 -2
  18. package/dist/store/database.js +42 -6
  19. package/dist/telegram/addressing.d.ts +1 -0
  20. package/dist/telegram/addressing.js +3 -0
  21. package/dist/telegram/gateway.d.ts +4 -1
  22. package/dist/telegram/gateway.js +23 -5
  23. package/dist/types.d.ts +26 -0
  24. package/dist/update.d.ts +5 -0
  25. package/dist/update.js +93 -5
  26. package/docs/agent-assisted-install.md +7 -6
  27. package/docs/install-for-beginners.md +12 -4
  28. package/docs/troubleshooting.md +13 -1
  29. package/docs/user-manual.md +12 -12
  30. package/docs/zh/agent-assisted-install.md +7 -6
  31. package/docs/zh/foxclaw-skill.md +4 -2
  32. package/docs/zh/install-for-beginners.md +12 -4
  33. package/docs/zh/troubleshooting.md +13 -1
  34. package/docs/zh/user-manual.md +12 -12
  35. package/package.json +1 -1
  36. package/skills/foxclaw/SKILL.md +28 -20
  37. package/skills/foxclaw/references/telegram-setup.md +9 -6
  38. package/skills/foxclaw/scripts/bootstrap_host.py +11 -8
  39. package/skills/foxclaw/scripts/bootstrap_remote.py +8 -4
package/dist/update.js CHANGED
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import process from 'node:process';
4
4
  import { spawn, spawnSync } from 'node:child_process';
5
5
  const PACKAGE_SPEC = '@foxden-app/foxclaw@latest';
6
+ const CODEX_PACKAGE_SPEC = '@openai/codex@latest';
6
7
  const UPDATE_STATUS_FILENAME = 'self-update.json';
7
8
  export function selfUpdateStatusPath(statusPath) {
8
9
  return path.join(path.dirname(statusPath), UPDATE_STATUS_FILENAME);
@@ -33,6 +34,7 @@ export function resolveSelfUpdateInstaller(entryPoint, nodePath = process.execPa
33
34
  command: pnpmCommand,
34
35
  installArgs: ['add', '--global', PACKAGE_SPEC],
35
36
  rootArgs: ['root', '--global'],
37
+ pnpmHome,
36
38
  };
37
39
  }
38
40
  const npmCommandName = process.platform === 'win32' ? 'npm.cmd' : 'npm';
@@ -43,6 +45,7 @@ export function resolveSelfUpdateInstaller(entryPoint, nodePath = process.execPa
43
45
  command: npmCommand,
44
46
  installArgs: ['exec', '--yes', '--package=pnpm@latest', '--', 'pnpm', 'add', '--global', PACKAGE_SPEC],
45
47
  rootArgs: ['exec', '--yes', '--package=pnpm@latest', '--', 'pnpm', 'root', '--global'],
48
+ pnpmHome,
46
49
  };
47
50
  }
48
51
  const adjacentNpm = path.join(path.dirname(nodePath), process.platform === 'win32' ? 'npm.cmd' : 'npm');
@@ -53,6 +56,65 @@ export function resolveSelfUpdateInstaller(entryPoint, nodePath = process.execPa
53
56
  rootArgs: ['root', '--global'],
54
57
  };
55
58
  }
59
+ export function resolveCodexUpdateInstaller(codexCliBin, nodePath = process.execPath, exists = fs.existsSync, env = process.env, realpath = fs.realpathSync, readText = (target) => fs.readFileSync(target, 'utf8')) {
60
+ const resolved = resolveManagedCodexEntryPoint(codexCliBin, realpath, readText);
61
+ if (!resolved)
62
+ return null;
63
+ const normalized = resolved.replace(/\\/g, '/');
64
+ const pnpmManaged = normalized.includes('/global/') && normalized.includes('/.pnpm/@openai+codex@');
65
+ const npmManaged = normalized.includes('/lib/node_modules/@openai/codex/')
66
+ || normalized.includes('/npm/node_modules/@openai/codex/');
67
+ if (!pnpmManaged && !npmManaged) {
68
+ return null;
69
+ }
70
+ const installer = resolveSelfUpdateInstaller(resolved, nodePath, exists, env);
71
+ return {
72
+ ...installer,
73
+ installArgs: installer.installArgs.map((argument) => (argument === PACKAGE_SPEC ? CODEX_PACKAGE_SPEC : argument)),
74
+ };
75
+ }
76
+ function resolveManagedCodexEntryPoint(codexCliBin, realpath, readText) {
77
+ const pending = [codexCliBin];
78
+ const visited = new Set();
79
+ while (pending.length > 0 && visited.size < 4) {
80
+ const candidate = pending.shift();
81
+ let resolved = candidate;
82
+ try {
83
+ resolved = realpath(candidate);
84
+ }
85
+ catch {
86
+ // Wrapper inspection below can still reveal its managed target.
87
+ }
88
+ if (visited.has(resolved))
89
+ continue;
90
+ visited.add(resolved);
91
+ const normalized = resolved.replace(/\\/g, '/');
92
+ if (isManagedCodexPackagePath(normalized)) {
93
+ return resolved;
94
+ }
95
+ let contents = '';
96
+ try {
97
+ contents = readText(resolved);
98
+ }
99
+ catch {
100
+ continue;
101
+ }
102
+ const embeddedPackageRoot = contents.match(/\/[^\s"']*\/global\/[^\s"']*\/\.pnpm\/@openai\+codex@[^\s"']*\/node_modules\/@openai\/codex/)?.[0];
103
+ if (embeddedPackageRoot) {
104
+ return path.join(embeddedPackageRoot, 'bin', 'codex.js');
105
+ }
106
+ const wrappedExecutable = contents.match(/\bexec\s+"([^"]*codex[^"]*)"/)?.[1];
107
+ if (wrappedExecutable) {
108
+ pending.push(wrappedExecutable);
109
+ }
110
+ }
111
+ return null;
112
+ }
113
+ function isManagedCodexPackagePath(normalized) {
114
+ return (normalized.includes('/global/') && normalized.includes('/.pnpm/@openai+codex@'))
115
+ || normalized.includes('/lib/node_modules/@openai/codex/')
116
+ || normalized.includes('/npm/node_modules/@openai/codex/');
117
+ }
56
118
  export function readSelfUpdateStatus(statusFile) {
57
119
  try {
58
120
  const parsed = JSON.parse(fs.readFileSync(statusFile, 'utf8'));
@@ -69,6 +131,7 @@ export function readSelfUpdateStatus(statusFile) {
69
131
  locale: parsed.locale,
70
132
  fromVersion: parsed.fromVersion,
71
133
  toVersion: typeof parsed.toVersion === 'string' ? parsed.toVersion : null,
134
+ ...(typeof parsed.codexUpdate === 'string' ? { codexUpdate: parsed.codexUpdate } : {}),
72
135
  error: typeof parsed.error === 'string' ? parsed.error : null,
73
136
  updatedAt: parsed.updatedAt,
74
137
  };
@@ -97,6 +160,7 @@ export function createSelfUpdateRuntime(options) {
97
160
  locale,
98
161
  fromVersion: options.version,
99
162
  toVersion: null,
163
+ codexUpdate: null,
100
164
  error: null,
101
165
  updatedAt: new Date().toISOString(),
102
166
  });
@@ -106,7 +170,7 @@ export function createSelfUpdateRuntime(options) {
106
170
  const child = spawn(options.nodePath, [options.entryPoint, 'update', '--notification-file', statusFile], {
107
171
  detached: true,
108
172
  stdio: ['ignore', logFd, logFd],
109
- env: process.env,
173
+ env: options.codexCliBin ? { ...process.env, CODEX_CLI_BIN: options.codexCliBin } : process.env,
110
174
  });
111
175
  child.unref();
112
176
  }
@@ -117,6 +181,7 @@ export function createSelfUpdateRuntime(options) {
117
181
  locale,
118
182
  fromVersion: options.version,
119
183
  toVersion: null,
184
+ codexUpdate: null,
120
185
  error: formatError(error),
121
186
  updatedAt: new Date().toISOString(),
122
187
  });
@@ -137,7 +202,10 @@ export function createSelfUpdateRuntime(options) {
137
202
  export function performSelfUpdate(options) {
138
203
  const env = options.env ?? process.env;
139
204
  let toVersion = null;
205
+ let codexUpdate = null;
140
206
  try {
207
+ codexUpdate = updateManagedCodexCli(options.codexCliBin ?? env.CODEX_CLI_BIN ?? '', options.nodePath, env);
208
+ console.log(`[UPDATE] ${codexUpdate}`);
141
209
  const installer = resolveSelfUpdateInstaller(options.entryPoint, options.nodePath, fs.existsSync, env);
142
210
  const installerEnv = buildInstallerEnv(options.entryPoint, installer, env);
143
211
  console.log(`[UPDATE] Installing ${PACKAGE_SPEC} with ${installer.manager}...`);
@@ -146,7 +214,7 @@ export function performSelfUpdate(options) {
146
214
  toVersion = readInstalledPackageVersion(updatedEntryPoint);
147
215
  console.log('[UPDATE] Running checks and restarting the FoxClaw service...');
148
216
  runInherited(options.nodePath, [updatedEntryPoint, 'start'], installerEnv);
149
- completeNotification(options.notificationFile, 'succeeded', toVersion, null);
217
+ completeNotification(options.notificationFile, 'succeeded', toVersion, codexUpdate, null);
150
218
  console.log(`[OK] FoxClaw updated and restarted: ${options.version} -> ${toVersion}`);
151
219
  return {
152
220
  ok: true,
@@ -157,7 +225,7 @@ export function performSelfUpdate(options) {
157
225
  }
158
226
  catch (error) {
159
227
  const message = formatError(error);
160
- completeNotification(options.notificationFile, 'failed', toVersion, message);
228
+ completeNotification(options.notificationFile, 'failed', toVersion, codexUpdate, message);
161
229
  console.error(`[FAIL] FoxClaw update failed: ${message}`);
162
230
  return {
163
231
  ok: false,
@@ -167,6 +235,23 @@ export function performSelfUpdate(options) {
167
235
  };
168
236
  }
169
237
  }
238
+ function updateManagedCodexCli(codexCliBin, nodePath, env) {
239
+ if (!codexCliBin) {
240
+ return 'Codex CLI update skipped: CODEX_CLI_BIN is not configured.';
241
+ }
242
+ const installer = resolveCodexUpdateInstaller(codexCliBin, nodePath, fs.existsSync, env);
243
+ if (!installer) {
244
+ return 'Codex CLI update skipped: configured installation is not a recognized global npm/pnpm package.';
245
+ }
246
+ try {
247
+ const installerEnv = buildInstallerEnv(fs.realpathSync(codexCliBin), installer, env);
248
+ runInherited(installer.command, installer.installArgs, installerEnv);
249
+ return `Codex CLI updated with ${installer.manager}.`;
250
+ }
251
+ catch (error) {
252
+ return `Codex CLI update failed without blocking FoxClaw update: ${formatError(error)}`;
253
+ }
254
+ }
170
255
  function executableCandidates(commandName, nodePath, env, preferred = []) {
171
256
  return [
172
257
  ...preferred,
@@ -175,7 +260,9 @@ function executableCandidates(commandName, nodePath, env, preferred = []) {
175
260
  ].filter((candidate, index, all) => candidate && all.indexOf(candidate) === index);
176
261
  }
177
262
  function buildInstallerEnv(entryPoint, installer, env) {
178
- const pnpmHome = installer.manager === 'pnpm' ? inferPnpmHomeFromEntryPoint(entryPoint) : null;
263
+ const pnpmHome = installer.manager === 'pnpm'
264
+ ? installer.pnpmHome ?? inferPnpmHomeFromEntryPoint(entryPoint)
265
+ : null;
179
266
  if (!pnpmHome) {
180
267
  return env;
181
268
  }
@@ -230,7 +317,7 @@ function readInstalledPackageVersion(updatedEntryPoint) {
230
317
  return 'unknown';
231
318
  }
232
319
  }
233
- function completeNotification(notificationFile, state, toVersion, error) {
320
+ function completeNotification(notificationFile, state, toVersion, codexUpdate, error) {
234
321
  if (!notificationFile) {
235
322
  return;
236
323
  }
@@ -242,6 +329,7 @@ function completeNotification(notificationFile, state, toVersion, error) {
242
329
  ...pending,
243
330
  state,
244
331
  toVersion,
332
+ codexUpdate,
245
333
  error,
246
334
  updatedAt: new Date().toISOString(),
247
335
  });
@@ -8,7 +8,7 @@ This works well with Codex, OpenClaw, QwenPaw, Hermes, OpenCode, Kimi CLI, or an
8
8
 
9
9
  Prepare these values before asking the agent:
10
10
 
11
- - `TG_BOT_TOKEN`: Telegram bot token from `@BotFather`
11
+ - `TG_BOT_TOKENS`: one or more comma-separated Telegram bot tokens from `@BotFather`
12
12
  - `TG_ALLOWED_USER_ID`: your numeric Telegram user id
13
13
  - `DEFAULT_CWD`: the folder where Codex should work
14
14
 
@@ -17,7 +17,7 @@ Optional later:
17
17
  - `TG_ALLOWED_CHAT_ID`
18
18
  - `TG_ALLOWED_TOPIC_ID`
19
19
 
20
- Use private Telegram chat first. Configure groups/topics only after private chat works.
20
+ Use private Telegram chat first. Configure groups/topics only after private chat works. Multiple tokens mean independent Codex runtimes inside one service; validate each bot privately.
21
21
 
22
22
  ## Copy-Paste Prompt
23
23
 
@@ -33,7 +33,7 @@ Published package:
33
33
  Use private Telegram chat first. Do not configure group/topic mode unless I explicitly provide TG_ALLOWED_CHAT_ID or TG_ALLOWED_TOPIC_ID.
34
34
 
35
35
  Here are the required values:
36
- TG_BOT_TOKEN=<paste token here>
36
+ TG_BOT_TOKENS=<paste one token, or comma-separated tokens here>
37
37
  TG_ALLOWED_USER_ID=<paste numeric Telegram user id here>
38
38
  DEFAULT_CWD=<paste absolute working directory here>
39
39
 
@@ -45,17 +45,18 @@ Tasks:
45
45
  5. Run foxclaw init, then write ~/.foxclaw/.env. Never print or commit the bot token.
46
46
  6. Run foxclaw doctor.
47
47
  7. Start FoxClaw with foxclaw start.
48
- 8. Ask me to send /help and /status to the Telegram bot.
48
+ 8. Ask me to send /help, /status, and /auth privately to each configured Telegram bot; confirm /auth names that bot runtime.
49
49
  9. Verify the final state:
50
50
  - foxclaw.service is active/enabled on Linux
51
51
  - foxclaw status works
52
- 10. Report the commands used, the final status, and the log command I should use if something stops working. Redact TG_BOT_TOKEN and never print the full token or full .env content.
52
+ 10. Report the commands used, the final status, and the log command I should use if something stops working. Redact TG_BOT_TOKENS and never print the full token or full .env content.
53
+ 11. If multiple bots are enabled, confirm foxclaw status lists independent app-servers; group-chat tests must mention or reply to the intended bot.
53
54
  ```
54
55
 
55
56
  ## Safety Notes
56
57
 
57
58
  - Do not paste bot tokens into public issue trackers or public chat logs.
58
59
  - Do not commit `.env`.
59
- - When reporting results, redact `TG_BOT_TOKEN`.
60
+ - When reporting results, redact `TG_BOT_TOKENS` and legacy `TG_BOT_TOKEN`.
60
61
  - Do not use `/` or your whole home directory as `DEFAULT_CWD` for a first install.
61
62
  - Use `foxclaw start` for normal service startup. Use foreground `foxclaw serve` only when troubleshooting.
@@ -123,7 +123,7 @@ npm install -g @foxden-app/foxclaw
123
123
  foxclaw init
124
124
  ```
125
125
 
126
- This creates the config file at `~/.foxclaw/.env` and prompts for the Telegram bot token, your numeric Telegram user id, and the default workspace.
126
+ This creates the config file at `~/.foxclaw/.env` and prompts for one or more comma-separated Telegram bot tokens, your numeric Telegram user id, and the default workspace. New installs use `TG_BOT_TOKENS`; legacy `TG_BOT_TOKEN` is only for existing single-bot configurations.
127
127
 
128
128
  If you prefer pnpm:
129
129
 
@@ -143,7 +143,7 @@ nano ~/.foxclaw/.env
143
143
  For a first private-chat install, fill only the important values:
144
144
 
145
145
  ```dotenv
146
- TG_BOT_TOKEN=123456789:replace_with_your_bot_token
146
+ TG_BOT_TOKENS=123456789:replace_with_your_bot_token
147
147
  TG_ALLOWED_USER_ID=123456789
148
148
  TG_ALLOWED_CHAT_ID=
149
149
  TG_ALLOWED_TOPIC_ID=
@@ -154,6 +154,14 @@ DEFAULT_SANDBOX_MODE=workspace-write
154
154
 
155
155
  Keep `TG_ALLOWED_CHAT_ID=` and `TG_ALLOWED_TOPIC_ID=` empty for the first install. Do not delete those lines; leaving them empty means private-chat mode.
156
156
 
157
+ Start with one bot. To add three independent Codex lanes later, put the tokens on the same line:
158
+
159
+ ```dotenv
160
+ TG_BOT_TOKENS=123456789:token_a,234567890:token_b,345678901:token_c
161
+ ```
162
+
163
+ FoxClaw still installs one service, but each bot receives its own app-server, session home, and current auth selection. After startup, privately send `/help` and `/status` to each bot.
164
+
157
165
  `DEFAULT_CWD` must be a real folder. Examples:
158
166
 
159
167
  ```dotenv
@@ -176,7 +184,7 @@ You want to see:
176
184
  ```text
177
185
  [OK] node >= 24
178
186
  [OK] codex cli available
179
- [OK] telegram bot token configured
187
+ [OK] telegram bot token(s) configured
180
188
  [OK] telegram allowed user configured
181
189
  [OK] default cwd exists
182
190
  ```
@@ -280,7 +288,7 @@ Update FoxClaw later:
280
288
  foxclaw update
281
289
  ```
282
290
 
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.
291
+ You can also send `/update` in an authorized Telegram chat. When every Telegram bot runtime, an enabled Weixin default runtime, and auth mirror writes are idle, it attempts to update an npm/pnpm-managed Codex CLI, upgrades FoxClaw, checks, restarts the service, and reports the result through the bot that started it.
284
292
 
285
293
  ## Next Step
286
294
 
@@ -20,7 +20,7 @@ journalctl --user -u foxclaw.service -f
20
20
  | --- | --- | --- |
21
21
  | `[FAIL] node >= 24` | Your current shell is using an older Node.js. | Install or activate Node.js 24+ by any method, then rerun `foxclaw doctor`. If the service uses old Node, reinstall it from a Node 24+ shell with `foxclaw start`. |
22
22
  | `[FAIL] codex cli available` | The `codex` command is not in PATH. | Install Codex CLI or fix PATH, then confirm `codex --version` works. |
23
- | `[FAIL] telegram bot token configured` | `TG_BOT_TOKEN` is missing from `.env`. | Copy the token from `@BotFather` into `.env`. |
23
+ | `[FAIL] telegram bot token(s) configured` | Neither `TG_BOT_TOKENS` nor legacy `TG_BOT_TOKEN` is present in `.env`. | Put one or more comma-separated `@BotFather` tokens in `TG_BOT_TOKENS`. |
24
24
  | `[FAIL] telegram allowed user configured` | `TG_ALLOWED_USER_ID` is missing from `.env`. | Get your numeric id from `@userinfobot` and add it to `.env`. |
25
25
  | `[FAIL] default cwd exists` | `DEFAULT_CWD` points to a folder that does not exist. | Create the folder or change `DEFAULT_CWD` to an existing absolute path. |
26
26
 
@@ -182,6 +182,18 @@ Bridge logs are stored here:
182
182
  tail -f ~/.foxclaw/logs/service.log
183
183
  ```
184
184
 
185
+ ## Checking Multi-Bot Mode
186
+
187
+ After configuring `TG_BOT_TOKENS`, `foxclaw status` should contain one bot id, connection status, and independent app-server for each token. Send `/status` privately to any bot to see the runtime summary; send `/auth` to confirm the panel names the current `@botname` and its auth directory.
188
+
189
+ Each isolated app-server log is stored at:
190
+
191
+ ```bash
192
+ tail -f ~/.foxclaw/logs/codex-app-server-bot<id>.log
193
+ ```
194
+
195
+ When multiple bots share a group, unaddressed messages intentionally do not trigger them; mention `@botname`, reply to the intended bot, or send `/status@botname`. When enabled, Weixin stays on the default Codex runtime and does not appear inside an isolated Telegram bot's thread list.
196
+
185
197
  ## ChatGPT Backend 403 Or Unable To Load Site
186
198
 
187
199
  If Telegram shows `ChatGPT backend 403 Forbidden`, or the app-server log contains `Unable to load site`, `cf-ray`, or `chatgpt.com/backend-api`, the auth file is not necessarily broken. The service process is usually reaching ChatGPT with the wrong network/proxy/IP.
@@ -100,7 +100,7 @@ Both install the same published npm package. Use one global package manager cons
100
100
 
101
101
  ### 1.6 Fill In The Config
102
102
 
103
- `foxclaw init` creates the default config file at `~/.foxclaw/.env` and prompts for the Telegram bot token, your numeric Telegram user id, and the default workspace. If the current shell has proxy variables such as `HTTP_PROXY`, `HTTPS_PROXY`, or `ALL_PROXY`, it also asks whether to save them into the FoxClaw config. When `HTTP_PROXY` or `HTTPS_PROXY` is configured, FoxClaw passes it to systemd/launchd explicitly and enables Node's env proxy support. Press Enter on any field to skip it, then edit manually if needed:
103
+ `foxclaw init` creates the default config file at `~/.foxclaw/.env` and prompts for one or more comma-separated Telegram bot tokens, your numeric Telegram user id, and the default workspace. If the current shell has proxy variables such as `HTTP_PROXY`, `HTTPS_PROXY`, or `ALL_PROXY`, it also asks whether to save them into the FoxClaw config. When `HTTP_PROXY` or `HTTPS_PROXY` is configured, FoxClaw passes it to systemd/launchd explicitly and enables Node's env proxy support. Press Enter on any field to skip it, then edit manually if needed:
104
104
 
105
105
  ```bash
106
106
  $EDITOR ~/.foxclaw/.env
@@ -109,7 +109,7 @@ $EDITOR ~/.foxclaw/.env
109
109
  Minimum private-chat config:
110
110
 
111
111
  ```dotenv
112
- TG_BOT_TOKEN=123456789:replace_with_your_bot_token
112
+ TG_BOT_TOKENS=123456789:replace_with_your_bot_token
113
113
  TG_ALLOWED_USER_ID=123456789
114
114
  TG_ALLOWED_CHAT_ID=
115
115
  TG_ALLOWED_TOPIC_ID=
@@ -120,7 +120,7 @@ DEFAULT_SANDBOX_MODE=workspace-write
120
120
 
121
121
  Fields:
122
122
 
123
- - `TG_BOT_TOKEN`: the token from `@BotFather`.
123
+ - `TG_BOT_TOKENS`: one or more `@BotFather` tokens separated by commas. The legacy single-bot `TG_BOT_TOKEN` setting remains compatible.
124
124
  - `TG_ALLOWED_USER_ID`: your numeric Telegram user id.
125
125
  - `TG_ALLOWED_CHAT_ID`: leave empty for the first private-chat setup.
126
126
  - `TG_ALLOWED_TOPIC_ID`: leave empty unless binding a Telegram topic.
@@ -136,7 +136,7 @@ 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.
139
+ For later upgrades, run `foxclaw update`. It uses the npm or pnpm installation method currently managing FoxClaw, attempts to update a globally npm/pnpm-managed Codex CLI, runs checks, and restarts the background service.
140
140
 
141
141
  Linux service logs:
142
142
 
@@ -230,10 +230,10 @@ Later commands are sorted by recent usage. Plain text, photos, and files continu
230
230
 
231
231
  ### 3.2 `/status`, `/account`, `/quota`, `/update`
232
232
 
233
- - `/status`: FoxClaw, app-server, current thread binding, model, access, and Codex usage summary. Local session, token, and visible-reply-throughput metrics use a background-generated historical snapshot instead of scanning large logs during the request; throughput is computed end-to-end for completed turns, excluding reasoning tokens while including waiting and tool execution time.
233
+ - `/status`: FoxClaw, app-server, current thread binding, model, access, and Codex usage summary. In multi-bot mode it also lists every bot's connection, current auth, active turns, and the most recent auth mirror and service/Codex update outcomes. Local session, token, and visible-reply-throughput metrics use a background-generated historical snapshot instead of scanning large logs during the request; throughput is computed end-to-end for completed turns, excluding reasoning tokens while including waiting and tool execution time.
234
234
  - `/account`: current Codex account.
235
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.
236
+ - `/update`: upgrade FoxClaw, attempt to update an npm/pnpm-managed Codex CLI, run checks, and restart the service; it refuses while any Telegram bot runtime, an enabled Weixin default runtime, or an auth mirror write is busy, then reports the result through the initiating bot after restart.
237
237
 
238
238
  ### 3.3 `/config`, `/requirements`, `/provider`
239
239
 
@@ -335,11 +335,11 @@ Watch mode mirrors live turn progress and approval requests. The watching chat i
335
335
 
336
336
  ## 6. Codex Login And Auth Rotation
337
337
 
338
- This is a key FoxClaw feature. Codex auth is usually stored at `~/.codex/auth.json`. FoxClaw stores multiple accounts as candidate files and switches which candidate the active `auth.json` points to.
338
+ This is a key FoxClaw feature. Codex auth is usually stored at `~/.codex/auth.json`. FoxClaw stores multiple accounts as candidate files and switches which candidate the active `auth.json` points to. In `TG_BOT_TOKENS` mode, each bot has an isolated Codex home, app-server, and current candidate, so bots can run and switch accounts independently; isolated Telegram runtimes force file-backed credential storage. Validated login/refresh credentials are safely mirrored between bot homes, but sessions are never shared.
339
339
 
340
340
  ### 6.1 File Format
341
341
 
342
- Candidate files live in the Codex auth directory, usually `~/.codex/`. If `CODEX_AUTH_DIR` is set, FoxClaw uses that directory.
342
+ In single-bot compatibility mode, candidate files live in the Codex auth directory, usually `~/.codex/`. If `CODEX_AUTH_DIR` is set, FoxClaw uses that directory. Multi-bot mode treats that directory as its candidate source and stores isolated bot copies under `~/.foxclaw/codex/telegram/bot<id>/home/`.
343
343
 
344
344
  Recommended layout:
345
345
 
@@ -357,7 +357,7 @@ FoxClaw recognizes candidate names in these forms:
357
357
  - `auth.json.<name>`
358
358
  - `auth.json-<name>`
359
359
 
360
- `auth.json` is what Codex currently uses. When switching accounts, FoxClaw points `auth.json` at one candidate. Candidate contents are Codex-generated JSON; FoxClaw treats those private fields as opaque and you should not hand-write them.
360
+ `auth.json` is what Codex currently uses. When switching accounts, FoxClaw points `auth.json` at one candidate. Candidate contents are Codex-generated JSON and should not be hand-written. In multi-bot mode, FoxClaw mirrors a candidate only when its account identity matches and its refresh timestamp is newer, preventing a same-name candidate from overwriting a different account.
361
361
 
362
362
  If you already have a working `auth.json`, you can save it as a candidate:
363
363
 
@@ -387,7 +387,7 @@ If the login is cancelled or fails, FoxClaw tries to restore the previous auth t
387
387
 
388
388
  ### 6.3 The `/auth` Panel
389
389
 
390
- `/auth` lists candidate accounts, the current account, and the auth directory. It also provides buttons for switching, disabling, login, and reload. The `5h|7d` numbers before each filename are the last recorded remaining percentages for the two quota windows; the current auth is refreshed when the panel opens, while other candidates are not switched merely to query quota.
390
+ `/auth` lists candidate accounts, the current account, and the auth directory. It also provides buttons for switching, disabling, login, and reload. In multi-bot mode the panel names the `@botname` runtime being managed, because private chats, groups, and topics on one bot share that bot's current auth. The `5h|7d` numbers before each filename are the last recorded remaining percentages for the two quota windows; the current auth is refreshed when the panel opens, while other candidates are not switched merely to query quota.
391
391
 
392
392
  Approximation:
393
393
 
@@ -416,7 +416,7 @@ Equivalent commands:
416
416
  - `/auth disable <n>`: skip candidate n during auto-rotation.
417
417
  - `/auth reload` or `/auth_reload`: restart app-server and reload the current `auth.json`.
418
418
 
419
- If active turns, pending approvals, pending user inputs, or MCP elicitations exist, FoxClaw refuses manual auth switching to avoid changing accounts mid-request.
419
+ If the requesting bot runtime has active turns, pending approvals, pending user inputs, or MCP elicitations, FoxClaw refuses manual auth switching to avoid changing accounts mid-request; another idle bot is unaffected.
420
420
 
421
421
  ### 6.4 How Auto-Rotation Works
422
422
 
@@ -452,7 +452,7 @@ auth.json_backup # backup account, enable or disable as needed
452
452
 
453
453
  ## 8. Safety
454
454
 
455
- - Do not share `TG_BOT_TOKEN`, `~/.codex/auth.json*`, or `.env`.
455
+ - Do not share `TG_BOT_TOKENS`, `TG_BOT_TOKEN`, `~/.codex/auth.json*`, or `.env`.
456
456
  - Do not use `/`, `/home`, `/Users`, or your whole home directory as the first `DEFAULT_CWD`.
457
457
  - When unsure, use `/permissions read-only` or select `Read-only` in `/setup`.
458
458
  - Group mode still only accepts `TG_ALLOWED_USER_ID`, but use trusted groups.
@@ -8,7 +8,7 @@
8
8
 
9
9
  先准备这几个值:
10
10
 
11
- - `TG_BOT_TOKEN`:从 `@BotFather` 拿到的 Telegram bot token
11
+ - `TG_BOT_TOKENS`:从 `@BotFather` 拿到的一个或多个 Telegram bot token,多项用逗号分隔
12
12
  - `TG_ALLOWED_USER_ID`:你的 Telegram 数字用户 ID
13
13
  - `DEFAULT_CWD`:希望 Codex 默认工作的目录
14
14
 
@@ -17,7 +17,7 @@
17
17
  - `TG_ALLOWED_CHAT_ID`
18
18
  - `TG_ALLOWED_TOPIC_ID`
19
19
 
20
- 第一次请先用 Telegram 私聊跑通。群组和话题模式等私聊稳定后再开。
20
+ 第一次请先用 Telegram 私聊跑通。群组和话题模式等私聊稳定后再开。多个 token 表示同一服务内多个独立 Codex runtime;每个 bot 都必须单独完成私聊验收。
21
21
 
22
22
  ## 复制给 agent 的安装提示词
23
23
 
@@ -32,7 +32,7 @@
32
32
  先使用 Telegram 私聊模式。除非我明确提供 TG_ALLOWED_CHAT_ID 或 TG_ALLOWED_TOPIC_ID,否则不要配置群组/话题模式。
33
33
 
34
34
  必需配置:
35
- TG_BOT_TOKEN=<把 token 粘贴在这里>
35
+ TG_BOT_TOKENS=<粘贴一个 token,或多个逗号分隔 token>
36
36
  TG_ALLOWED_USER_ID=<把 Telegram 数字用户 ID 粘贴在这里>
37
37
  DEFAULT_CWD=<把绝对工作目录粘贴在这里>
38
38
 
@@ -44,17 +44,18 @@ DEFAULT_CWD=<把绝对工作目录粘贴在这里>
44
44
  5. 运行 foxclaw init,然后写入 ~/.foxclaw/.env。不要打印或提交 bot token。
45
45
  6. 运行 foxclaw doctor。
46
46
  7. 用 foxclaw start 启动 FoxClaw。
47
- 8. 让我在 Telegram bot 里发送 /help 和 /status
47
+ 8. 让我在每个已配置的 Telegram bot 私聊里发送 /help、/status 和 /auth;确认 /auth 显示对应 bot runtime
48
48
  9. 验证最终状态:
49
49
  - Linux 上 foxclaw.service 处于 active/enabled
50
50
  - foxclaw status 可以正常输出
51
- 10. 汇报执行过的命令、最终状态和后续看日志的命令。请隐藏 TG_BOT_TOKEN,不要打印完整 token 或完整 .env。
51
+ 10. 汇报执行过的命令、最终状态和后续看日志的命令。请隐藏 TG_BOT_TOKENS,不要打印完整 token 或完整 .env。
52
+ 11. 如果启用了多个 bot,确认 foxclaw status 列出独立 app-server;群聊测试必须点名或回复目标 bot。
52
53
  ```
53
54
 
54
55
  ## 安全注意事项
55
56
 
56
57
  - 不要把 bot token 粘贴到公开 issue、公开聊天或代码仓库。
57
58
  - 不要提交 `.env`。
58
- - 汇报结果时隐藏 `TG_BOT_TOKEN`。
59
+ - 汇报结果时隐藏 `TG_BOT_TOKENS` 和兼容变量 `TG_BOT_TOKEN`。
59
60
  - 第一次安装不要把 `/`、整个 `/Users`、整个 `/home` 或完整 home 目录设为 `DEFAULT_CWD`。
60
61
  - 日常启动用 `foxclaw start`;只有排障时才用前台模式 `foxclaw serve`。
@@ -10,15 +10,17 @@
10
10
 
11
11
  ## 基本流程
12
12
 
13
- 1. 准备 `TG_BOT_TOKEN`、`TG_ALLOWED_USER_ID` 和 `DEFAULT_CWD`。
13
+ 1. 准备 `TG_BOT_TOKENS`(一个或多个逗号分隔的 bot token)、`TG_ALLOWED_USER_ID` 和 `DEFAULT_CWD`。
14
14
  2. 让 Codex 使用 `skills/foxclaw`。
15
15
  3. 如果是远程机器,提供 SSH 目标。
16
16
  4. 让 Codex 执行安装、写配置、跑 `foxclaw doctor`。
17
- 5. 启动服务后,在 Telegram bot 里发送 `/help` 和 `/status` 验证。
17
+ 5. 启动服务后,在每个配置的 Telegram bot 私聊里发送 `/help`、`/status` 和 `/auth` 验证。
18
18
 
19
19
  ## 注意事项
20
20
 
21
21
  - 不要让 agent 把完整 bot token 打印到日志或提交到仓库。
22
22
  - 第一次请先用私聊模式跑通。
23
+ - 多个 token 会在同一服务中建立多个独立 Codex home、session 与 auth 选择;群组中必须点名或回复目标 bot。
24
+ - 同时启用微信时,微信仍使用默认 Codex runtime,不共享隔离 Telegram bot 的线程。
23
25
  - 不要把整个 home 目录或根目录作为首次 `DEFAULT_CWD`。
24
26
  - 只有在 `doctor` 通过、服务已启动、Telegram 首条消息验证通过后,才算安装完成。
@@ -121,7 +121,7 @@ npm install -g @foxden-app/foxclaw
121
121
  foxclaw init
122
122
  ```
123
123
 
124
- 这会创建默认配置文件 `~/.foxclaw/.env`,并提示你填写 Telegram bot tokenTelegram 数字用户 ID 和默认工作目录。
124
+ 这会创建默认配置文件 `~/.foxclaw/.env`,并提示你填写一个或多个 Telegram bot token(多个用英文逗号分隔)、Telegram 数字用户 ID 和默认工作目录。新安装使用 `TG_BOT_TOKENS`;旧的 `TG_BOT_TOKEN` 仅用于兼容已有单 bot 配置。
125
125
 
126
126
  如果你用 pnpm:
127
127
 
@@ -141,7 +141,7 @@ nano ~/.foxclaw/.env
141
141
  第一次私聊模式只需要重点填写这些值:
142
142
 
143
143
  ```dotenv
144
- TG_BOT_TOKEN=123456789:replace_with_your_bot_token
144
+ TG_BOT_TOKENS=123456789:replace_with_your_bot_token
145
145
  TG_ALLOWED_USER_ID=123456789
146
146
  TG_ALLOWED_CHAT_ID=
147
147
  TG_ALLOWED_TOPIC_ID=
@@ -152,6 +152,14 @@ DEFAULT_SANDBOX_MODE=workspace-write
152
152
 
153
153
  `TG_ALLOWED_CHAT_ID=` 和 `TG_ALLOWED_TOPIC_ID=` 第一次保持为空,不要删掉这两行;留空表示私聊模式。
154
154
 
155
+ 只有一个 bot 时先按上面的配置跑通即可。需要三条互不打断的 Codex 会话时,把多个 token 写在同一行:
156
+
157
+ ```dotenv
158
+ TG_BOT_TOKENS=123456789:token_a,234567890:token_b,345678901:token_c
159
+ ```
160
+
161
+ FoxClaw 仍只安装一个服务,但每个 bot 会有独立 app-server、会话目录和当前 auth。服务启动后请分别私聊每个 bot 发送 `/help` 与 `/status`。
162
+
155
163
  `DEFAULT_CWD` 必须是真实存在的目录,例如:
156
164
 
157
165
  ```dotenv
@@ -174,7 +182,7 @@ foxclaw doctor
174
182
  ```text
175
183
  [OK] node >= 24
176
184
  [OK] codex cli available
177
- [OK] telegram bot token configured
185
+ [OK] telegram bot token(s) configured
178
186
  [OK] telegram allowed user configured
179
187
  [OK] default cwd exists
180
188
  ```
@@ -278,7 +286,7 @@ foxclaw uninstall-systemd
278
286
  foxclaw update
279
287
  ```
280
288
 
281
- 也可以在已授权的 Telegram 私聊里发送 `/update`。它会在当前没有运行中回复、审批或待确认问题时,完成升级、自检和服务重启,并在重启后回报结果。
289
+ 也可以在已授权的 Telegram 私聊里发送 `/update`。它会在所有 Telegram bot、已启用的微信默认 runtime 和 auth 镜像写入都空闲时,尝试升级 npm/pnpm 安装的 Codex CLI,完成 FoxClaw 升级、自检和服务重启,并在发起命令的 bot 中回报结果。
282
290
 
283
291
  如果 `~/.foxclaw/.env` 已经存在,`foxclaw init` 会先询问是否更新 Telegram 和工作目录相关字段,其它配置保持不变。
284
292
 
@@ -20,7 +20,7 @@ journalctl --user -u foxclaw.service -f
20
20
  | --- | --- | --- |
21
21
  | `[FAIL] node >= 24` | 当前 shell 使用的是旧版 Node.js。 | 先用任意方式安装或切换到 Node.js 24+,再重新执行 `foxclaw doctor`。如果服务仍用旧 Node,从 Node 24+ 的 shell 里重新执行 `foxclaw start`。 |
22
22
  | `[FAIL] codex cli available` | `codex` 命令不在 PATH 里。 | 安装 Codex CLI 或修正 PATH,再确认 `codex --version` 可用。 |
23
- | `[FAIL] telegram bot token configured` | `.env` 里缺少 `TG_BOT_TOKEN`。 | 从 `@BotFather` 复制 token,填入 `.env`。 |
23
+ | `[FAIL] telegram bot token(s) configured` | `.env` 里没有 `TG_BOT_TOKENS`,也没有兼容变量 `TG_BOT_TOKEN`。 | 从 `@BotFather` 复制一个或多个 token,用逗号分隔填入 `TG_BOT_TOKENS`。 |
24
24
  | `[FAIL] telegram allowed user configured` | `.env` 里缺少 `TG_ALLOWED_USER_ID`。 | 从 `@userinfobot` 获取数字 ID,填入 `.env`。 |
25
25
  | `[FAIL] default cwd exists` | `DEFAULT_CWD` 指向不存在的目录。 | 创建该目录,或把 `DEFAULT_CWD` 改成一个真实存在的绝对路径。 |
26
26
 
@@ -183,6 +183,18 @@ Bridge 日志默认在:
183
183
  tail -f ~/.foxclaw/logs/service.log
184
184
  ```
185
185
 
186
+ ## 多 bot 模式核查
187
+
188
+ 配置 `TG_BOT_TOKENS` 后,`foxclaw status` 的 `bots` 列表应为每个 token 显示一个 bot id、连接状态和独立 app-server。私聊任一 bot 发送 `/status` 会显示全部 runtime 摘要;发送 `/auth` 应显示当前 `@botname` 和该 bot 的 auth 目录。
189
+
190
+ 每个隔离 app-server 的日志路径为:
191
+
192
+ ```bash
193
+ tail -f ~/.foxclaw/logs/codex-app-server-bot<id>.log
194
+ ```
195
+
196
+ 群组里配置了多个 bot 时,普通未点名消息不会触发它们;使用 `@botname`、回复目标 bot,或 `/status@botname`。微信启用后仍使用默认 Codex runtime,不会出现在某个 Telegram bot 的隔离线程中。
197
+
186
198
  ## ChatGPT 后端 403 或 Unable to load site
187
199
 
188
200
  如果 Telegram 里看到 `ChatGPT backend 403 Forbidden`,或者 app-server 日志里出现 `Unable to load site`、`cf-ray`、`chatgpt.com/backend-api`,通常不是 `auth.json` 文件坏了,而是服务进程访问 ChatGPT 后端时没有走正确网络。