@bolloon/bolloon-agent 0.4.33 → 0.5.1
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/contacts/chain.js +27 -7
- package/dist/agents/execution-supervisor.js +580 -40
- 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/model-policy.js +651 -0
- package/dist/agents/network-pulse.js +304 -18
- package/dist/agents/pi-sdk-tools.js +84 -33
- package/dist/agents/pi-sdk.js +157 -42
- package/dist/agents/run-store.js +71 -5
- package/dist/agents/skill-readiness.js +52 -10
- package/dist/agents/subagent-manager.js +209 -3
- package/dist/agents/task/task-runner.js +17 -4
- package/dist/agents/task-group.js +8 -51
- package/dist/agents/task-public-text.js +89 -0
- package/dist/agents/workflow-pivot-loop.js +19 -12
- package/dist/cli/clipboard.js +65 -0
- package/dist/cli/commands/index.js +1 -0
- package/dist/cli/commands/tasks.js +350 -2
- package/dist/cli/identity-command.js +177 -0
- package/dist/cli/ink-app.js +145 -17
- package/dist/cli/log-gate.js +386 -0
- package/dist/cli/mention-data.js +2 -0
- package/dist/cli/model-selector.js +538 -0
- package/dist/cli/reply-hygiene.js +54 -0
- package/dist/cli/setup-wizard.js +757 -69
- package/dist/cli/startup-notice.js +160 -0
- package/dist/cli/stores.js +22 -1
- package/dist/cli/theme.js +49 -0
- package/dist/cli/tui-select.js +970 -0
- package/dist/cli/update-commands.js +150 -24
- package/dist/cli-entry.js +137 -20
- 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 +578 -38
- 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/model-policy.js +706 -0
- package/dist/electron-build/agents/model-policy.js.map +1 -0
- package/dist/electron-build/agents/network-pulse.js +307 -19
- package/dist/electron-build/agents/network-pulse.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-tools.js +84 -33
- package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-types.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +156 -41
- package/dist/electron-build/agents/pi-sdk.js.map +1 -1
- package/dist/electron-build/agents/run-store.js +75 -6
- 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 +16 -3
- package/dist/electron-build/agents/task/task-runner.js.map +1 -1
- package/dist/electron-build/agents/workflow-pivot-loop.js +19 -12
- package/dist/electron-build/agents/workflow-pivot-loop.js.map +1 -1
- package/dist/electron-build/llm/config-store.js +146 -6
- package/dist/electron-build/llm/config-store.js.map +1 -1
- package/dist/electron-build/llm/connection-probe.js +764 -0
- package/dist/electron-build/llm/connection-probe.js.map +1 -0
- package/dist/electron-build/llm/custom-provider-store.js +272 -0
- package/dist/electron-build/llm/custom-provider-store.js.map +1 -0
- package/dist/electron-build/llm/data/provider-catalog-baked.js +23 -0
- package/dist/electron-build/llm/data/provider-catalog-baked.js.map +1 -0
- package/dist/electron-build/llm/model-catalog.js +778 -0
- package/dist/electron-build/llm/model-catalog.js.map +1 -0
- package/dist/electron-build/llm/model-discovery.js +1202 -0
- package/dist/electron-build/llm/model-discovery.js.map +1 -0
- package/dist/electron-build/llm/model-selection.js +1315 -0
- package/dist/electron-build/llm/model-selection.js.map +1 -0
- package/dist/electron-build/llm/pi-ai.js +70 -11
- package/dist/electron-build/llm/pi-ai.js.map +1 -1
- package/dist/electron-build/llm/provider-catalog.js +969 -0
- package/dist/electron-build/llm/provider-catalog.js.map +1 -0
- package/dist/electron-build/llm/provider-registry.js +950 -0
- package/dist/electron-build/llm/provider-registry.js.map +1 -0
- package/dist/electron-build/llm/tool-name.js +266 -0
- package/dist/electron-build/llm/tool-name.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-facts.js +236 -0
- package/dist/electron-build/utils/dual-source-facts.js.map +1 -0
- package/dist/electron-build/utils/dual-source.js +347 -0
- package/dist/electron-build/utils/dual-source.js.map +1 -0
- package/dist/electron-build/utils/update-manager.js +328 -104
- package/dist/electron-build/utils/update-manager.js.map +1 -1
- package/dist/electron-build/utils/update-state.js +9 -11
- package/dist/electron-build/utils/update-state.js.map +1 -1
- package/dist/electron-build/utils/version-identity.js +193 -0
- package/dist/electron-build/utils/version-identity.js.map +1 -0
- package/dist/electron-build/utils/version-info.js +57 -31
- package/dist/electron-build/utils/version-info.js.map +1 -1
- package/dist/index.js +385 -108
- package/dist/ios/bolloon-web.json +10 -0
- 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 +362 -0
- package/dist/ios/mobile-core.js +3102 -310
- package/dist/ios/mobile-privacy.js +185 -0
- package/dist/ios/mobile-tasks.js +339 -0
- package/dist/ios/mobile-update.js +1442 -0
- package/dist/ios/mobile.css +19 -0
- package/dist/ios/mobile.html +96 -0
- package/dist/ios/mobile.js +1013 -41
- package/dist/ios/routes-chain-index.js +108 -0
- package/dist/ios/routes-contacts.js +420 -0
- package/dist/ios/routes-llm-config.js +344 -52
- package/dist/ios/routes-mobile-tasks.js +353 -0
- package/dist/ios/routes-onchain-trade.js +99 -0
- package/dist/ios/routes-x402-info.js +1 -0
- package/dist/ios/server.js +921 -0
- package/dist/ios/sw.js +1 -1
- package/dist/ios/task-frame-server.js +211 -0
- package/dist/llm/config-store.js +146 -6
- package/dist/llm/connection-probe.js +744 -0
- package/dist/llm/custom-provider-store.js +224 -0
- package/dist/llm/data/provider-catalog-baked.js +19 -0
- package/dist/llm/model-catalog.js +717 -0
- package/dist/llm/model-discovery.js +1137 -0
- package/dist/llm/model-selection.js +1241 -0
- package/dist/llm/pi-ai.js +70 -11
- package/dist/llm/provider-catalog.js +898 -0
- package/dist/llm/provider-registry.js +917 -0
- package/dist/llm/tool-name.js +220 -0
- package/dist/orbitdb/cid-database.js +81 -16
- package/dist/orbitdb/ipfs-node.js +31 -2
- package/dist/setup/onboard.js +34 -8
- package/dist/utils/dual-source-facts.js +223 -0
- package/dist/utils/dual-source.js +297 -0
- package/dist/utils/update-cli.js +16 -21
- package/dist/utils/update-health.js +35 -5
- package/dist/utils/update-manager.js +325 -96
- package/dist/utils/update-state.js +13 -10
- package/dist/utils/version-identity.js +176 -0
- package/dist/utils/version-info.js +27 -22
- package/dist/web/bolloon-web.json +10 -0
- package/dist/web/mobile-contacts.js +44 -11
- package/dist/web/mobile-core.js +2901 -580
- package/dist/web/mobile-tasks.js +339 -0
- package/dist/web/mobile-update.js +1442 -0
- package/dist/web/mobile.css +19 -0
- package/dist/web/mobile.html +64 -0
- package/dist/web/mobile.js +838 -35
- package/dist/web/routes-llm-config.js +344 -52
- package/dist/web/routes-mobile-tasks.js +353 -0
- package/dist/web/server.js +163 -4
- package/dist/web/sw.js +1 -1
- package/package.json +3 -1
- package/scripts/build-web.ts +36 -0
|
@@ -52,12 +52,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
52
52
|
};
|
|
53
53
|
})();
|
|
54
54
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
55
|
-
exports.NPM_FETCH_RETRY_FLAGS = exports.IN_FLIGHT_STAGES = exports.MIN_FREE_BYTES = void 0;
|
|
55
|
+
exports.NPM_FETCH_RETRY_FLAGS = exports.IN_FLIGHT_STAGES = exports.MIN_FREE_BYTES = exports.classifyRegistryError = exports.REFUSED_STATUSES = exports.checkExitCode = exports.isKnownVersion = exports.compareVersions = exports.parseVersion = exports.DEV_BACK_TO_STABLE_HINT = exports.DEV_CHANNEL_WARNING = void 0;
|
|
56
56
|
exports.parseJsonFromStdout = parseJsonFromStdout;
|
|
57
|
-
exports.parseVersion = parseVersion;
|
|
58
|
-
exports.compareVersions = compareVersions;
|
|
59
|
-
exports.isKnownVersion = isKnownVersion;
|
|
60
|
-
exports.classifyRegistryError = classifyRegistryError;
|
|
61
57
|
exports.queryRegistryDoc = queryRegistryDoc;
|
|
62
58
|
exports.checkForUpdate = checkForUpdate;
|
|
63
59
|
exports.freeBytesAt = freeBytesAt;
|
|
@@ -78,8 +74,20 @@ const https = __importStar(require("https"));
|
|
|
78
74
|
const http = __importStar(require("http"));
|
|
79
75
|
const child_process_1 = require("child_process");
|
|
80
76
|
const version_info_js_1 = require("./version-info.js");
|
|
77
|
+
Object.defineProperty(exports, "DEV_CHANNEL_WARNING", { enumerable: true, get: function () { return version_info_js_1.DEV_CHANNEL_WARNING; } });
|
|
78
|
+
Object.defineProperty(exports, "DEV_BACK_TO_STABLE_HINT", { enumerable: true, get: function () { return version_info_js_1.DEV_BACK_TO_STABLE_HINT; } });
|
|
79
|
+
const version_identity_js_1 = require("./version-identity.js");
|
|
80
|
+
Object.defineProperty(exports, "parseVersion", { enumerable: true, get: function () { return version_identity_js_1.parseVersion; } });
|
|
81
|
+
Object.defineProperty(exports, "compareVersions", { enumerable: true, get: function () { return version_identity_js_1.compareVersions; } });
|
|
82
|
+
Object.defineProperty(exports, "isKnownVersion", { enumerable: true, get: function () { return version_identity_js_1.isKnownVersion; } });
|
|
83
|
+
Object.defineProperty(exports, "checkExitCode", { enumerable: true, get: function () { return version_identity_js_1.checkExitCode; } });
|
|
84
|
+
Object.defineProperty(exports, "REFUSED_STATUSES", { enumerable: true, get: function () { return version_identity_js_1.REFUSED_STATUSES; } });
|
|
85
|
+
// registry 侧的错误分类也在纯那一半 (手机端要用同一份分类) —— 定义在 dual-source-facts.ts。
|
|
86
|
+
const dual_source_facts_js_1 = require("./dual-source-facts.js");
|
|
87
|
+
Object.defineProperty(exports, "classifyRegistryError", { enumerable: true, get: function () { return dual_source_facts_js_1.classifyRegistryError; } });
|
|
88
|
+
const dual_source_js_1 = require("./dual-source.js");
|
|
81
89
|
const update_state_js_1 = require("./update-state.js");
|
|
82
|
-
// ── 版本比较 (
|
|
90
|
+
// ── 版本比较 (唯一一份在 version-identity.ts; 上面已再导出) ────────────────
|
|
83
91
|
/**
|
|
84
92
|
* 从子进程 stdout 里取 JSON —— **不要**按"行首是否 { "过滤:
|
|
85
93
|
* `JSON.stringify(x, null, 2)` 是**多行**的, 只留第一行 `{` 会解析失败
|
|
@@ -107,28 +115,6 @@ function parseJsonFromStdout(stdout) {
|
|
|
107
115
|
return null;
|
|
108
116
|
}
|
|
109
117
|
}
|
|
110
|
-
function parseVersion(v) {
|
|
111
|
-
const clean = String(v || '').trim().replace(/^v/, '').split('-')[0];
|
|
112
|
-
const parts = clean.split('.').map((p) => parseInt(p.replace(/\D.*$/, ''), 10));
|
|
113
|
-
return parts.map((n) => (Number.isFinite(n) ? n : 0));
|
|
114
|
-
}
|
|
115
|
-
/** -1 = a<b, 0 = 相等, 1 = a>b (只比数值段, 忽略预发布标签) */
|
|
116
|
-
function compareVersions(a, b) {
|
|
117
|
-
const pa = parseVersion(a);
|
|
118
|
-
const pb = parseVersion(b);
|
|
119
|
-
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
120
|
-
const x = pa[i] ?? 0;
|
|
121
|
-
const y = pb[i] ?? 0;
|
|
122
|
-
if (x < y)
|
|
123
|
-
return -1;
|
|
124
|
-
if (x > y)
|
|
125
|
-
return 1;
|
|
126
|
-
}
|
|
127
|
-
return 0;
|
|
128
|
-
}
|
|
129
|
-
function isKnownVersion(v) {
|
|
130
|
-
return !!v && v !== 'unknown' && /^\d/.test(String(v).trim().replace(/^v/, ''));
|
|
131
|
-
}
|
|
132
118
|
function httpGetJson(url, timeoutMs = 10000) {
|
|
133
119
|
return new Promise((resolve, reject) => {
|
|
134
120
|
const client = url.startsWith('https') ? https : http;
|
|
@@ -141,27 +127,12 @@ function httpGetJson(url, timeoutMs = 10000) {
|
|
|
141
127
|
req.on('timeout', () => { req.destroy(new Error('timeout')); });
|
|
142
128
|
});
|
|
143
129
|
}
|
|
144
|
-
const OFFLINE_CODES = new Set(['ENOTFOUND', 'EAI_AGAIN', 'ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'EHOSTUNREACH', 'ENETUNREACH', 'ENETDOWN', 'EAI_FAIL', 'UND_ERR_CONNECT_TIMEOUT']);
|
|
145
|
-
/** 网络类错误 → offline; 其它 (HTTP 5xx / 解析失败 / 包不存在) → registry_unavailable。 */
|
|
146
|
-
function classifyRegistryError(err, httpStatus) {
|
|
147
|
-
const code = String(err?.code || '');
|
|
148
|
-
if (OFFLINE_CODES.has(code) || /timeout|timed out|ENOTFOUND|getaddrinfo|network|socket hang up|超时/i.test(String(err?.message || ''))) {
|
|
149
|
-
return { kind: 'offline', detail: `${code || err?.message || '网络不可达'}` };
|
|
150
|
-
}
|
|
151
|
-
if (httpStatus && httpStatus >= 500)
|
|
152
|
-
return { kind: 'registry_unavailable', detail: `registry 返回 HTTP ${httpStatus}` };
|
|
153
|
-
if (httpStatus === 404)
|
|
154
|
-
return { kind: 'registry_unavailable', detail: `registry 上没有 ${version_info_js_1.PKG_NAME} (HTTP 404)` };
|
|
155
|
-
if (httpStatus && httpStatus >= 400)
|
|
156
|
-
return { kind: 'registry_unavailable', detail: `registry 返回 HTTP ${httpStatus}` };
|
|
157
|
-
return { kind: 'registry_unavailable', detail: code || err?.message || '未知错误' };
|
|
158
|
-
}
|
|
159
130
|
async function queryRegistryDoc(pkg = version_info_js_1.PKG_NAME, timeoutMs = 10000) {
|
|
160
131
|
const url = `${version_info_js_1.NPM_REGISTRY_BASE}/${encodeURIComponent(pkg).replace('%40', '@')}`;
|
|
161
132
|
try {
|
|
162
133
|
const { status, body } = await httpGetJson(url, timeoutMs);
|
|
163
134
|
if (status < 200 || status >= 300)
|
|
164
|
-
return { ok: false, ...classifyRegistryError(null, status) };
|
|
135
|
+
return { ok: false, ...(0, dual_source_facts_js_1.classifyRegistryError)(null, status) };
|
|
165
136
|
let raw;
|
|
166
137
|
try {
|
|
167
138
|
raw = JSON.parse(body);
|
|
@@ -183,34 +154,52 @@ async function queryRegistryDoc(pkg = version_info_js_1.PKG_NAME, timeoutMs = 10
|
|
|
183
154
|
return { ok: true, doc: { latest: distTags.latest || versions[versions.length - 1], distTags, versions, gitHeads } };
|
|
184
155
|
}
|
|
185
156
|
catch (e) {
|
|
186
|
-
return { ok: false, ...classifyRegistryError(e) };
|
|
157
|
+
return { ok: false, ...(0, dual_source_facts_js_1.classifyRegistryError)(e) };
|
|
187
158
|
}
|
|
188
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* 检查结论 → 退出码 / "源不可达必拒" 的这一组结论:
|
|
162
|
+
* **唯一一份在 `version-identity.ts`** (上面已 import+再导出)。
|
|
163
|
+
* §12.5 的核心就是这一句: **源不可达 / 版本不存在 → 拒绝并说清 (退出码 2)**,
|
|
164
|
+
* 不许静默装回旧版, 也不许打印"✅ 已是最新"。桌面与手机共用同一份。
|
|
165
|
+
*/
|
|
189
166
|
/**
|
|
190
167
|
* 检查更新。**这是唯一的检查入口** —— CLI / 启动后台 / Python 检查器 / 安装脚本都走它。
|
|
191
168
|
*
|
|
192
|
-
* 结论优先级 (刻意定死, 防"模糊行为"):
|
|
169
|
+
* 结论优先级 (刻意定死, 防"模糊行为"; §12.3 在 stable 序列里插入两个双源结论):
|
|
193
170
|
* 1. 读不到本地版本 → local_version_unknown (绝不默认 0.0.0 后继续)
|
|
194
171
|
* 2. 安装方式不支持自动更新 → unsupported_installation (仍会带出 latestVersion)
|
|
195
172
|
* 3. 显式跳过 / 节流 → check_skipped (结论来自缓存)
|
|
196
173
|
* 4. 网络不可达 → offline (绝不显示"已是最新")
|
|
197
|
-
* 5.
|
|
198
|
-
* 6.
|
|
199
|
-
* 7.
|
|
174
|
+
* 5. GitHub 不可达 (**dev 通道**) → github_unavailable (dev 只有这一个源, 拒绝)
|
|
175
|
+
* 6. registry 不可用 / 包不存在 → registry_unavailable
|
|
176
|
+
* 7. 两个源指向不同版本 → cross_check_mismatch (stable, 拒绝)
|
|
177
|
+
* 8. 有新版 (semver / dev 比 sha) → update_available
|
|
178
|
+
* 9. 否则 → up_to_date
|
|
179
|
+
*
|
|
180
|
+
* 两套比较语义**显式分开** (§12.1): stable 比 semver; dev 比 commit sha (不碰 semver)。
|
|
200
181
|
*/
|
|
201
182
|
async function checkForUpdate(opts = {}) {
|
|
202
183
|
const home = opts.home ?? (await Promise.resolve().then(() => __importStar(require('../setup/setup-store.js')))).resolveBolloonHome();
|
|
203
184
|
const prefs = await (0, update_state_js_1.readUpdatePrefs)({ home, env: opts.env });
|
|
204
185
|
const channel = (0, version_info_js_1.resolveUpdateChannel)(opts.channel || prefs.channel);
|
|
186
|
+
const channelKind = (0, version_info_js_1.channelKindOf)(channel);
|
|
205
187
|
const install = opts.installation || (0, version_info_js_1.detectInstallation)({ home });
|
|
206
188
|
const pkg = (0, version_info_js_1.readPackageAt)(install.packageRoot);
|
|
207
|
-
const current = isKnownVersion(pkg?.version) ? String(pkg?.version) : 'unknown';
|
|
189
|
+
const current = (0, version_identity_js_1.isKnownVersion)(pkg?.version) ? String(pkg?.version) : 'unknown';
|
|
190
|
+
const currentBase = (0, version_info_js_1.baseVersionOf)(current);
|
|
191
|
+
/** 磁盘身份里就是 dev 快照的 sha —— 不从状态文件猜 */
|
|
192
|
+
const installedDevSha = (0, version_info_js_1.devShaFromIdentity)(current);
|
|
193
|
+
const installedChannel = installedDevSha ? 'dev' : 'stable';
|
|
208
194
|
const now = new Date().toISOString();
|
|
209
195
|
const state = await (0, update_state_js_1.readUpdateState)(home);
|
|
210
196
|
const persist = opts.persist !== false;
|
|
197
|
+
/** 注入 registry = 受控检查 (GitHub 事实同样只认注入, 不打真网) */
|
|
198
|
+
const controlled = !!opts.registry;
|
|
211
199
|
const base = {
|
|
212
200
|
currentVersion: current,
|
|
213
201
|
channel,
|
|
202
|
+
channelKind,
|
|
214
203
|
source: install.updateSource,
|
|
215
204
|
installMethod: install.method,
|
|
216
205
|
checkedAt: now,
|
|
@@ -231,13 +220,22 @@ async function checkForUpdate(opts = {}) {
|
|
|
231
220
|
lastCheckAt: r.checkedAt,
|
|
232
221
|
lastCheckStatus: r.status,
|
|
233
222
|
lastCheckReason: r.reason,
|
|
223
|
+
// 双源 (§12): 谁装的 / 哪个 sha / 能切回哪 / 两个源各给了什么
|
|
224
|
+
installedChannel: r.installedChannel ?? installedChannel,
|
|
225
|
+
installedDevSha: r.installedDevSha ?? null,
|
|
226
|
+
sourceFacts: r.sourceFacts ?? null,
|
|
227
|
+
switchableTo: r.switchableTo ?? null,
|
|
234
228
|
}, home);
|
|
235
229
|
}
|
|
236
230
|
return r;
|
|
237
231
|
};
|
|
238
232
|
// 1. 本地版本读不到 —— 不猜
|
|
239
|
-
if (!isKnownVersion(current)) {
|
|
240
|
-
return commit({
|
|
233
|
+
if (!(0, version_identity_js_1.isKnownVersion)(current)) {
|
|
234
|
+
return commit({
|
|
235
|
+
...base, status: 'local_version_unknown', latestVersion: null,
|
|
236
|
+
installedChannel: null, installedDevSha: null,
|
|
237
|
+
reason: `读不到 ${install.packageRoot}/package.json 的版本`,
|
|
238
|
+
});
|
|
241
239
|
}
|
|
242
240
|
// 3. 显式跳过 / 节流
|
|
243
241
|
const throttled = !opts.force && state.lastCheckAt
|
|
@@ -253,6 +251,9 @@ async function checkForUpdate(opts = {}) {
|
|
|
253
251
|
status: 'check_skipped',
|
|
254
252
|
cachedStatus: cached,
|
|
255
253
|
latestVersion: state.latestVersion,
|
|
254
|
+
installedChannel, installedDevSha,
|
|
255
|
+
switchableTo: state.switchableTo ?? null,
|
|
256
|
+
sourceFacts: state.sourceFacts ?? null,
|
|
256
257
|
reason: opts.offline
|
|
257
258
|
? `离线模式: 使用缓存结论 (${cached})`
|
|
258
259
|
: `距上次检查不足 ${prefs.checkIntervalHours}h, 使用缓存结论 (${cached})`,
|
|
@@ -262,38 +263,124 @@ async function checkForUpdate(opts = {}) {
|
|
|
262
263
|
await (0, update_state_js_1.writeUpdateState)({ lastCheckReason: r.reason }, home);
|
|
263
264
|
return r;
|
|
264
265
|
}
|
|
265
|
-
//
|
|
266
|
-
const registry =
|
|
266
|
+
// 两个源并行查 (受控检查里 GitHub 事实只认注入)
|
|
267
|
+
const [registry, githubRes] = await Promise.all([
|
|
268
|
+
opts.registry ? Promise.resolve(opts.registry) : queryRegistryDoc(version_info_js_1.PKG_NAME),
|
|
269
|
+
opts.github
|
|
270
|
+
? Promise.resolve(opts.github)
|
|
271
|
+
: (controlled || opts.offline ? Promise.resolve(null) : (0, dual_source_js_1.fetchGithubFacts)()),
|
|
272
|
+
]);
|
|
267
273
|
const latest = registry.ok ? (registry.doc.distTags[(0, version_info_js_1.distTagForChannel)(channel)] || registry.doc.latest) : null;
|
|
268
|
-
const
|
|
269
|
-
const
|
|
274
|
+
const githubReport = githubRes ? (0, dual_source_js_1.toGithubReport)(githubRes) : null;
|
|
275
|
+
const sourceFacts = {
|
|
276
|
+
npm: registry.ok
|
|
277
|
+
? { reachable: true, latest }
|
|
278
|
+
: { reachable: false, latest: null, detail: registry.detail },
|
|
279
|
+
github: githubReport
|
|
280
|
+
? {
|
|
281
|
+
reachable: githubReport.reachable,
|
|
282
|
+
reason: githubReport.reason, detail: githubReport.detail, retryAt: githubReport.retryAt ?? null,
|
|
283
|
+
ref: githubReport.ref, headSha: githubReport.headSha, newestVersion: githubReport.newestVersion,
|
|
284
|
+
releases: githubReport.releases, tags: githubReport.tags,
|
|
285
|
+
}
|
|
286
|
+
: null,
|
|
287
|
+
crossCheck: null,
|
|
288
|
+
};
|
|
289
|
+
const targetPublishedOf = (v) => v && registry.ok ? registry.doc.versions.includes((0, version_info_js_1.baseVersionOf)(v)) : undefined;
|
|
290
|
+
const rollbackSupportedOf = registry.ok ? registry.doc.versions.includes(currentBase) : undefined;
|
|
291
|
+
// 2. 安装方式不支持自动更新 —— 仍把 latest / 可回滚性带出来, 但结论就是"不支持"
|
|
270
292
|
if (install.method === 'development' || install.method === 'unknown' || install.method === 'release-binary') {
|
|
271
293
|
return commit({
|
|
272
294
|
...base, status: 'unsupported_installation', latestVersion: latest,
|
|
295
|
+
installedChannel, installedDevSha, github: githubReport, sourceFacts,
|
|
296
|
+
switchableTo: state.switchableTo ?? null,
|
|
273
297
|
reason: `${install.reason}; 这种安装方式不支持自动更新`,
|
|
274
298
|
targetPublished: targetPublishedOf(latest),
|
|
275
299
|
rollbackSupported: rollbackSupportedOf,
|
|
276
300
|
});
|
|
277
301
|
}
|
|
278
|
-
//
|
|
302
|
+
// ── dev 通道: git ref + commit sha (不碰 semver) ─────────────────────────
|
|
303
|
+
if (channelKind === 'git-ref') {
|
|
304
|
+
/** dev 的"一键回 stable"目标 = registry 上的 latest (§12.4 硬约束 3) */
|
|
305
|
+
const backToStable = { channel: 'stable', source: 'npm', target: latest };
|
|
306
|
+
const devBase = {
|
|
307
|
+
...base, installedChannel, installedDevSha, github: githubReport,
|
|
308
|
+
switchableTo: backToStable, sourceFacts,
|
|
309
|
+
};
|
|
310
|
+
// dev 只有 GitHub 一个源 → 不可达就是**直接拒** (没有第二个源可退, §12.3)
|
|
311
|
+
if (!githubReport || !githubReport.reachable) {
|
|
312
|
+
const reason = githubReport?.reason || (opts.offline ? 'offline' : 'http_error');
|
|
313
|
+
const detail = githubReport?.detail
|
|
314
|
+
|| (opts.offline ? '离线模式: 未查询 GitHub 源' : '本次未查询 GitHub 源 (受控检查未注入 GitHub 事实)');
|
|
315
|
+
return commit({
|
|
316
|
+
...devBase, status: 'github_unavailable', latestVersion: null, dev: null, targetIdentity: null,
|
|
317
|
+
reason: `github_unavailable(${reason}): ${detail} — dev 通道只有 GitHub 一个源, 拒绝安装 (不回落到 stable 装一个 npm 版本)`,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
if (!githubReport.headSha) {
|
|
321
|
+
return commit({
|
|
322
|
+
...devBase, status: 'github_unavailable', latestVersion: null, dev: null, targetIdentity: null,
|
|
323
|
+
reason: `github_unavailable(not_found): GitHub 上没有 refs/heads/master 或读不到 HEAD — 拒绝安装一个身份不明的快照`,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
const dev = (0, dual_source_js_1.compareDevSnapshots)(installedDevSha, githubReport.headSha, githubReport.ref);
|
|
327
|
+
const targetIdentity = (0, version_info_js_1.devIdentity)(currentBase, githubReport.headSha);
|
|
328
|
+
const common = {
|
|
329
|
+
...devBase, dev, targetIdentity, latestVersion: targetIdentity, targetPublished: true,
|
|
330
|
+
rollbackSupported: rollbackSupportedOf,
|
|
331
|
+
};
|
|
332
|
+
if (dev.same)
|
|
333
|
+
return commit({ ...common, status: 'up_to_date', reason: dev.detail });
|
|
334
|
+
return commit({
|
|
335
|
+
...common, status: 'update_available',
|
|
336
|
+
reason: `${dev.detail}${registry.ok ? '' : ` · 注意: registry 不可达 (${registry.detail}) — 回 stable 会需要它`}`,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
// ── stable 通道: npm 是权威, GitHub 是交叉校验 (§12.1) ───────────────────
|
|
340
|
+
// 4/5. registry 拿不到 —— 绝不显示"已是最新" (GitHub 的事实一并摆出来, 方便判断是哪一源的问题)
|
|
279
341
|
if (!registry.ok) {
|
|
280
342
|
return commit({
|
|
281
343
|
...base,
|
|
282
344
|
status: registry.kind === 'offline' ? 'offline' : 'registry_unavailable',
|
|
283
345
|
latestVersion: null,
|
|
284
|
-
|
|
346
|
+
installedChannel, installedDevSha, github: githubReport, sourceFacts,
|
|
347
|
+
switchableTo: state.switchableTo ?? null,
|
|
348
|
+
reason: `${registry.detail}${githubReport ? ` · ${(0, dual_source_js_1.renderGithubReportLine)(githubReport)}` : ''}`,
|
|
285
349
|
});
|
|
286
350
|
}
|
|
351
|
+
const crossCheck = githubRes && githubRes.ok ? (0, dual_source_js_1.crossCheckStable)(latest, githubRes.facts) : null;
|
|
352
|
+
sourceFacts.crossCheck = crossCheck ? { kind: crossCheck.kind, blocking: crossCheck.blocking, detail: crossCheck.detail } : null;
|
|
353
|
+
const switchableTo = installedDevSha
|
|
354
|
+
? { channel: 'stable', source: 'npm', target: latest }
|
|
355
|
+
: { channel: 'dev', source: 'github', target: githubReport?.headSha ? githubReport.headSha.slice(0, 7) : null };
|
|
356
|
+
const stableBase = {
|
|
357
|
+
...base, installedChannel, installedDevSha, github: githubReport, crossCheck, sourceFacts, switchableTo,
|
|
358
|
+
};
|
|
287
359
|
const targetPublished = !!latest && registry.doc.versions.includes(latest);
|
|
288
360
|
const rollbackSupported = rollbackSupportedOf === true;
|
|
289
|
-
//
|
|
290
|
-
if (
|
|
361
|
+
// 7. 两个源指向不同版本 → 拒绝 (不许按 GitHub 的记录去装)
|
|
362
|
+
if (crossCheck?.kind === 'mismatch') {
|
|
363
|
+
return commit({
|
|
364
|
+
...stableBase, status: 'cross_check_mismatch', latestVersion: latest,
|
|
365
|
+
targetPublished, rollbackSupported,
|
|
366
|
+
reason: `cross_check_mismatch: ${crossCheck.detail} (npm latest=${latest} / GitHub ${crossCheck.githubVersion || '无记录'})`,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
// 8/9. 比较: 当前是 dev 快照时**显式**判"切回 stable", 而不是拿 semver 硬比
|
|
370
|
+
if ((0, version_info_js_1.isDevIdentity)(current)) {
|
|
371
|
+
return commit({
|
|
372
|
+
...stableBase, status: 'update_available', latestVersion: latest,
|
|
373
|
+
targetPublished, rollbackSupported,
|
|
374
|
+
reason: `当前装的是 dev 快照 ${current} → 切回 stable 的 ${latest || '未知'} (一键: ${version_info_js_1.DEV_BACK_TO_STABLE_HINT})`,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
if (latest && (0, version_identity_js_1.compareVersions)(currentBase, latest) < 0) {
|
|
291
378
|
return commit({
|
|
292
|
-
...
|
|
293
|
-
reason: `当前 ${current} → 目标 ${latest}`,
|
|
379
|
+
...stableBase, status: 'update_available', latestVersion: latest, targetPublished, rollbackSupported,
|
|
380
|
+
reason: `当前 ${current} → 目标 ${latest}${crossCheck ? ` · 交叉校验: ${crossCheck.kind}` : ''}`,
|
|
294
381
|
});
|
|
295
382
|
}
|
|
296
|
-
return commit({ ...
|
|
383
|
+
return commit({ ...stableBase, status: 'up_to_date', latestVersion: latest, targetPublished, rollbackSupported });
|
|
297
384
|
}
|
|
298
385
|
const WILL_NOT_TOUCH = [
|
|
299
386
|
'~/.bolloon/config.json',
|
|
@@ -415,6 +502,7 @@ async function buildUpdatePlan(opts = {}) {
|
|
|
415
502
|
const home = opts.home ?? (await Promise.resolve().then(() => __importStar(require('../setup/setup-store.js')))).resolveBolloonHome();
|
|
416
503
|
const install = opts.installation || (0, version_info_js_1.detectInstallation)({ home });
|
|
417
504
|
const check = await checkForUpdate({ ...opts, home, persist: false, installation: install });
|
|
505
|
+
const isDev = check.channelKind === 'git-ref';
|
|
418
506
|
const risk = [
|
|
419
507
|
...await collectInstallRisk(install),
|
|
420
508
|
...(opts.workloadRiskOverride || await collectWorkloadRisk()),
|
|
@@ -429,6 +517,33 @@ async function buildUpdatePlan(opts = {}) {
|
|
|
429
517
|
: check.status === 'registry_unavailable' ? `registry 不可用: ${check.reason}`
|
|
430
518
|
: 'npm registry 可达',
|
|
431
519
|
});
|
|
520
|
+
/**
|
|
521
|
+
* GitHub 侧风险 (§12.3) —— **dev 通道阻塞, stable 通道只提醒**:
|
|
522
|
+
* dev 只有 GitHub 一个源, 不可达就没有"退回哪去"这件事; stable 的权威是 npm, GitHub 只是交叉校验源。
|
|
523
|
+
*/
|
|
524
|
+
const gh = check.github;
|
|
525
|
+
risk.push({
|
|
526
|
+
id: 'github_reachable',
|
|
527
|
+
label: 'GitHub 源可达 (dev 通道的源 / stable 的交叉校验源)',
|
|
528
|
+
ok: !gh ? true : gh.reachable,
|
|
529
|
+
blocking: isDev && (!gh || !gh.reachable),
|
|
530
|
+
advisory: !isDev,
|
|
531
|
+
detail: !gh
|
|
532
|
+
? '未查询 GitHub 源 (离线或受控检查未注入)'
|
|
533
|
+
: gh.reachable
|
|
534
|
+
? `可达 · master HEAD ${gh.headSha ? gh.headSha.slice(0, 7) : '未知'} · Release ${gh.releases ?? 0} 个 · Tag ${gh.tags ?? 0} 个`
|
|
535
|
+
: `github_unavailable(${gh.reason}): ${gh.detail || ''}${gh.retryAt ? ` (限流重试 ${gh.retryAt})` : ''}`,
|
|
536
|
+
});
|
|
537
|
+
risk.push({
|
|
538
|
+
id: 'cross_check',
|
|
539
|
+
label: '两个源指向同一版本 (npm ↔ GitHub)',
|
|
540
|
+
ok: check.crossCheck ? check.crossCheck.kind !== 'mismatch' : true,
|
|
541
|
+
blocking: !!check.crossCheck?.blocking,
|
|
542
|
+
advisory: check.crossCheck?.kind === 'missing_record' || check.crossCheck?.kind === 'no_record_at_all',
|
|
543
|
+
detail: !check.crossCheck
|
|
544
|
+
? (isDev ? 'dev 通道不看 npm (GitHub 是唯一源)' : '未做交叉校验 (GitHub 源未查)')
|
|
545
|
+
: check.crossCheck.detail,
|
|
546
|
+
});
|
|
432
547
|
risk.push({
|
|
433
548
|
id: 'target_published',
|
|
434
549
|
label: '目标版本真实存在',
|
|
@@ -444,24 +559,36 @@ async function buildUpdatePlan(opts = {}) {
|
|
|
444
559
|
ok: check.rollbackSupported !== false,
|
|
445
560
|
blocking: false,
|
|
446
561
|
detail: check.rollbackSupported === false
|
|
447
|
-
? `registry 上找不到当前版本 ${check.currentVersion} — 更新失败将无法用 npm 回滚`
|
|
448
|
-
: check.rollbackSupported === true ? `registry 上存在 ${check.currentVersion} (可回滚)` : '未检查 (离线)',
|
|
562
|
+
? `registry 上找不到当前版本 ${(0, version_info_js_1.baseVersionOf)(check.currentVersion)} — 更新失败将无法用 npm 回滚`
|
|
563
|
+
: check.rollbackSupported === true ? `registry 上存在 ${(0, version_info_js_1.baseVersionOf)(check.currentVersion)} (可回滚)` : '未检查 (离线)',
|
|
449
564
|
});
|
|
450
565
|
const blockers = risk.filter((r) => !r.ok && r.blocking).map((r) => `${r.label}: ${r.detail}`);
|
|
451
566
|
const advisories = risk.filter((r) => !r.ok && !r.blocking).map((r) => `${r.label}: ${r.detail}`);
|
|
452
|
-
const target = check.
|
|
453
|
-
? check.latestVersion
|
|
567
|
+
const target = check.status === 'update_available'
|
|
568
|
+
? (check.targetIdentity || check.latestVersion || null)
|
|
454
569
|
: null;
|
|
570
|
+
const warnings = [];
|
|
571
|
+
if (isDev) {
|
|
572
|
+
warnings.push(version_info_js_1.DEV_CHANNEL_WARNING);
|
|
573
|
+
warnings.push(version_info_js_1.DEV_BACK_TO_STABLE_HINT);
|
|
574
|
+
}
|
|
575
|
+
if (check.installedDevSha && !isDev)
|
|
576
|
+
warnings.push(`当前装的是 dev 快照 (commit ${check.installedDevSha}), 本次要切回 stable: 会把 GitHub master 快照换成 registry 上的 latest`);
|
|
455
577
|
return {
|
|
456
578
|
ok: blockers.length === 0 && !!target,
|
|
457
579
|
currentVersion: check.currentVersion,
|
|
458
580
|
targetVersion: target,
|
|
459
581
|
channel: check.channel,
|
|
582
|
+
channelKind: check.channelKind || (0, version_info_js_1.channelKindOf)(check.channel),
|
|
583
|
+
targetIdentity: check.targetIdentity ?? null,
|
|
584
|
+
warnings,
|
|
460
585
|
installMethod: check.installMethod,
|
|
461
586
|
source: check.source,
|
|
462
587
|
installDir: install.installDir,
|
|
463
588
|
needsRestart: !!target,
|
|
464
|
-
willUpdate: target && install.method === 'npm-global'
|
|
589
|
+
willUpdate: target && install.method === 'npm-global'
|
|
590
|
+
? (isDev ? [`GitHub master 快照 ${check.github?.headSha?.slice(0, 7) || ''} (身份 ${target})`, version_info_js_1.PKG_NAME] : [version_info_js_1.PKG_NAME, version_info_js_1.CONSTRAINT_PKG_NAME])
|
|
591
|
+
: [],
|
|
465
592
|
willNotTouch: WILL_NOT_TOUCH,
|
|
466
593
|
risk, blockers, advisories,
|
|
467
594
|
strategies: ['now', 'wait', 'cancel'],
|
|
@@ -471,10 +598,18 @@ async function buildUpdatePlan(opts = {}) {
|
|
|
471
598
|
}
|
|
472
599
|
function renderUpdatePlan(plan) {
|
|
473
600
|
const L = [];
|
|
474
|
-
L.push(`当前版本: ${plan.currentVersion}`);
|
|
601
|
+
L.push(`当前版本: ${plan.currentVersion}${plan.check.installedDevSha ? ` (dev 快照 commit ${plan.check.installedDevSha})` : ''}`);
|
|
475
602
|
L.push(`目标版本: ${plan.targetVersion || '(无可用更新)'}`);
|
|
476
603
|
L.push(`安装方式: ${plan.installMethod}`);
|
|
477
|
-
L.push(`更新通道: ${plan.channel} (
|
|
604
|
+
L.push(`更新通道: ${plan.channel} (比较语义: ${plan.channelKind})`);
|
|
605
|
+
L.push(`更新来源: ${plan.source}${plan.channelKind === 'git-ref' ? ' · GitHub master (唯一源)' : ' · npm registry (权威) + GitHub (交叉校验)'}`);
|
|
606
|
+
L.push((0, dual_source_js_1.renderGithubReportLine)(plan.check.github));
|
|
607
|
+
if (plan.check.crossCheck)
|
|
608
|
+
L.push(`交叉校验: ${plan.check.crossCheck.kind}${plan.check.crossCheck.blocking ? ' (阻塞)' : ''} — ${plan.check.crossCheck.detail}`);
|
|
609
|
+
if (plan.check.dev)
|
|
610
|
+
L.push(`dev 快照: ${plan.check.dev.detail}`);
|
|
611
|
+
for (const w of plan.warnings)
|
|
612
|
+
L.push(w);
|
|
478
613
|
L.push(`安装目录: ${plan.installDir}`);
|
|
479
614
|
L.push('');
|
|
480
615
|
L.push('将更新:');
|
|
@@ -505,7 +640,9 @@ function renderUpdatePlan(plan) {
|
|
|
505
640
|
L.push(` 建议策略: 等当前 Run 结束后更新 (bolloon update --now --wait)`);
|
|
506
641
|
}
|
|
507
642
|
L.push('');
|
|
508
|
-
L.push(
|
|
643
|
+
L.push(`可选: 立即更新 (bolloon update --now --channel ${plan.channel}) / 等当前 Run 结束 (bolloon update --now --wait --channel ${plan.channel}) / 取消`);
|
|
644
|
+
if (plan.channelKind === 'git-ref')
|
|
645
|
+
L.push(`一键回稳定版: bolloon update now --channel stable`);
|
|
509
646
|
L.push(`默认: ${plan.defaultStrategy === 'wait' ? '等待当前 Run 结束后更新' : '立即更新'}`);
|
|
510
647
|
return L.join('\n');
|
|
511
648
|
}
|
|
@@ -570,21 +707,44 @@ async function applyUpdate(opts = {}) {
|
|
|
570
707
|
};
|
|
571
708
|
const plan = await buildUpdatePlan({
|
|
572
709
|
home, env: opts.env, force: true, installation: install, registry: opts.registry,
|
|
710
|
+
github: opts.github, channel: opts.channel,
|
|
573
711
|
workloadRiskOverride: opts.workloadRiskOverride,
|
|
574
712
|
});
|
|
575
713
|
const from = plan.currentVersion;
|
|
576
714
|
const to = plan.targetVersion || plan.currentVersion;
|
|
715
|
+
const isDev = plan.channelKind === 'git-ref';
|
|
716
|
+
/** dev 的目标 commit (身份就是它, 不是版本号) */
|
|
717
|
+
const devSha = plan.check.dev?.headSha || plan.check.github?.headSha || null;
|
|
577
718
|
const record = async (r, historyStatus) => {
|
|
578
719
|
const rec = { at: new Date().toISOString(), from: r.from, to: r.to, status: historyStatus, durationMs: r.durationMs, reason: r.reason };
|
|
579
720
|
// 记账失败**不能**把"更新其实成功了"变成向上抛异常 (真被测试 teardown 竞态抓到过:
|
|
580
721
|
// HOME 在写到一半时消失 → rename ENOENT)。失败就如实记进 reason, 结果照常返回。
|
|
581
722
|
try {
|
|
582
723
|
await (0, update_state_js_1.appendUpdateHistory)(rec, home);
|
|
724
|
+
/**
|
|
725
|
+
* 双源落盘 (§12.4 硬约束 2): 装成 dev 时记下 `{channel, devSha, devRef, checkedAt}`;
|
|
726
|
+
* 切回 stable 时把安装通道改回 stable 并把 dev 记录保留成"上次那个 dev 快照"。
|
|
727
|
+
*/
|
|
728
|
+
const dualSource = isDev && r.ok
|
|
729
|
+
? {
|
|
730
|
+
installedChannel: 'dev',
|
|
731
|
+
installedDevSha: devSha ? devSha.slice(0, 7) : (0, version_info_js_1.devShaFromIdentity)(r.to),
|
|
732
|
+
devSha: devSha || null,
|
|
733
|
+
devRef: `refs/heads/master`,
|
|
734
|
+
devCheckedAt: rec.at,
|
|
735
|
+
switchableTo: { channel: 'stable', source: 'npm', target: (0, version_info_js_1.baseVersionOf)(r.from) },
|
|
736
|
+
}
|
|
737
|
+
: {};
|
|
738
|
+
const backToStable = !isDev && r.ok
|
|
739
|
+
? { installedChannel: 'stable', installedDevSha: null, switchableTo: { channel: 'dev', source: 'github', target: devSha ? devSha.slice(0, 7) : null } }
|
|
740
|
+
: {};
|
|
583
741
|
await (0, update_state_js_1.writeUpdateState)({
|
|
584
742
|
lastUpdate: rec,
|
|
585
743
|
lastFailure: r.ok ? null : { at: rec.at, stage: r.failedAt || r.stage, reason: r.reason || '未知原因' },
|
|
586
744
|
needsRestart: r.ok ? r.needsRestart : false,
|
|
587
745
|
currentVersion: r.from,
|
|
746
|
+
...dualSource,
|
|
747
|
+
...backToStable,
|
|
588
748
|
}, home);
|
|
589
749
|
}
|
|
590
750
|
catch (e) {
|
|
@@ -594,8 +754,16 @@ async function applyUpdate(opts = {}) {
|
|
|
594
754
|
return r;
|
|
595
755
|
};
|
|
596
756
|
if (!plan.targetVersion) {
|
|
597
|
-
|
|
598
|
-
|
|
757
|
+
/**
|
|
758
|
+
* §12.5 的核心分流: **源不可达 / 版本不存在 → 拒绝并说清**,
|
|
759
|
+
* 绝不把"没查到"说成"已是最新"。
|
|
760
|
+
*/
|
|
761
|
+
const refused = version_identity_js_1.REFUSED_STATUSES.includes(plan.check.status);
|
|
762
|
+
await stage('blocked', refused ? `拒绝更新 (${plan.check.status})` : '没有可执行的更新');
|
|
763
|
+
const reason = refused
|
|
764
|
+
? `拒绝更新 (${plan.check.status}): ${plan.check.reason || plan.blockers[0] || '源不可达 / 版本不存在'}`
|
|
765
|
+
: (plan.blockers[0] || '已是最新, 无需更新');
|
|
766
|
+
return record({ stage: 'blocked', ok: false, from, to, durationMs: Date.now() - started, reason, needsRestart: false }, 'blocked');
|
|
599
767
|
}
|
|
600
768
|
if (plan.blockers.length > 0) {
|
|
601
769
|
await stage('blocked', plan.blockers.join('; '));
|
|
@@ -612,47 +780,84 @@ async function applyUpdate(opts = {}) {
|
|
|
612
780
|
if (!lock.ok) {
|
|
613
781
|
const held = lock.heldBy;
|
|
614
782
|
await stage('blocked', `已有更新进程 (pid ${held?.pid})`);
|
|
615
|
-
return record({ stage: 'blocked', ok: false, from, to, durationMs: Date.now() - started, reason:
|
|
783
|
+
return record({ stage: 'blocked', ok: false, from, to, durationMs: Date.now() - started, reason: `另一个更新进程持有锁 (pid ${held?.pid}, ${held?.at})`, needsRestart: false }, 'blocked');
|
|
616
784
|
}
|
|
617
785
|
const tmpRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'bolloon-update-'));
|
|
618
786
|
const runNpm = opts.runNpm || defaultRunNpm;
|
|
619
787
|
let stagedTarball = null;
|
|
788
|
+
/** 切换时交给 npm 的安装目标: stable = `<包>@<semver>`; dev = 本地 dev 快照 tarball */
|
|
789
|
+
let switchSpec = `${version_info_js_1.PKG_NAME}@${to}`;
|
|
790
|
+
/**
|
|
791
|
+
* 实际装入的身份。dev 的身份来自**源码树的 package.json + master sha**, 可能与检查阶段的预测
|
|
792
|
+
* 略有差别 (源码树的版本号才是真的) → 以产物为准, 校验/验证/记账都用它, 不用预测值糊过去。
|
|
793
|
+
*/
|
|
794
|
+
let effectiveTo = to;
|
|
620
795
|
try {
|
|
621
|
-
// 1.
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
796
|
+
// 1. 取产物到临时目录 (不动现有安装)
|
|
797
|
+
if (isDev) {
|
|
798
|
+
// dev: 源是 GitHub master 的 git ref, 身份是 commit sha (§12.1)
|
|
799
|
+
if (!devSha) {
|
|
800
|
+
throw Object.assign(new Error('dev 通道没有拿到 master HEAD 的 commit sha — 拒绝安装一个身份不明的快照'), { stage: 'downloading' });
|
|
801
|
+
}
|
|
802
|
+
await stage('downloading', `取 GitHub master @ ${devSha.slice(0, 7)} (${version_info_js_1.DEV_BACK_TO_STABLE_HINT})`);
|
|
803
|
+
const snap = opts.devSnapshot
|
|
804
|
+
? { ok: true, tarball: opts.devSnapshot.tarball, identity: opts.devSnapshot.identity, sha: opts.devSnapshot.sha, source: 'injected', built: false, dir: '' }
|
|
805
|
+
: await (0, dual_source_js_1.prepareDevSnapshot)({
|
|
806
|
+
sha: devSha, workDir: tmpRoot, env: opts.env,
|
|
807
|
+
runNpm,
|
|
808
|
+
onStage: (s, d) => { try {
|
|
809
|
+
opts.onStage?.(s, d);
|
|
810
|
+
}
|
|
811
|
+
catch { /* 回调失败不影响流程 */ } },
|
|
812
|
+
});
|
|
813
|
+
if (!snap.ok) {
|
|
814
|
+
const kind = 'kind' in snap ? String(snap.kind) : 'dev_source_failed';
|
|
815
|
+
const reason = 'reason' in snap ? String(snap.reason) : '';
|
|
816
|
+
const detail = 'detail' in snap ? String(snap.detail) : '未知原因';
|
|
817
|
+
throw Object.assign(new Error(`${kind}/${reason}: ${detail}`), { stage: 'downloading' });
|
|
818
|
+
}
|
|
819
|
+
stagedTarball = snap.tarball;
|
|
820
|
+
switchSpec = snap.tarball;
|
|
821
|
+
effectiveTo = snap.identity;
|
|
822
|
+
await stage('staged', `dev 快照身份 ${snap.identity} (来源 ${snap.source}${snap.built ? ', 已现场构建' : ''})`);
|
|
626
823
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
824
|
+
else {
|
|
825
|
+
await stage('downloading', `npm pack ${version_info_js_1.PKG_NAME}@${to}`);
|
|
826
|
+
const packed = runNpm(['pack', `${version_info_js_1.PKG_NAME}@${to}`, '--pack-destination', tmpRoot, '--loglevel=error'], tmpRoot);
|
|
827
|
+
if (packed.code !== 0) {
|
|
828
|
+
throw Object.assign(new Error(`下载 ${version_info_js_1.PKG_NAME}@${to} 失败: ${(packed.stderr || packed.stdout).trim().slice(0, 400)}`), { stage: 'downloading' });
|
|
829
|
+
}
|
|
830
|
+
const tarballs = (await fsp.readdir(tmpRoot)).filter((f) => f.endsWith('.tgz'));
|
|
831
|
+
if (tarballs.length === 0)
|
|
832
|
+
throw Object.assign(new Error('npm pack 没有产出 tarball'), { stage: 'downloading' });
|
|
833
|
+
stagedTarball = path.join(tmpRoot, tarballs[0]);
|
|
834
|
+
}
|
|
835
|
+
// 2. 解压 + 校验包内容 (版本 + 入口) —— 两个源共用同一条校验
|
|
836
|
+
if (!isDev)
|
|
837
|
+
await stage('staged', `解压并校验 ${path.basename(stagedTarball)}`);
|
|
633
838
|
const extractDir = path.join(tmpRoot, 'extract');
|
|
634
839
|
await fsp.mkdir(extractDir, { recursive: true });
|
|
635
840
|
const tar = (0, child_process_1.spawnSync)('tar', ['-xzf', stagedTarball, '-C', extractDir], { encoding: 'utf-8', timeout: 120_000 });
|
|
636
841
|
if (tar.status !== 0)
|
|
637
842
|
throw Object.assign(new Error(`解压失败: ${(tar.stderr || '').slice(0, 300)}`), { stage: 'staged' });
|
|
638
|
-
const stagedCheck = validateStagedPackage(path.join(extractDir, 'package'),
|
|
843
|
+
const stagedCheck = validateStagedPackage(path.join(extractDir, 'package'), effectiveTo);
|
|
639
844
|
if (!stagedCheck.ok)
|
|
640
845
|
throw Object.assign(new Error(`目标包校验失败: ${stagedCheck.detail}`), { stage: 'staged' });
|
|
641
|
-
// 3. 切换 (npm 完成替换; 旧版本在替换成功前不会消失)
|
|
642
|
-
await stage('switching', `npm install -g ${version_info_js_1.PKG_NAME}@${to}`);
|
|
643
|
-
const installed = runNpm(['install', '-g',
|
|
846
|
+
// 3. 切换 (npm 完成替换; 旧版本在替换成功前不会消失) —— 两个源共用同一条替换路径
|
|
847
|
+
await stage('switching', isDev ? `npm install -g <dev 快照 ${path.basename(switchSpec)}>` : `npm install -g ${version_info_js_1.PKG_NAME}@${to}`);
|
|
848
|
+
const installed = runNpm(['install', '-g', switchSpec, '--no-fund', '--no-audit', '--loglevel=error'], os.tmpdir());
|
|
644
849
|
if (installed.code !== 0) {
|
|
645
850
|
throw Object.assign(new Error(`npm 安装失败: ${(installed.stderr || installed.stdout).trim().slice(0, 400)}`), { stage: 'switching' });
|
|
646
851
|
}
|
|
647
852
|
// 4. 验证: 磁盘版本 + 新入口真能启动 + 健康检查
|
|
648
853
|
await stage('verifying', '核对磁盘版本并试启新入口');
|
|
649
854
|
const verify = opts.verifyInstall || defaultVerifyInstall;
|
|
650
|
-
const ok = await verify(
|
|
855
|
+
const ok = await verify(effectiveTo, home);
|
|
651
856
|
if (!ok)
|
|
652
|
-
throw Object.assign(new Error(`切换后验证失败: 新版本 ${
|
|
653
|
-
await stage('succeeded', `已更新到 ${
|
|
857
|
+
throw Object.assign(new Error(`切换后验证失败: 新版本 ${effectiveTo} 未能正确启动`), { stage: 'verifying' });
|
|
858
|
+
await stage('succeeded', `已更新到 ${effectiveTo}`);
|
|
654
859
|
const out = {
|
|
655
|
-
stage: 'succeeded', ok: true, from, to, durationMs: Date.now() - started, needsRestart: true, stagedTarball,
|
|
860
|
+
stage: 'succeeded', ok: true, from, to: effectiveTo, durationMs: Date.now() - started, needsRestart: true, stagedTarball,
|
|
656
861
|
};
|
|
657
862
|
await record(out, 'succeeded');
|
|
658
863
|
return out;
|
|
@@ -662,25 +867,26 @@ async function applyUpdate(opts = {}) {
|
|
|
662
867
|
const reason = e?.message || String(e);
|
|
663
868
|
await stage(failureStage, reason);
|
|
664
869
|
// 失败处理: 保留旧版本 → 能回滚就回滚 → 写原因 → 释放锁
|
|
870
|
+
const fromBase = (0, version_info_js_1.baseVersionOf)(from);
|
|
665
871
|
let finalStage = 'failed';
|
|
666
872
|
let finalReason = reason;
|
|
667
873
|
try {
|
|
668
874
|
const nowVersion = installedVersionOnDisk(install.packageRoot);
|
|
669
|
-
if (install.method === 'npm-global' && nowVersion && nowVersion !== from && nowVersion !==
|
|
670
|
-
await stage('rolled_back', `检测到半更新 (磁盘版本 ${nowVersion}), 回滚到 ${
|
|
671
|
-
const rb = runNpm(['install', '-g', `${version_info_js_1.PKG_NAME}@${
|
|
875
|
+
if (install.method === 'npm-global' && nowVersion && nowVersion !== from && nowVersion !== effectiveTo) {
|
|
876
|
+
await stage('rolled_back', `检测到半更新 (磁盘版本 ${nowVersion}), 回滚到 ${fromBase}`);
|
|
877
|
+
const rb = runNpm(['install', '-g', `${version_info_js_1.PKG_NAME}@${fromBase}`, '--no-fund', '--no-audit', '--loglevel=error'], os.tmpdir());
|
|
672
878
|
const after = installedVersionOnDisk(install.packageRoot);
|
|
673
|
-
if (rb.code === 0 && after ===
|
|
879
|
+
if (rb.code === 0 && after === fromBase) {
|
|
674
880
|
finalStage = 'rolled_back';
|
|
675
|
-
finalReason = `${reason}; 已回滚到 ${
|
|
881
|
+
finalReason = `${reason}; 已回滚到 ${fromBase}`;
|
|
676
882
|
}
|
|
677
883
|
else {
|
|
678
|
-
finalReason = `${reason}; 回滚也未成功 (磁盘版本 ${after || 'unknown'}) — 请手动: npm install -g ${version_info_js_1.PKG_NAME}@${
|
|
884
|
+
finalReason = `${reason}; 回滚也未成功 (磁盘版本 ${after || 'unknown'}) — 请手动: npm install -g ${version_info_js_1.PKG_NAME}@${fromBase}`;
|
|
679
885
|
}
|
|
680
886
|
}
|
|
681
|
-
else if (nowVersion ===
|
|
887
|
+
else if (nowVersion === effectiveTo && failureStage === 'verifying') {
|
|
682
888
|
// 装上了但验证没过: 保留新版本但如实报失败, 并给出回滚指令
|
|
683
|
-
finalReason = `${reason}; 磁盘版本已是 ${
|
|
889
|
+
finalReason = `${reason}; 磁盘版本已是 ${effectiveTo}, 如需回退: npm install -g ${version_info_js_1.PKG_NAME}@${fromBase}`;
|
|
684
890
|
}
|
|
685
891
|
}
|
|
686
892
|
catch (e2) {
|
|
@@ -721,12 +927,26 @@ async function readUpdateStatus(opts = {}) {
|
|
|
721
927
|
const st = await (0, update_state_js_1.readUpdateState)(home);
|
|
722
928
|
const prefs = await (0, update_state_js_1.readUpdatePrefs)({ home, env: opts.env });
|
|
723
929
|
const lock = (0, update_state_js_1.readUpdateLock)(home);
|
|
724
|
-
const install = (0, version_info_js_1.detectInstallation)({ home });
|
|
930
|
+
const install = opts.installation || (0, version_info_js_1.detectInstallation)({ home });
|
|
725
931
|
const pkg = (0, version_info_js_1.readPackageAt)(install.packageRoot);
|
|
932
|
+
const currentVersion = (0, version_identity_js_1.isKnownVersion)(pkg?.version) ? String(pkg?.version) : st.currentVersion;
|
|
933
|
+
const installedDevSha = (0, version_info_js_1.devShaFromIdentity)(currentVersion);
|
|
934
|
+
const channel = (0, version_info_js_1.resolveUpdateChannel)(opts.channel || prefs.channel);
|
|
726
935
|
return {
|
|
727
|
-
currentVersion
|
|
936
|
+
currentVersion,
|
|
937
|
+
// 安装通道从**磁盘身份**判定 (不从状态文件猜); 状态里的 dev 记录另外带出来做历史
|
|
938
|
+
installedChannel: installedDevSha ? 'dev' : 'stable',
|
|
939
|
+
installedDevSha,
|
|
940
|
+
switchableTo: st.switchableTo ?? (installedDevSha
|
|
941
|
+
? { channel: 'stable', source: 'npm', target: st.latestVersion }
|
|
942
|
+
: { channel: 'dev', source: 'github', target: null }),
|
|
943
|
+
lastDevSha: st.devSha ?? null,
|
|
944
|
+
devRef: st.devRef ?? null,
|
|
945
|
+
devCheckedAt: st.devCheckedAt ?? null,
|
|
946
|
+
sourceFacts: st.sourceFacts ?? null,
|
|
728
947
|
latestVersion: st.latestVersion,
|
|
729
|
-
channel
|
|
948
|
+
channel,
|
|
949
|
+
channelKind: (0, version_info_js_1.channelKindOf)(channel),
|
|
730
950
|
installMethod: install.method,
|
|
731
951
|
installDir: install.installDir,
|
|
732
952
|
entryPath: install.entryPath,
|
|
@@ -765,6 +985,10 @@ async function updateSummaryFor(home) {
|
|
|
765
985
|
latestVersion: st.latestVersion,
|
|
766
986
|
lastUpdate: st.lastUpdate ? { at: st.lastUpdate.at, from: st.lastUpdate.from, to: st.lastUpdate.to, status: st.lastUpdate.status, reason: st.lastUpdate.reason } : null,
|
|
767
987
|
needsRestart: st.needsRestart,
|
|
988
|
+
// 双源 (§12.5): "我装的是哪个源 / 哪个 commit / 能切回哪" 从同一份状态读
|
|
989
|
+
installedChannel: st.installedChannel ?? null,
|
|
990
|
+
installedDevSha: st.installedChannel === 'dev' ? (st.installedDevSha ?? null) : null,
|
|
991
|
+
switchableTo: st.switchableTo ?? null,
|
|
768
992
|
};
|
|
769
993
|
}
|
|
770
994
|
//# sourceMappingURL=update-manager.js.map
|