@foxden-app/foxclaw 0.5.71 → 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 +1 -0
- package/CHANGELOG.md +10 -0
- package/dist/update.d.ts +1 -0
- package/dist/update.js +32 -21
- package/docs/cross-node-auth-sync.md +2 -0
- package/docs/zh/cross-node-auth-sync.md +2 -0
- package/package.json +1 -1
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,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.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
|
+
|
|
5
15
|
## 0.5.71 - 2026-07-11
|
|
6
16
|
|
|
7
17
|
### 中文
|
package/dist/update.d.ts
CHANGED
|
@@ -87,5 +87,6 @@ export declare function readPendingClusterUpdateBroadcast(filePath: string): Pen
|
|
|
87
87
|
export declare function writePendingClusterUpdateBroadcast(filePath: string, value: PendingClusterUpdateBroadcast): void;
|
|
88
88
|
export declare function clearPendingClusterUpdateBroadcast(filePath: string): void;
|
|
89
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;
|
|
90
91
|
export declare function extractReleaseNotes(changelog: string, version: string, locale: AppLocale): string[] | null;
|
|
91
92
|
export {};
|
package/dist/update.js
CHANGED
|
@@ -13,7 +13,9 @@ export function inferPnpmHomeFromEntryPoint(entryPoint) {
|
|
|
13
13
|
const normalizedEntryPoint = entryPoint.replace(/\\/g, '/');
|
|
14
14
|
const globalMarker = '/global/';
|
|
15
15
|
const globalIndex = normalizedEntryPoint.indexOf(globalMarker);
|
|
16
|
-
|
|
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);
|
|
@@ -32,23 +34,6 @@ export function inferPnpmFallbackPackageFromEntryPoint(entryPoint) {
|
|
|
32
34
|
export function resolveSelfUpdateInstaller(entryPoint, nodePath = process.execPath, exists = fs.existsSync, env = process.env) {
|
|
33
35
|
const pnpmHome = inferPnpmHomeFromEntryPoint(entryPoint);
|
|
34
36
|
if (pnpmHome) {
|
|
35
|
-
const commandName = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
|
|
36
|
-
const candidates = executableCandidates(commandName, nodePath, env, [
|
|
37
|
-
path.join(pnpmHome, commandName),
|
|
38
|
-
path.join(pnpmHome, 'bin', commandName),
|
|
39
|
-
env.PNPM_HOME?.trim() ? path.join(env.PNPM_HOME.trim(), commandName) : '',
|
|
40
|
-
env.PNPM_HOME?.trim() ? path.join(env.PNPM_HOME.trim(), 'bin', commandName) : '',
|
|
41
|
-
]);
|
|
42
|
-
const pnpmCommand = candidates.find((candidate) => exists(candidate));
|
|
43
|
-
if (pnpmCommand) {
|
|
44
|
-
return {
|
|
45
|
-
manager: 'pnpm',
|
|
46
|
-
command: pnpmCommand,
|
|
47
|
-
installArgs: ['add', '--global', PACKAGE_SPEC],
|
|
48
|
-
rootArgs: ['root', '--global'],
|
|
49
|
-
pnpmHome,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
37
|
const npmCommandName = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
53
38
|
const npmCommand = executableCandidates(npmCommandName, nodePath, env)
|
|
54
39
|
.find((candidate) => exists(candidate)) ?? npmCommandName;
|
|
@@ -56,8 +41,8 @@ export function resolveSelfUpdateInstaller(entryPoint, nodePath = process.execPa
|
|
|
56
41
|
return {
|
|
57
42
|
manager: 'pnpm',
|
|
58
43
|
command: npmCommand,
|
|
59
|
-
installArgs: ['exec', '--yes', `--package=${fallbackPackage}`, '--', 'pnpm', 'add', '--global', PACKAGE_SPEC],
|
|
60
|
-
rootArgs: ['exec', '--yes', `--package=${fallbackPackage}`, '--', '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'],
|
|
61
46
|
pnpmHome,
|
|
62
47
|
};
|
|
63
48
|
}
|
|
@@ -493,6 +478,29 @@ export function resolveFoxclawEntryPointFromGlobalRoot(globalRoot, exists = fs.e
|
|
|
493
478
|
];
|
|
494
479
|
return candidates.find(candidate => exists(candidate)) ?? null;
|
|
495
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
|
+
}
|
|
496
504
|
function resolveUpdatedEntryPoint(installer, env) {
|
|
497
505
|
const result = spawnSync(installer.command, installer.rootArgs, { encoding: 'utf8', env });
|
|
498
506
|
if (result.error) {
|
|
@@ -505,7 +513,10 @@ function resolveUpdatedEntryPoint(installer, env) {
|
|
|
505
513
|
if (!globalRoot) {
|
|
506
514
|
throw new Error(`Could not locate the updated global package root using ${installer.manager}.`);
|
|
507
515
|
}
|
|
508
|
-
const updatedEntryPoint =
|
|
516
|
+
const updatedEntryPoint = (installer.pnpmHome
|
|
517
|
+
? resolveFoxclawEntryPointFromPnpmHome(installer.pnpmHome)
|
|
518
|
+
: null)
|
|
519
|
+
?? resolveFoxclawEntryPointFromGlobalRoot(globalRoot);
|
|
509
520
|
if (!updatedEntryPoint) {
|
|
510
521
|
throw new Error(`Updated FoxClaw entry point was not found below ${globalRoot} (checked direct and node_modules layouts).`);
|
|
511
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
|