dsh-opencode-palette 1.7.1 → 2.0.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.
@@ -0,0 +1,42 @@
1
+ {
2
+ "version": 1,
3
+ "pluginId": "dsh-opencode-palette",
4
+ "counts": { "resident": 7, "ondemand": 0, "selfmon": 0 },
5
+ "events": {
6
+ "host.call": {
7
+ "level": "info",
8
+ "kind": "resident",
9
+ "fields": ["method", "latencyMs", "ok", "kind", "pluginId"]
10
+ },
11
+ "host.call.fail": {
12
+ "level": "warn",
13
+ "kind": "resident",
14
+ "fields": ["method", "kind", "errorHash", "pluginId"]
15
+ },
16
+ "update.install.exec": {
17
+ "level": "info",
18
+ "kind": "resident",
19
+ "fields": ["route", "ok", "exitCode", "durationMs", "pluginId"]
20
+ },
21
+ "log.forward.summary": {
22
+ "level": "warn",
23
+ "kind": "resident",
24
+ "fields": ["droppedDelta", "totalDropped", "reason", "windowMs"]
25
+ },
26
+ "log.switch.watchdog": {
27
+ "level": "warn",
28
+ "kind": "resident",
29
+ "fields": ["op", "timeoutMs", "stage"]
30
+ },
31
+ "log.export.fail": {
32
+ "level": "warn",
33
+ "kind": "resident",
34
+ "fields": ["op", "reason", "errorHash"]
35
+ },
36
+ "host.channel.fail": {
37
+ "level": "error",
38
+ "kind": "resident",
39
+ "fields": ["stage", "path", "reason"]
40
+ }
41
+ }
42
+ }
package/lib/index.js CHANGED
@@ -1,13 +1,161 @@
1
1
  /**
2
- * dsh-opencode-palette v1.7.1 — 宿主半(no-op)
2
+ * runtime/host.mjs — dsh-opencode-palette 宿主半(构建产物为 package/lib/index.js)
3
3
  *
4
- * 主题的全部工作在浏览器端(./client.js)完成:
5
- * - 38 个 opencode 主题(37 内置 JSON:33 TUI + 4 桌面 2.0,另加 system)经数据驱动管线解析
6
- * - theme.overrideTokens 覆盖注册 token + <style> 层 CSS 变量/元素规则
7
- * 宿主半只保证 loader 条目可解析、可挂载。
4
+ * 三件事:
5
+ * 1. 日志落盘:dsh-log 的宿主引擎,落 <DSH_HOME>/logs/dsh-opencode-palette/YYYY-MM-DD.log;
6
+ * 2. 更新三电话:dsh-plugin-update 的 createHostUpdate(查状态 / 查新版 / 装更新);
7
+ * 3. 面板通道:自注册精确 HTTP 路由 /api/opencode-palette,把电话分发给浏览器半。
8
+ *
9
+ * 通道为什么不用 harness.handle / host.call:那套是 cordis_define 动态包专属的沙箱方言,
10
+ * 已安装插件的宿主半拿不到 harness、浏览器半拿不到 host(证据见地图 #14 的 #16 票结论)。
11
+ * 走 DSH 公开的 /api 载体:宿主 connection.fetch.register 注册精确路由,客户端
12
+ * connection.rpc.call('/api', 'opencode-palette', { method, payload })——与 deck、im-companion 同构。
13
+ *
14
+ * 更新包为什么走 ./vendor/ 相对路径而不是 import 'dsh-plugin-update':
15
+ * 更新包的读取器用 containingPackage(import.meta.url, 目标包名) 从【自己的文件位置】往上找目标包。
16
+ * 以 npm 依赖形态安装时它住在 <profile>/node_modules/dsh-plugin-update,往上永远找不到
17
+ * dsh-opencode-palette,于是:运行版本读不到(host 入口直接抛 unknown-profile),且环境判定里的
18
+ * sameLoadedPackage 恒为假 → blockedReason 恒为 installation-changed → 一键升级永远不可用。
19
+ * 把它的 dist 原样放进本包 lib/vendor/ 下,import.meta.url 就落在本包内,两条判定都成立。
20
+ * vendor 副本由构建从 npm 包复制(见 scripts/build-client.mjs),门禁断言与 npm 包逐字节一致。
8
21
  */
9
- export const name = "dsh-opencode-palette"
22
+ import { readFileSync } from 'node:fs'
23
+ import { mkdir, readdir, readFile, unlink, writeFile } from 'node:fs/promises'
24
+ import { homedir } from 'node:os'
25
+ import { dirname, join } from 'node:path'
26
+ import { fileURLToPath } from 'node:url'
27
+ import { createHostLog, registerHostLogPhones } from 'dsh-log/host'
28
+ import { createHostUpdate } from './vendor/dsh-plugin-update/host.js'
29
+ import { PLUGIN_ID, PHONE_PREFIX, ROUTE_PATH, TARGET_PACKAGE_NAME } from './channel.mjs'
30
+
31
+ export const name = PLUGIN_ID
32
+ // 通道要 connection;subprocess / desktopProfiles / desktopPnpm 由更新包自己按需取(它内部注入),
33
+ // 不写进顶层 inject:普通 DSH 没有 desktop* 服务,写进顶层会让插件在普通宿主里装配失败。
34
+ export const inject = ['connection']
35
+
36
+ // 落点:<DSH_HOME>/logs/dsh-opencode-palette/YYYY-MM-DD.log,开关 <DSH_HOME>/logs/log-switch-dsh-opencode-palette.json
37
+ // (与同机 dsh-prompt 的既成落点同构;DSH 不提供 cache 目录 API,缓存根只有 <DSH_HOME>)
38
+ const LOG_DIR_NAME = PLUGIN_ID
39
+ const SWITCH_FILE_NAME = 'log-switch-' + PLUGIN_ID + '.json'
40
+ const RPC_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/
41
+ const ENDPOINT_PATTERN = /^[A-Za-z0-9_$.-]+$/
42
+
43
+ const PACKAGE_DIR = dirname(dirname(fileURLToPath(import.meta.url)))
44
+
45
+ /** 本进程正在跑的版本:启动那一刻读自己的 package.json(= 加载进内存的那份代码的版本)。
46
+ * 不能用「每次调用现读磁盘」的写法:装完新版后磁盘版本会变,进程里跑的却还是旧的,
47
+ * 现读会让 pending-restart 永远判不出来(更新包据此决定是否提示「待重启」)。 */
48
+ export const runningVersion = readRunningVersion()
49
+ function readRunningVersion() {
50
+ try {
51
+ const manifest = JSON.parse(readFileSync(join(PACKAGE_DIR, 'package.json'), 'utf8'))
52
+ return typeof manifest.version === 'string' ? manifest.version : '0.0.0'
53
+ } catch {
54
+ return '0.0.0'
55
+ }
56
+ }
57
+
58
+ /** 事件清单:随包落 lib/event-list.json(构建从 runtime/event-list.json 复制)。读不到就用 null(日志照记,只是没有白名单元数据)。 */
59
+ export function readEventList() {
60
+ try {
61
+ return JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), 'event-list.json'), 'utf8'))
62
+ } catch {
63
+ return null
64
+ }
65
+ }
66
+
67
+ /** DSH 家目录:$DSH_HOME(去空白后非空)否则 ~/.dsh,与 @deepseek-ai/dsh-home-paths 同一规则
68
+ * (更新包的 defaultHomeDir 也是同一条;tests/update-integration.test.mjs 里有两边一致的断言)。 */
69
+ export function dshHomeDir(env, osHome) {
70
+ const raw = env && typeof env.DSH_HOME === 'string' && env.DSH_HOME.trim() ? env.DSH_HOME : join(osHome, '.dsh')
71
+ return raw === '~' ? osHome : raw.replace(/^~[\\/]/, osHome + '/')
72
+ }
73
+
74
+ /** 给日志包的文件服务(INTEGRATION.md 步骤 2 的形状表):六个方法全走 node:fs/promises。
75
+ * 为什么不直接传 ctx.get('fs'):DSH 的 fs 服务没有 mkdir / unlink,且部署的是沙箱实现
76
+ * (workspace-write 只允许写 workspace 内),写用户家目录会被 FS_SANDBOX_DENIED 拒掉。 */
77
+ export function createLogFileService(readTextImpl, writeTextImpl, mkdirImpl, unlinkImpl, listDirImpl) {
78
+ return {
79
+ resolve: function (pathStr) { return String(pathStr) },
80
+ readText: function (target) { return readTextImpl(String(target), 'utf8') },
81
+ writeText: function (target, text) { return writeTextImpl(String(target), String(text), 'utf8') },
82
+ mkdir: function (dir) { return mkdirImpl(String(dir), { recursive: true }) },
83
+ unlink: function (target) { return unlinkImpl(String(target)) },
84
+ listDir: function (dirTarget) { return listDirImpl(String(dirTarget)) },
85
+ }
86
+ }
87
+
88
+ /** 宿主半装配入口:建日志库 → 建更新电话 → 合成电话表 → 注册通道。 */
89
+ export function apply(ctx) {
90
+ const homeDir = dshHomeDir(process.env, homedir())
91
+ const eventList = readEventList()
92
+
93
+ const hostLog = createHostLog(
94
+ {
95
+ fs: createLogFileService(readFile, writeFile, mkdir, unlink, readdir),
96
+ timer: ctx.get('timer'),
97
+ getCacheDir: function () { return join(homeDir, 'logs') },
98
+ getPlatform: function () { return { os: process.platform, path: { join: join }, fs: null } },
99
+ DEFAULT_CWD: process.cwd(),
100
+ },
101
+ { pluginId: PLUGIN_ID, prefix: PHONE_PREFIX, logDirName: LOG_DIR_NAME, switchFileName: SWITCH_FILE_NAME, eventList: eventList }
102
+ )
103
+
104
+ const registry = new Map()
105
+ registerHostLogPhones(registry, hostLog)
106
+
107
+ const update = createHostUpdate(
108
+ { ctx: ctx, logCtx: { fire: function (level, event, fields) { return hostLog.store.log(level, event, fields) } }, readerOverrides: { runningVersion: runningVersion } },
109
+ { pluginId: PLUGIN_ID, prefix: PHONE_PREFIX, targetPackageName: TARGET_PACKAGE_NAME }
110
+ )
111
+ for (const phoneName of Object.keys(update.handlers)) registry.set(phoneName, update.handlers[phoneName])
112
+
113
+ const fail = function (stage, reason) {
114
+ try { hostLog.store.log('error', 'host.channel.fail', { stage: stage, path: ROUTE_PATH, reason: String(reason).slice(0, 120) }) } catch (e) { /* 忽略 */ }
115
+ }
116
+
117
+ const dispatch = async function (phoneName, args) {
118
+ const handler = registry.get(phoneName)
119
+ if (typeof handler !== 'function') {
120
+ return { ok: false, error: { code: 'internal', message: 'unknown phone: ' + phoneName, details: {} } }
121
+ }
122
+ try {
123
+ return { ok: true, value: await handler(args === null || args === undefined ? {} : args) }
124
+ } catch (e) {
125
+ fail('dispatch', String((e && e.message) || e))
126
+ return { ok: false, error: { code: 'internal', message: String((e && e.message) || e), details: {} } }
127
+ }
128
+ }
129
+
130
+ const routeFetch = async function (request) {
131
+ if (!request || request.method !== 'POST') return new Response('method not allowed', { status: 405 })
132
+ let body = null
133
+ try { body = await request.json() } catch (e) { return new Response('body is not JSON', { status: 400 }) }
134
+ const rpcId = typeof (body && body.rpcId) === 'string' && RPC_ID_PATTERN.test(body.rpcId) ? body.rpcId : 'invalid-request'
135
+ const reply = function (result) { return Response.json({ type: 'server-response', rpcId: rpcId, result: result }) }
136
+ const reject = function (why) { return reply({ ok: false, error: { code: 'gateway/bad-request', message: why, details: {} } }) }
137
+ if (!body || body.type !== 'client-request') return reject('invalid client-request message')
138
+ const call = body.payload
139
+ if (call === null || typeof call !== 'object') return reject('missing call payload')
140
+ const phoneName = call.method
141
+ if (typeof phoneName !== 'string' || !ENDPOINT_PATTERN.test(phoneName)) return reject('invalid phone name')
142
+ const pathname = String((request.url && request.url.split('?')[0]) || '')
143
+ if (!pathname.endsWith(ROUTE_PATH)) return reject('request path is not on route ' + ROUTE_PATH)
144
+ return reply(await dispatch(phoneName, call.payload))
145
+ }
10
146
 
11
- export function apply() {
12
- // no-op:客户端半负责一切
147
+ try {
148
+ const connection = ctx.get('connection')
149
+ const fetchRegistry = connection === undefined || connection === null ? undefined : connection.fetch
150
+ if (fetchRegistry === undefined || typeof fetchRegistry.register !== 'function') {
151
+ fail('register', 'connection.fetch.register-missing')
152
+ return
153
+ }
154
+ fetchRegistry.register({ path: ROUTE_PATH, methods: ['POST'], requestBody: 'buffered', fetch: routeFetch })
155
+ } catch (e) {
156
+ const message = String((e && e.message) || e)
157
+ // 重复注册(live patch reload / 回滚重放)算让位:那个实例继续服务即可,不当故障。
158
+ if (message.indexOf('already registered') >= 0 || message.indexOf('duplicate') >= 0) return
159
+ fail('register', message)
160
+ }
13
161
  }
@@ -0,0 +1,30 @@
1
+ // 派生文件(构建生成,人手不改):由 npm 包 dsh-plugin-update@0.1.1 的 dist/ 原样复制。
2
+ // 为什么必须放进本包:更新包的读取器用 containingPackage(import.meta.url, 目标包名) 从自己的文件位置
3
+ // 往上找目标包;以 npm 依赖形态安装时它住在别的目录,会抛 unknown-profile 且环境判定恒为 installation-changed。
4
+ // 门禁:tests/update-panel.test.mjs 断言本副本与 npm 包逐字节一致(除本头部)。
5
+ // vendor-source: dsh-plugin-update@0.1.1 dist/client.js
6
+ // AUTO-GENERATED by node packages/dsh-plugin-update/build.mjs — DO NOT EDIT. Source: packages/dsh-plugin-update/src/client.ts
7
+ import { buildPhoneNames, DEFAULT_PANEL_POLL_MS, MIN_PANEL_POLL_MS } from "./config.js";
8
+ import { buildPhoneName, buildPhoneNames as buildPhoneNames2 } from "./config.js";
9
+ import { manualCommand as manualCommand2 } from "./commands.js";
10
+ const CLIENT_POLL = {
11
+ defaultMs: DEFAULT_PANEL_POLL_MS,
12
+ minMs: MIN_PANEL_POLL_MS
13
+ };
14
+ function buildClientPhoneNames(prefix) {
15
+ return buildPhoneNames(prefix);
16
+ }
17
+ function assertPollInterval(ms) {
18
+ if (typeof ms !== "number" || !Number.isFinite(ms) || ms < MIN_PANEL_POLL_MS) {
19
+ throw new Error("[dsh-plugin-update] \u9762\u677F\u8F6E\u8BE2\u95F4\u9694\u975E\u6CD5\uFF1A\u4E0D\u5F97\u5C0F\u4E8E 250 \u6BEB\u79D2\uFF08\u6536\u5230 " + JSON.stringify(ms) + "\uFF09");
20
+ }
21
+ return ms;
22
+ }
23
+ export {
24
+ CLIENT_POLL,
25
+ assertPollInterval,
26
+ buildClientPhoneNames,
27
+ buildPhoneName,
28
+ buildPhoneNames2 as buildPhoneNames,
29
+ manualCommand2 as manualCommand
30
+ };
@@ -0,0 +1,85 @@
1
+ // 派生文件(构建生成,人手不改):由 npm 包 dsh-plugin-update@0.1.1 的 dist/ 原样复制。
2
+ // 为什么必须放进本包:更新包的读取器用 containingPackage(import.meta.url, 目标包名) 从自己的文件位置
3
+ // 往上找目标包;以 npm 依赖形态安装时它住在别的目录,会抛 unknown-profile 且环境判定恒为 installation-changed。
4
+ // 门禁:tests/update-panel.test.mjs 断言本副本与 npm 包逐字节一致(除本头部)。
5
+ // vendor-source: dsh-plugin-update@0.1.1 dist/commands.js
6
+ // AUTO-GENERATED by node packages/dsh-plugin-update/build.mjs — DO NOT EDIT. Source: packages/dsh-plugin-update/src/commands.ts
7
+ const PACKAGE_NAME = "dsh-mattpocock-skills-deck";
8
+ const NPM_REGISTRY = "https://registry.npmjs.org/";
9
+ const INSTALL_TIMEOUT_MS = 15 * 6e4;
10
+ function validVersion(v) {
11
+ return typeof v === "string" && /^\d+\.\d+\.\d+$/.test(v);
12
+ }
13
+ function parseTriple(v) {
14
+ const parts = String(v).split(".");
15
+ if (parts.length > 3) return null;
16
+ const nums = [];
17
+ for (const p of parts) {
18
+ if (!/^\d+$/.test(p)) return null;
19
+ const n = Number(p);
20
+ if (!Number.isSafeInteger(n)) return null;
21
+ nums.push(n);
22
+ }
23
+ while (nums.length < 3) nums.push(0);
24
+ return [nums[0], nums[1], nums[2]];
25
+ }
26
+ function compareVersions(a, b) {
27
+ const pa = parseTriple(a);
28
+ const pb = parseTriple(b);
29
+ if (!pa || !pb) throw new Error("invalid-release");
30
+ for (let i = 0; i < 3; i++) {
31
+ if (pa[i] < pb[i]) return -1;
32
+ if (pa[i] > pb[i]) return 1;
33
+ }
34
+ return 0;
35
+ }
36
+ function usableProfileName(raw) {
37
+ const name = typeof raw === "string" ? raw.trim() : "";
38
+ if (!name || name.length > 255 || name.startsWith("-")) return null;
39
+ if ([".", "..", "node_modules"].includes(name)) return null;
40
+ return name;
41
+ }
42
+ function installRecipe(input) {
43
+ const name = usableProfileName(input?.profileName);
44
+ const version = input?.version;
45
+ if (!name || !validVersion(version)) return null;
46
+ const kind = input?.environmentKind;
47
+ if (kind !== "desktop" && kind !== "cli") return null;
48
+ const targetName = input?.targetPackageName ?? PACKAGE_NAME;
49
+ const registry = input?.registryUrl ?? NPM_REGISTRY;
50
+ const timeoutMs = input?.timeoutMs ?? INSTALL_TIMEOUT_MS;
51
+ if (!targetName || !registry) return null;
52
+ return {
53
+ route: kind === "desktop" ? "desktop-service" : "cli-process",
54
+ profileName: name,
55
+ version,
56
+ // 参数数组原样交给命令行工具:使用范围名不做引号包裹、不按空格拆分。
57
+ pluginArgs: ["add", "--save-exact", `${targetName}@${version}`, `--registry=${registry}`],
58
+ timeoutMs
59
+ };
60
+ }
61
+ function manualCommand(input) {
62
+ if (input.sourceInstall || input.blockedReason === "source-install" || input.blockedReason === "unknown-profile") return null;
63
+ const name = usableProfileName(input.profileName);
64
+ if (!name) return null;
65
+ const targetName = input.targetPackageName ?? PACKAGE_NAME;
66
+ const registry = input.registryUrl ?? NPM_REGISTRY;
67
+ if (!targetName || !registry) return null;
68
+ const arg = /^[A-Za-z0-9_.-]+$/.test(name) ? name : JSON.stringify(name);
69
+ const picks = [input.latestVersion, input.jobTargetVersion, input.installedVersion].filter(validVersion);
70
+ let version = picks.length > 0 ? picks[0] : "latest";
71
+ try {
72
+ const ranked = picks.filter((v) => compareVersions(v, input.runningVersion) >= 0);
73
+ if (ranked.length > 0) {
74
+ version = ranked[0];
75
+ for (const v of ranked) if (compareVersions(v, version) === 1) version = v;
76
+ }
77
+ } catch {
78
+ }
79
+ return `dsh plugin --profile ${arg} add --save-exact ${targetName}@${version} --registry=${registry}`;
80
+ }
81
+ export {
82
+ INSTALL_TIMEOUT_MS,
83
+ installRecipe,
84
+ manualCommand
85
+ };
@@ -0,0 +1,102 @@
1
+ // 派生文件(构建生成,人手不改):由 npm 包 dsh-plugin-update@0.1.1 的 dist/ 原样复制。
2
+ // 为什么必须放进本包:更新包的读取器用 containingPackage(import.meta.url, 目标包名) 从自己的文件位置
3
+ // 往上找目标包;以 npm 依赖形态安装时它住在别的目录,会抛 unknown-profile 且环境判定恒为 installation-changed。
4
+ // 门禁:tests/update-panel.test.mjs 断言本副本与 npm 包逐字节一致(除本头部)。
5
+ // vendor-source: dsh-plugin-update@0.1.1 dist/config.js
6
+ // AUTO-GENERATED by node packages/dsh-plugin-update/build.mjs — DO NOT EDIT. Source: packages/dsh-plugin-update/src/config.ts
7
+ const DEFAULT_PREFIX = "wf";
8
+ const PHONE_ACTIONS = ["updateStatus", "updateCheck", "updateInstall"];
9
+ const DEFAULT_TARGET_PACKAGE = "dsh-mattpocock-skills-deck";
10
+ const DEFAULT_REGISTRY = "https://registry.npmjs.org/";
11
+ const LEGACY_PLUGIN_ID = "dsh-mattpocock-skills-deck";
12
+ const LEGACY_DIR_SEGMENT = "updates";
13
+ const STATE_FILE = "state.json";
14
+ const LOCK_FILE = "install.lock";
15
+ const BACKUP_FILE = "before.json";
16
+ const DEFAULT_CHECK_TIMEOUT_MS = 1e4;
17
+ const DEFAULT_CONFIRMATION_TTL_MS = 10 * 6e4;
18
+ const DEFAULT_INSTALL_TIMEOUT_MS = 15 * 6e4;
19
+ const DEFAULT_PANEL_POLL_MS = 1e3;
20
+ const RECHECK_WINDOW_MS = 2e3;
21
+ const MIN_PANEL_POLL_MS = 250;
22
+ function assertPluginId(value) {
23
+ if (typeof value !== "string" || value.length === 0) {
24
+ throw new Error("[dsh-plugin-update] \u63D2\u4EF6\u6807\u8BC6 pluginId \u5FC5\u586B\uFF1A\u987B\u4E3A\u975E\u7A7A\u5B57\u7B26\u4E32\uFF08\u6536\u5230 " + JSON.stringify(value) + "\uFF09");
25
+ }
26
+ if (value.includes("/") || value.includes("\\") || value.includes("\0")) {
27
+ throw new Error("[dsh-plugin-update] \u63D2\u4EF6\u6807\u8BC6 pluginId \u975E\u6CD5\uFF1A\u4E0D\u5F97\u542B\u6709\u8DEF\u5F84\u5206\u9694\u7B26\uFF08\u6536\u5230 " + JSON.stringify(value) + "\uFF09");
28
+ }
29
+ return value;
30
+ }
31
+ function assertPrefix(value, role) {
32
+ if (typeof value !== "string" || value.length === 0) {
33
+ throw new Error("[dsh-plugin-update] " + role + " \u975E\u6CD5\uFF1A\u987B\u4E3A\u975E\u7A7A\u5B57\u7B26\u4E32\uFF08\u6536\u5230 " + JSON.stringify(value) + "\uFF09");
34
+ }
35
+ if (value.includes(".") || value.includes("/") || value.includes("\\") || /\s/.test(value)) {
36
+ throw new Error("[dsh-plugin-update] " + role + " \u975E\u6CD5\uFF1A\u4E0D\u5F97\u542B\u6709\u70B9\u3001\u8DEF\u5F84\u5206\u9694\u7B26\u6216\u7A7A\u767D\uFF08\u6536\u5230 " + JSON.stringify(value) + "\uFF09");
37
+ }
38
+ return value;
39
+ }
40
+ function assertPositiveFinite(value, role) {
41
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
42
+ throw new Error("[dsh-plugin-update] " + role + " \u975E\u6CD5\uFF1A\u987B\u4E3A\u6709\u9650\u5927\u4E8E 0 \u7684\u6570\uFF08\u6536\u5230 " + JSON.stringify(value) + "\uFF09");
43
+ }
44
+ return value;
45
+ }
46
+ function resolveUpdateConfig(input) {
47
+ if (!input || typeof input !== "object") {
48
+ throw new Error("[dsh-plugin-update] \u5EFA\u66F4\u65B0\u80FD\u529B\u7F3A\u5C11\u914D\u7F6E\uFF1A\u63D2\u4EF6\u6807\u8BC6 pluginId \u5FC5\u586B");
49
+ }
50
+ const pluginId = assertPluginId(input.pluginId);
51
+ const prefix = input.prefix === void 0 ? DEFAULT_PREFIX : assertPrefix(input.prefix, "\u7535\u8BDD\u540D\u524D\u7F00 prefix");
52
+ const targetPackageName = input.targetPackageName === void 0 ? DEFAULT_TARGET_PACKAGE : String(input.targetPackageName);
53
+ if (!targetPackageName) {
54
+ throw new Error("[dsh-plugin-update] \u76EE\u6807\u5305\u540D targetPackageName \u975E\u6CD5\uFF1A\u987B\u4E3A\u975E\u7A7A\u5B57\u7B26\u4E32");
55
+ }
56
+ const registryUrl = input.registryUrl === void 0 ? DEFAULT_REGISTRY : String(input.registryUrl);
57
+ if (!registryUrl) {
58
+ throw new Error("[dsh-plugin-update] \u5B98\u65B9\u6E90 registryUrl \u975E\u6CD5\uFF1A\u987B\u4E3A\u975E\u7A7A\u5B57\u7B26\u4E32");
59
+ }
60
+ const homeDir = input.homeDir === void 0 ? null : String(input.homeDir);
61
+ const checkTimeoutMs = input.checkTimeoutMs === void 0 ? DEFAULT_CHECK_TIMEOUT_MS : assertPositiveFinite(input.checkTimeoutMs, "\u8054\u7F51\u8D85\u65F6 checkTimeoutMs");
62
+ const confirmationTtlMs = input.confirmationTtlMs === void 0 ? DEFAULT_CONFIRMATION_TTL_MS : assertPositiveFinite(input.confirmationTtlMs, "\u51ED\u8BC1\u6709\u6548\u671F confirmationTtlMs");
63
+ const installTimeoutMs = input.installTimeoutMs === void 0 ? DEFAULT_INSTALL_TIMEOUT_MS : assertPositiveFinite(input.installTimeoutMs, "\u5B89\u88C5\u65F6\u9650 installTimeoutMs");
64
+ const panelPollMs = input.panelPollMs === void 0 ? DEFAULT_PANEL_POLL_MS : assertPositiveFinite(input.panelPollMs, "\u9762\u677F\u8F6E\u8BE2 panelPollMs");
65
+ if (panelPollMs < MIN_PANEL_POLL_MS) {
66
+ throw new Error("[dsh-plugin-update] \u9762\u677F\u8F6E\u8BE2 panelPollMs \u975E\u6CD5\uFF1A\u4E0D\u5F97\u5C0F\u4E8E 250 \u6BEB\u79D2\uFF08\u6536\u5230 " + JSON.stringify(input.panelPollMs) + "\uFF09");
67
+ }
68
+ return { pluginId, prefix, targetPackageName, registryUrl, homeDir, checkTimeoutMs, confirmationTtlMs, installTimeoutMs, panelPollMs };
69
+ }
70
+ function buildPhoneNames(prefix) {
71
+ const checked = assertPrefix(prefix, "\u7535\u8BDD\u540D\u524D\u7F00 prefix");
72
+ return {
73
+ updateStatus: checked + ".updateStatus",
74
+ updateCheck: checked + ".updateCheck",
75
+ updateInstall: checked + ".updateInstall"
76
+ };
77
+ }
78
+ function buildPhoneName(prefix, action) {
79
+ return buildPhoneNames(prefix)[action];
80
+ }
81
+ export {
82
+ BACKUP_FILE,
83
+ DEFAULT_CHECK_TIMEOUT_MS,
84
+ DEFAULT_CONFIRMATION_TTL_MS,
85
+ DEFAULT_INSTALL_TIMEOUT_MS,
86
+ DEFAULT_PANEL_POLL_MS,
87
+ DEFAULT_PREFIX,
88
+ DEFAULT_REGISTRY,
89
+ DEFAULT_TARGET_PACKAGE,
90
+ LEGACY_DIR_SEGMENT,
91
+ LEGACY_PLUGIN_ID,
92
+ LOCK_FILE,
93
+ MIN_PANEL_POLL_MS,
94
+ PHONE_ACTIONS,
95
+ RECHECK_WINDOW_MS,
96
+ STATE_FILE,
97
+ assertPluginId,
98
+ assertPrefix,
99
+ buildPhoneName,
100
+ buildPhoneNames,
101
+ resolveUpdateConfig
102
+ };
@@ -0,0 +1,120 @@
1
+ // 派生文件(构建生成,人手不改):由 npm 包 dsh-plugin-update@0.1.1 的 dist/ 原样复制。
2
+ // 为什么必须放进本包:更新包的读取器用 containingPackage(import.meta.url, 目标包名) 从自己的文件位置
3
+ // 往上找目标包;以 npm 依赖形态安装时它住在别的目录,会抛 unknown-profile 且环境判定恒为 installation-changed。
4
+ // 门禁:tests/update-panel.test.mjs 断言本副本与 npm 包逐字节一致(除本头部)。
5
+ // vendor-source: dsh-plugin-update@0.1.1 dist/gate.js
6
+ // AUTO-GENERATED by node packages/dsh-plugin-update/build.mjs — DO NOT EDIT. Source: packages/dsh-plugin-update/src/gate.ts
7
+ import { assertPluginId } from "./config.js";
8
+ const EVENT_LEVELS = ["error", "warn", "info", "debug"];
9
+ const EVENT_KINDS = ["resident", "ondemand", "selfmon"];
10
+ function isNonEmptyString(value) {
11
+ return typeof value === "string" && value.length > 0;
12
+ }
13
+ function assertStringArray(value, what) {
14
+ if (!Array.isArray(value)) throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A" + what + " \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6570\u7EC4");
15
+ const seen = [];
16
+ for (const item of value) {
17
+ if (!isNonEmptyString(item)) throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A" + what + " \u91CC\u6709\u7A7A\u5B57\u6BB5\u540D");
18
+ if (seen.indexOf(item) >= 0) throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A" + what + " \u91CC\u5B57\u6BB5\u540D\u91CD\u590D\uFF1A" + item);
19
+ seen.push(item);
20
+ }
21
+ return seen;
22
+ }
23
+ function parseEventListManifest(value) {
24
+ if (typeof value === "string") {
25
+ throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A\u8DEF\u5F84\u5F62\u5F0F\u8BF7\u8C03\u7528\u65B9\u81EA\u5DF1\u8BFB\u6210\u5BF9\u8C61\u518D\u4F20\u5165\uFF0C\u66F4\u65B0\u5305\u4E0D\u8BFB\u76D8");
26
+ }
27
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
28
+ throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A\u53EA\u6536\u5BF9\u8C61\u5F62\u5F0F\uFF08\u7A7A\u6A21\u677F\u89C1\u5305\u5185\u7684 event-list.template.json\uFF09");
29
+ }
30
+ const input = value;
31
+ if (input["version"] !== 1) {
32
+ throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1Aversion \u73B0\u5728\u53EA\u8BA4 1\uFF08\u6536\u5230 " + JSON.stringify(input["version"]) + "\uFF09");
33
+ }
34
+ const pluginId = assertPluginId(input["pluginId"]);
35
+ const countsRaw = input["counts"];
36
+ if (!countsRaw || typeof countsRaw !== "object" || Array.isArray(countsRaw)) {
37
+ throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1Acounts \u5FC5\u987B\u662F\u542B\u4E09\u7C7B\u8BA1\u6570\u7684\u5BF9\u8C61");
38
+ }
39
+ const countsRecord = countsRaw;
40
+ const counts = { resident: 0, ondemand: 0, selfmon: 0 };
41
+ for (const kind of EVENT_KINDS) {
42
+ const n = countsRecord[kind];
43
+ if (typeof n !== "number" || !isFinite(n) || Math.floor(n) !== n || n < 0) {
44
+ throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1Acounts." + kind + " \u5FC5\u987B\u662F\u975E\u8D1F\u6574\u6570");
45
+ }
46
+ counts[kind] = n;
47
+ }
48
+ const eventsRaw = input["events"];
49
+ if (!eventsRaw || typeof eventsRaw !== "object" || Array.isArray(eventsRaw)) {
50
+ throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1Aevents \u5FC5\u987B\u662F\u4E8B\u4EF6\u540D\u5230\u6761\u76EE\u7684\u5BF9\u8C61");
51
+ }
52
+ const events = {};
53
+ for (const name of Object.keys(eventsRaw)) {
54
+ events[name] = parseEventEntry(name, eventsRaw[name]);
55
+ }
56
+ return { version: 1, pluginId, counts, events };
57
+ }
58
+ function parseEventEntry(name, value) {
59
+ if (!isNonEmptyString(name)) throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A\u4E8B\u4EF6\u540D\u4E0D\u80FD\u4E3A\u7A7A");
60
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
61
+ throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A\u4E8B\u4EF6 " + name + " \u5FC5\u987B\u662F\u5BF9\u8C61");
62
+ }
63
+ const input = value;
64
+ if (EVENT_LEVELS.indexOf(input["level"]) < 0) {
65
+ throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A\u4E8B\u4EF6 " + name + " \u7684 level \u53EA\u8BB8 error\u3001warn\u3001info\u3001debug");
66
+ }
67
+ if (EVENT_KINDS.indexOf(input["kind"]) < 0) {
68
+ throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A\u4E8B\u4EF6 " + name + " \u7684 kind \u53EA\u8BB8 resident\u3001ondemand\u3001selfmon");
69
+ }
70
+ for (const key of Object.keys(input)) {
71
+ if (["level", "kind", "fields", "codes", "rules", "guard"].indexOf(key) < 0) {
72
+ throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A\u4E8B\u4EF6 " + name + " \u6709\u4E0D\u8BA4\u8BC6\u7684\u952E " + key);
73
+ }
74
+ }
75
+ const entry = {
76
+ level: input["level"],
77
+ kind: input["kind"],
78
+ fields: assertStringArray(input["fields"], "\u4E8B\u4EF6 " + name + " \u7684 fields")
79
+ };
80
+ if (input["codes"] !== void 0) entry.codes = assertStringArray(input["codes"], "\u4E8B\u4EF6 " + name + " \u7684 codes");
81
+ if (input["rules"] !== void 0) entry.rules = assertStringArray(input["rules"], "\u4E8B\u4EF6 " + name + " \u7684 rules");
82
+ if (input["guard"] !== void 0) {
83
+ if (typeof input["guard"] !== "string") {
84
+ throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A\u4E8B\u4EF6 " + name + " \u7684 guard \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
85
+ }
86
+ entry.guard = input["guard"];
87
+ }
88
+ return entry;
89
+ }
90
+ function checkEventFields(manifest, eventName, fieldNames) {
91
+ const entry = manifest.events[eventName];
92
+ if (!entry) return { ok: false, unknownEvent: true, unknownFields: [] };
93
+ const allowed = new Set(entry.fields);
94
+ const unknownFields = (Array.isArray(fieldNames) ? fieldNames : []).filter(
95
+ (field) => !allowed.has(field)
96
+ );
97
+ return { ok: unknownFields.length === 0, unknownEvent: false, unknownFields };
98
+ }
99
+ function checkEventCounts(manifest) {
100
+ const actual = { resident: 0, ondemand: 0, selfmon: 0 };
101
+ for (const name of Object.keys(manifest.events)) {
102
+ actual[manifest.events[name].kind] += 1;
103
+ }
104
+ const problems = [];
105
+ for (const kind of EVENT_KINDS) {
106
+ if (actual[kind] !== manifest.counts[kind]) {
107
+ problems.push(
108
+ "[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355\u8BA1\u6570\u5BF9\u4E0D\u4E0A\uFF1A" + kind + " \u7C7B\u5B9E\u9645 " + actual[kind] + " \u6761\uFF0C\u6E05\u5355\u81EA\u62A5 " + manifest.counts[kind] + " \u6761"
109
+ );
110
+ }
111
+ }
112
+ return { ok: problems.length === 0, problems };
113
+ }
114
+ export {
115
+ EVENT_KINDS,
116
+ EVENT_LEVELS,
117
+ checkEventCounts,
118
+ checkEventFields,
119
+ parseEventListManifest
120
+ };