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 +4 -4
- package/assets/plugin.tgz +0 -0
- package/assets/release.json +30 -12
- package/bin/native-recovery.mjs +3 -3
- package/bin/setup.mjs +10 -3
- package/lib/install-control.js +120 -22
- package/lib/native-recovery.js +1 -1
- package/lib/update-worker.js +10 -2
- package/package.json +1 -1
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
|
package/assets/release.json
CHANGED
|
@@ -1,29 +1,47 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.7.
|
|
2
|
+
"version": "1.7.10",
|
|
3
3
|
"catalog": {
|
|
4
4
|
"schemaVersion": 1,
|
|
5
|
-
"revision": "installer-1.7.
|
|
6
|
-
"issuedAt":
|
|
7
|
-
"expiresAt":
|
|
5
|
+
"revision": "installer-1.7.10",
|
|
6
|
+
"issuedAt": 1790187001683,
|
|
7
|
+
"expiresAt": 1792606201683,
|
|
8
8
|
"releases": [
|
|
9
9
|
{
|
|
10
|
-
"version": "1.7.
|
|
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
|
-
"
|
|
17
|
-
"
|
|
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.
|
|
24
|
-
"sha256": "
|
|
25
|
-
"bytes":
|
|
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": [],
|
package/bin/native-recovery.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/** Verify a restored
|
|
2
|
-
*
|
|
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 !==
|
|
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
|
-
|
|
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()
|
|
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
|
-
|
|
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)
|
package/lib/install-control.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/* Generated from the shared plugin installation sources. */
|
|
2
2
|
|
|
3
3
|
// src/install-control.ts
|
|
4
|
-
import
|
|
5
|
-
import
|
|
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
|
|
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
|
|
1078
|
-
import
|
|
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?.()}` &&
|
|
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 =
|
|
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 =
|
|
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 = [
|
|
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",
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
1174
|
-
if (!/^[a-f0-9]{32}$/.test(id) || !/^[a-f0-9]{48}$/.test(config.token) ||
|
|
1175
|
-
const scope = resolveAgentProfileScope("", process.argv, home), profile =
|
|
1176
|
-
const cli =
|
|
1177
|
-
const manifest = JSON.parse(
|
|
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(
|
|
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 =
|
|
1235
|
-
const job = JSON.parse(
|
|
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(
|
|
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
|
};
|
package/lib/native-recovery.js
CHANGED
|
@@ -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 !==
|
|
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;
|
package/lib/update-worker.js
CHANGED
|
@@ -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) {
|