@fanchao8609/agent_brain_sync 1.28.1 → 1.28.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/abs.js CHANGED
@@ -64,10 +64,17 @@ async function cmdUpdate({ yes }) {
64
64
  const cmp = cmpVersion(cur, latest);
65
65
  console.log(`当前: ${cur}`);
66
66
  console.log(`最新: ${latest}`);
67
+
67
68
  if (cmp === 0) {
68
- console.log('已是最新,无需升级。');
69
+ // ★ 版本对不代表宿主模板对(2026-11-07 实报)。
70
+ // 现场:以前被双副本的旧代码刷过宿主,版本号已被 npm 推上去,但 ~/.pi/
71
+ // 里仍是旧模板 —— 此时报「已是最新」然后什么都不做,用户以为升级完了,
72
+ // 界面却永远不变。故照样刷新一遍(install 幂等,无副作用)。
73
+ console.log('已是最新,仍刷新一次宿主模板(幂等)。');
74
+ await refreshHosts(yes);
69
75
  return;
70
76
  }
77
+
71
78
  // 本地比 registry 新 —— 典型场景:开发中从源码装过全局,或在改一个未发布的版本。
72
79
  // 此时不能走 npm i -g @latest:那会把本地版**降级**覆盖掉(2026-10-06 实际发生:
73
80
  // 源码 1.16.2 被拉回 registry 的 1.16.1)。只比较 `===` 而没有方向判断,就是这个坑。
@@ -77,6 +84,7 @@ async function cmdUpdate({ yes }) {
77
84
  console.log(' 想把它正式发布: npm publish(在源码目录,发布后 update 即恢复正常)');
78
85
  return;
79
86
  }
87
+
80
88
  console.log(`\n升级 ${cur} → ${latest} …`);
81
89
  const r = await runCmd('npm', ['i', '-g', '@fanchao8609/agent_brain_sync@latest']);
82
90
  if (r === null) {
@@ -84,36 +92,48 @@ async function cmdUpdate({ yes }) {
84
92
  process.exit(1);
85
93
  }
86
94
  console.log(r);
87
- // ★ 重装宿主必须交给**刚装好的那份新代码**去做,不能用本进程(2026-11-07 实报)。
88
- //
89
- // 病根:本进程是旧版 bin/abs.js,通过 import 加载的也是**旧版 src/install.js**。
90
- // 旧 install.js 里的 stableBinPath() 用 process.execPath 推导全局包路径,
91
- // 于是 hook 模板里的 @@ABS_BIN@@ 被烧成**旧副本**的路径,模板快照也是旧的。
92
- // 结果:npm 已升到新版(npm ls -g 显示 1.28.0),但 ~/.pi/ 里写的还是旧模板,
93
- // 界面一点没变,而且 hook 从此指向旧副本 —— 下一版继续错。
94
- // 实测(fxh-mb):/usr/bin/abs -> /usr/lib/...(1.26.0,PATH 优先)
95
- // 与 /opt/node.../bin/abs -> /opt/...(1.28.0,npm 的 prefix,不在 PATH)。
96
- //
97
- // 修法:升级后用 `npm prefix -g` 算出新包的真实路径,把「刷新宿主」转交给
98
- // 那个新包的 bin/abs.js 执行(子进程)。它算 stableBinPath 时会指向自己。
99
- // 找不到新包才退回本进程的 runInstall(至少不比原来差)。
95
+ await refreshHosts(yes);
96
+ }
97
+
98
+ /**
99
+ * 把宿主 hook/skill/编辑器刷新一遍。
100
+ *
101
+ * ★ 必须交给**刚装好的那份新代码**去做,不能用本进程(2026-11-07 实报)。
102
+ *
103
+ * 病根:本进程是旧版 bin/abs.js,通过 import 加载的也是**旧版 src/install.js**。
104
+ * 旧 install.js 的 stableBinPath() 用 process.execPath 推导全局包路径,于是
105
+ * hook 模板里的 @@ABS_BIN@@ 被烧成**旧副本**的路径,模板快照也是旧的。
106
+ * 结果:npm 已升到新版(npm ls -g 显示 1.28.0),但 ~/.pi/ 里写的还是旧模板,
107
+ * 界面一点没变,而且 hook 从此指向旧副本 —— 下一版继续错。
108
+ * 实测(fxh-mb):/usr/bin/abs → /usr/lib/...(1.26.0,PATH 优先)
109
+ * 与 /opt/node.../bin/abs → /opt/...(1.28.0,npm 的 prefix,不在 PATH)。
110
+ *
111
+ * 修法:用 `npm prefix -g` 算出新包的真实路径,把刷新转交给那个新包的
112
+ * bin/abs.js 执行(子进程)。它算 stableBinPath 时会指向自己。
113
+ * 找不到新包才退回本进程的 runInstall(至少不比原来差)。
114
+ */
115
+ async function refreshHosts(yes) {
100
116
  const freshEntry = await freshInstallEntry();
101
117
  console.log('\n刷新四宿主 hook/skill(hook 脚本烧的是绝对路径 + 模板快照,升级后必须重装)…');
102
- if (freshEntry) {
103
- if (freshEntry !== resolveBinPath()) console.log(` 用新装的副本执行: ${freshEntry}`);
104
- // --all:update 的语义是「把升好的版本铺到所有已装宿主」,不能二次弹窗重选。
105
- // 旧代码调 runInstall({agent: undefined}) 在 TTY 下会弹菜单,回车只装 pi ——
106
- // 另装过 omp / Claude Code 的机器升级后那几个宿主永远不刷新(2026-11-07 实报)。
107
- const args = ['install', '--all'];
108
- if (yes) args.push('--yes');
109
- const code = await runInherit(process.execPath, [freshEntry, ...args]);
110
- if (code !== 0) {
111
- console.error(`\n✗ 刷新宿主失败(退出码 ${code})。手动重跑:node ${freshEntry} install --all`);
112
- process.exit(1);
113
- }
114
- } else {
118
+ if (!freshEntry) {
115
119
  console.log(' ⚠ 未定位到新安装的副本,用当前进程刷新(可能仍是旧模板)');
116
120
  await runInstall({ agent: undefined, mcp: true, skill: true, yes: yes !== false });
121
+ console.log('\n完成。重启宿主(pi / omp / opencode / claude-code / codex)后新 hook 生效。');
122
+ return;
123
+ }
124
+ if (freshEntry !== resolveBinPath()) console.log(` 用新装的副本执行: ${freshEntry}`);
125
+ // 宿主选择交给新副本的交互菜单:**默认勾 pi,其余多选**(2026-11-07 用户定)。
126
+ // 不传 --yes —— 传了等于「不问、只装 pi」,另装过 omp / Claude Code 的机器
127
+ // 那几个宿主升级后永远不刷新(实报)。
128
+ // 也不用 --all —— 那会往没在用的宿主配置里写条目,与「默认只装 pi、
129
+ // 其余 opt-in」的既定策略相反。
130
+ const args = ['install'];
131
+ // 已是非交互场景(update --yes、脚本、CI)时不模拟菜单,交给 pickAgents 走默认 pi。
132
+ if (yes) args.push('--yes');
133
+ const code = await runInherit(process.execPath, [freshEntry, ...args]);
134
+ if (code !== 0) {
135
+ console.error(`\n✗ 刷新宿主失败(退出码 ${code})。手动重跑:node ${freshEntry} install`);
136
+ process.exit(1);
117
137
  }
118
138
  console.log('\n完成。重启宿主(pi / omp / opencode / claude-code / codex)后新 hook 生效。');
119
139
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanchao8609/agent_brain_sync",
3
- "version": "1.28.1",
3
+ "version": "1.28.3",
4
4
  "description": "agent-brain-sync: 跨会话 AI 编码记忆 — hook 纯触发 + CLI/MCP 读写 .brain markdown 图谱, 防并发写保护。",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/install.js CHANGED
@@ -5,6 +5,7 @@
5
5
  // - Codex: ~/.codex/hooks.json。
6
6
  // - OpenCode / Pi: 官方只吃 TS plugin/extension(无 shell-hook 配置) → 初版给出手工指引。
7
7
  // 纪律: 幂等(重复安装=更新)、原子写(tmp+rename)、卸载只删自己装的、写入前备份。
8
+ import { execFileSync } from 'node:child_process';
8
9
  import { promises as fs } from 'node:fs';
9
10
  import * as fsSync from 'node:fs';
10
11
  import { homedir } from 'node:os';
@@ -475,6 +476,62 @@ export function staleBinWarning(binOverride) {
475
476
  ].join('\n');
476
477
  }
477
478
 
479
+ /**
480
+ * 双副本检测:本机是否还装着**另一份**本包?
481
+ *
482
+ * ★ 为何需要(2026-11-07 实报):远程机器有两份全局安装 ——
483
+ * /usr/bin/abs → /usr/lib/node_modules/...(旧)
484
+ * npm prefix → /opt/node.../lib/node_modules/...(新)
485
+ * PATH 里 `/usr/bin` 在前,于是 `abs update` 跑的是旧副本;它用自己那份旧
486
+ * install.js 刷宿主,把旧模板和旧 bin 路径写进 ~/.pi/,界面一点不变。
487
+ *
488
+ * 这里只做**检测与报告**,不自动改 PATH —— 改用户 shell 配置是越权动作。
489
+ * 正确修法在 bin/abs.js 的 cmdUpdate 里(把刷新动作转交给 npm prefix 下的新副本);
490
+ * 本函数负责让「装完之后仍然指错」这件事在输出里可见,而不是静默。
491
+ *
492
+ * @returns {string|null} 有另一份时返回提示,否则 null。
493
+ */
494
+ export function duplicateInstallWarning() {
495
+ let pkgName = '';
496
+ try {
497
+ pkgName = JSON.parse(fsSync.readFileSync(join(ABS_DIR, 'package.json'), 'utf8')).name || '';
498
+ } catch { return null; }
499
+ if (!pkgName) return null;
500
+
501
+ // ★ 只关心「两份都是全局安装」的情形。
502
+ // 从开发仓库直接跑 install(self 不在 node_modules 下)是正常的开发流程,
503
+ // 此时与全局那份版本不同是预期的 —— 那不是双副本故障,报出来只会是噪声
504
+ // (2026-11-07 自测时先误报了一次)。
505
+ if (!/[\\/]node_modules[\\/]/.test(ABS_DIR)) return null;
506
+
507
+ let npmRoot;
508
+ try {
509
+ npmRoot = execFileSync('npm', ['prefix', '-g'], { encoding: 'utf8' }).trim();
510
+ } catch { return null; }
511
+ if (!npmRoot) return null;
512
+
513
+ // npm prefix 下的那份(「正确」的那份)与本进程所在那份(可能正是旧副本)
514
+ const correct = [join(npmRoot, 'lib', 'node_modules', pkgName), join(npmRoot, 'node_modules', pkgName)]
515
+ .find((x) => fsSync.existsSync(x));
516
+ if (!correct) return null;
517
+ const self = ABS_DIR;
518
+ try {
519
+ if (fsSync.realpathSync(correct) === fsSync.realpathSync(self)) return null; // 同一份,正常
520
+ } catch { return null; }
521
+
522
+ let correctVer = '?', selfVer = '?';
523
+ try { correctVer = JSON.parse(fsSync.readFileSync(join(correct, 'package.json'), 'utf8')).version || '?'; } catch {}
524
+ try { selfVer = JSON.parse(fsSync.readFileSync(join(self, 'package.json'), 'utf8')).version || '?'; } catch {}
525
+
526
+ return [
527
+ '⚠ 本机存在两份 abs 全局安装,本次安装用的是:',
528
+ ` ${self} (v${selfVer})`,
529
+ ' npm 装的那份在:',
530
+ ` ${correct} (v${correctVer})`,
531
+ ` 修法: 用新那份重跑 —— node ${join(correct, 'bin', 'abs.js')} install --all`,
532
+ ].join('\n');
533
+ }
534
+
478
535
  function agentsSkillWarning() {
479
536
  const p = join(homedir(), '.agents', 'skills', 'abs-agent-brain-sync', 'SKILL.md');
480
537
  if (!fsSync.existsSync(p)) return null;
@@ -960,7 +1017,12 @@ const INSTALLERS = {
960
1017
  };
961
1018
 
962
1019
  export function installSummary() {
963
- return HOSTS.map((h) => ` ${h.key.padEnd(12)} ${h.label}`).join('\n');
1020
+ // 标出默认项,否则用户看不出「回车 = 只装 pi」到底装哪个(2026-11-07 用户要求:
1021
+ // 默认勾 pi,其余多选)。
1022
+ return HOSTS.map((h) => {
1023
+ const mark = h.key === DEFAULT_AGENT ? ' ← 默认(回车只装这个,要多个请用逗号列出)' : '';
1024
+ return ` ${h.key.padEnd(12)} ${h.label}${mark}`;
1025
+ }).join('\n');
964
1026
  }
965
1027
 
966
1028
  export async function runInstall({ agent, all = false, mcp = true, skill = true, yes = false } = {}) {
@@ -1005,6 +1067,8 @@ export async function runInstall({ agent, all = false, mcp = true, skill = true,
1005
1067
  }
1006
1068
  const stale = staleBinWarning();
1007
1069
  if (stale) console.log('\n' + stale);
1070
+ const dup = duplicateInstallWarning();
1071
+ if (dup) console.log('\n' + dup);
1008
1072
 
1009
1073
  console.log('\n完成。项目内运行 abs init 建图谱; 会话里说 "abs load" 续接。');
1010
1074
  }