@foxden-app/foxclaw 0.5.70 → 0.5.72

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/.env.example CHANGED
@@ -68,6 +68,7 @@ CODEX_CLI_BIN=/absolute/path/to/codex
68
68
  # put the node's contact token first and list peer contact @usernames below.
69
69
  # AUTH_SYNC_ENABLED=true
70
70
  # AUTH_SYNC_KEY=<shared-key-with-at-least-32-bytes>
71
+ # Imported remote auth is not relayed; list every contact bot that must receive this node's auth directly.
71
72
  # AUTH_SYNC_PEERS=@other_node_bot,@third_node_bot
72
73
  # AUTH_SYNC_CLUSTER_ID=my-codex-auth-pool
73
74
  # AUTH_SYNC_NODE_ID=
package/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
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.72 - 2026-07-11
6
+
7
+ ### 中文
8
+ - 完整适配 pnpm 11 隔离全局包布局:升级后从 pnpm 生成的 `foxclaw` shim 读取 `global/v11/<instance>/node_modules/.../dist/main.js` 真实入口,不再把 `pnpm root -g` 误当成包目录。
9
+ - pnpm 管理的 FoxClaw 自更新现在按现有安装布局固定 pnpm major,避免 pnpm 10 安装误选 `PNPM_HOME/bin` 中的 pnpm 11;本次更新显式设置 `minimumReleaseAge=0`,确保刚发布的 `@latest` 不会被 pnpm 11 默认 24 小时发布年龄策略回退到旧版。
10
+
11
+ ### English
12
+ - Fully supports pnpm 11's isolated global package layout. After installation, FoxClaw reads the real `global/v11/<instance>/node_modules/.../dist/main.js` target from pnpm's generated CLI shim instead of treating `pnpm root -g` as the package directory.
13
+ - Self-update now pins the pnpm major to the existing installation layout, preventing a pnpm 10 installation from selecting pnpm 11 under `PNPM_HOME/bin`. The update also sets `minimumReleaseAge=0` so pnpm 11's default 24-hour release-age policy does not resolve `@latest` to an older FoxClaw release.
14
+
15
+ ## 0.5.71 - 2026-07-11
16
+
17
+ ### 中文
18
+ - 自更新找不到当前 pnpm 可执行文件时,不再通过 `pnpm@latest` 跨 major 安装:`global/5` 旧布局固定使用 pnpm 10,`global/v11` 布局固定使用 pnpm 11,避免同一次升级同时产生两套全局目录和 CLI 入口。
19
+ - 保留 pnpm 10 与 pnpm 11 两种全局 root 解析,确保安装后的自检和服务重启使用与当前 FoxClaw 安装布局一致的新入口。
20
+
21
+ ### English
22
+ - Self-update no longer crosses pnpm major versions through `pnpm@latest` when the current pnpm executable is unavailable. The legacy `global/5` layout uses pnpm 10, while `global/v11` uses pnpm 11, preventing one update from creating duplicate global trees and CLI entry points.
23
+ - Kept global-root resolution for both pnpm 10 and pnpm 11 so post-install checks and service restart use the new entry point from the same layout as the current FoxClaw installation.
24
+
5
25
  ## 0.5.70 - 2026-07-11
6
26
 
7
27
  ### 中文
package/dist/update.d.ts CHANGED
@@ -65,6 +65,7 @@ export interface PendingClusterUpdateBroadcast {
65
65
  }
66
66
  export declare function selfUpdateStatusPath(statusPath: string): string;
67
67
  export declare function inferPnpmHomeFromEntryPoint(entryPoint: string): string | null;
68
+ export declare function inferPnpmFallbackPackageFromEntryPoint(entryPoint: string): string;
68
69
  export declare function resolveSelfUpdateInstaller(entryPoint: string, nodePath?: string, exists?: (target: string) => boolean, env?: NodeJS.ProcessEnv): SelfUpdateInstaller;
69
70
  export declare function resolveCodexUpdateInstaller(codexCliBin: string, nodePath?: string, exists?: (target: string) => boolean, env?: NodeJS.ProcessEnv, realpath?: (target: string) => string, readText?: (target: string) => string): SelfUpdateInstaller | null;
70
71
  export declare function readSelfUpdateStatus(statusFile: string): SelfUpdateStatus | null;
@@ -86,5 +87,6 @@ export declare function readPendingClusterUpdateBroadcast(filePath: string): Pen
86
87
  export declare function writePendingClusterUpdateBroadcast(filePath: string, value: PendingClusterUpdateBroadcast): void;
87
88
  export declare function clearPendingClusterUpdateBroadcast(filePath: string): void;
88
89
  export declare function resolveFoxclawEntryPointFromGlobalRoot(globalRoot: string, exists?: (target: string) => boolean): string | null;
90
+ export declare function resolveFoxclawEntryPointFromPnpmHome(pnpmHome: string, exists?: (target: string) => boolean, readText?: (target: string) => string): string | null;
89
91
  export declare function extractReleaseNotes(changelog: string, version: string, locale: AppLocale): string[] | null;
90
92
  export {};
package/dist/update.js CHANGED
@@ -13,39 +13,36 @@ export function inferPnpmHomeFromEntryPoint(entryPoint) {
13
13
  const normalizedEntryPoint = entryPoint.replace(/\\/g, '/');
14
14
  const globalMarker = '/global/';
15
15
  const globalIndex = normalizedEntryPoint.indexOf(globalMarker);
16
- if (globalIndex <= 0 || !normalizedEntryPoint.includes('/.pnpm/')) {
16
+ const pnpm10Layout = normalizedEntryPoint.includes('/.pnpm/');
17
+ const pnpm11Layout = /\/global\/v\d+\/[^/]+\/node_modules\/@foxden-app\/foxclaw\//.test(normalizedEntryPoint);
18
+ if (globalIndex <= 0 || (!pnpm10Layout && !pnpm11Layout)) {
17
19
  return null;
18
20
  }
19
21
  return normalizedEntryPoint.slice(0, globalIndex);
20
22
  }
23
+ export function inferPnpmFallbackPackageFromEntryPoint(entryPoint) {
24
+ const normalizedEntryPoint = entryPoint.replace(/\\/g, '/');
25
+ const versionedLayout = normalizedEntryPoint.match(/\/global\/v(\d+)(?:\/|$)/);
26
+ if (versionedLayout?.[1]) {
27
+ return `pnpm@${versionedLayout[1]}`;
28
+ }
29
+ if (/\/global\/\d+(?:\/|$)/.test(normalizedEntryPoint)) {
30
+ return 'pnpm@10';
31
+ }
32
+ return 'pnpm@latest';
33
+ }
21
34
  export function resolveSelfUpdateInstaller(entryPoint, nodePath = process.execPath, exists = fs.existsSync, env = process.env) {
22
35
  const pnpmHome = inferPnpmHomeFromEntryPoint(entryPoint);
23
36
  if (pnpmHome) {
24
- const commandName = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
25
- const candidates = executableCandidates(commandName, nodePath, env, [
26
- path.join(pnpmHome, commandName),
27
- path.join(pnpmHome, 'bin', commandName),
28
- env.PNPM_HOME?.trim() ? path.join(env.PNPM_HOME.trim(), commandName) : '',
29
- env.PNPM_HOME?.trim() ? path.join(env.PNPM_HOME.trim(), 'bin', commandName) : '',
30
- ]);
31
- const pnpmCommand = candidates.find((candidate) => exists(candidate));
32
- if (pnpmCommand) {
33
- return {
34
- manager: 'pnpm',
35
- command: pnpmCommand,
36
- installArgs: ['add', '--global', PACKAGE_SPEC],
37
- rootArgs: ['root', '--global'],
38
- pnpmHome,
39
- };
40
- }
41
37
  const npmCommandName = process.platform === 'win32' ? 'npm.cmd' : 'npm';
42
38
  const npmCommand = executableCandidates(npmCommandName, nodePath, env)
43
39
  .find((candidate) => exists(candidate)) ?? npmCommandName;
40
+ const fallbackPackage = inferPnpmFallbackPackageFromEntryPoint(entryPoint);
44
41
  return {
45
42
  manager: 'pnpm',
46
43
  command: npmCommand,
47
- installArgs: ['exec', '--yes', '--package=pnpm@latest', '--', 'pnpm', 'add', '--global', PACKAGE_SPEC],
48
- rootArgs: ['exec', '--yes', '--package=pnpm@latest', '--', 'pnpm', 'root', '--global'],
44
+ installArgs: ['exec', '--yes', `--package=${fallbackPackage}`, '--', 'pnpm', '--config.minimum-release-age=0', 'add', '--global', PACKAGE_SPEC],
45
+ rootArgs: ['exec', '--yes', `--package=${fallbackPackage}`, '--', 'pnpm', '--config.minimum-release-age=0', 'root', '--global'],
49
46
  pnpmHome,
50
47
  };
51
48
  }
@@ -481,6 +478,29 @@ export function resolveFoxclawEntryPointFromGlobalRoot(globalRoot, exists = fs.e
481
478
  ];
482
479
  return candidates.find(candidate => exists(candidate)) ?? null;
483
480
  }
481
+ export function resolveFoxclawEntryPointFromPnpmHome(pnpmHome, exists = fs.existsSync, readText = (target) => fs.readFileSync(target, 'utf8')) {
482
+ const shimCandidates = [
483
+ path.join(pnpmHome, process.platform === 'win32' ? 'foxclaw.cmd' : 'foxclaw'),
484
+ path.join(pnpmHome, 'bin', process.platform === 'win32' ? 'foxclaw.cmd' : 'foxclaw'),
485
+ ];
486
+ for (const shim of shimCandidates) {
487
+ if (!exists(shim))
488
+ continue;
489
+ let contents = '';
490
+ try {
491
+ contents = readText(shim);
492
+ }
493
+ catch {
494
+ continue;
495
+ }
496
+ const matches = contents.match(/(?:[A-Za-z]:[\\/]|\/)[^\r\n"']*?[\\/]node_modules[\\/]@foxden-app[\\/]foxclaw[\\/]dist[\\/]main\.js/g);
497
+ const entryPoint = matches?.at(-1) ?? null;
498
+ if (entryPoint && exists(entryPoint)) {
499
+ return entryPoint;
500
+ }
501
+ }
502
+ return null;
503
+ }
484
504
  function resolveUpdatedEntryPoint(installer, env) {
485
505
  const result = spawnSync(installer.command, installer.rootArgs, { encoding: 'utf8', env });
486
506
  if (result.error) {
@@ -493,7 +513,10 @@ function resolveUpdatedEntryPoint(installer, env) {
493
513
  if (!globalRoot) {
494
514
  throw new Error(`Could not locate the updated global package root using ${installer.manager}.`);
495
515
  }
496
- const updatedEntryPoint = resolveFoxclawEntryPointFromGlobalRoot(globalRoot);
516
+ const updatedEntryPoint = (installer.pnpmHome
517
+ ? resolveFoxclawEntryPointFromPnpmHome(installer.pnpmHome)
518
+ : null)
519
+ ?? resolveFoxclawEntryPointFromGlobalRoot(globalRoot);
497
520
  if (!updatedEntryPoint) {
498
521
  throw new Error(`Updated FoxClaw entry point was not found below ${globalRoot} (checked direct and node_modules layouts).`);
499
522
  }
@@ -113,6 +113,8 @@ For more peers, separate bot usernames with commas:
113
113
  AUTH_SYNC_PEERS=@foxclaw_node_a_bot,@foxclaw_node_b_bot,@foxclaw_node_c_bot
114
114
  ```
115
115
 
116
+ Auth imported from a remote peer is not automatically forwarded to other peers, which prevents synchronization loops. A star topology therefore guarantees only direct leaf-to-hub synchronization. If auth newly produced on any leaf must reach every node, every contact bot must list all other contact bots in `AUTH_SYNC_PEERS`, with matching reciprocal allowlists on those peers.
117
+
116
118
  Generate a shared key with a password manager or `openssl`:
117
119
 
118
120
  ```bash
@@ -113,6 +113,8 @@ AUTH_AUTO_DELETE_NEEDS_REPAIR=false
113
113
  AUTH_SYNC_PEERS=@foxclaw_node_a_bot,@foxclaw_node_b_bot,@foxclaw_node_c_bot
114
114
  ```
115
115
 
116
+ 远端导入的 auth 不会再次自动转发给本节点的其他 peer,以避免同步环路。因此星形拓扑只保证每个叶子与中心节点直接互通;如果任意叶子节点产生的新 auth 都要到达所有节点,每个联系人 bot 都必须把其他联系人 bot 列入 `AUTH_SYNC_PEERS`,并在对端做对称 allowlist 配置。
117
+
116
118
  建议用密码管理器或 `openssl` 生成共享密钥:
117
119
 
118
120
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxden-app/foxclaw",
3
- "version": "0.5.70",
3
+ "version": "0.5.72",
4
4
  "description": "Foxden local execution claw for controlling Codex from trusted chat interfaces.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",