@bolloon/bolloon-agent 0.4.32 → 0.5.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/dist/agents/chain/explorer.js +52 -0
- package/dist/agents/chain/index.js +2 -0
- package/dist/agents/chain/onchain-trade.js +15 -1
- package/dist/agents/contacts/chain.js +27 -7
- package/dist/agents/execution-supervisor.js +493 -35
- package/dist/agents/external-events.js +35 -6
- package/dist/agents/gateway-group.js +100 -12
- package/dist/agents/goal-criteria.js +10 -3
- package/dist/agents/goal-flywheel/block-executor.js +346 -0
- package/dist/agents/goal-flywheel/continuation-decision.js +592 -0
- package/dist/agents/goal-flywheel/goal-change.js +754 -0
- package/dist/agents/goal-flywheel/index.js +26 -0
- package/dist/agents/goal-flywheel/memory-layers.js +227 -0
- package/dist/agents/goal-flywheel/run-closure.js +809 -0
- package/dist/agents/goal-flywheel/skill-candidate.js +628 -0
- package/dist/agents/goal-flywheel/types.js +329 -0
- package/dist/agents/goal-flywheel/wiring/change.js +288 -0
- package/dist/agents/goal-flywheel/wiring/closure.js +697 -0
- package/dist/agents/goal-flywheel/wiring/continuation.js +432 -0
- package/dist/agents/goal-flywheel/wiring/contract.js +270 -0
- package/dist/agents/goal-flywheel/wiring/index.js +26 -0
- package/dist/agents/goal-flywheel/wiring/monitor.js +254 -0
- package/dist/agents/goal-flywheel/wiring/seams.js +592 -0
- package/dist/agents/goal-flywheel/work-contract.js +478 -0
- package/dist/agents/goal-flywheel/work-monitor.js +488 -0
- package/dist/agents/goal-flywheel-wiring.js +1870 -0
- package/dist/agents/goal-state-reducer.js +406 -0
- package/dist/agents/goal-store.js +35 -1
- package/dist/agents/mobile-flywheel-view.js +144 -0
- package/dist/agents/mobile-task-actions-verify.js +74 -0
- package/dist/agents/mobile-task-actions.js +178 -0
- package/dist/agents/mobile-task-views.js +199 -0
- package/dist/agents/network-pulse.js +525 -27
- package/dist/agents/pi-sdk.js +75 -20
- package/dist/agents/run-store.js +18 -5
- package/dist/agents/skill-readiness.js +52 -10
- package/dist/agents/subagent-manager.js +209 -3
- package/dist/agents/task/task-onchain-runner.js +19 -1
- package/dist/agents/task/task-runner.js +36 -6
- package/dist/agents/task-board.js +702 -0
- package/dist/agents/task-group.js +524 -0
- package/dist/agents/task-public-text.js +89 -0
- package/dist/cli/commands/index.js +3 -0
- package/dist/cli/commands/tasks.js +977 -8
- package/dist/cli/identity-command.js +177 -0
- package/dist/cli/protocol-envelope.js +4 -0
- package/dist/cli/setup-wizard.js +71 -0
- package/dist/cli/update-commands.js +150 -24
- package/dist/cli-entry.js +12 -2
- package/dist/electron-build/agents/chain/explorer.js +59 -0
- package/dist/electron-build/agents/chain/explorer.js.map +1 -0
- package/dist/electron-build/agents/contacts/chain.js +26 -6
- package/dist/electron-build/agents/contacts/chain.js.map +1 -1
- package/dist/electron-build/agents/execution-supervisor.js +492 -34
- package/dist/electron-build/agents/execution-supervisor.js.map +1 -1
- package/dist/electron-build/agents/external-events.js +34 -5
- package/dist/electron-build/agents/external-events.js.map +1 -1
- package/dist/electron-build/agents/goal-criteria.js +10 -3
- package/dist/electron-build/agents/goal-criteria.js.map +1 -1
- package/dist/electron-build/agents/goal-flywheel/continuation-decision.js +598 -0
- package/dist/electron-build/agents/goal-flywheel/continuation-decision.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/goal-change.js +773 -0
- package/dist/electron-build/agents/goal-flywheel/goal-change.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/memory-layers.js +269 -0
- package/dist/electron-build/agents/goal-flywheel/memory-layers.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/run-closure.js +818 -0
- package/dist/electron-build/agents/goal-flywheel/run-closure.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/skill-candidate.js +642 -0
- package/dist/electron-build/agents/goal-flywheel/skill-candidate.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/types.js +333 -0
- package/dist/electron-build/agents/goal-flywheel/types.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/change.js +294 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/change.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/closure.js +742 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/closure.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/continuation.js +444 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/continuation.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/contract.js +279 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/contract.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/index.js +111 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/index.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/monitor.js +264 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/monitor.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/seams.js +612 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/seams.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/work-contract.js +488 -0
- package/dist/electron-build/agents/goal-flywheel/work-contract.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/work-monitor.js +501 -0
- package/dist/electron-build/agents/goal-flywheel/work-monitor.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel-wiring.js +1962 -0
- package/dist/electron-build/agents/goal-flywheel-wiring.js.map +1 -0
- package/dist/electron-build/agents/goal-state-reducer.js +414 -0
- package/dist/electron-build/agents/goal-state-reducer.js.map +1 -0
- package/dist/electron-build/agents/goal-store.js +35 -1
- package/dist/electron-build/agents/goal-store.js.map +1 -1
- package/dist/electron-build/agents/local-signer.js +101 -0
- package/dist/electron-build/agents/local-signer.js.map +1 -0
- package/dist/electron-build/agents/network-pulse.js +531 -28
- package/dist/electron-build/agents/network-pulse.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +74 -19
- package/dist/electron-build/agents/pi-sdk.js.map +1 -1
- package/dist/electron-build/agents/run-store.js +20 -5
- package/dist/electron-build/agents/run-store.js.map +1 -1
- package/dist/electron-build/agents/skill-readiness.js +52 -9
- package/dist/electron-build/agents/skill-readiness.js.map +1 -1
- package/dist/electron-build/agents/task/task-runner.js +35 -5
- package/dist/electron-build/agents/task/task-runner.js.map +1 -1
- package/dist/electron-build/agents/task-board.js +761 -0
- package/dist/electron-build/agents/task-board.js.map +1 -0
- package/dist/electron-build/agents/task-contract.js +382 -0
- package/dist/electron-build/agents/task-contract.js.map +1 -0
- package/dist/electron-build/orbitdb/cid-database.js +83 -17
- package/dist/electron-build/orbitdb/cid-database.js.map +1 -1
- package/dist/electron-build/orbitdb/ipfs-node.js +31 -2
- package/dist/electron-build/orbitdb/ipfs-node.js.map +1 -1
- package/dist/electron-build/utils/dual-source.js +471 -0
- package/dist/electron-build/utils/dual-source.js.map +1 -0
- package/dist/electron-build/utils/update-manager.js +331 -57
- package/dist/electron-build/utils/update-manager.js.map +1 -1
- package/dist/electron-build/utils/update-state.js +16 -1
- package/dist/electron-build/utils/update-state.js.map +1 -1
- package/dist/electron-build/utils/version-info.js +82 -2
- package/dist/electron-build/utils/version-info.js.map +1 -1
- package/dist/index.js +103 -12
- package/dist/ios/chain-explorer.js +617 -0
- package/dist/ios/explorer.html +163 -0
- package/dist/ios/index.html +96 -0
- package/dist/ios/mobile-agent.js +2 -2
- package/dist/ios/mobile-contacts.js +329 -0
- package/dist/ios/mobile-core.js +1379 -226
- package/dist/ios/mobile-privacy.js +185 -0
- package/dist/ios/mobile.css +19 -0
- package/dist/ios/mobile.html +96 -0
- package/dist/ios/mobile.js +706 -7
- package/dist/ios/routes-chain-index.js +108 -0
- package/dist/ios/routes-contacts.js +420 -0
- package/dist/ios/routes-onchain-trade.js +99 -0
- package/dist/ios/routes-x402-info.js +1 -0
- package/dist/ios/server.js +762 -0
- package/dist/ios/task-frame-server.js +211 -0
- package/dist/orbitdb/cid-database.js +81 -16
- package/dist/orbitdb/ipfs-node.js +31 -2
- package/dist/utils/dual-source.js +426 -0
- package/dist/utils/update-cli.js +16 -21
- package/dist/utils/update-health.js +35 -5
- package/dist/utils/update-manager.js +329 -57
- package/dist/utils/update-state.js +16 -1
- package/dist/utils/version-info.js +75 -1
- package/dist/web/mobile-contacts.js +44 -11
- package/dist/web/mobile-core.js +1197 -513
- package/dist/web/mobile-tasks.js +339 -0
- package/dist/web/mobile.css +19 -0
- package/dist/web/mobile.html +64 -0
- package/dist/web/mobile.js +531 -1
- package/dist/web/routes-mobile-tasks.js +353 -0
- package/dist/web/server.js +163 -4
- package/package.json +3 -1
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* identity-command.ts — `bolloon identity init|show` (2026-09-24)
|
|
3
|
+
*
|
|
4
|
+
* 补的真缺口: 建本机身份以前**只有** `bolloon setup` —— 一个 readline 交互向导。
|
|
5
|
+
* 没有 TTY 的环境 (CI / 容器 / ssh 管道 / 第二实例的自动化) 跑它会
|
|
6
|
+
* `readline was closed` (ERR_USE_AFTER_CLOSE), 于是"新机器/第二个身份"根本建不出来,
|
|
7
|
+
* 而群/任务/签名这条链全靠 `~/.bolloon/identity.json`。
|
|
8
|
+
*
|
|
9
|
+
* 本模块只做薄包装 (不重实现任何密钥学):
|
|
10
|
+
* · `init` — 幂等建 `~/.bolloon/identity.json` (复用 `setup-wizard.initLocalIdentity`,
|
|
11
|
+
* 它内部走 `KeyManager.generate/saveToFile`, 与 `src/index.ts:bootstrapIdentity` 同一条路径);
|
|
12
|
+
* 已存在 → **一个字不改** + `action:'reused'` + exit 0; 损坏 → **拒绝覆盖** (要 --force)。
|
|
13
|
+
* · `show` — 只出 did / 公钥指纹 / 文件权限; **绝不打印私钥**。
|
|
14
|
+
*
|
|
15
|
+
* 红线: 本模块任何路径都不把 `privateKey` 写进 stdout / data / human。
|
|
16
|
+
* 兜底还有 `finalizeEnvelope` 的 `redactSecrets` (AUDIT_FORBIDDEN_KEYS 含 `privateKey`)。
|
|
17
|
+
*/
|
|
18
|
+
import * as fs from 'fs/promises';
|
|
19
|
+
import * as os from 'os';
|
|
20
|
+
import * as crypto from 'crypto';
|
|
21
|
+
import { okEnvelope, failEnvelope, line, title, hint, plain, has, } from './protocol-envelope.js';
|
|
22
|
+
import { getLocalIdentityFile, initLocalIdentity } from './setup-wizard.js';
|
|
23
|
+
import { AUDIT_FORBIDDEN_KEYS } from '../agents/task-contract.js';
|
|
24
|
+
export const IDENTITY_USAGE = `
|
|
25
|
+
${title('bolloon identity')}
|
|
26
|
+
bolloon identity init [--force] [--json]
|
|
27
|
+
非交互建本机身份 → ~/.bolloon/identity.json (文件模式 0600)
|
|
28
|
+
字段与既有完全一致: keyType='Ed25519' · privateKey · publicKey · did · createdAt · version
|
|
29
|
+
幂等: 已存在 → 一个字不改 (action=reused), 仍然 exit 0
|
|
30
|
+
损坏 (读不出 did) → 拒绝覆盖 (exit 1); 确认重来加 --force (会先备份成 .bak-<时间戳>)
|
|
31
|
+
**绝不打印私钥** (输出只有 did / 文件路径 / 文件权限)
|
|
32
|
+
bolloon identity show [--json]
|
|
33
|
+
看本机身份: did · 公钥指纹(sha256 前 16 位) · 文件权限 · 创建时间 (不含私钥)
|
|
34
|
+
|
|
35
|
+
选项: --json · --quiet · --force (只对 init 有效)
|
|
36
|
+
说明: 只建 identity.json; 供应商/API key 仍走 bolloon setup (或 bolloon setup --provider … --api-key …);
|
|
37
|
+
用户称呼/归属身份在 ~/.bolloon/identity/user.json (bolloon setup --name 写它)
|
|
38
|
+
`;
|
|
39
|
+
/** 公钥指纹 (sha256 前 16 位 hex) —— 不可逆, 不含密钥材料 */
|
|
40
|
+
function fingerprint(pub) {
|
|
41
|
+
return `sha256:${crypto.createHash('sha256').update(String(pub || '')).digest('hex').slice(0, 16)}`;
|
|
42
|
+
}
|
|
43
|
+
/** 读现有身份文件 (只取**非私密**字段; privateKey 连读都不读出来) */
|
|
44
|
+
async function readIdentityPublicFacts(home) {
|
|
45
|
+
const file = getLocalIdentityFile(home);
|
|
46
|
+
try {
|
|
47
|
+
const j = JSON.parse(await fs.readFile(file, 'utf-8'));
|
|
48
|
+
const pub = typeof j?.publicKey === 'string' ? j.publicKey : null;
|
|
49
|
+
return {
|
|
50
|
+
exists: true,
|
|
51
|
+
did: typeof j?.did === 'string' && j.did ? j.did : null,
|
|
52
|
+
keyType: typeof j?.keyType === 'string' ? j.keyType : null,
|
|
53
|
+
createdAt: typeof j?.createdAt === 'string' ? j.createdAt : null,
|
|
54
|
+
version: typeof j?.version === 'string' ? j.version : null,
|
|
55
|
+
fingerprint: pub ? fingerprint(pub) : null,
|
|
56
|
+
// 只报**非私密**字段名 (schema 一致性的证据落在测试里: 那边直接读文件比对 6 个键)。
|
|
57
|
+
// 私钥类字段名 (AUDIT_FORBIDDEN_KEYS: privateKey/…) 连名字都不出现在输出里 ——
|
|
58
|
+
// 免得"输出里有个 privateKey 字样"被当成可疑, 也让 `grep privateKey` 这种粗检查能直接过。
|
|
59
|
+
keys: Object.keys(j ?? {}).filter((k) => !AUDIT_FORBIDDEN_KEYS.includes(k)).sort(),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return { exists: false };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** `bolloon identity init` —— 幂等建本机身份 */
|
|
67
|
+
async function identityInit(flags) {
|
|
68
|
+
const head = 'bolloon identity init';
|
|
69
|
+
const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
70
|
+
const force = has(flags, '--force');
|
|
71
|
+
let r;
|
|
72
|
+
try {
|
|
73
|
+
r = await initLocalIdentity(home, { force });
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
return {
|
|
77
|
+
envelope: failEnvelope('INTERNAL_ERROR', `建身份失败: ${String(e?.message || e).slice(0, 200)}`, { file: getLocalIdentityFile(home), created: false }, [], 'needs_human'),
|
|
78
|
+
human: `${title(head)}\n 建身份失败: ${String(e?.message || e).slice(0, 200)}`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (!r.ok) {
|
|
82
|
+
// 拒绝覆盖 (损坏 / force 下备份失败): 如实失败, 不静默重建
|
|
83
|
+
return {
|
|
84
|
+
envelope: failEnvelope('POLICY_DENIED', r.reason || '拒绝覆盖既有身份文件', { file: r.file, action: r.action, created: false, overwritten: false }, [], 'needs_human'),
|
|
85
|
+
human: `${title(head)}\n ${r.reason || '拒绝覆盖'}\n\n${hint('静默盖掉一个可能还能救的身份 = 丢钥匙: 本命令宁可失败')}`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const reused = r.action === 'reused';
|
|
89
|
+
const facts = await readIdentityPublicFacts(home);
|
|
90
|
+
const payload = {
|
|
91
|
+
action: r.action,
|
|
92
|
+
did: r.did ?? null,
|
|
93
|
+
file: r.file,
|
|
94
|
+
mode: r.mode ?? null,
|
|
95
|
+
created: !reused,
|
|
96
|
+
changed: false,
|
|
97
|
+
keyMaterialInOutput: false,
|
|
98
|
+
publicFields: facts.exists ? facts.keys : null,
|
|
99
|
+
fingerprint: facts.exists ? facts.fingerprint : null,
|
|
100
|
+
keyType: facts.exists ? facts.keyType : null,
|
|
101
|
+
createdAt: facts.exists ? facts.createdAt : null,
|
|
102
|
+
version: facts.exists ? facts.version : null,
|
|
103
|
+
nextSteps: reused
|
|
104
|
+
? ['身份已存在, 未改动 (幂等); 直接用即可']
|
|
105
|
+
: ['跑任务/建群/签名现在有身份可用: bolloon task group create --name <群名>', '要配模型供应商/API key: bolloon setup'],
|
|
106
|
+
};
|
|
107
|
+
return {
|
|
108
|
+
envelope: okEnvelope('OK', reused
|
|
109
|
+
? `本机身份已存在, 未改动 (${String(r.did).slice(0, 24)}…)`
|
|
110
|
+
: `已建本机身份 (${String(r.did).slice(0, 24)}…), 文件模式 ${r.mode}`, payload, [], null),
|
|
111
|
+
human: [
|
|
112
|
+
title(head),
|
|
113
|
+
line('动作', reused ? 'reused (已存在, 一个字没改 — 幂等)' : 'created (新建)'),
|
|
114
|
+
line('DID', r.did ?? '(无)'),
|
|
115
|
+
line('身份文件', r.file),
|
|
116
|
+
line('文件模式', r.mode ?? '(读不到)'),
|
|
117
|
+
line('字段', facts.exists ? facts.keys.join(' · ') : '(无)'),
|
|
118
|
+
line('公钥指纹', facts.exists ? facts.fingerprint : '(无)'),
|
|
119
|
+
'',
|
|
120
|
+
` ${hint(reused ? '幂等: 第二个身份请换一个 HOME (HOME=/tmp/x bolloon identity init)' : '下一步')}`,
|
|
121
|
+
...(reused
|
|
122
|
+
? [' 要用第二个身份: `HOME=/tmp/second bolloon identity init` (每个 HOME 一对自己的密钥)']
|
|
123
|
+
: [' 建群: bolloon task group create --name <群名>', ' 模型/API key: bolloon setup (交互向导)']),
|
|
124
|
+
].join('\n'),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/** `bolloon identity show` —— 看本机身份 (不含私钥) */
|
|
128
|
+
async function identityShow(flags) {
|
|
129
|
+
const head = 'bolloon identity show';
|
|
130
|
+
const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
131
|
+
const facts = await readIdentityPublicFacts(home);
|
|
132
|
+
const file = getLocalIdentityFile(home);
|
|
133
|
+
if (!facts.exists) {
|
|
134
|
+
return {
|
|
135
|
+
envelope: failEnvelope('NOT_FOUND', `本机还没有身份文件: ${file}`, { file, exists: false, howTo: 'bolloon identity init', keyMaterialInOutput: false }, [], 'needs_human'),
|
|
136
|
+
human: `${title(head)}\n 本机还没有身份文件: ${file}\n\n${hint('建一个: bolloon identity init (非交互, 0600, 幂等)')}`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const payload = {
|
|
140
|
+
file,
|
|
141
|
+
exists: true,
|
|
142
|
+
did: facts.did,
|
|
143
|
+
keyType: facts.keyType,
|
|
144
|
+
createdAt: facts.createdAt,
|
|
145
|
+
version: facts.version,
|
|
146
|
+
fingerprint: facts.fingerprint,
|
|
147
|
+
publicFields: facts.keys,
|
|
148
|
+
keyMaterialInOutput: false,
|
|
149
|
+
};
|
|
150
|
+
return {
|
|
151
|
+
envelope: okEnvelope('OK', `本机身份: ${String(facts.did).slice(0, 24)}…`, payload, [], null),
|
|
152
|
+
human: [
|
|
153
|
+
title(head),
|
|
154
|
+
line('DID', facts.did ?? '(缺 did — 文件可能损坏)'),
|
|
155
|
+
line('身份文件', file),
|
|
156
|
+
line('keyType', facts.keyType ?? '(无)'),
|
|
157
|
+
line('version', facts.version ?? '(无)'),
|
|
158
|
+
line('createdAt', facts.createdAt ?? '(无)'),
|
|
159
|
+
line('公钥指纹', facts.fingerprint ?? '(无)'),
|
|
160
|
+
line('非私密字段', facts.keys.join(' · ')),
|
|
161
|
+
'',
|
|
162
|
+
' 私钥只在本机文件里 (0600), 不进任何输出/日志',
|
|
163
|
+
].join('\n'),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/** 命令入口 (cli-entry 用 runCommand 包它: 统一 --json/--quiet/--timeout/异常兜底) */
|
|
167
|
+
export async function identityCommand(flags) {
|
|
168
|
+
const sub = String(flags.positionals[0] ?? '').trim().toLowerCase();
|
|
169
|
+
if (sub === 'init')
|
|
170
|
+
return identityInit(flags);
|
|
171
|
+
if (sub === 'show' || sub === 'status')
|
|
172
|
+
return identityShow(flags);
|
|
173
|
+
return {
|
|
174
|
+
envelope: failEnvelope('INVALID_ARGUMENT', sub ? `未知 identity 子命令: ${sub}` : '缺少 identity 子命令 (init | show)', { usage: plain(IDENTITY_USAGE.trim()), accepted: ['bolloon identity init', 'bolloon identity show'] }, [], 'needs_human'),
|
|
175
|
+
human: IDENTITY_USAGE,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -71,6 +71,10 @@ const OPTIONS_WITH_VALUE = new Set([
|
|
|
71
71
|
'--confirmation-window', '--proof-version', '--from-block', '--gate',
|
|
72
72
|
// 2026-09-22 (P6b): 链上**写**操作的**授权意图**声明 (MCP 写 tool 必填; 只可能收紧, 不可能放权)
|
|
73
73
|
'--payment-mode',
|
|
74
|
+
// 2026-09-23 (C1): 任务公告板 `bolloon task publish|board|claim` 的显式公告号
|
|
75
|
+
'--announcement-id',
|
|
76
|
+
// 2026-09-23 (C7): 群聊通道 `bolloon task announce|trail|post` 的群链接/groupId + 极短事实字段
|
|
77
|
+
'--group', '--round', '--criteria', '--kind', '--hash', '--bytes', '--checks', '--verdict', '--limit', '--from',
|
|
74
78
|
]);
|
|
75
79
|
export function parseFlags(args) {
|
|
76
80
|
const flags = { json: false, quiet: false, positionals: [], options: new Map(), raw: args };
|
package/dist/cli/setup-wizard.js
CHANGED
|
@@ -109,6 +109,77 @@ export async function writeUserIdentity(name, home = os.homedir()) {
|
|
|
109
109
|
await fs.writeFile(file, JSON.stringify(identity, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
110
110
|
return { identity, created, file };
|
|
111
111
|
}
|
|
112
|
+
// ---------------------------------------------------------------- 本机 DIAP 身份 (非交互)
|
|
113
|
+
/** 本机 DIAP 身份文件 —— 与 index.ts `bootstrapIdentity()` / local-signer / network-pulse 同源 */
|
|
114
|
+
export function getLocalIdentityFile(home = os.homedir()) {
|
|
115
|
+
return path.join(home, '.bolloon', 'identity.json');
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* **非交互**建本机 DIAP 身份 (2026-09-24)。给新机器 / 第二实例用 ——
|
|
119
|
+
* `bolloon setup` 是 readline 交互向导, 在没有 TTY 的环境里会 `readline was closed`
|
|
120
|
+
* (ERR_USE_AFTER_CLOSE), 于是自动化流程根本建不出身份。
|
|
121
|
+
*
|
|
122
|
+
* 三条硬纪律:
|
|
123
|
+
* ① **复用现有生成逻辑**, 不新写一套密钥学 —— 走 `KeyManager.generate()` +
|
|
124
|
+
* `KeyManager.saveToFile()` (与 `src/index.ts:bootstrapIdentity` 完全同一条路径),
|
|
125
|
+
* 落盘字段因此天然一致: `{ keyType:'Ed25519', privateKey, publicKey, did, createdAt, version }`,
|
|
126
|
+
* 文件模式 `0600`。
|
|
127
|
+
* ② **幂等**: 已存在且能解析出 did → 不动、`action:'reused'` (调用方 exit 0)。
|
|
128
|
+
* 文件存在但**读不出 did** (损坏) → `action:'refused'` 并**拒绝覆盖** ——
|
|
129
|
+
* 静默盖掉一个可能还能救的身份是丢钥匙, 比失败糟得多; 要重来必须显式 `force`
|
|
130
|
+
* (覆盖前先把老文件备份成 `identity.json.bak-<时间戳>`)。
|
|
131
|
+
* ③ **绝不打印/返回私钥**: 返回体里只有 did / 文件路径 / 权限。
|
|
132
|
+
*/
|
|
133
|
+
export async function initLocalIdentity(home = os.homedir(), opts = {}) {
|
|
134
|
+
const file = getLocalIdentityFile(home);
|
|
135
|
+
const readDid = async () => {
|
|
136
|
+
try {
|
|
137
|
+
const j = JSON.parse(await fs.readFile(file, 'utf-8'));
|
|
138
|
+
return j && typeof j.did === 'string' && j.did ? j.did : null;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const exists = await fs.stat(file).then(() => true).catch(() => false);
|
|
145
|
+
if (exists) {
|
|
146
|
+
const did = await readDid();
|
|
147
|
+
if (did && !opts.force) {
|
|
148
|
+
return { ok: true, action: 'reused', file, did, mode: await fileMode(file) };
|
|
149
|
+
}
|
|
150
|
+
if (!did && !opts.force) {
|
|
151
|
+
return {
|
|
152
|
+
ok: false, action: 'refused', file,
|
|
153
|
+
reason: `${file} 已存在但读不出 did (损坏?) → **不覆盖** (那可能是丢钥匙)。确认要重建再加 --force (会先备份成 .bak-<时间戳>)`,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
// force: 先备份再重建 (备份失败 → 不重建)
|
|
157
|
+
try {
|
|
158
|
+
await fs.copyFile(file, `${file}.bak-${Date.now()}`);
|
|
159
|
+
}
|
|
160
|
+
catch (e) {
|
|
161
|
+
return { ok: false, action: 'refused', file, reason: `--force 下备份老文件失败, 拒绝重建: ${String(e?.message || e).slice(0, 160)}` };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const { KeyManager } = await import('@diap/sdk');
|
|
165
|
+
const kp = KeyManager.generate();
|
|
166
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
167
|
+
await KeyManager.saveToFile(kp, file); // 同 bootstrapIdentity: 0600 + 6 字段
|
|
168
|
+
try {
|
|
169
|
+
await fs.chmod(file, 0o600);
|
|
170
|
+
}
|
|
171
|
+
catch { /* 某些平台/文件系统 no-op */ }
|
|
172
|
+
return { ok: true, action: 'created', file, did: String(kp.did || ''), mode: await fileMode(file) };
|
|
173
|
+
}
|
|
174
|
+
/** 文件权限 (八进制字符串; 读不到 → undefined) */
|
|
175
|
+
async function fileMode(file) {
|
|
176
|
+
try {
|
|
177
|
+
return ((await fs.stat(file)).mode & 0o777).toString(8);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
112
183
|
// ---------------------------------------------------------------- 首次运行判断
|
|
113
184
|
// providerUsable 只有一份实现 (setup-store)
|
|
114
185
|
/** 首次运行: 没有可用供应商, 或还没有用户身份 */
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
* 2 检查不可用 (离线 / registry 不可用 / 读不到本地版本) —— 与"没有更新"区分开
|
|
14
14
|
*/
|
|
15
15
|
import { resolveBolloonHome } from '../setup/setup-store.js';
|
|
16
|
-
import { collectVersionInfo, renderVersionText, renderVersionJson, } from '../utils/version-info.js';
|
|
17
|
-
import { checkForUpdate, buildUpdatePlan, renderUpdatePlan, applyUpdate, readUpdateStatus, readHistory, renderHistory, updateSummaryFor, compareVersions, } from '../utils/update-manager.js';
|
|
16
|
+
import { collectVersionInfo, renderVersionText, renderVersionJson, DEV_CHANNEL_WARNING, DEV_BACK_TO_STABLE_HINT, channelKindOf, isDevIdentity, } from '../utils/version-info.js';
|
|
17
|
+
import { checkForUpdate, buildUpdatePlan, renderUpdatePlan, applyUpdate, readUpdateStatus, readHistory, renderHistory, updateSummaryFor, compareVersions, checkExitCode, REFUSED_STATUSES, } from '../utils/update-manager.js';
|
|
18
|
+
import { renderGithubReportLine } from '../utils/dual-source.js';
|
|
18
19
|
import { runDoctor, renderDoctor } from '../utils/update-health.js';
|
|
19
20
|
import { detectRuntimeReport, bootstrapRuntimes, renderRuntimeReport, renderBootstrapPlan, RUNTIME_MIN, readRuntimeConfigWithFallback, } from '../utils/runtime-bootstrap.js';
|
|
20
21
|
const RESET = '\x1b[0m';
|
|
@@ -25,6 +26,45 @@ const YELLOW = '\x1b[38;2;245;158;11m';
|
|
|
25
26
|
const RED = '\x1b[38;2;239;68;68m';
|
|
26
27
|
const out = (s) => process.stdout.write(s + '\n');
|
|
27
28
|
const err = (s) => process.stderr.write(s + '\n');
|
|
29
|
+
// ── --channel (§12.2: 只覆盖本次进程, 不落盘) ───────────────────────────────
|
|
30
|
+
/** 允许的通道字面量 (`--channel stable|dev`; beta 仍是"与 stable 同源"的老口径)。 */
|
|
31
|
+
const CHANNEL_VALUES = ['stable', 'dev', 'beta'];
|
|
32
|
+
/**
|
|
33
|
+
* 解析 `--channel <v>` / `--channel=<v>`。
|
|
34
|
+
* 给了不认识的值 → **拒绝并说清** (不静默落回 stable, 那会让人以为切成功了)。
|
|
35
|
+
*/
|
|
36
|
+
export function parseChannelArg(args) {
|
|
37
|
+
let raw = null;
|
|
38
|
+
for (let i = 0; i < args.length; i++) {
|
|
39
|
+
const a = args[i];
|
|
40
|
+
if (a === '--channel') {
|
|
41
|
+
raw = args[i + 1] ?? '';
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
if (a.startsWith('--channel=')) {
|
|
45
|
+
raw = a.slice('--channel='.length);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
if (a === 'channel') {
|
|
49
|
+
raw = args[i + 1] ?? '';
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (raw === null)
|
|
54
|
+
return { given: false };
|
|
55
|
+
const v = raw.trim().toLowerCase();
|
|
56
|
+
if (!CHANNEL_VALUES.includes(v)) {
|
|
57
|
+
return { given: true, error: `--channel 只接受 ${CHANNEL_VALUES.join('|')}, 收到 "${raw}" — 拒绝执行 (没有静默落回 stable)` };
|
|
58
|
+
}
|
|
59
|
+
return { channel: v, given: true };
|
|
60
|
+
}
|
|
61
|
+
/** dev 通道的三条硬约束 (§12.4) 在这里统一打印: 显式警告 + 记录 sha + 一键回 stable。 */
|
|
62
|
+
function printDevNotice() {
|
|
63
|
+
out('');
|
|
64
|
+
out(DEV_CHANNEL_WARNING);
|
|
65
|
+
out(DEV_BACK_TO_STABLE_HINT);
|
|
66
|
+
out('');
|
|
67
|
+
}
|
|
28
68
|
// ── 版本 ────────────────────────────────────────────────────────────────────
|
|
29
69
|
export async function runVersionCommand(args = []) {
|
|
30
70
|
// 2026-09-19 (leo): 子命令一律**裸词**, 不再带 `--` 前缀 (只有 `--version` 本身保留 --)
|
|
@@ -78,22 +118,39 @@ function statusConclusion(status, latest, current) {
|
|
|
78
118
|
const cmp = !latest ? '' : compareVersions(current, latest) < 0 ? ` — 已有更新版 ${latest}, 需按该安装方式手动升级` : ` — 本地 ${current} 已是 registry 上最新`;
|
|
79
119
|
return `${YELLOW}当前安装方式不支持自动更新${RESET}${cmp}`;
|
|
80
120
|
}
|
|
121
|
+
case 'github_unavailable': return `${RED}GitHub 源不可用${RESET} — ${BOLD}不能判定为最新${RESET}`;
|
|
122
|
+
case 'cross_check_mismatch': return `${RED}两个源不一致${RESET} — npm 与 GitHub 指向不同版本, ${BOLD}拒绝更新${RESET}`;
|
|
81
123
|
default: return '尚未检查';
|
|
82
124
|
}
|
|
83
125
|
}
|
|
84
126
|
export function renderCheckResult(r) {
|
|
127
|
+
const dev = r.channelKind === 'git-ref';
|
|
85
128
|
const L = [];
|
|
86
|
-
L.push(`当前版本: ${r.currentVersion}`);
|
|
87
|
-
L.push(
|
|
88
|
-
L.push(`更新通道: ${r.channel} (
|
|
129
|
+
L.push(`当前版本: ${r.currentVersion}${r.installedDevSha ? ` (dev 快照 commit ${r.installedDevSha})` : ''}`);
|
|
130
|
+
L.push(`${dev ? '最新快照' : '最新版本'}: ${r.latestVersion || '未知'}`);
|
|
131
|
+
L.push(`更新通道: ${r.channel} (比较语义: ${r.channelKind || channelKindOf(r.channel)})`);
|
|
132
|
+
L.push(`当前来源: ${r.installedChannel === 'dev' ? `dev (GitHub master 快照 @ ${r.installedDevSha})` : 'stable (npm registry)'}`);
|
|
89
133
|
L.push(`安装方式: ${r.installMethod}`);
|
|
134
|
+
if (dev)
|
|
135
|
+
L.push(renderGithubReportLine(r.github));
|
|
136
|
+
else {
|
|
137
|
+
L.push(renderGithubReportLine(r.github));
|
|
138
|
+
if (r.crossCheck)
|
|
139
|
+
L.push(`交叉校验: ${r.crossCheck.kind}${r.crossCheck.blocking ? ' (阻塞)' : ''} — ${r.crossCheck.detail}`);
|
|
140
|
+
}
|
|
141
|
+
if (r.dev)
|
|
142
|
+
L.push(`dev 判定: ${r.dev.detail}`);
|
|
143
|
+
if (r.switchableTo)
|
|
144
|
+
L.push(`能切回: ${r.switchableTo.channel} (${r.switchableTo.source}${r.switchableTo.target ? ` @ ${r.switchableTo.target}` : ''})`);
|
|
90
145
|
L.push(`检查时间: ${r.checkedAt}${r.fromCache ? ' (缓存)' : ''}`);
|
|
91
146
|
L.push(`结论: ${statusConclusion(r.status, r.latestVersion, r.currentVersion)}`);
|
|
92
147
|
if (r.reason)
|
|
93
148
|
L.push(`说明: ${r.reason}`);
|
|
94
149
|
L.push('');
|
|
95
150
|
if (r.status === 'update_available') {
|
|
96
|
-
L.push(`下一步: ${BOLD}bolloon update plan${RESET} 看计划, ${BOLD}bolloon update now${RESET} 执行更新`);
|
|
151
|
+
L.push(`下一步: ${BOLD}bolloon update plan --channel ${r.channel}${RESET} 看计划, ${BOLD}bolloon update now --channel ${r.channel}${RESET} 执行更新`);
|
|
152
|
+
if (dev)
|
|
153
|
+
L.push(DEV_CHANNEL_WARNING), L.push(DEV_BACK_TO_STABLE_HINT);
|
|
97
154
|
}
|
|
98
155
|
else if (r.status === 'unsupported_installation') {
|
|
99
156
|
L.push('下一步: 按上面"说明"里对应安装方式更新 (源码目录: git pull && npm install && npm run build:all)');
|
|
@@ -101,6 +158,14 @@ export function renderCheckResult(r) {
|
|
|
101
158
|
else if (r.status === 'offline' || r.status === 'registry_unavailable') {
|
|
102
159
|
L.push('下一步: 检查网络/代理后重跑 `bolloon update`; 也可以 `bolloon update status` 看上次结论');
|
|
103
160
|
}
|
|
161
|
+
else if (r.status === 'github_unavailable') {
|
|
162
|
+
L.push('下一步: 检查到 GitHub 的网络/代理/限流后重跑 `bolloon update --channel dev`;'
|
|
163
|
+
+ `${BOLD}dev 通道不会退回 stable 装旧版${RESET} —— 要稳定版请显式 --channel stable`);
|
|
164
|
+
}
|
|
165
|
+
else if (r.status === 'cross_check_mismatch') {
|
|
166
|
+
L.push('下一步: 两个发布源不一致, 先查清哪个是对的 (npm: registry dist-tags / GitHub: Release 记录);'
|
|
167
|
+
+ '本条不执行任何安装, 也不会按 GitHub 的 tag 去装。');
|
|
168
|
+
}
|
|
104
169
|
else if (r.status === 'local_version_unknown') {
|
|
105
170
|
L.push('下一步: 重新安装 (npm install -g @bolloon/bolloon-agent) 以恢复 package.json');
|
|
106
171
|
}
|
|
@@ -109,32 +174,63 @@ export function renderCheckResult(r) {
|
|
|
109
174
|
export function renderStatusReport(s) {
|
|
110
175
|
const L = [];
|
|
111
176
|
const lu = s.lastUpdate;
|
|
177
|
+
const dev = s.installedChannel === 'dev';
|
|
112
178
|
L.push(`${BOLD}更新状态${RESET}`);
|
|
113
179
|
L.push(`当前版本: ${s.currentVersion}`);
|
|
180
|
+
// ── §12.5: 必须说清"当前装的是哪个源 + 哪个版本/commit sha + 能切回哪个源" ──
|
|
181
|
+
L.push(`安装来源: ${dev
|
|
182
|
+
? `dev (GitHub master 快照, ref ${s.devRef || 'refs/heads/master'}, commit ${s.installedDevSha || '未知'})`
|
|
183
|
+
: 'stable (npm registry)'}`);
|
|
184
|
+
L.push(`身份标识: ${dev ? `commit ${s.installedDevSha || '未知'}` : s.currentVersion}`);
|
|
185
|
+
L.push(`能切回: ${s.switchableTo
|
|
186
|
+
? `${s.switchableTo.channel} (${s.switchableTo.source}${s.switchableTo.target ? ` @ ${s.switchableTo.target}` : ''}) — 一键切: bolloon update now --channel ${s.switchableTo.channel}`
|
|
187
|
+
: '无 (未知来源)'}`);
|
|
188
|
+
if (s.lastDevSha && !dev)
|
|
189
|
+
L.push(`上次 dev: commit ${s.lastDevSha}${s.devCheckedAt ? ` @ ${s.devCheckedAt}` : ''} (已切回 stable)`);
|
|
114
190
|
L.push(`最新版本: ${s.latestVersion || (s.lastCheckStatus ? '未知' : '尚未检查')}`);
|
|
115
191
|
L.push(`是否最新: ${statusConclusion(s.lastCheckStatus, s.latestVersion, s.currentVersion)}`);
|
|
116
|
-
L.push(`更新通道: ${s.channel}`);
|
|
192
|
+
L.push(`更新通道: ${s.channel} (比较语义: ${s.channelKind})`);
|
|
117
193
|
L.push(`安装方式: ${s.installMethod}`);
|
|
118
194
|
L.push(`安装目录: ${s.installDir}`);
|
|
119
195
|
L.push(`运行入口: ${s.entryPath}`);
|
|
196
|
+
if (s.sourceFacts) {
|
|
197
|
+
const f = s.sourceFacts;
|
|
198
|
+
L.push(`源事实: npm ${f.npm?.reachable ? `可达 (latest=${f.npm.latest || '未知'})` : `不可达${f.npm?.detail ? ` (${f.npm.detail})` : ''}`}`
|
|
199
|
+
+ ` · GitHub ${f.github?.reachable
|
|
200
|
+
? `可达 (HEAD ${f.github.headSha ? f.github.headSha.slice(0, 7) : '未知'}, Release ${f.github.releases ?? 0}, Tag ${f.github.tags ?? 0})`
|
|
201
|
+
: `不可达${f.github?.reason ? ` (github_unavailable(${f.github.reason}))` : ' (未查询)'}`}`);
|
|
202
|
+
if (f.crossCheck)
|
|
203
|
+
L.push(`交叉校验: ${f.crossCheck.kind}${f.crossCheck.blocking ? ' (阻塞)' : ''} — ${f.crossCheck.detail}`);
|
|
204
|
+
}
|
|
120
205
|
L.push(`最近检查: ${s.lastCheckAt || '从未'}${s.lastCheckReason ? ` (${s.lastCheckReason})` : ''}`);
|
|
121
206
|
L.push(`最近更新: ${lu ? `${lu.at} ${lu.from} → ${lu.to} [${lu.status}]${lu.reason ? ` ${lu.reason}` : ''}` : '无记录'}`);
|
|
122
207
|
L.push(`最近失败: ${s.lastFailure ? `${s.lastFailure.at} @${s.lastFailure.stage}: ${s.lastFailure.reason}` : '无'}`);
|
|
123
208
|
L.push(`需要重启: ${s.needsRestart ? '是 (新版已就位, 重启后生效)' : '否'}`);
|
|
124
209
|
L.push(`更新锁: ${s.lock ? `有 (pid ${s.lock.pid}, ${s.lock.at}${s.lock.stale ? ', 已陈旧' : ''})` : '无'}`);
|
|
125
210
|
L.push(`开关: 检查更新=${s.prefs.checkUpdates} (${s.prefs.sources.checkUpdates}) · 自动安装=${s.prefs.autoInstall} (${s.prefs.sources.autoInstall}) · 自动重启=${s.prefs.autoRestart} (${s.prefs.sources.autoRestart})`);
|
|
211
|
+
if (dev) {
|
|
212
|
+
L.push('');
|
|
213
|
+
L.push(DEV_CHANNEL_WARNING);
|
|
214
|
+
L.push(DEV_BACK_TO_STABLE_HINT);
|
|
215
|
+
}
|
|
126
216
|
return L.join('\n');
|
|
127
217
|
}
|
|
128
218
|
// ── update 子命令 ───────────────────────────────────────────────────────────
|
|
129
219
|
export async function runUpdateCommand(args = []) {
|
|
130
220
|
const home = resolveBolloonHome();
|
|
131
|
-
// 裸词优先 (bolloon update plan / status / history / now / wait / force / json),
|
|
221
|
+
// 裸词优先 (bolloon update plan / status / history / now / wait / force / json / --channel dev),
|
|
132
222
|
// 同时兼容旧的 --plan / --status / ... 写法 (已有脚本不断裂)。
|
|
133
223
|
const has = (...f) => f.some((x) => args.includes(x));
|
|
134
224
|
const json = has('json', '--json');
|
|
225
|
+
const ch = parseChannelArg(args);
|
|
226
|
+
if (ch.error) {
|
|
227
|
+
err(`${RED}✗ ${ch.error}${RESET}`);
|
|
228
|
+
return 2;
|
|
229
|
+
}
|
|
230
|
+
const channel = ch.channel;
|
|
135
231
|
// ── 状态 ──
|
|
136
232
|
if (has('status', '--status')) {
|
|
137
|
-
const s = await readUpdateStatus({ home });
|
|
233
|
+
const s = await readUpdateStatus({ home, channel });
|
|
138
234
|
if (json) {
|
|
139
235
|
out(JSON.stringify(s, null, 2));
|
|
140
236
|
return 0;
|
|
@@ -156,21 +252,26 @@ export async function runUpdateCommand(args = []) {
|
|
|
156
252
|
}
|
|
157
253
|
// ── 计划 ──
|
|
158
254
|
if (has('plan', '--plan')) {
|
|
159
|
-
const plan = await buildUpdatePlan({ home, force: true });
|
|
255
|
+
const plan = await buildUpdatePlan({ home, force: true, channel });
|
|
160
256
|
if (json) {
|
|
161
257
|
out(JSON.stringify(plan, null, 2));
|
|
162
258
|
return 0;
|
|
163
259
|
}
|
|
164
260
|
out(renderUpdatePlan(plan));
|
|
261
|
+
if (plan.channelKind === 'git-ref')
|
|
262
|
+
printDevNotice();
|
|
165
263
|
return 0;
|
|
166
264
|
}
|
|
167
265
|
// ── 执行 ──
|
|
168
266
|
if (has('now', '--now')) {
|
|
169
267
|
const wait = has('wait', '--wait');
|
|
170
268
|
const force = has('force', '--force', '-f');
|
|
171
|
-
const plan = await buildUpdatePlan({ home, force: true });
|
|
172
|
-
if (!json)
|
|
269
|
+
const plan = await buildUpdatePlan({ home, force: true, channel });
|
|
270
|
+
if (!json) {
|
|
173
271
|
out(renderUpdatePlan(plan));
|
|
272
|
+
if (plan.channelKind === 'git-ref')
|
|
273
|
+
printDevNotice();
|
|
274
|
+
}
|
|
174
275
|
if (!plan.ok && plan.blockers.length > 0) {
|
|
175
276
|
if (json)
|
|
176
277
|
out(JSON.stringify({ stage: 'blocked', reason: plan.blockers.join('; ') }, null, 2));
|
|
@@ -178,6 +279,17 @@ export async function runUpdateCommand(args = []) {
|
|
|
178
279
|
return 1;
|
|
179
280
|
}
|
|
180
281
|
if (!plan.targetVersion) {
|
|
282
|
+
/**
|
|
283
|
+
* §12.5: 源不可达 / 版本不存在 → **拒绝并说清**, 退出码 2。
|
|
284
|
+
* 绝不打印"✅ 已是最新, 无需更新" (那正是这次要收敛掉的骗人行为)。
|
|
285
|
+
*/
|
|
286
|
+
if (REFUSED_STATUSES.includes(plan.check.status)) {
|
|
287
|
+
if (json)
|
|
288
|
+
out(JSON.stringify({ stage: 'blocked', refused: true, status: plan.check.status, reason: plan.check.reason }, null, 2));
|
|
289
|
+
err(`${RED}✗ 拒绝更新${RESET} (${plan.check.status}): ${plan.check.reason || '源不可达 / 版本不存在'}`);
|
|
290
|
+
err(' 没有安装任何东西, 也没有静默退回旧版。');
|
|
291
|
+
return 2;
|
|
292
|
+
}
|
|
181
293
|
if (json)
|
|
182
294
|
out(JSON.stringify({ stage: 'succeeded', ok: true, reason: '已是最新', from: plan.currentVersion, to: plan.currentVersion }, null, 2));
|
|
183
295
|
out(`${GREEN}✓ 已是最新, 无需更新${RESET} (${plan.currentVersion})`);
|
|
@@ -185,27 +297,34 @@ export async function runUpdateCommand(args = []) {
|
|
|
185
297
|
}
|
|
186
298
|
const res = await applyUpdate({
|
|
187
299
|
home,
|
|
300
|
+
channel,
|
|
188
301
|
strategy: wait ? 'wait' : 'now',
|
|
189
302
|
force,
|
|
190
303
|
onStage: json ? undefined : (stage, detail) => out(` [${stage}] ${detail}`),
|
|
191
304
|
});
|
|
192
305
|
if (json) {
|
|
193
306
|
out(JSON.stringify(res, null, 2));
|
|
194
|
-
return res.ok ? 0 : 1;
|
|
307
|
+
return res.ok ? 0 : (res.stage === 'blocked' ? 2 : 1);
|
|
195
308
|
}
|
|
196
309
|
if (res.ok) {
|
|
197
310
|
out(`${GREEN}✓ 更新成功${RESET}: ${res.from} → ${res.to} (${(res.durationMs / 1000).toFixed(1)}s)`);
|
|
311
|
+
if (isDevIdentity(res.to)) {
|
|
312
|
+
out(` dev 快照已记录: commit ${res.to.split('+dev.')[1] || '未知'} (ref refs/heads/master) — bolloon update status 可查`);
|
|
313
|
+
}
|
|
198
314
|
out(' 正在做更新后健康检查...');
|
|
199
315
|
const health = await runDoctor({ bolloonHome: home }).catch(() => null);
|
|
200
316
|
if (health) {
|
|
201
317
|
out(renderDoctor(health));
|
|
202
318
|
}
|
|
319
|
+
if (isDevIdentity(res.to))
|
|
320
|
+
printDevNotice();
|
|
203
321
|
out(`${YELLOW}请重启 bolloon 以使用新版本${RESET}`);
|
|
204
322
|
return 0;
|
|
205
323
|
}
|
|
206
324
|
if (res.stage === 'blocked') {
|
|
325
|
+
const refused = /拒绝更新|github_unavailable|cross_check_mismatch/.test(res.reason || '');
|
|
207
326
|
err(`${YELLOW}! ${res.reason}${RESET}`);
|
|
208
|
-
return 1;
|
|
327
|
+
return refused ? 2 : 1;
|
|
209
328
|
}
|
|
210
329
|
err(`${RED}✗ 更新失败${RESET} (${res.stage}): ${res.reason}`);
|
|
211
330
|
err(` 旧版本仍在: ${res.from} — 可继续使用; 详情: bolloon update status`);
|
|
@@ -214,21 +333,17 @@ export async function runUpdateCommand(args = []) {
|
|
|
214
333
|
return 1;
|
|
215
334
|
}
|
|
216
335
|
// ── 默认: 只检查, 给清晰结论 (显式命令 = 忽略节流) ──
|
|
217
|
-
const r = await checkForUpdate({ home, force: true });
|
|
336
|
+
const r = await checkForUpdate({ home, force: true, channel });
|
|
218
337
|
if (json) {
|
|
219
338
|
out(JSON.stringify(r, null, 2));
|
|
220
|
-
return r.status
|
|
339
|
+
return checkExitCode(r.status);
|
|
221
340
|
}
|
|
222
341
|
out(renderCheckResult(r));
|
|
223
342
|
if (r.status !== 'up_to_date' && r.status !== 'check_skipped') {
|
|
224
343
|
out('');
|
|
225
344
|
out(`${YELLOW}提示${RESET}: 本次只检查, 没有安装任何东西。`);
|
|
226
345
|
}
|
|
227
|
-
|
|
228
|
-
return 2;
|
|
229
|
-
if (r.status === 'update_available' && compareVersions(r.currentVersion, r.latestVersion || r.currentVersion) >= 0)
|
|
230
|
-
return 0;
|
|
231
|
-
return 0;
|
|
346
|
+
return checkExitCode(r.status);
|
|
232
347
|
}
|
|
233
348
|
// ── doctor ──────────────────────────────────────────────────────────────────
|
|
234
349
|
export async function runDoctorCommand(args = []) {
|
|
@@ -323,12 +438,12 @@ export async function runRuntimeCommand(args = []) {
|
|
|
323
438
|
}
|
|
324
439
|
export const UPDATE_HELP = `
|
|
325
440
|
${BOLD}版本与更新:${RESET} (子命令一律裸词, 不带 -- 前缀)
|
|
326
|
-
bolloon --version 版本 +
|
|
441
|
+
bolloon --version 版本 + 安装方式/目录/入口/通道/来源身份/上游/是否最新
|
|
327
442
|
bolloon --version verbose 诊断版 (构建时间/git/Node/npm/Python/配置目录/最近更新)
|
|
328
443
|
bolloon --version json 机器版 (同一份 VersionInfo, 给脚本与问题报告)
|
|
329
444
|
bolloon update 只检查并给结论 (不安装任何东西)
|
|
330
445
|
bolloon update plan 更新计划: 要更新什么 / 不会动什么 / 风险检查
|
|
331
|
-
bolloon update status 更新状态:
|
|
446
|
+
bolloon update status 更新状态: 装的哪个源+版本/commit+能切回哪/最近检查/失败/开关
|
|
332
447
|
bolloon update history [N] 最近 N 次更新 (时间 · 版本变化 · 结果 · 耗时 · 原因)
|
|
333
448
|
bolloon update now 真正执行更新 (默认先看计划)
|
|
334
449
|
bolloon update now wait 等当前 Run 结束后再更新 (有长期任务时的默认建议)
|
|
@@ -336,6 +451,15 @@ ${BOLD}版本与更新:${RESET} (子命令一律裸词, 不带 -- 前缀)
|
|
|
336
451
|
bolloon doctor json 同上, 机器可读
|
|
337
452
|
bolloon runtime [plan|install|json] 运行时 (Node/npm/Git/Python) 检查与安装 (install 需显式 yes)
|
|
338
453
|
|
|
454
|
+
${BOLD}双源 (--channel):${RESET}
|
|
455
|
+
--channel stable (默认) npm registry 是权威 (dist-tags.latest), GitHub Tag/Release 只做交叉校验;
|
|
456
|
+
比较语义 = semver (npm 的 latest ↔ GitHub 同名 tag)
|
|
457
|
+
--channel dev GitHub master HEAD 是唯一源; 比较语义 = git ref + commit sha (不用 semver);
|
|
458
|
+
身份 = <版本>+dev.<sha7>; 未走发布门, 会打印警告并记录 sha
|
|
459
|
+
一键回稳定版: bolloon update now --channel stable
|
|
460
|
+
--channel 只覆盖本次进程, 不落盘 (config 里的 updateChannel 仍是默认值)
|
|
461
|
+
源不可达 / 版本不存在 → 拒绝并说清 (退出码 2), 不会静默装回旧版
|
|
462
|
+
|
|
339
463
|
${BOLD}开关 (config.json):${RESET}
|
|
340
464
|
checkUpdates (默认 true) 启动时后台检查
|
|
341
465
|
autoInstall (默认 false) 自动安装 —— 默认关, 需显式打开
|
|
@@ -344,6 +468,8 @@ ${BOLD}环境变量 (临时覆盖, 不落盘):${RESET}
|
|
|
344
468
|
BOLLOON_SKIP_UPDATE=1 本次不检查
|
|
345
469
|
BOLLOON_AUTO_UPDATE=1 本次允许自动安装
|
|
346
470
|
BOLLOON_UPDATE_CHANNEL=stable|beta|dev
|
|
471
|
+
BOLLOON_GITHUB_API 覆盖 GitHub API 根 (镜像/受控验收用)
|
|
472
|
+
BOLLOON_DEV_SOURCE_DIR dev 快照用本地源码树 (必须与该 sha 一致, 否则拒绝)
|
|
347
473
|
|
|
348
|
-
${BOLD}退出码:${RESET} 0 正常 · 1 执行失败 · 2 检查不可用 (离线/registry
|
|
474
|
+
${BOLD}退出码:${RESET} 0 正常 · 1 执行失败 · 2 检查不可用 (离线/registry 不可用/GitHub 不可用/两源不一致/读不到本地版本)
|
|
349
475
|
`;
|
package/dist/cli-entry.js
CHANGED
|
@@ -26,11 +26,12 @@ import { collectVersionInfo } from './utils/version-info.js';
|
|
|
26
26
|
import { runServiceGroup, GROUPS_HELP } from './cli/commands/index.js';
|
|
27
27
|
// 2026-09-21 (P4): MCP 适配层 (`bolloon mcp serve` = stdio, 只调 P3 服务层)
|
|
28
28
|
import { runMcpCommand } from './cli/commands/mcp.js';
|
|
29
|
-
import { legacyJson } from './cli/protocol-envelope.js';
|
|
29
|
+
import { legacyJson, parseFlags, runCommand } from './cli/protocol-envelope.js';
|
|
30
|
+
import { identityCommand } from './cli/identity-command.js';
|
|
30
31
|
import { createRequire } from 'module';
|
|
31
32
|
const _require = createRequire(import.meta.url);
|
|
32
33
|
/** P3 `bolloon task <子命令>` 的已知子命令 —— 其它一律当 M1 任务正文 (既有体验不动) */
|
|
33
|
-
const TASK_SUBCOMMANDS = new Set(['send', 'list', 'status', 'cancel', 'retry', 'result', 'inbox', 'accept', 'reject', 'run', 'complete']);
|
|
34
|
+
const TASK_SUBCOMMANDS = new Set(['send', 'list', 'status', 'cancel', 'retry', 'result', 'inbox', 'accept', 'reject', 'run', 'complete', 'publish', 'board', 'claim', 'announce', 'trail', 'post', 'group']);
|
|
34
35
|
const isWindows = process.platform === 'win32';
|
|
35
36
|
// ANSI 颜色 — Bolloon Web UI 配色 truecolor
|
|
36
37
|
function _fg(r, g, b) { return `\x1b[38;2;${r};${g};${b}m`; }
|
|
@@ -62,6 +63,8 @@ ${BOLD}选项:${RESET}
|
|
|
62
63
|
|
|
63
64
|
${BOLD}命令:${RESET}
|
|
64
65
|
bolloon setup 初始化向导 (你的称呼 + 模型供应商 + API key + 连通性测试)
|
|
66
|
+
bolloon identity init 非交互建本机身份 (~/.bolloon/identity.json, 0600, 幂等; 新机器/第二实例用)
|
|
67
|
+
bolloon identity show 看本机身份 (只出 DID/指纹, 绝不打印私钥)
|
|
65
68
|
bolloon update [plan|status|history|now] 检查更新 / 计划 / 状态 / 历史 / 执行 (默认只检查)
|
|
66
69
|
bolloon doctor 安装入口 + 版本事实 + 更新状态自洽性诊断
|
|
67
70
|
bolloon runtime [plan|install] 运行时 (Node/npm/Git/Python) 检查与安装
|
|
@@ -203,6 +206,9 @@ function parseArgs() {
|
|
|
203
206
|
case 'setup':
|
|
204
207
|
case 'init':
|
|
205
208
|
return { mode: 'setup', args: args.slice(1) };
|
|
209
|
+
// 2026-09-24: 非交互建本机身份 (`bolloon identity init` → ~/.bolloon/identity.json)
|
|
210
|
+
case 'identity':
|
|
211
|
+
return { mode: 'identity', args: args.slice(1) };
|
|
206
212
|
case 'read':
|
|
207
213
|
case 'summarize':
|
|
208
214
|
case 'improve':
|
|
@@ -890,6 +896,10 @@ async function main() {
|
|
|
890
896
|
case 'setup':
|
|
891
897
|
await handleSetupCommand(args);
|
|
892
898
|
break;
|
|
899
|
+
// 2026-09-24: bolloon identity — 非交互建/看本机身份 (新机器/第二实例用; 幂等, 不打印私钥)
|
|
900
|
+
case 'identity':
|
|
901
|
+
process.exit(await runCommand(parseFlags(args), identityCommand));
|
|
902
|
+
break;
|
|
893
903
|
case 'passthrough':
|
|
894
904
|
// 传递所有参数给主程序
|
|
895
905
|
try {
|