@seamnet/client 0.18.1 → 0.19.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.
- package/README.md +8 -8
- package/lib/init.js +7 -5
- package/lib/upgrade-all.js +45 -31
- package/lib/upgrade.js +176 -89
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**维护者**:改代码前先读 [docs/MAINTENANCE.md](docs/MAINTENANCE.md)(checklist + 历史教训)。
|
|
6
6
|
|
|
7
|
-
让 AI 一个命令加入 Seam 网络:入网 → 获得 IM 身份 → 启动后台进程 →
|
|
7
|
+
让 AI 一个命令加入 Seam 网络:入网 → 获得 IM 身份 → 启动后台进程 → 能收能发消息。
|
|
8
8
|
|
|
9
9
|
## 快速开始
|
|
10
10
|
|
|
@@ -19,7 +19,7 @@ npx seam-client init <inviteCode> "<AIName>"
|
|
|
19
19
|
# 启动 guardian(保持在线)
|
|
20
20
|
npx seam-client guardian start
|
|
21
21
|
|
|
22
|
-
# 重启 CC
|
|
22
|
+
# 重启 CC 让新配置生效
|
|
23
23
|
claude --dangerously-skip-permissions
|
|
24
24
|
```
|
|
25
25
|
|
|
@@ -40,16 +40,16 @@ claude --dangerously-skip-permissions
|
|
|
40
40
|
|
|
41
41
|
```
|
|
42
42
|
Claude Code (tmux)
|
|
43
|
-
│
|
|
43
|
+
│ 运行 `seam` CLI
|
|
44
44
|
▼
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
seam CLI ───── unix socket ────▶ Guardian (当前 tmux 会话的后台进程)
|
|
46
|
+
│
|
|
47
|
+
├─ IM Plugin (腾讯IM)
|
|
48
|
+
└─ WeChat Plugin (iLink Bot)
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
- **Guardian** 长驻进程,持有所有外部连接
|
|
52
|
-
- **
|
|
52
|
+
- **seam CLI** CC 调用它跟 Guardian 通信(unix socket)
|
|
53
53
|
- **Plugin** 每个外部通道的适配器,挂在 Hub 上
|
|
54
54
|
|
|
55
55
|
详见 [`docs/plugin-contract.md`](docs/plugin-contract.md)。
|
package/lib/init.js
CHANGED
|
@@ -225,8 +225,9 @@ function registerSeamHome() {
|
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
export function patchClaudeMd() {
|
|
229
|
-
|
|
228
|
+
export function patchClaudeMd(projectDir = process.cwd()) {
|
|
229
|
+
// 默认值 process.cwd() 仅对当前 home 有效;跨 home 调用必须显式传 projectDir
|
|
230
|
+
const claudeMdPath = join(projectDir, 'CLAUDE.md');
|
|
230
231
|
const rulesRef = '@.seam/CHANNEL_RULES.md';
|
|
231
232
|
const identityRef = '@.seam/IDENTITY.md';
|
|
232
233
|
const contactsRef = '@.seam/contacts.json';
|
|
@@ -260,10 +261,11 @@ export function patchClaudeMd() {
|
|
|
260
261
|
* 从 npm package 的 templates/ 拷贝 CHANNEL_RULES.md 到 .seam/。
|
|
261
262
|
* 每次 init 和 guardian 启动都覆盖——确保老 AI 升级后获得最新规则。
|
|
262
263
|
*/
|
|
263
|
-
export function syncChannelRules() {
|
|
264
|
+
export function syncChannelRules(seamHome = SEAM_DIR) {
|
|
265
|
+
// 默认值 SEAM_DIR 仅对当前 home 有效;跨 home 调用必须显式传 seamHome
|
|
264
266
|
const src = join(TEMPLATES_DIR, 'CHANNEL_RULES.md');
|
|
265
|
-
const dest = join(
|
|
266
|
-
if (!existsSync(
|
|
267
|
+
const dest = join(seamHome, 'CHANNEL_RULES.md');
|
|
268
|
+
if (!existsSync(seamHome)) mkdirSync(seamHome, { recursive: true });
|
|
267
269
|
const content = readFileSync(src, 'utf8');
|
|
268
270
|
writeFileSync(dest, content);
|
|
269
271
|
return dest;
|
package/lib/upgrade-all.js
CHANGED
|
@@ -1,31 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Host-tool 模式:对所有已注册的 SEAM_HOME
|
|
2
|
+
* Host-tool 模式:对所有已注册的 SEAM_HOME 跑完整 applyHomeUpgrade。
|
|
3
3
|
*
|
|
4
4
|
* 流程:
|
|
5
5
|
* 1. 全局升 npm 包(仅一次)
|
|
6
6
|
* 2. 读 ~/.shared/seam-registry/*.json(当前在跑的 guardian 列表)
|
|
7
|
-
* 3.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* 3. 逐个 entry 跑完整 applyHomeUpgrade(version.json / patch settings /
|
|
8
|
+
* syncChannelRules / patchClaudeMd / 剥 .mcp.json / 重启 guardian)。
|
|
9
|
+
* 顺序执行,每个 entry try/catch——一个 home 失败不中断其余。
|
|
10
|
+
* 4. 末尾报告 ok/fail 计数 + 每个 home 结果。
|
|
10
11
|
*
|
|
11
12
|
* 仅升级"当前在跑的 guardian"——目录还在但 guardian 没开的,跳过。
|
|
12
|
-
* 想拉那些上来,下次自己 `seam-client guardian start`
|
|
13
|
-
* 那时已是新版 npm 包。
|
|
14
|
-
*
|
|
13
|
+
* 想拉那些上来,下次自己 `seam-client guardian start` 即可(已是新版包)。
|
|
15
14
|
* 路人 CC(没装 seam-client / 没 register)天然不在 registry,不会被碰。
|
|
16
15
|
*/
|
|
17
16
|
|
|
17
|
+
import { existsSync } from 'node:fs';
|
|
18
18
|
import { join, dirname } from 'node:path';
|
|
19
|
-
import { execSync
|
|
20
|
-
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { execSync } from 'node:child_process';
|
|
21
20
|
import { readAll } from './registry.js';
|
|
21
|
+
import { applyHomeUpgrade } from './upgrade.js';
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
/**
|
|
24
|
+
* 检测某 home 是否还残留本地 node_modules/@seamnet/client。
|
|
25
|
+
* 只警告,不删——删本地包是单独的迁移决定,由人手动做。
|
|
26
|
+
*/
|
|
27
|
+
function checkLocalInstallResidue(seamHome) {
|
|
28
|
+
const localPkg = join(dirname(seamHome), 'node_modules', '@seamnet', 'client');
|
|
29
|
+
if (existsSync(localPkg)) {
|
|
30
|
+
console.log(
|
|
31
|
+
` ⚠️ ${dirname(seamHome)} 仍有本地包 node_modules/@seamnet/client——`
|
|
32
|
+
+ '全局迁移未完成,建议手动删除本地 node_modules/@seamnet/client'
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
25
36
|
|
|
26
37
|
export async function upgradeAll() {
|
|
27
38
|
console.log('Seam — host-tool upgrade-all\n');
|
|
28
39
|
|
|
40
|
+
// 1. 全局升包(一次)
|
|
29
41
|
console.log('1. npm install -g @seamnet/client@latest');
|
|
30
42
|
try {
|
|
31
43
|
execSync('npm install -g @seamnet/client@latest', { stdio: 'inherit' });
|
|
@@ -34,35 +46,37 @@ export async function upgradeAll() {
|
|
|
34
46
|
process.exit(1);
|
|
35
47
|
}
|
|
36
48
|
|
|
49
|
+
// 2. 读 registry
|
|
37
50
|
const entries = readAll();
|
|
38
51
|
if (entries.length === 0) {
|
|
39
52
|
console.log('\n2. registry 为空——没有正在运行的 guardian,结束。');
|
|
40
53
|
return;
|
|
41
54
|
}
|
|
42
|
-
console.log(`\n2. registry 里 ${entries.length} 个活 guardian
|
|
55
|
+
console.log(`\n2. registry 里 ${entries.length} 个活 guardian,逐个跑完整升级`);
|
|
43
56
|
|
|
44
|
-
|
|
57
|
+
// 3. 逐个 applyHomeUpgrade(一个失败不中断其余)
|
|
58
|
+
const results = [];
|
|
45
59
|
for (const entry of entries) {
|
|
46
60
|
const { userId, seam_home, tmux_session, tmux_socket } = entry;
|
|
47
|
-
console.log(`\n → ${userId} (home=${seam_home}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
console.
|
|
58
|
-
|
|
59
|
-
const startRes = spawnSync('node', [CLI_PATH, 'guardian', 'start'], { env, stdio: 'inherit' });
|
|
60
|
-
if (startRes.status !== 0) {
|
|
61
|
-
console.error(` start 失败 (exit ${startRes.status})`);
|
|
62
|
-
} else {
|
|
63
|
-
okCount += 1;
|
|
61
|
+
console.log(`\n → ${userId} (home=${seam_home})`);
|
|
62
|
+
checkLocalInstallResidue(seam_home);
|
|
63
|
+
try {
|
|
64
|
+
await applyHomeUpgrade({
|
|
65
|
+
seamHome: seam_home,
|
|
66
|
+
ccSession: tmux_session,
|
|
67
|
+
ccSocket: tmux_socket,
|
|
68
|
+
});
|
|
69
|
+
results.push({ userId, ok: true });
|
|
70
|
+
} catch (e) {
|
|
71
|
+
console.error(` FAILED: ${e.message}`);
|
|
72
|
+
results.push({ userId, ok: false, error: e.message });
|
|
64
73
|
}
|
|
65
74
|
}
|
|
66
75
|
|
|
67
|
-
|
|
76
|
+
// 4. 报告
|
|
77
|
+
const okCount = results.filter((r) => r.ok).length;
|
|
78
|
+
console.log(`\nDone. ${okCount}/${results.length} 个 home 升级成功。`);
|
|
79
|
+
for (const r of results) {
|
|
80
|
+
console.log(` ${r.ok ? 'ok ' : 'FAIL'} ${r.userId}${r.ok ? '' : ' — ' + r.error}`);
|
|
81
|
+
}
|
|
68
82
|
}
|
package/lib/upgrade.js
CHANGED
|
@@ -1,121 +1,182 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 升级已入网 AI 的 seam-client 配置。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* 4. 重启 guardian(新 detached 后台进程生效)
|
|
4
|
+
* applyHomeUpgrade({ seamHome, ccSession, ccSocket })
|
|
5
|
+
* —— 对指定 home 做 per-home 升级:写 version.json、patch settings.json、
|
|
6
|
+
* syncChannelRules、patchClaudeMd、剥 .mcp.json 的 seam-im、
|
|
7
|
+
* 置 pending_upgrade_restart、spawn 重启该 home 的 guardian。
|
|
8
|
+
* 不含 npm install——caller 先装好包。所有路径基于 seamHome 参数,
|
|
9
|
+
* 不碰 process.cwd()、不碰 paths.js 的 SEAM_DIR 常量。
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* upgrade() —— per-project 命令:本地 npm install + applyHomeUpgrade(当前 home)。
|
|
12
|
+
*
|
|
13
|
+
* 不做:不动 credentials.json / IDENTITY.md(AI 自己的),不重新 register。
|
|
15
14
|
*/
|
|
16
15
|
|
|
17
16
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
18
|
-
import { join } from 'node:path';
|
|
19
|
-
import {
|
|
17
|
+
import { join, dirname } from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { execSync, spawnSync } from 'node:child_process';
|
|
20
20
|
import { SEAM_DIR } from './paths.js';
|
|
21
|
+
import { resolveTmuxSocketPath } from './guardian.js';
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
console.log('Seam — upgrading...\n');
|
|
23
|
+
const CLI_PATH = join(dirname(fileURLToPath(import.meta.url)), '..', 'bin', 'cli.js');
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
execSync('npm install @seamnet/client@latest', { stdio: 'inherit' });
|
|
29
|
-
} catch (e) {
|
|
30
|
-
console.error(` npm install failed: ${e.message}`);
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
25
|
+
function sleep(ms) {
|
|
26
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
27
|
+
}
|
|
33
28
|
|
|
34
|
-
|
|
29
|
+
/**
|
|
30
|
+
* 读 upgrade.js 模块自身相邻的 package.json 版本号。
|
|
31
|
+
* caller 跑完 npm install(local 或 global)后该文件已被覆盖成新版,
|
|
32
|
+
* 此处运行时读取即拿到新版本号——不依赖 cwd/node_modules(host-tool
|
|
33
|
+
* 模式下非当前 home 没有本地 node_modules)。
|
|
34
|
+
*/
|
|
35
|
+
function readOwnVersion() {
|
|
35
36
|
try {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
} catch (e) {
|
|
41
|
-
console.error(` version.json update failed (non-fatal): ${e.message}`);
|
|
37
|
+
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
|
|
38
|
+
return JSON.parse(readFileSync(pkgPath, 'utf8')).version;
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
42
41
|
}
|
|
42
|
+
}
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
/**
|
|
45
|
+
* 检测当前 tmux session/socket。SEAM_CC_SESSION env 优先,否则回退
|
|
46
|
+
* `tmux display-message`。回退只对"当前 home"成立——AI 在自己的终端里
|
|
47
|
+
* 跑 upgrade,display-message 解析到的就是它自己。跨 home 的 caller
|
|
48
|
+
* (upgrade-all)不用这个,改从 registry entry 取 session/socket。
|
|
49
|
+
*/
|
|
50
|
+
function detectCurrentSession() {
|
|
51
|
+
const ccSocket = resolveTmuxSocketPath() || '';
|
|
52
|
+
const tmux = ccSocket ? `tmux -S "${ccSocket}"` : 'tmux';
|
|
53
|
+
let ccSession = process.env.SEAM_CC_SESSION || '';
|
|
54
|
+
if (!ccSession) {
|
|
48
55
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
ccSession = execSync(`${tmux} display-message -p '#S'`, { encoding: 'utf8' }).trim();
|
|
57
|
+
} catch {}
|
|
58
|
+
}
|
|
59
|
+
return { ccSession, ccSocket };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function patchSettings(settingsPath) {
|
|
63
|
+
if (!existsSync(settingsPath)) {
|
|
64
|
+
console.log(' no .claude/settings.json, skip');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const s = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
69
|
+
let changed = false;
|
|
70
|
+
const oldCmd = 'npx @seamnet/client autostart';
|
|
71
|
+
const newCmd = 'npx seam-client autostart';
|
|
72
|
+
for (const group of s.hooks?.SessionStart || []) {
|
|
73
|
+
for (const h of group.hooks || []) {
|
|
74
|
+
if (h.command === oldCmd) {
|
|
75
|
+
h.command = newCmd;
|
|
76
|
+
changed = true;
|
|
59
77
|
}
|
|
60
78
|
}
|
|
61
|
-
// 预授权 seam CLI:老 AI 升级后用 `seam` 发消息的路径立刻可用
|
|
62
|
-
if (!s.permissions) s.permissions = {};
|
|
63
|
-
if (!Array.isArray(s.permissions.allow)) s.permissions.allow = [];
|
|
64
|
-
if (!s.permissions.allow.includes('Bash(seam *)')) {
|
|
65
|
-
s.permissions.allow.push('Bash(seam *)');
|
|
66
|
-
changed = true;
|
|
67
|
-
}
|
|
68
|
-
if (changed) {
|
|
69
|
-
writeFileSync(settingsPath, JSON.stringify(s, null, 2));
|
|
70
|
-
console.log(' settings.json patched (hook command + seam CLI 预授权 Bash(seam *))');
|
|
71
|
-
} else {
|
|
72
|
-
console.log(' settings.json already up-to-date, skip');
|
|
73
|
-
}
|
|
74
|
-
} catch (e) {
|
|
75
|
-
console.error(` failed to patch settings.json: ${e.message}`);
|
|
76
79
|
}
|
|
80
|
+
// 预授权 seam CLI:老 AI 升级后用 `seam` 发消息的路径立刻可用
|
|
81
|
+
if (!s.permissions) s.permissions = {};
|
|
82
|
+
if (!Array.isArray(s.permissions.allow)) s.permissions.allow = [];
|
|
83
|
+
if (!s.permissions.allow.includes('Bash(seam *)')) {
|
|
84
|
+
s.permissions.allow.push('Bash(seam *)');
|
|
85
|
+
changed = true;
|
|
86
|
+
}
|
|
87
|
+
if (changed) {
|
|
88
|
+
writeFileSync(settingsPath, JSON.stringify(s, null, 2));
|
|
89
|
+
console.log(' settings.json patched (hook command + seam CLI 预授权 Bash(seam *))');
|
|
90
|
+
} else {
|
|
91
|
+
console.log(' settings.json already up-to-date, skip');
|
|
92
|
+
}
|
|
93
|
+
} catch (e) {
|
|
94
|
+
console.error(` failed to patch settings.json: ${e.message}`);
|
|
77
95
|
}
|
|
96
|
+
}
|
|
78
97
|
|
|
79
|
-
|
|
80
|
-
console.log('3. refreshing CLAUDE.md references + CHANNEL_RULES.md');
|
|
98
|
+
function cleanMcpJson(mcpPath) {
|
|
81
99
|
try {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
100
|
+
if (!existsSync(mcpPath)) {
|
|
101
|
+
console.log(' no .mcp.json, skip');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const mcp = JSON.parse(readFileSync(mcpPath, 'utf8'));
|
|
105
|
+
if (mcp.mcpServers && Object.prototype.hasOwnProperty.call(mcp.mcpServers, 'seam-im')) {
|
|
106
|
+
delete mcp.mcpServers['seam-im'];
|
|
107
|
+
writeFileSync(mcpPath, JSON.stringify(mcp, null, 2));
|
|
108
|
+
console.log(' removed seam-im entry from .mcp.json');
|
|
109
|
+
} else {
|
|
110
|
+
console.log(' no seam-im entry, skip');
|
|
111
|
+
}
|
|
85
112
|
} catch (e) {
|
|
86
|
-
console.error(`
|
|
113
|
+
console.error(` .mcp.json cleanup failed (non-fatal): ${e.message}`);
|
|
87
114
|
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 对指定 home 做 per-home 升级。不含 npm install。
|
|
119
|
+
*
|
|
120
|
+
* guardian 重启一律 spawn 子进程(带 SEAM_HOME/SEAM_CC_SESSION/SEAM_CC_SOCKET
|
|
121
|
+
* env)——不在进程内调 guardianStop/Start,那俩认死的 SEAM_DIR/PID_PATH,
|
|
122
|
+
* 对非当前 home 会操作错对象。
|
|
123
|
+
*
|
|
124
|
+
* @param {object} o
|
|
125
|
+
* @param {string} o.seamHome 绝对路径,<project>/.seam
|
|
126
|
+
* @param {string} [o.ccSession] 该 home 的 tmux session(此处不回退 display-message)
|
|
127
|
+
* @param {string} [o.ccSocket] 该 home 的 tmux socket
|
|
128
|
+
*/
|
|
129
|
+
export async function applyHomeUpgrade({ seamHome, ccSession, ccSocket }) {
|
|
130
|
+
if (!seamHome) throw new Error('applyHomeUpgrade: seamHome required');
|
|
131
|
+
const projectDir = dirname(seamHome);
|
|
88
132
|
|
|
89
|
-
//
|
|
90
|
-
// mcp-serve 已不存在——必须在 CC restart 之前剥掉,否则重启会加载到死引用)
|
|
91
|
-
console.log('3b. cleaning seam-im from .mcp.json');
|
|
133
|
+
// version.json
|
|
92
134
|
try {
|
|
93
|
-
const
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
} else {
|
|
101
|
-
console.log(' no seam-im entry, skip');
|
|
102
|
-
}
|
|
103
|
-
} else {
|
|
104
|
-
console.log(' no .mcp.json, skip');
|
|
135
|
+
const version = readOwnVersion();
|
|
136
|
+
if (version) {
|
|
137
|
+
writeFileSync(
|
|
138
|
+
join(seamHome, 'version.json'),
|
|
139
|
+
JSON.stringify({ version, upgradedAt: new Date().toISOString() }, null, 2)
|
|
140
|
+
);
|
|
141
|
+
console.log(` version.json → ${version}`);
|
|
105
142
|
}
|
|
106
143
|
} catch (e) {
|
|
107
|
-
console.error(`
|
|
144
|
+
console.error(` version.json update failed (non-fatal): ${e.message}`);
|
|
108
145
|
}
|
|
109
146
|
|
|
110
|
-
//
|
|
111
|
-
|
|
147
|
+
// patch .claude/settings.json
|
|
148
|
+
patchSettings(join(projectDir, '.claude', 'settings.json'));
|
|
149
|
+
|
|
150
|
+
// 刷新 CHANNEL_RULES.md + CLAUDE.md 引用(显式传 home/projectDir)
|
|
112
151
|
try {
|
|
113
|
-
const {
|
|
114
|
-
|
|
115
|
-
|
|
152
|
+
const { syncChannelRules, patchClaudeMd } = await import('./init.js');
|
|
153
|
+
syncChannelRules(seamHome);
|
|
154
|
+
patchClaudeMd(projectDir);
|
|
155
|
+
console.log(' CHANNEL_RULES + CLAUDE.md refreshed');
|
|
156
|
+
} catch (e) {
|
|
157
|
+
console.error(` CHANNEL_RULES/CLAUDE.md refresh failed (non-fatal): ${e.message}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 剥 .mcp.json 的 seam-im(CC restart 前剥掉指向已删 mcp-serve 的死引用)
|
|
161
|
+
cleanMcpJson(join(projectDir, '.mcp.json'));
|
|
116
162
|
|
|
117
|
-
|
|
118
|
-
|
|
163
|
+
// 重启 guardian:spawn 子进程 stop → 置 flag → start
|
|
164
|
+
const env = {
|
|
165
|
+
...process.env,
|
|
166
|
+
SEAM_HOME: seamHome,
|
|
167
|
+
SEAM_CC_SESSION: ccSession || '',
|
|
168
|
+
SEAM_CC_SOCKET: ccSocket || '',
|
|
169
|
+
};
|
|
170
|
+
const stopRes = spawnSync(process.execPath, [CLI_PATH, 'stop'], { env, stdio: 'inherit' });
|
|
171
|
+
if (stopRes.status !== 0) {
|
|
172
|
+
// stop 非 0 不致命——guardian 可能本来就没在跑
|
|
173
|
+
console.log(` guardian stop exited ${stopRes.status}(可能本来没跑,继续)`);
|
|
174
|
+
}
|
|
175
|
+
await sleep(1000);
|
|
176
|
+
|
|
177
|
+
// 置 pending_upgrade_restart——新 guardian 启动时读到会自动重启 CC
|
|
178
|
+
try {
|
|
179
|
+
const statePath = join(seamHome, 'state.json');
|
|
119
180
|
let stateJson = {};
|
|
120
181
|
if (existsSync(statePath)) {
|
|
121
182
|
try { stateJson = JSON.parse(readFileSync(statePath, 'utf8')); } catch {}
|
|
@@ -123,10 +184,36 @@ export async function upgrade() {
|
|
|
123
184
|
if (!stateJson.guardian) stateJson.guardian = {};
|
|
124
185
|
stateJson.guardian.pending_upgrade_restart = true;
|
|
125
186
|
writeFileSync(statePath, JSON.stringify(stateJson, null, 2));
|
|
187
|
+
} catch (e) {
|
|
188
|
+
console.error(` state.json flag failed (non-fatal): ${e.message}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const startRes = spawnSync(process.execPath, [CLI_PATH, 'guardian', 'start'], { env, stdio: 'inherit' });
|
|
192
|
+
if (startRes.status !== 0) {
|
|
193
|
+
throw new Error(`guardian start failed (exit ${startRes.status})`);
|
|
194
|
+
}
|
|
195
|
+
console.log(' guardian restarted');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export async function upgrade() {
|
|
199
|
+
console.log('Seam — upgrading...\n');
|
|
200
|
+
|
|
201
|
+
// 1. 本地升包
|
|
202
|
+
console.log('1. npm install @seamnet/client@latest');
|
|
203
|
+
try {
|
|
204
|
+
execSync('npm install @seamnet/client@latest', { stdio: 'inherit' });
|
|
205
|
+
} catch (e) {
|
|
206
|
+
console.error(` npm install failed: ${e.message}`);
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
126
209
|
|
|
127
|
-
|
|
210
|
+
// 2. 对当前 home 跑 applyHomeUpgrade(session 走 display-message 检测)
|
|
211
|
+
console.log('2. applying per-home upgrade');
|
|
212
|
+
const { ccSession, ccSocket } = detectCurrentSession();
|
|
213
|
+
try {
|
|
214
|
+
await applyHomeUpgrade({ seamHome: SEAM_DIR, ccSession, ccSocket });
|
|
128
215
|
} catch (e) {
|
|
129
|
-
console.error(`
|
|
216
|
+
console.error(` upgrade failed: ${e.message}`);
|
|
130
217
|
console.log('\n 请手动 /exit 并重新启动 Claude Code。');
|
|
131
218
|
return;
|
|
132
219
|
}
|