dsh-wechat-remote 1.7.9 → 1.7.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Agent远程管理助手 · DSH 插件安装器
2
2
 
3
- 当前版本为 `1.7.9`,内嵌同版本正式插件。
3
+ 当前版本为 `1.7.9`,内嵌同版本正式插件。
4
4
 
5
5
  首次安装和升级旧插件使用同一个命令:
6
6
 
@@ -16,9 +16,9 @@ DSH 的新版本、预览版本或未登记的主机架构不会仅因缺少验
16
16
 
17
17
  本版沿用此前已验证的跨平台安装方案,具体发行包与宿主组合以验收记录为准。普通 Node 启动、独立的 macOS launchd 和 Linux 用户级 systemd 服务有对应的自动恢复方式;无法确认启动方式时不自动停止主机。
18
18
 
19
- 安装过程中请等待完成,勿重复执行命令。
20
-
21
- 本版安装器内置完整插件,安装当前版本不依赖 GitHub 附件下载。安装后,WebUI 一键更新在 GitHub 网络下载失败时可以从已核验的 npm 安装包中取回同一插件。该能力不回写旧插件;旧插件遇到 GitHub 下载失败时,先使用上面的 npm 命令升级即可。
19
+ 安装过程中请等待完成,勿重复执行命令。
20
+
21
+ 本版安装器内置完整插件,安装当前版本不依赖 GitHub 附件下载。安装后,WebUI 一键更新在 GitHub 网络下载失败时可以从已核验的 npm 安装包中取回同一插件。该能力不回写旧插件;旧插件遇到 GitHub 下载失败时,先使用上面的 npm 命令升级即可。
22
22
 
23
23
  旧插件的一键更新如被版本或架构判断拦截,可用上述安装命令升级一次。从插件 1.7.6 起,未登记的 CPU 架构或 DSH 版本不再影响更新入口;执行前仍会检查实际安装与重启条件,无需因此降低 DSH 版本或删除节点。
24
24
 
package/assets/plugin.tgz CHANGED
Binary file
@@ -1,29 +1,47 @@
1
1
  {
2
- "version": "1.7.9",
2
+ "version": "1.7.10",
3
3
  "catalog": {
4
4
  "schemaVersion": 1,
5
- "revision": "installer-1.7.9",
6
- "issuedAt": 1790053952064,
7
- "expiresAt": 1792473152064,
5
+ "revision": "installer-1.7.10",
6
+ "issuedAt": 1790187001683,
7
+ "expiresAt": 1792606201683,
8
8
  "releases": [
9
9
  {
10
- "version": "1.7.9",
10
+ "version": "1.7.10",
11
11
  "channel": "stable",
12
12
  "dsh": [
13
- "0.1.5-rc.1"
13
+ "0.1.5-rc.1",
14
+ "0.1.7-rc.1"
14
15
  ],
15
16
  "platforms": [
16
- "windows",
17
- "macos"
17
+ "macos",
18
+ "linux"
18
19
  ],
19
20
  "architectures": [
20
21
  "x64"
21
22
  ],
22
23
  "asset": {
23
- "url": "https://github.com/martinbear1/dsh-wechat-remote/releases/download/v1.7.9/harness-remote-dsh-wechat-remote-1.7.9.tgz",
24
- "sha256": "c0b592f90d67e63af3b4f03592bf1365828c7d404f0b5d3a812ca4a1a8439cda",
25
- "bytes": 10361441
26
- }
24
+ "url": "https://github.com/martinbear1/dsh-wechat-remote/releases/download/v1.7.10/harness-remote-dsh-wechat-remote-1.7.10.tgz",
25
+ "sha256": "f146d61eb8f301012d5c52da82efb469a38e46d27a6f16d128b546b68650d48e",
26
+ "bytes": 10362228
27
+ },
28
+ "targets": [
29
+ {
30
+ "platform": "macos",
31
+ "arch": "x64",
32
+ "dsh": [
33
+ "0.1.5-rc.1",
34
+ "0.1.7-rc.1"
35
+ ]
36
+ },
37
+ {
38
+ "platform": "linux",
39
+ "arch": "x64",
40
+ "dsh": [
41
+ "0.1.5-rc.1"
42
+ ]
43
+ }
44
+ ]
27
45
  }
28
46
  ],
29
47
  "blocked": [],
@@ -1,5 +1,5 @@
1
- /** Verify a restored, plugin-free DSH through the same native control adapter.
2
- * Only the first-install rollback uses this; ordinary updates probe the plugin.
1
+ /** Verify a restored DSH through the same independent native control adapter.
2
+ * Also supports restoring an already-broken plugin, without claiming it is fixed.
3
3
  * Bundled outside the profile so neither a failed add nor rollback removes it. */
4
4
  import fs from 'node:fs'
5
5
  import path from 'node:path'
@@ -29,7 +29,7 @@ export async function verifyNativeRestore(job, start, sessionIds, readableIds) {
29
29
  && /^http:\/\/127\.0\.0\.1:[1-9]\d{0,4}$/.test(value.origin), 180000)
30
30
  const host = await request('describe')
31
31
  if (host.pid !== ref.pid || host.home !== job.home || host.profile !== job.profile
32
- || host.cli !== job.cli || host.dshVersion !== job.dshVersion || host.pluginVersion !== '0.0.0') {
32
+ || host.cli !== job.cli || host.dshVersion !== job.dshVersion || host.pluginVersion !== job.previousVersion) {
33
33
  throw new Error('恢复后的 DSH 身份不匹配。')
34
34
  }
35
35
  const items = (await request('read', { method: 'session.list' })).items
package/bin/setup.mjs CHANGED
@@ -66,7 +66,10 @@ async function installStopped({ cli, home, profile, profileName, directory, id,
66
66
  const dsh = validateDshCli(cli)
67
67
  let version = '0.0.0'
68
68
  try { version = JSON.parse(fs.readFileSync(path.join(profile, 'node_modules', packageName, 'package.json'), 'utf8')).version } catch {}
69
- const selected = await selectRelease({ dshVersion: dsh.version, pluginVersion: version, platform: process.platform, arch: process.arch }, assetsRoot, repair)
69
+ // A stopped host cannot prove its installed copy is loadable. Re-executing
70
+ // the normal installation command also repairs that copy (never downgrades),
71
+ // using the same backed-up native add rather than a second repair workflow.
72
+ const selected = await selectRelease({ dshVersion: dsh.version, pluginVersion: version, platform: process.platform, arch: process.arch }, assetsRoot, repair || version !== '0.0.0')
70
73
  fs.mkdirSync(profile, { recursive: true, mode: 0o700 })
71
74
  if (fs.realpathSync(profile) !== profile) throw new Error('无法确认 DSH 配置目录的实际位置。')
72
75
  const lock = path.join(profile, '.harness-remote-update.lock')
@@ -178,7 +181,9 @@ export async function install({ profileName = 'web', cli, home: configuredHome,
178
181
  let ready = false
179
182
  try {
180
183
  const response = await fetch(`http://127.0.0.1:${host.localPort}/gate/status`, { signal: AbortSignal.timeout(5000), redirect: 'error' })
181
- await response.arrayBuffer(); ready = response.ok
184
+ await response.arrayBuffer()
185
+ const business = response.ok ? await control(job, 'health') : undefined
186
+ ready = business?.ready === true
182
187
  } catch {}
183
188
  if (!ready) {
184
189
  console.log('检测到已安装插件尚未就绪,正在重新安装修复…')
@@ -192,7 +197,9 @@ export async function install({ profileName = 'web', cli, home: configuredHome,
192
197
  validateJob(job)
193
198
  fs.writeFileSync(path.join(directory, 'release.tgz'), selected.archive, { mode: 0o600, flag: 'wx' })
194
199
  fs.copyFileSync(path.join(root, 'lib/update-worker.js'), path.join(directory, 'update-worker.js'))
195
- if (host.pluginVersion === '0.0.0') fs.copyFileSync(path.join(root, 'lib/native-recovery.js'), path.join(directory, 'native-recovery.js'))
200
+ // Recovery belongs to the independent installer, including when the old
201
+ // plugin already failed before this operation and cannot serve health RPC.
202
+ fs.copyFileSync(path.join(root, 'lib/native-recovery.js'), path.join(directory, 'native-recovery.js'))
196
203
  writePrivateJsonAtomic(path.join(directory, 'job.json'), job)
197
204
  await control(job, 'launch')
198
205
  const ready = await waitForJson(path.join(directory, 'worker-ready.json'), v => v.id === id)
@@ -1,8 +1,8 @@
1
1
  /* Generated from the shared plugin installation sources. */
2
2
 
3
3
  // src/install-control.ts
4
- import fs2 from "node:fs";
5
- import path5 from "node:path";
4
+ import fs3 from "node:fs";
5
+ import path6 from "node:path";
6
6
  import http from "node:http";
7
7
 
8
8
  // src/dsh-runtime.ts
@@ -617,6 +617,84 @@ function withPresentationProjections(block) {
617
617
  return { ...source, values };
618
618
  }
619
619
 
620
+ // src/dsh-host-contract.ts
621
+ import fs from "node:fs";
622
+ import path4 from "node:path";
623
+ var contracts = /* @__PURE__ */ new WeakMap();
624
+ function runningDshVersion(entry = process.argv[1]) {
625
+ if (!entry) return;
626
+ let directory;
627
+ try {
628
+ directory = path4.dirname(fs.realpathSync(entry));
629
+ } catch {
630
+ return;
631
+ }
632
+ for (let depth = 0; depth < 8; depth++) {
633
+ try {
634
+ const manifest = JSON.parse(fs.readFileSync(path4.join(directory, "package.json"), "utf8"));
635
+ if (manifest.name === "@deepseek-ai/dsh") return manifest.version;
636
+ } catch {
637
+ }
638
+ const parent = path4.dirname(directory);
639
+ if (parent === directory) break;
640
+ directory = parent;
641
+ }
642
+ }
643
+ function usesDuplexEvents(version) {
644
+ if (!version) return false;
645
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-[\w.-]+)?$/.exec(version);
646
+ if (!match) throw new Error("\u65E0\u6CD5\u8BC6\u522B\u5F53\u524D DSH \u4E8B\u4EF6\u534F\u8BAE\u7248\u672C");
647
+ const [, major, minor, patch] = match.map(Number);
648
+ return major > 0 || minor > 1 || minor === 1 && patch >= 7;
649
+ }
650
+ function openHostEvents(gateway, endpoint, payload, signal) {
651
+ let contract = contracts.get(gateway);
652
+ if (!contract) {
653
+ contract = { duplex: usesDuplexEvents(runningDshVersion()) };
654
+ contracts.set(gateway, contract);
655
+ }
656
+ signal.throwIfAborted();
657
+ const empty = { async *[Symbol.asyncIterator]() {
658
+ } };
659
+ return contract.duplex ? gateway.wireStream.open(endpoint, payload, empty, void 0, signal) : gateway.wireStream.open(endpoint, payload, signal);
660
+ }
661
+ function projectedSubagentCatalog(parent, projection, rows) {
662
+ const entries = projection?.values?.subagentCatalog;
663
+ if (!Array.isArray(entries) || !Array.isArray(rows)) throw new Error("DSH \u5B50\u4EE3\u7406\u76EE\u5F55\u6682\u4E0D\u53EF\u8BFB\u53D6");
664
+ const byId = new Map(rows.map((row) => [row.sessionId, row]));
665
+ const parents = new Set(rows.filter((row) => row.origin === "subagent").map((row) => row.parentSessionId));
666
+ const ids = /* @__PURE__ */ new Set();
667
+ return { parentAvailable: byId.get(parent)?.agentAvailable === true, entries: entries.map((entry) => {
668
+ if (typeof entry.id !== "string" || !entry.id || ids.has(entry.id)) throw new Error("DSH \u5B50\u4EE3\u7406\u76EE\u5F55\u4E0D\u5B8C\u6574");
669
+ ids.add(entry.id);
670
+ if (entry.mode !== "one-shot" && entry.mode !== "continuable") return { id: entry.id, kind: "diagnostic", reason: "unsupported" };
671
+ const row = byId.get(entry.id);
672
+ if (row && (row.origin !== "subagent" || row.parentSessionId !== parent)) throw new Error("DSH \u5B50\u4EE3\u7406\u5F52\u5C5E\u4E0D\u4E00\u81F4");
673
+ return {
674
+ id: entry.id,
675
+ kind: "child",
676
+ mode: entry.mode,
677
+ ...typeof entry.label === "string" ? { label: entry.label } : {},
678
+ activity: row?.running === true ? "running" : "inactive",
679
+ hasChildren: parents.has(entry.id)
680
+ };
681
+ }) };
682
+ }
683
+ async function invokeHostRemote(ctx, gateway, request) {
684
+ if (request.namespace !== "subagents" || request.method !== "list") return gateway.invoke(request);
685
+ const registry = ctx.get("typert")?.local;
686
+ if (registry?.get("subagents/list") || registry?.hasSeen?.("subagents/list") || !usesDuplexEvents(runningDshVersion())) return gateway.invoke(request);
687
+ const parent = request.args.parentSessionId;
688
+ if (typeof parent !== "string" || !parent || parent.length > 256) throw new Error("\u7236\u4F1A\u8BDD\u6807\u8BC6\u65E0\u6548");
689
+ request.signal?.throwIfAborted();
690
+ const [projection, listing] = await Promise.all([
691
+ gateway.invoke({ namespace: "session", method: "projections", args: { request: { sessionId: parent } }, signal: request.signal }),
692
+ gateway.invoke({ namespace: "session", method: "list", args: { _request: {} }, signal: request.signal })
693
+ ]);
694
+ request.signal?.throwIfAborted();
695
+ return projectedSubagentCatalog(parent, projection, listing?.items);
696
+ }
697
+
620
698
  // src/dsh-protocol-compat.ts
621
699
  var SINGLE_REQUEST_METHODS = /* @__PURE__ */ new Set([
622
700
  "session.attachment",
@@ -817,8 +895,8 @@ function resolveTypertGateway(ctx) {
817
895
  const candidate = ctx.get("typertGateway");
818
896
  if (!candidate || typeof candidate.invoke !== "function" || typeof candidate.stream !== "function") return null;
819
897
  return {
820
- wireStream: candidate.wireStream,
821
- invoke: (request) => candidate.invoke(request),
898
+ wireStream: candidate.wireStream ? { open: (endpoint, payload, signal) => openHostEvents(candidate, endpoint, payload, signal) } : void 0,
899
+ invoke: (request) => invokeHostRemote(ctx, candidate, request),
822
900
  stream: (request) => candidate.stream(request),
823
901
  commandAttachmentField: () => {
824
902
  const registry = ctx.get("typert");
@@ -1074,8 +1152,8 @@ async function invokeLegacyRpc(gateway, request, options) {
1074
1152
  }
1075
1153
 
1076
1154
  // src/install-lifecycle.ts
1077
- import fs from "node:fs";
1078
- import path4 from "node:path";
1155
+ import fs2 from "node:fs";
1156
+ import path5 from "node:path";
1079
1157
  import { execFileSync as execFileSync2, spawn } from "node:child_process";
1080
1158
  var serviceName = (s) => /^[A-Za-z0-9_.@-]{1,180}$/.test(s);
1081
1159
  function run(command, args) {
@@ -1084,13 +1162,13 @@ function run(command, args) {
1084
1162
  function validateManager(value) {
1085
1163
  if (value.kind === "process") return;
1086
1164
  if (value.kind === "systemd" && process.platform === "linux" && serviceName(value.unit) && value.unit.endsWith(".service")) return;
1087
- if (value.kind === "launchd" && process.platform === "darwin" && serviceName(value.label) && /^gui\/\d+$/.test(value.domain) && value.domain === `gui/${process.getuid?.()}` && path4.isAbsolute(value.plist) && fs.statSync(value.plist).isFile()) return;
1165
+ if (value.kind === "launchd" && process.platform === "darwin" && serviceName(value.label) && /^gui\/\d+$/.test(value.domain) && value.domain === `gui/${process.getuid?.()}` && path5.isAbsolute(value.plist) && fs2.statSync(value.plist).isFile()) return;
1088
1166
  throw new Error("\u65E0\u6CD5\u786E\u8BA4\u539F\u540E\u53F0\u670D\u52A1\uFF0C\u672A\u505C\u6B62 DSH\u3002");
1089
1167
  }
1090
1168
  function currentHostManager() {
1091
1169
  if (process.env.PM2_HOME || process.env.NODE_APP_INSTANCE || process.env.KUBERNETES_SERVICE_HOST || process.env.container) throw new Error("\u6B64\u540E\u53F0\u7BA1\u7406\u65B9\u5F0F\u5C1A\u4E0D\u652F\u6301\u81EA\u52A8\u91CD\u542F\u3002");
1092
1170
  if (process.platform === "linux") {
1093
- const group = fs.readFileSync("/proc/self/cgroup", "utf8");
1171
+ const group = fs2.readFileSync("/proc/self/cgroup", "utf8");
1094
1172
  const units = group.split(/[\n/]/).filter((s) => serviceName(s) && s.endsWith(".service"));
1095
1173
  const unit = units.at(-1);
1096
1174
  if (unit && !/^user@\d+\.service$/.test(unit)) {
@@ -1119,17 +1197,17 @@ function currentHostManager() {
1119
1197
  }
1120
1198
  function startUpdateWorker(manager, directory, executable) {
1121
1199
  validateManager(manager);
1122
- const id = path4.basename(directory);
1200
+ const id = path5.basename(directory);
1123
1201
  if (!/^[a-f0-9]{32}$/.test(id)) throw new Error("\u65E0\u6548\u7684\u66F4\u65B0\u4EFB\u52A1");
1124
- const args = [path4.join(directory, "update-worker.js"), path4.join(directory, "job.json")];
1202
+ const args = [path5.join(directory, "update-worker.js"), path5.join(directory, "job.json")];
1125
1203
  if (manager.kind === "systemd") {
1126
1204
  run("systemd-run", ["--user", "--quiet", "--collect", `--unit=dsh-wechat-update-${id}`, "--property=Type=exec", `--working-directory=${directory}`, executable, ...args]);
1127
1205
  } else if (manager.kind === "launchd") {
1128
- run("/bin/launchctl", ["submit", "-l", `dsh.wechat.update.${id}`, "-o", path4.join(directory, "worker.log"), "-e", path4.join(directory, "worker.log"), "--", executable, ...args]);
1206
+ run("/bin/launchctl", ["submit", "-l", `dsh.wechat.update.${id}`, "-o", path5.join(directory, "worker.log"), "-e", path5.join(directory, "worker.log"), "--", executable, ...args]);
1129
1207
  } else {
1130
- const log = fs.openSync(path4.join(directory, "worker.log"), "a", 384);
1208
+ const log = fs2.openSync(path5.join(directory, "worker.log"), "a", 384);
1131
1209
  const child = spawn(executable, args, { cwd: directory, env: process.env, windowsHide: true, detached: true, stdio: ["ignore", log, log] });
1132
- fs.closeSync(log);
1210
+ fs2.closeSync(log);
1133
1211
  child.unref();
1134
1212
  child.on("error", () => {
1135
1213
  });
@@ -1157,6 +1235,17 @@ function assertNativeUpdateCapabilities(context) {
1157
1235
  }
1158
1236
 
1159
1237
  // src/install-control.ts
1238
+ async function nativeInstallSessions(ctx, signal) {
1239
+ const controller = ctx.get("sessionController");
1240
+ if (typeof controller?.list !== "function") throw new Error("DSH \u539F\u751F\u4F1A\u8BDD\u68C0\u67E5\u4E0D\u53EF\u7528\uFF0C\u672A\u505C\u6B62\u8282\u70B9\u3002");
1241
+ signal.throwIfAborted();
1242
+ const value = await controller.list({}, signal);
1243
+ signal.throwIfAborted();
1244
+ if (!Array.isArray(value?.items) || value.items.some((s) => typeof s.sessionId !== "string" || !s.sessionId || typeof s.running !== "boolean") || new Set(value.items.map((s) => s.sessionId)).size !== value.items.length) {
1245
+ throw new Error("DSH \u539F\u751F\u4F1A\u8BDD\u72B6\u6001\u4E0D\u5B8C\u6574\uFF0C\u672A\u505C\u6B62\u8282\u70B9\u3002");
1246
+ }
1247
+ return value;
1248
+ }
1160
1249
  async function quiesceNativeHost(ctx, read, disposing) {
1161
1250
  const items = (await read("session.list")).items;
1162
1251
  if (!Array.isArray(items) || items.some((s) => s.running !== false)) throw new Error("\u8BF7\u7B49\u5F85\u8FD0\u884C\u4E2D\u7684\u4F1A\u8BDD\u7ED3\u675F\u540E\u518D\u66F4\u65B0\u3002");
@@ -1170,11 +1259,11 @@ async function quiesceNativeHost(ctx, read, disposing) {
1170
1259
  async function createInstallControl(context, config) {
1171
1260
  const ctx = context.root;
1172
1261
  assertNativeUpdateCapabilities(ctx);
1173
- const home = adapterDshHome(), id = path5.basename(config.directory);
1174
- if (!/^[a-f0-9]{32}$/.test(id) || !/^[a-f0-9]{48}$/.test(config.token) || path5.dirname(config.directory) !== path5.join(home, "harness-remote-updates") || fs2.realpathSync(config.directory) !== config.directory) throw new Error("\u5B89\u88C5\u63A7\u5236\u8BF7\u6C42\u4E0D\u5C5E\u4E8E\u5F53\u524D DSH\u3002");
1175
- const scope = resolveAgentProfileScope("", process.argv, home), profile = path5.join(home, "profiles", scope);
1176
- const cli = fs2.realpathSync(process.argv[1]);
1177
- const manifest = JSON.parse(fs2.readFileSync(path5.resolve(cli, "../../package.json"), "utf8"));
1262
+ const home = adapterDshHome(), id = path6.basename(config.directory);
1263
+ if (!/^[a-f0-9]{32}$/.test(id) || !/^[a-f0-9]{48}$/.test(config.token) || path6.dirname(config.directory) !== path6.join(home, "harness-remote-updates") || fs3.realpathSync(config.directory) !== config.directory) throw new Error("\u5B89\u88C5\u63A7\u5236\u8BF7\u6C42\u4E0D\u5C5E\u4E8E\u5F53\u524D DSH\u3002");
1264
+ const scope = resolveAgentProfileScope("", process.argv, home), profile = path6.join(home, "profiles", scope);
1265
+ const cli = fs3.realpathSync(process.argv[1]);
1266
+ const manifest = JSON.parse(fs3.readFileSync(path6.resolve(cli, "../../package.json"), "utf8"));
1178
1267
  if (manifest.name !== "@deepseek-ai/dsh" || process.execArgv.length) throw new Error("\u6B64 DSH \u542F\u52A8\u65B9\u5F0F\u5C1A\u4E0D\u652F\u6301\u81EA\u52A8\u66F4\u65B0\u3002");
1179
1268
  const manager = currentHostManager(), webPort = resolveDshWebRuntime(ctx, process.env).port;
1180
1269
  const ports = deriveGatePorts(scope, loadAgentDescriptor().agentInstanceId);
@@ -1183,6 +1272,9 @@ async function createInstallControl(context, config) {
1183
1272
  let launched = false, quiesced = false;
1184
1273
  const read = async (method, payload = {}) => {
1185
1274
  if (!["session.list", "session.history"].includes(method)) throw new Error("\u4E0D\u652F\u6301\u7684\u5B89\u88C5\u68C0\u67E5");
1275
+ if (method === "session.list" && typeof ctx.get("sessionController")?.list === "function") {
1276
+ return nativeInstallSessions(ctx, AbortSignal.timeout(1e4));
1277
+ }
1186
1278
  const request = { type: "client-request", rpcId: "installer-read", method, payload };
1187
1279
  const response = gateway ? await invokeLegacyRpc(gateway, request, { signal: AbortSignal.timeout(1e4), describeHost: () => ({}) }) : await (await fetch(`http://127.0.0.1:${webPort}/api/${method}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(request), signal: AbortSignal.timeout(1e4), redirect: "error" })).json();
1188
1280
  if (!response.result?.ok) throw new Error("\u65E0\u6CD5\u9A8C\u8BC1 DSH \u4F1A\u8BDD\u72B6\u6001\uFF0C\u672A\u505C\u6B62\u8282\u70B9\u3002");
@@ -1190,7 +1282,7 @@ async function createInstallControl(context, config) {
1190
1282
  };
1191
1283
  const version = () => {
1192
1284
  try {
1193
- return JSON.parse(fs2.readFileSync(path5.join(profile, "node_modules/@harness-remote/dsh-wechat-remote/package.json"), "utf8")).version;
1285
+ return JSON.parse(fs3.readFileSync(path6.join(profile, "node_modules/@harness-remote/dsh-wechat-remote/package.json"), "utf8")).version;
1194
1286
  } catch {
1195
1287
  return "0.0.0";
1196
1288
  }
@@ -1230,9 +1322,14 @@ async function createInstallControl(context, config) {
1230
1322
  quiesced
1231
1323
  });
1232
1324
  if (req.url === "/read" && !quiesced) return json(200, await read(input.method, input.payload));
1325
+ if (req.url === "/health" && !quiesced) {
1326
+ if (!gateway) throw new Error("DSH \u4E1A\u52A1\u63A5\u53E3\u5C1A\u672A\u5C31\u7EEA");
1327
+ await gateway.invoke({ namespace: "session", method: "list", args: { _request: {} }, signal: AbortSignal.timeout(1e4) });
1328
+ return json(200, { ready: true });
1329
+ }
1233
1330
  if (req.url === "/launch" && !launched && !quiesced) {
1234
- const filename = path5.join(config.directory, "job.json");
1235
- const job = JSON.parse(fs2.readFileSync(filename, "utf8"));
1331
+ const filename = path6.join(config.directory, "job.json");
1332
+ const job = JSON.parse(fs3.readFileSync(filename, "utf8"));
1236
1333
  if (job.id !== id || job.controlOrigin !== origin || job.statusToken !== config.token || job.parentPid !== process.pid || job.home !== home || job.profile !== profile || job.cli !== cli || job.pnpm !== config.pnpm) throw new Error("\u5B89\u88C5\u76EE\u6807\u53D1\u751F\u53D8\u5316");
1237
1334
  startUpdateWorker(manager, config.directory, process.execPath);
1238
1335
  launched = true;
@@ -1277,7 +1374,7 @@ async function createInstallControl(context, config) {
1277
1374
  server.closeAllConnections();
1278
1375
  server.close();
1279
1376
  }
1280
- writePrivateJsonAtomic(path5.join(config.directory, "control-ready.json"), { origin, pid: process.pid });
1377
+ writePrivateJsonAtomic(path6.join(config.directory, "control-ready.json"), { origin, pid: process.pid });
1281
1378
  return { origin, close };
1282
1379
  }
1283
1380
  var apply = async (ctx, config) => {
@@ -1288,5 +1385,6 @@ export {
1288
1385
  apply,
1289
1386
  createInstallControl,
1290
1387
  inject,
1388
+ nativeInstallSessions,
1291
1389
  quiesceNativeHost
1292
1390
  };
@@ -7452,7 +7452,7 @@ async function verifyNativeRestore(job, start, sessionIds, readableIds) {
7452
7452
  const child = start();
7453
7453
  ref = await waitForJson(path2.join(directory, "control-ready.json"), (value) => Number.isInteger(value.pid) && value.pid > 0 && (!child || value.pid === child.pid) && /^http:\/\/127\.0\.0\.1:[1-9]\d{0,4}$/.test(value.origin), 18e4);
7454
7454
  const host = await request("describe");
7455
- if (host.pid !== ref.pid || host.home !== job.home || host.profile !== job.profile || host.cli !== job.cli || host.dshVersion !== job.dshVersion || host.pluginVersion !== "0.0.0") {
7455
+ if (host.pid !== ref.pid || host.home !== job.home || host.profile !== job.profile || host.cli !== job.cli || host.dshVersion !== job.dshVersion || host.pluginVersion !== job.previousVersion) {
7456
7456
  throw new Error("\u6062\u590D\u540E\u7684 DSH \u8EAB\u4EFD\u4E0D\u5339\u914D\u3002");
7457
7457
  }
7458
7458
  const items = (await request("read", { method: "session.list" })).items;
@@ -564,6 +564,7 @@ async function executeUpdate(job, progress, quiesce) {
564
564
  const emit = (phase, n, message) => progress({ phase, progress: n, message, terminal: false });
565
565
  let stopped = false, disposed = false, modified = false, newChild;
566
566
  let before = {}, sessionIds = [], readableIds = [];
567
+ let preexistingFailure = false;
567
568
  try {
568
569
  emit("preparing", 25, "\u51C6\u5907\u5B89\u88C5\u5DE5\u5177\uFF0C\u5F53\u524D\u8282\u70B9\u4ECD\u53EF\u4F7F\u7528");
569
570
  const runtime = await pinInstallRuntime({ executable: job.executable, cli: job.pnpm, version: INSTALL_PNPM_VERSION }, job.directory);
@@ -572,6 +573,13 @@ async function executeUpdate(job, progress, quiesce) {
572
573
  emit("checking", 50, "\u786E\u8BA4\u4F1A\u8BDD\u7A7A\u95F2\u5E76\u4FDD\u5B58\u72B6\u6001");
573
574
  const old = job.controlOrigin ? await control(job, "describe") : await describe(job);
574
575
  if (old.pluginVersion !== job.previousVersion) throw new Error("\u5F53\u524D\u63D2\u4EF6\u5728\u68C0\u67E5\u540E\u53D1\u751F\u53D8\u5316");
576
+ if (job.controlOrigin && job.previousVersion !== "0.0.0") {
577
+ try {
578
+ preexistingFailure = (await control(job, "health")).ready !== true;
579
+ } catch {
580
+ preexistingFailure = true;
581
+ }
582
+ }
575
583
  const list = (await beforeRpc(job, "session.list")).items;
576
584
  if (!Array.isArray(list) || list.some((s) => s.running !== false)) throw new Error("\u8BF7\u7B49\u5F85\u5168\u90E8\u4F1A\u8BDD\u7ED3\u675F\u540E\u518D\u66F4\u65B0");
577
585
  sessionIds = list.map((s) => s.sessionId).sort();
@@ -643,7 +651,7 @@ async function executeUpdate(job, progress, quiesce) {
643
651
  fs4.renameSync(job.profile, path4.join(job.directory, "profile-failed"));
644
652
  fs4.renameSync(previous, job.profile);
645
653
  }
646
- if (job.previousVersion === "0.0.0") {
654
+ if (job.previousVersion === "0.0.0" || preexistingFailure) {
647
655
  const recovery = await import(pathToFileURL(path4.join(job.directory, "native-recovery.js")).href);
648
656
  await recovery.verifyNativeRestore(job, () => start(job), sessionIds, readableIds);
649
657
  } else {
@@ -664,7 +672,7 @@ async function executeUpdate(job, progress, quiesce) {
664
672
  return { phase: "attention", progress: 100, message: "\u81EA\u52A8\u6062\u590D\u672A\u5B8C\u6210\u3002\u5907\u4EFD\u5DF2\u4FDD\u7559\uFF0C\u8BF7\u6309\u4E3B\u673A\u66F4\u65B0\u8BB0\u5F55\u6062\u590D\uFF1B\u4E0D\u8981\u5220\u9664\u8282\u70B9\u6216\u6570\u636E\u3002", terminal: true, ok: false, rollback: false };
665
673
  }
666
674
  }
667
- return { phase: "failed", progress: 100, message: (error instanceof Error ? error.message : "\u66F4\u65B0\u5931\u8D25") + (rollback ? "\uFF1B\u5DF2\u6062\u590D\u539F\u63D2\u4EF6\u3002" : "\uFF1B\u5F53\u524D\u63D2\u4EF6\u672A\u66FF\u6362\u3002"), terminal: true, ok: false, rollback };
675
+ return { phase: "failed", progress: 100, message: (error instanceof Error ? error.message : "\u66F4\u65B0\u5931\u8D25") + (rollback ? preexistingFailure ? "\uFF1B\u5DF2\u6062\u590D\u5B89\u88C5\u524D\u72B6\u6001\uFF0C\u539F\u6709\u63D2\u4EF6\u6545\u969C\u5C1A\u672A\u4FEE\u590D\uFF0C\u6570\u636E\u5DF2\u4FDD\u7559\u3002" : "\uFF1B\u5DF2\u6062\u590D\u539F\u63D2\u4EF6\u3002" : "\uFF1B\u5F53\u524D\u63D2\u4EF6\u672A\u66FF\u6362\u3002"), terminal: true, ok: false, rollback };
668
676
  }
669
677
  }
670
678
  async function workerMain(filename) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-wechat-remote",
3
- "version": "1.7.9",
3
+ "version": "1.7.10",
4
4
  "private": false,
5
5
  "description": "Agent远程管理助手:DSH 微信连接插件的一键安装与升级",
6
6
  "type": "module",