dsh-wechat-remote 1.7.12 → 1.7.13
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/assets/plugin.tgz +0 -0
- package/assets/release.json +10 -10
- package/bin/native-recovery.mjs +4 -7
- package/lib/companion-updates.js +3 -1
- package/lib/native-recovery.js +14 -6
- package/lib/update-session-verification.js +16 -0
- package/lib/update-worker.js +15 -4
- package/native/lib/client.js +2 -2
- package/native/lib/companion-updates.js +8 -2
- package/native/lib/plugin-version.d.ts +1 -1
- package/native/lib/plugin-version.js +1 -1
- package/native/lib/types/client/native-update-store.d.ts +7 -0
- package/native/lib/types/client/web-update-store.d.ts +1 -1
- package/native/lib/update-service.js +1 -1
- package/native/lib/update-session-verification.d.ts +13 -0
- package/native/lib/update-session-verification.js +25 -0
- package/native/lib/update-worker.d.ts +1 -0
- package/native/lib/update-worker.js +4 -7
- package/native/package.json +3 -3
- package/package.json +1 -1
package/assets/plugin.tgz
CHANGED
|
Binary file
|
package/assets/release.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.7.
|
|
2
|
+
"version": "1.7.13",
|
|
3
3
|
"catalog": {
|
|
4
4
|
"schemaVersion": 1,
|
|
5
|
-
"revision": "installer-1.7.
|
|
6
|
-
"issuedAt":
|
|
7
|
-
"expiresAt":
|
|
5
|
+
"revision": "installer-1.7.13",
|
|
6
|
+
"issuedAt": 1790629914471,
|
|
7
|
+
"expiresAt": 1793049114471,
|
|
8
8
|
"releases": [
|
|
9
9
|
{
|
|
10
|
-
"version": "1.7.
|
|
10
|
+
"version": "1.7.13",
|
|
11
11
|
"channel": "stable",
|
|
12
12
|
"dsh": [
|
|
13
|
-
"0.1.
|
|
13
|
+
"0.1.5-rc.2"
|
|
14
14
|
],
|
|
15
15
|
"platforms": [
|
|
16
16
|
"windows"
|
|
@@ -19,16 +19,16 @@
|
|
|
19
19
|
"x64"
|
|
20
20
|
],
|
|
21
21
|
"asset": {
|
|
22
|
-
"url": "https://github.com/martinbear1/dsh-wechat-remote/releases/download/v1.7.
|
|
23
|
-
"sha256": "
|
|
24
|
-
"bytes":
|
|
22
|
+
"url": "https://github.com/martinbear1/dsh-wechat-remote/releases/download/v1.7.13/harness-remote-dsh-wechat-remote-1.7.13.tgz",
|
|
23
|
+
"sha256": "91ded20e6805b7cf27034bc5dbda443937ad8f7d905d087366080ace080b1893",
|
|
24
|
+
"bytes": 10422958
|
|
25
25
|
},
|
|
26
26
|
"targets": [
|
|
27
27
|
{
|
|
28
28
|
"platform": "windows",
|
|
29
29
|
"arch": "x64",
|
|
30
30
|
"dsh": [
|
|
31
|
-
"0.1.
|
|
31
|
+
"0.1.5-rc.2"
|
|
32
32
|
]
|
|
33
33
|
}
|
|
34
34
|
]
|
package/bin/native-recovery.mjs
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
import fs from 'node:fs'
|
|
5
5
|
import path from 'node:path'
|
|
6
6
|
import { randomBytes } from 'node:crypto'
|
|
7
|
-
import { attachControl, waitForJson } from './native-control.mjs'
|
|
7
|
+
import { attachControl, waitForJson } from './native-control.mjs'
|
|
8
|
+
import { verifySessionService } from '../lib/update-session-verification.js'
|
|
8
9
|
|
|
9
|
-
export async function verifyNativeRestore(job, start,
|
|
10
|
+
export async function verifyNativeRestore(job, start, readableIds) {
|
|
10
11
|
const directory = path.join(job.home, 'harness-remote-updates', randomBytes(16).toString('hex'))
|
|
11
12
|
fs.mkdirSync(directory, { mode: 0o700 })
|
|
12
13
|
fs.writeFileSync(path.join(directory, 'package.json'), '{"type":"module"}\n', { mode: 0o600 })
|
|
@@ -32,11 +33,7 @@ export async function verifyNativeRestore(job, start, sessionIds, readableIds) {
|
|
|
32
33
|
|| host.cli !== job.cli || host.dshVersion !== job.dshVersion || host.pluginVersion !== job.previousVersion) {
|
|
33
34
|
throw new Error('恢复后的 DSH 身份不匹配。')
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
-
if (!Array.isArray(items) || JSON.stringify(items.map(s => s.sessionId).sort()) !== JSON.stringify(sessionIds)) {
|
|
37
|
-
throw new Error('恢复后的会话列表不一致。')
|
|
38
|
-
}
|
|
39
|
-
for (const sessionId of readableIds) await request('read', { method: 'session.history', payload: { sessionId, maxMessages: 1 } })
|
|
36
|
+
await verifySessionService((method, payload) => request('read', { method, payload }), readableIds)
|
|
40
37
|
} finally {
|
|
41
38
|
try { remove() }
|
|
42
39
|
finally { if (ref) { try { await request('close') } catch {} } }
|
package/lib/companion-updates.js
CHANGED
|
@@ -546,7 +546,9 @@ function offerCompanionUpdate(home, from, source, version) {
|
|
|
546
546
|
const directory = root(home), file = path5.join(directory, `${to}.json`);
|
|
547
547
|
try {
|
|
548
548
|
const existing = read(file);
|
|
549
|
-
if (existing.schema === 2 && existing.from === from && existing.version === version && existing.previous === peer.version)
|
|
549
|
+
if (existing.schema === 2 && existing.from === from && existing.version === version && existing.previous === peer.version) {
|
|
550
|
+
if (!source || existing.source || decision(home, existing) === "approve") return existing;
|
|
551
|
+
}
|
|
550
552
|
if (versionPattern2.test(existing.version) && compareVersions(existing.version, version) > 0) return;
|
|
551
553
|
} catch {
|
|
552
554
|
}
|
package/lib/native-recovery.js
CHANGED
|
@@ -7430,8 +7430,20 @@ async function waitForJson(filename, accept, timeout = 2e4) {
|
|
|
7430
7430
|
throw error;
|
|
7431
7431
|
}
|
|
7432
7432
|
|
|
7433
|
+
// installer/lib/update-session-verification.js
|
|
7434
|
+
async function verifySessionService(read, previouslyReadable) {
|
|
7435
|
+
const items = (await read("session.list"))?.items;
|
|
7436
|
+
if (!Array.isArray(items) || items.some((item) => !item || typeof item.sessionId !== "string" || !item.sessionId || typeof item.running !== "boolean") || new Set(items.map((item) => item.sessionId)).size !== items.length) {
|
|
7437
|
+
throw new Error("\u4E3B\u673A\u4F1A\u8BDD\u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6548\u7684\u4F1A\u8BDD\u5217\u8868");
|
|
7438
|
+
}
|
|
7439
|
+
const visible = new Set(items.map((item) => item.sessionId));
|
|
7440
|
+
for (const sessionId of new Set(previouslyReadable)) {
|
|
7441
|
+
if (visible.has(sessionId)) await read("session.history", { sessionId, maxMessages: 1 });
|
|
7442
|
+
}
|
|
7443
|
+
}
|
|
7444
|
+
|
|
7433
7445
|
// installer/bin/native-recovery.mjs
|
|
7434
|
-
async function verifyNativeRestore(job, start,
|
|
7446
|
+
async function verifyNativeRestore(job, start, readableIds) {
|
|
7435
7447
|
const directory = path2.join(job.home, "harness-remote-updates", randomBytes2(16).toString("hex"));
|
|
7436
7448
|
fs2.mkdirSync(directory, { mode: 448 });
|
|
7437
7449
|
fs2.writeFileSync(path2.join(directory, "package.json"), '{"type":"module"}\n', { mode: 384 });
|
|
@@ -7455,11 +7467,7 @@ async function verifyNativeRestore(job, start, sessionIds, readableIds) {
|
|
|
7455
7467
|
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
7468
|
throw new Error("\u6062\u590D\u540E\u7684 DSH \u8EAB\u4EFD\u4E0D\u5339\u914D\u3002");
|
|
7457
7469
|
}
|
|
7458
|
-
|
|
7459
|
-
if (!Array.isArray(items) || JSON.stringify(items.map((s) => s.sessionId).sort()) !== JSON.stringify(sessionIds)) {
|
|
7460
|
-
throw new Error("\u6062\u590D\u540E\u7684\u4F1A\u8BDD\u5217\u8868\u4E0D\u4E00\u81F4\u3002");
|
|
7461
|
-
}
|
|
7462
|
-
for (const sessionId of readableIds) await request("read", { method: "session.history", payload: { sessionId, maxMessages: 1 } });
|
|
7470
|
+
await verifySessionService((method, payload) => request("read", { method, payload }), readableIds);
|
|
7463
7471
|
} finally {
|
|
7464
7472
|
try {
|
|
7465
7473
|
remove();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/* Generated from the shared plugin installation sources. */
|
|
2
|
+
|
|
3
|
+
// src/update-session-verification.ts
|
|
4
|
+
async function verifySessionService(read, previouslyReadable) {
|
|
5
|
+
const items = (await read("session.list"))?.items;
|
|
6
|
+
if (!Array.isArray(items) || items.some((item) => !item || typeof item.sessionId !== "string" || !item.sessionId || typeof item.running !== "boolean") || new Set(items.map((item) => item.sessionId)).size !== items.length) {
|
|
7
|
+
throw new Error("\u4E3B\u673A\u4F1A\u8BDD\u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6548\u7684\u4F1A\u8BDD\u5217\u8868");
|
|
8
|
+
}
|
|
9
|
+
const visible = new Set(items.map((item) => item.sessionId));
|
|
10
|
+
for (const sessionId of new Set(previouslyReadable)) {
|
|
11
|
+
if (visible.has(sessionId)) await read("session.history", { sessionId, maxMessages: 1 });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
verifySessionService
|
|
16
|
+
};
|
package/lib/update-worker.js
CHANGED
|
@@ -86,6 +86,18 @@ function publishPrivateJson(file, value, replace) {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
// src/update-session-verification.ts
|
|
90
|
+
async function verifySessionService(read, previouslyReadable) {
|
|
91
|
+
const items = (await read("session.list"))?.items;
|
|
92
|
+
if (!Array.isArray(items) || items.some((item) => !item || typeof item.sessionId !== "string" || !item.sessionId || typeof item.running !== "boolean") || new Set(items.map((item) => item.sessionId)).size !== items.length) {
|
|
93
|
+
throw new Error("\u4E3B\u673A\u4F1A\u8BDD\u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6548\u7684\u4F1A\u8BDD\u5217\u8868");
|
|
94
|
+
}
|
|
95
|
+
const visible = new Set(items.map((item) => item.sessionId));
|
|
96
|
+
for (const sessionId of new Set(previouslyReadable)) {
|
|
97
|
+
if (visible.has(sessionId)) await read("session.history", { sessionId, maxMessages: 1 });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
89
101
|
// src/install-profile.ts
|
|
90
102
|
import fs from "node:fs";
|
|
91
103
|
import path from "node:path";
|
|
@@ -669,9 +681,7 @@ async function executeUpdate(job, progress, quiesce) {
|
|
|
669
681
|
await healthy(job, job.targetVersion);
|
|
670
682
|
await verifyFence(job);
|
|
671
683
|
assertPreserved(before, durableSnapshot(job));
|
|
672
|
-
|
|
673
|
-
if (JSON.stringify(after) !== JSON.stringify(sessionIds)) throw new Error("\u91CD\u542F\u540E\u4F1A\u8BDD\u5217\u8868\u4E0D\u4E00\u81F4");
|
|
674
|
-
for (const id of readableIds) await rpc(job, "session.history", { sessionId: id, maxMessages: 1 });
|
|
684
|
+
await verifySessionService((method, payload) => rpc(job, method, payload), readableIds);
|
|
675
685
|
writePrivateJsonAtomic(path4.join(job.directory, "verification-complete.json"), { id: job.id });
|
|
676
686
|
return { phase: "complete", progress: 100, message: "\u63D2\u4EF6\u66F4\u65B0\u5B8C\u6210\uFF0CDSH \u5DF2\u6062\u590D\uFF1B\u539F\u8282\u70B9\u65E0\u9700\u91CD\u65B0\u914D\u5BF9\u3002", terminal: true, ok: true };
|
|
677
687
|
} catch (error) {
|
|
@@ -713,7 +723,7 @@ async function executeUpdate(job, progress, quiesce) {
|
|
|
713
723
|
}
|
|
714
724
|
if (job.previousVersion === "0.0.0" || preexistingFailure) {
|
|
715
725
|
const recovery = await import(pathToFileURL(path4.join(job.directory, "native-recovery.js")).href);
|
|
716
|
-
await recovery.verifyNativeRestore(job, () => start(job),
|
|
726
|
+
await recovery.verifyNativeRestore(job, () => start(job), readableIds);
|
|
717
727
|
} else {
|
|
718
728
|
start(job);
|
|
719
729
|
await healthy(job, job.previousVersion);
|
|
@@ -865,6 +875,7 @@ if (process.argv[1] && path4.resolve(process.argv[1]) === fileURLToPath(import.m
|
|
|
865
875
|
});
|
|
866
876
|
}
|
|
867
877
|
export {
|
|
878
|
+
assertPreserved,
|
|
868
879
|
captureCandidateLock,
|
|
869
880
|
control,
|
|
870
881
|
durableSnapshot,
|
package/native/lib/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__ModuleLoader__.load({id:"@harness-remote/dsh-wechat-remote",factory:(require)=>{var module={exports:{}};var exports=module.exports;"use strict";var
|
|
1
|
+
window.__ModuleLoader__.load({id:"@harness-remote/dsh-wechat-remote",factory:(require)=>{var module={exports:{}};var exports=module.exports;"use strict";var he=Object.create;var I=Object.defineProperty;var ue=Object.getOwnPropertyDescriptor;var ge=Object.getOwnPropertyNames;var fe=Object.getPrototypeOf,me=Object.prototype.hasOwnProperty;var ve=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports),be=(r,e)=>{for(var t in e)I(r,t,{get:e[t],enumerable:!0})},L=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of ge(e))!me.call(r,i)&&i!==t&&I(r,i,{get:()=>e[i],enumerable:!(n=ue(e,i))||n.enumerable});return r};var C=(r,e,t)=>(t=r!=null?he(fe(r)):{},L(e||!r||!r.__esModule?I(t,"default",{value:r,enumerable:!0}):t,r)),ye=r=>L(I({},"__esModule",{value:!0}),r);var x=ve((Se,G)=>{"use strict";var _e=`.hr_98198fd_root {
|
|
2
2
|
box-sizing: border-box;\r
|
|
3
3
|
width: 100%;\r
|
|
4
4
|
max-width: 760px;\r
|
|
@@ -374,6 +374,6 @@ window.__ModuleLoader__.load({id:"@harness-remote/dsh-wechat-remote",factory:(re
|
|
|
374
374
|
.hr_98198fd_updateCommand { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 12px; }\r
|
|
375
375
|
.hr_98198fd_updateCommand code { flex: 1 1 240px; white-space: pre-wrap; overflow-wrap: anywhere; user-select: text; }\r
|
|
376
376
|
.hr_98198fd_updateChecked { display: block; margin-top: 10px; color: var(--text-secondary, #858b96); }\r
|
|
377
|
-
`,W="@harness-remote/dsh-wechat-remote/HarnessRemoteSettings.module.css";if(typeof document<"u"&&document.querySelector("style[data-plugin-css="+JSON.stringify(W)+"]")===null){let r=document.createElement("style");r.dataset.plugin="@harness-remote/dsh-wechat-remote",r.dataset.pluginCss=W,r.textContent=ye,document.head.appendChild(r)}G.exports={root:"hr_98198fd_root",hero:"hr_98198fd_hero",connectCard:"hr_98198fd_connectCard",pairingCard:"hr_98198fd_pairingCard",identity:"hr_98198fd_identity",mark:"hr_98198fd_mark",identityCopy:"hr_98198fd_identityCopy",pairingHead:"hr_98198fd_pairingHead",securityNote:"hr_98198fd_securityNote",sidebarButton:"hr_98198fd_sidebarButton",actions:"hr_98198fd_actions",pageDialog:"hr_98198fd_pageDialog",pageDialogContent:"hr_98198fd_pageDialogContent",helpText:"hr_98198fd_helpText",linkedSection:"hr_98198fd_linkedSection",companionCard:"hr_98198fd_companionCard",overall:"hr_98198fd_overall",statusDot:"hr_98198fd_statusDot",capabilities:"hr_98198fd_capabilities",capability:"hr_98198fd_capability",capabilityCopy:"hr_98198fd_capabilityCopy",notice:"hr_98198fd_notice",secondaryButton:"hr_98198fd_secondaryButton",primaryButton:"hr_98198fd_primaryButton",qrArea:"hr_98198fd_qrArea",qr:"hr_98198fd_qr",qrPlaceholder:"hr_98198fd_qrPlaceholder",qrValidity:"hr_98198fd_qrValidity",updateCard:"hr_98198fd_updateCard",updateLabel:"hr_98198fd_updateLabel",updateProgress:"hr_98198fd_updateProgress",updateVersions:"hr_98198fd_updateVersions",updateCommand:"hr_98198fd_updateCommand",updateChecked:"hr_98198fd_updateChecked"}});var xe={};ve(xe,{apply:()=>we,inject:()=>_e});module.exports=be(xe);var Q=require("react"),Z=require("@deepseek-ai/dsh-client-ui-primitives");var q=C(x(),1),f=require("react/jsx-runtime");function X({value:r,onDecide:e,deciding:t=!1}){if(!r||r.state==="idle"||!r.message)return null;let n={"confirmation-required":"是否同时更新另一端插件?",deferred:"已暂缓另一端更新",pending:"另一端插件更新待处理",busy:"等待另一端任务结束",preparing:"正在准备另一端插件更新",installing:"正在更新另一端插件",verifying:"正在核验另一端恢复",recovering:"正在恢复另一端原插件","restart-required":"另一端待启动或重启确认","self-restart-required":"当前端待重启,尚未生效",unverified:"两端更新状态待核实",complete:"两端插件已对齐",unavailable:"另一端未确认更新成功"},s=["preparing","installing","verifying","recovering"].includes(r.state);return(0,f.jsxs)("aside",{className:q.default.companionCard,"data-state":r.state,role:"status","aria-live":"polite",children:[(0,f.jsx)("strong",{children:n[r.state]||"另一端插件更新状态"}),(0,f.jsx)("p",{children:r.message}),r.versions?(0,f.jsxs)("small",{children:[r.versions.current==="desktop"?"Desktop":"Web",":运行 ",r.versions.running," · 已安装 ",r.versions.installed??"未确认",";",r.versions.peer==="desktop"?"Desktop":"Web",":已安装 ",r.versions.peerInstalled??"未确认或未启用"]}):null,["confirmation-required","deferred"].includes(r.state)&&r.offerId&&e?(0,f.jsxs)("div",{children:[(0,f.jsx)("button",{type:"button",className:q.default.primaryButton,disabled:t,onClick:()=>e("approve"),children:"确认并等待空闲后更新"}),(0,f.jsx)("button",{type:"button",className:q.default.secondaryButton,disabled:t,onClick:()=>e("later"),children:"稍后再说"})]}):null,s?(0,f.jsx)("progress",{"aria-label":"另一端插件更新进度"}):null,r.state==="busy"?(0,f.jsx)("small",{children:"不用手动停止任务。请保持两端运行,空闲后会继续检查。"}):s?(0,f.jsx)("small",{children:"请保持两端当前状态,暂勿退出、重启或重复安装。关闭本设置页不会取消更新。"}):r.state==="unavailable"?(0,f.jsx)("small",{children:"当前节点可继续使用;请在另一端核对版本,必要时使用其原安装入口。"}):null]})}async function F(r,e,t=fetch){let n=await r(),s=n.gate;if(!s)throw new Error("当前节点没有提供配对入口");let o=s.profileScope==="desktop",a=s.localDoor?.port,l=Number.isSafeInteger(a)&&a>=1&&a<=65535?`http://127.0.0.1:${a}`:null;if(s.management==="authenticated-rpc")return{host:n,runtime:s,localOrigin:o?null:l,status:()=>e("status"),pairCode:()=>e("pair-code"),decide:(h,g)=>e("companion-decision",{offerId:h,action:g})};if(o||s.management==="unavailable"||!l||s.localDoor.state!=="listening")throw new Error("当前节点配对入口尚未就绪");let u=async h=>{let g=await t(l+h,{cache:"no-store"});if(!g.ok)throw new Error(`配对请求失败 (${g.status})`);return g.json()};return{host:n,runtime:s,localOrigin:l,status:()=>u("/gate/status"),pairCode:()=>u("/pair/code"),decide:async(h,g)=>{let _=await t(l+"/gate/companion/decision",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({offerId:h,action:g})});if(!_.ok)throw new Error("更新通知已变化,请刷新后重试");return _.json()}}}var I=class{constructor(e,t=fetch){this.origin=e;this.fetcher=t}value={check:null,checking:!1,progress:null,error:"",copied:!1,resumeFailed:!1};listeners=new Set;reading;installing=!1;disposed=!1;job=null;timer;pollGeneration=0;recovering=!1;getSnapshot=()=>this.value;subscribe=e=>this.disposed?()=>{}:(this.listeners.add(e),this.listeners.size===1&&this.refresh(),()=>{this.listeners.delete(e)});patch(e){if(!this.disposed){this.value={...this.value,...e};for(let t of this.listeners)t()}}refresh=()=>this.disposed?Promise.resolve():this.reading?this.reading:(this.patch({checking:!0,error:"",copied:!1}),this.reading=(async()=>{try{let e=await this.fetcher(this.origin+"/gate/update/check",{signal:AbortSignal.timeout(1e4)}),t=await e.json();if(!e.ok)throw new Error(t.error||"暂时无法检查更新");if(!t.advice?.current||typeof t.advice.label!="string")throw new Error("更新检查返回信息不完整");if(this.patch({check:t}),this.installing||this.job||this.recovering)return;t.activeJob?.statusOrigin?this.follow(t.activeJob):t.lastResult?.phase==="unknown"?this.patch({progress:t.lastResult}):t.mode==="busy"?this.patch({progress:{phase:"preparing",progress:0,message:"更新仍在准备或等待确认,请稍后重新检查;不要重复安装。",terminal:!0}}):this.patch({progress:t.lastResult??null})}catch(e){this.patch({check:null,error:e instanceof Error?e.message:"暂时无法检查更新"})}finally{this.patch({checking:!1})}})().finally(()=>{this.reading=void 0}),this.reading);follow(e){if(this.disposed||this.job?.jobId===e.jobId)return;this.job=e;let t=++this.pollGeneration,n=Date.now()+10*6e4;clearTimeout(this.timer),this.patch({progress:{phase:"recovering",progress:0,message:"正在恢复更新进度…",terminal:!1}});let s=async()=>{if(this.disposed||t!==this.pollGeneration)return;let o;try{let a=await this.fetcher(e.statusOrigin+"/status",{headers:{Authorization:"Bearer "+e.statusToken},signal:AbortSignal.timeout(4e3)});if(!a.ok)throw new Error("进度暂不可用");let l=await a.json();if(l.jobId!==e.jobId||typeof l.terminal!="boolean"||typeof l.message!="string")throw new Error("进度不匹配");o=l}catch{try{let a=await this.fetcher(this.origin+"/gate/update/status",{signal:AbortSignal.timeout(3e3)}),l=await a.json();a.ok&&l.lastResult?.jobId===e.jobId&&(o=l.lastResult)}catch{}}if(!(this.disposed||t!==this.pollGeneration)){if(o&&(this.patch({progress:o}),o.terminal)){this.job=null,o.ok?this.resume(e.jobId):this.refresh();return}if(Date.now()>n){this.job=null,this.patch({progress:{phase:"unknown",progress:0,message:"暂时无法确认更新结果。请重新打开此主机 WebUI 检查版本;不要重复安装或删除节点。",terminal:!0}});return}this.timer=setTimeout(()=>void s(),1e3)}};s()}async resume(e){this.recovering=!0;let t=Date.now()+3e4,n=async()=>{try{let s=await this.fetcher(this.origin+"/gate/update/resume?job="+encodeURIComponent(e),{signal:AbortSignal.timeout(3e3)});if(!s.ok)throw new Error("尚未恢复");let o=await s.json();if(!o.url)throw new Error("缺少恢复地址");let a=new URL(o.url);if(a.origin!==window.location.origin||a.pathname!=="/"||a.username||a.password||a.hash)throw new Error("恢复地址不匹配");this.disposed||window.location.replace(a.href)}catch{if(this.disposed)return;if(Date.now()>=t){this.recovering=!1,this.patch({resumeFailed:!0});return}this.timer=setTimeout(()=>void n(),1e3)}};await n()}install=async()=>{let{check:e,checking:t,error:n,progress:s}=this.value;if(this.disposed||!e?.canInstall||t||n||s&&!s.terminal||this.installing||this.job||this.recovering)return;this.installing=!0,this.patch({error:"",check:{...e,canInstall:!1,ticket:""},progress:{phase:"download",progress:10,message:"正在下载并验证更新包;当前插件尚未替换",terminal:!1}});let o=!1;try{let a=await this.fetcher(this.origin+"/gate/update/start",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({ticket:e.ticket})}),l=await a.json();if(o=!a.ok&&typeof l.error=="string",!a.ok||!l.statusOrigin)throw new Error(l.error||"无法取得更新进度,请重新检查");this.follow(l)}catch(a){if(this.disposed)return;if(o)this.patch({progress:{phase:"failed",progress:0,message:(a instanceof Error?a.message:"更新暂不可用")+"。请重新检查更新。",terminal:!0,ok:!1}});else{try{let l=await this.fetcher(this.origin+"/gate/update/status",{signal:AbortSignal.timeout(3e3)}),u=await l.json();if(l.ok&&u.activeJob?.statusOrigin){this.follow(u.activeJob);return}}catch{}this.patch({progress:{phase:"unknown",progress:0,message:"连接中断,暂时无法确认更新结果。请稍后检查更新;不要重复安装或删除节点。",terminal:!0}})}}finally{this.installing=!1}};copyCommand=async()=>{if(!(!this.value.check?.manualCommand||this.value.checking||this.installing||this.job))try{await navigator.clipboard.writeText(this.value.check.manualCommand),this.patch({copied:!0})}catch{this.patch({error:"复制失败,请手动选中下方命令复制。"})}};dispose=()=>{this.disposed=!0,this.pollGeneration++,clearTimeout(this.timer),this.listeners.clear()}};var U=class{constructor(e){this.call=e}value={checking:!1,starting:!1,check:null,status:null,error:null};listeners=new Set;reading;timer;disposed=!1;getSnapshot=()=>this.value;patch(e){if(!this.disposed){this.value={...this.value,...e};for(let t of this.listeners)t()}}subscribe=e=>(this.listeners.add(e),!this.value.check&&!this.value.starting?this.refresh():this.active()&&this.poll(),()=>{this.listeners.delete(e),this.listeners.size||(clearTimeout(this.timer),this.timer=void 0)});active(){return this.value.status&&["preparing","installing","unknown","restart-required"].includes(this.value.status.phase)}refresh=()=>this.disposed||this.value.starting?Promise.resolve():this.reading?this.reading:(this.patch({checking:!0,error:null}),this.reading=(async()=>{try{let e=await this.call("update-check");if(!e?.advice||!e.status||typeof e.canInstall!="boolean")throw Error("invalid update response");this.patch({check:e,status:e.status}),this.active()&&this.poll()}catch{let e=!1;try{let t=await this.call("status");e=!!(t&&!t.plugin)}catch{}this.patch({check:null,error:e?"当前运行的连接服务尚不支持此更新入口。若刚安装新版,请在任务结束后退出并重新打开 Desktop;无需重新配对。仍不可用时请在原生插件管理器核对安装。":"本次更新检查未完成,不能据此判断已是最新版。请稍后重试;配对和聊天不受此次检查失败影响。"})}finally{this.patch({checking:!1})}})().finally(()=>{this.reading=void 0}),this.reading);install=async()=>{let e=this.value.check;if(!(this.disposed||this.value.starting||this.value.checking||!e?.canInstall||!e.ticket)){this.patch({starting:!0,error:null,check:{...e,canInstall:!1,ticket:""}});try{let t=await this.call("update-start",{ticket:e.ticket});this.patch({status:t})}catch(t){this.patch({status:t?.code==="update/not-started"?{phase:"failed",message:t instanceof Error?t.message:"尚未启动更新,请重新检查"}:{phase:"unknown",message:"更新提交结果暂未确认,正在核对;请勿重复安装。"}})}finally{this.patch({starting:!1}),this.poll()}}};poll(){this.disposed||this.timer||!this.listeners.size||!this.active()||(this.timer=setTimeout(async()=>{try{let e=await this.call("update-status");if(!e||!["idle","preparing","installing","restart-required","complete","failed","unknown"].includes(e.phase))throw Error("invalid status");this.patch({status:e,error:e.phase==="idle"?"尚未发现已接受的更新,请重新检查后再操作。":null})}catch{this.patch({error:"更新状态暂不可用;恢复连接后继续核对,不会重复安装。"})}finally{this.timer=void 0,this.poll()}},2e3))}dispose(){this.disposed=!0,clearTimeout(this.timer),this.listeners.clear()}};var A=()=>({loadState:"loading",qrState:"idle",status:null,host:null,runtime:null,localOrigin:null,qr:null,error:null,decisionError:null,deciding:!1}),P=class{constructor(e,t){this.describeHost=e;this.callManagement=t}updates;nativeUpdates;value=A();listeners=new Set;timer;reading;pairing;disposed=!1;identity="";generation=0;getSnapshot=()=>this.value;subscribe=e=>this.disposed?()=>{}:(this.listeners.add(e),this.timer||(this.refresh(),this.timer=setInterval(()=>void this.refresh(),2e3)),()=>{this.listeners.delete(e),this.listeners.size||(clearInterval(this.timer),this.timer=void 0)});patch(e){if(!this.disposed){this.value={...this.value,...e};for(let t of this.listeners)t()}}async client(){let e=await F(this.describeHost,this.callManagement);if(this.disposed)throw new Error("disposed");let t=`${e.host.agentInstanceId??e.host.computerName}/${e.runtime.profileScope??"web"}`;return this.identity&&this.identity!==t&&(this.generation++,this.value=A(),this.updates?.dispose(),this.updates=void 0,this.nativeUpdates?.dispose(),this.nativeUpdates=void 0),this.identity=t,e.runtime.profileScope==="desktop"&&!this.nativeUpdates&&(this.nativeUpdates=new U(this.callManagement)),this.updates?.origin!==e.localOrigin&&(this.updates?.dispose(),this.updates=e.localOrigin?new I(e.localOrigin):void 0),this.patch({host:e.host,runtime:e.runtime,localOrigin:e.localOrigin}),e}refresh=()=>this.disposed?Promise.resolve():this.reading?this.reading:(this.reading=(async()=>{try{let e=await this.client(),t=this.generation,n={...await e.status()};if(n.companionUpdate?.state==="complete"&&!n.plugin&&(n.companionUpdate={state:"unverified",message:"当前连接服务尚未提供运行版本核验,不能确认两端已对齐。若刚安装插件,请在任务结束后退出并重新打开当前应用;无需重新配对。"}),t!==this.generation)return;this.patch({status:n,runtime:n.gate??e.runtime,loadState:"ready",error:null,...this.value.qr&&this.value.qr.expiresAt<=Date.now()?{qrState:"expired",qr:null}:{}})}catch{this.patch({loadState:"error",error:"连接服务暂未就绪。请确认 DSH 正在运行,然后重试。"})}})().finally(()=>{this.reading=void 0}),this.reading);generateQr=()=>this.disposed?Promise.resolve():this.pairing?this.pairing:(this.patch({qrState:"loading",qr:null,error:null}),this.pairing=(async()=>{try{let e=await this.client(),t=this.generation,n=await e.pairCode();if(t!==this.generation)return;if(!n?.qrDataUrl?.startsWith("data:image/")||!Number.isFinite(n.expiresAt)||n.expiresAt<=Date.now())throw new Error("invalid QR");this.patch({qr:n,qrState:"ready"}),await this.refresh()}catch{this.patch({qr:null,qrState:"error",error:"暂时无法生成配对二维码,请稍后重试。"})}})().finally(()=>{this.pairing=void 0}),this.pairing);decide=async e=>{let t=this.value.status?.companionUpdate?.offerId;if(!(this.disposed||this.value.deciding||!t)){this.patch({deciding:!0,decisionError:null});try{let n=await this.client();if(this.value.status?.companionUpdate?.offerId!==t)throw new Error("stale offer");await n.decide(t,e),await this.reading,await this.refresh()}catch{this.patch({decisionError:"更新选择未确认,请刷新后核对;不要重复安装。"})}finally{this.patch({deciding:!1})}}};dispose=()=>{this.disposed=!0,this.generation++,clearInterval(this.timer),this.timer=void 0,this.listeners.clear(),this.value=A(),this.updates?.dispose(),this.updates=void 0,this.nativeUpdates?.dispose(),this.nativeUpdates=void 0}},$=new WeakMap;function B(r,e){let t=$.get(r);return t||(t=new P(r,e),$.set(r,t)),t}var w=C(x(),1),v=require("react/jsx-runtime");function E({describeHost:r,callManagement:e,onDismiss:t,onOpenDetails:n,store:s=B(r,e)}){let{status:o,deciding:a,decisionError:l,error:u,loadState:h}=(0,Q.useSyncExternalStore)(s.subscribe,s.getSnapshot,s.getSnapshot),g=o?.companionUpdate,_=l||u,S=(0,v.jsxs)("div",{children:[(0,v.jsx)(X,{value:g,deciding:a||h!=="ready",onDecide:H=>void s.decide(H)}),!g&&!_?(0,v.jsx)("p",{className:w.default.helpText,children:"正在检查联动更新状态;尚未确认的更新不会自动启动。"}):g?.state==="idle"?(0,v.jsx)("p",{className:w.default.helpText,children:"未发现需要联动更新的另一端。当前节点可独立使用,不会自动安装另一端或启用已停用的插件。"}):null,_?(0,v.jsx)("p",{role:"alert",children:_}):null,t?(0,v.jsxs)("div",{className:w.default.actions,children:[n?(0,v.jsx)("button",{type:"button",className:w.default.primaryButton,onClick:n,children:"前往微信连接"}):null,(0,v.jsx)("button",{type:"button",className:w.default.secondaryButton,onClick:t,children:"关闭提示(不会确认更新)"})]}):null]});return t?(0,v.jsx)(Z.Modal,{open:!0,title:"微信连接 · 联动更新",closeLabel:"关闭提示",className:w.default.pageDialog,contentClassName:w.default.pageDialogContent,onClose:t,children:S}):S}var D=require("react"),K=C(x(),1),b=require("react/jsx-runtime");function Y({wide:r,openPlugin:e}){let[t,n]=(0,D.useState)(!1),[s,o]=(0,D.useState)(!1),a=(0,D.useRef)(!1),l=async()=>{if(!a.current){a.current=!0,o(!0);try{await e()||n(!0)}finally{a.current=!1,o(!1)}}};return(0,b.jsxs)(b.Fragment,{children:[(0,b.jsxs)("button",{type:"button",className:K.default.sidebarButton,"data-wide":r,title:"微信连接","aria-label":"微信连接",disabled:s,onClick:()=>void l(),children:[(0,b.jsxs)("svg",{width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.7","aria-hidden":"true",children:[(0,b.jsx)("path",{d:"M20 11.5a8 8 0 0 1-8 8 9 9 0 0 1-3.5-.7L4 20l1.2-4.2A7.8 7.8 0 0 1 4 11.5a8 8 0 0 1 16 0Z"}),(0,b.jsx)("path",{d:"M8 11.5h.01M12 11.5h.01M16 11.5h.01",strokeWidth:"2.5",strokeLinecap:"round"})]}),r?(0,b.jsx)("span",{children:s?"正在打开…":"微信连接"}):null]}),t?(0,b.jsx)("small",{role:"status",children:"插件详情暂不可用,请打开「设置 → 微信连接」。"}):null]})}async function ee(r){try{let e=r("pluginNavigation"),t=r("remote.pluginManager");if(typeof e?.openBundle!="function"||typeof t?.listBundles!="function")return!1;let n=await t.listBundles();if(!n.ok||!Array.isArray(n.value))return!1;let s=n.value.filter(a=>a.enabled&&["dsh-wechat-remote","@harness-remote/dsh-wechat-remote"].includes(a.name));if(s.length!==1)return!1;let o=r("pluginNavigation");return o!==e||typeof o.openBundle!="function"?!1:(o.openBundle(s[0].name),!0)}catch{return!1}}var j=require("react"),ie=require("@deepseek-ai/dsh-client-ui-primitives"),c=C(x(),1);var te=require("react"),m=C(x(),1),d=require("react/jsx-runtime");function re({store:r}){let{check:e,checking:t,progress:n,error:s,copied:o,resumeFailed:a}=(0,te.useSyncExternalStore)(r.subscribe,r.getSnapshot,r.getSnapshot),l=!!(n&&!n.terminal);return(0,d.jsxs)("div",{className:m.default.updateCard,children:[(0,d.jsxs)("div",{className:m.default.pairingHead,children:[(0,d.jsx)("div",{children:(0,d.jsx)("strong",{children:"当前端插件更新"})}),(0,d.jsx)("button",{type:"button",className:m.default.secondaryButton,disabled:t||l,onClick:()=>void r.refresh(),children:t?"检查中…":"检查更新"})]}),e?(0,d.jsxs)(d.Fragment,{children:[e.channel==="preview"?(0,d.jsx)("span",{className:m.default.updateLabel,"data-severity":"recommended",children:"预览通道"}):null,(0,d.jsxs)("div",{className:m.default.updateVersions,children:[(0,d.jsxs)("span",{children:["DSH ",(0,d.jsx)("strong",{children:e.advice.current.agentVersion||"未知"})]}),(0,d.jsxs)("span",{children:["插件 ",(0,d.jsx)("strong",{children:e.advice.current.pluginVersion||"未知"})]})]}),t?null:(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)("strong",{className:m.default.updateLabel,"data-severity":e.advice.severity,role:"status",children:e.advice.label}),e.advice.targetVersion?(0,d.jsxs)("p",{children:["可更新至 ",e.advice.targetVersion,e.mode==="manual"?" · 需手动更新":""]}):null,e.advice.severity==="unknown"||e.advice.severity==="required"||e.mode==="manual"||e.mode==="busy"?(0,d.jsxs)("details",{children:[(0,d.jsx)("summary",{children:e.mode==="manual"?"手动更新说明":"查看说明"}),e.reason?(0,d.jsx)("p",{children:e.reason}):null,(0,d.jsx)("p",{children:e.advice.message})]}):null,e.mode==="manual"&&e.manualCommand?(0,d.jsxs)("div",{className:m.default.updateCommand,children:[(0,d.jsx)("code",{children:e.manualCommand}),(0,d.jsx)("button",{type:"button",className:m.default.secondaryButton,disabled:l,onClick:()=>void r.copyCommand(),children:o?"已复制":"复制命令"})]}):null,e.canInstall?(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)("button",{type:"button",className:m.default.primaryButton,disabled:l||!!s,onClick:()=>void r.install(),children:"更新并重启"}),(0,d.jsx)("small",{className:m.default.updateChecked,children:"只更新当前 Web 插件;会短暂断开连接,保留原配对和会话"})]}):null,e.advice.checkedAt&&e.advice.severity!=="unknown"?(0,d.jsxs)("small",{className:m.default.updateChecked,children:["最近检查 ",new Date(e.advice.checkedAt).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})]}):null]})]}):null,s?(0,d.jsx)("p",{role:"alert",children:s}):null,n?(0,d.jsxs)("div",{role:"status","aria-live":"polite",children:[!n.terminal||n.ok===!0?(0,d.jsx)("progress",{className:m.default.updateProgress,max:100,value:n.progress}):null,(0,d.jsx)("p",{children:n.message}),a?(0,d.jsx)("p",{role:"alert",children:"插件已完成更新,但未能自动打开 WebUI。请使用 DSH 启动时显示的地址打开,无需重复更新。"}):null]}):null]})}var ne=require("react"),y=C(x(),1),p=require("react/jsx-runtime");function ae({store:r}){let{check:e,status:t,checking:n,starting:s,error:o}=(0,ne.useSyncExternalStore)(r.subscribe,r.getSnapshot,r.getSnapshot),a=s||t?.phase==="preparing"||t?.phase==="installing",l=t&&["preparing","installing","restart-required","unknown"].includes(t.phase),u=t?.phase==="restart-required"||t?.phase==="complete",h=t?.phase==="restart-required"?"已安装,待重新打开 Desktop":t?.phase==="unknown"?"更新结果待核对":a?"正在更新 Desktop 插件":t?.phase==="complete"?"更新已生效":"";return(0,p.jsxs)("div",{className:y.default.updateCard,children:[(0,p.jsxs)("div",{className:y.default.pairingHead,children:[(0,p.jsx)("strong",{children:"当前端插件更新 · Desktop"}),(0,p.jsx)("button",{type:"button",className:y.default.secondaryButton,disabled:n||a,onClick:()=>void r.refresh(),children:n?"检查中…":"检查更新"})]}),e?(0,p.jsxs)(p.Fragment,{children:[e.channel==="preview"?(0,p.jsx)("span",{className:y.default.updateLabel,"data-severity":"recommended",children:"预览通道"}):null,(0,p.jsxs)("div",{className:y.default.updateVersions,children:[(0,p.jsxs)("span",{children:["DSH ",(0,p.jsx)("strong",{children:e.advice.current.agentVersion})]}),(0,p.jsxs)("span",{children:["运行中的插件 ",(0,p.jsx)("strong",{children:e.advice.current.pluginVersion})]})]}),(0,p.jsx)("strong",{className:y.default.updateLabel,"data-severity":u?"none":e.advice.severity,children:h||e.advice.label}),u&&t.targetVersion?(0,p.jsxs)("p",{children:["已安装版本 ",t.targetVersion]}):!l&&e.advice.targetVersion?(0,p.jsxs)("p",{children:["可更新至 ",e.advice.targetVersion]}):null,!l&&e.reason?(0,p.jsx)("p",{children:e.reason}):null,!l&&e.canInstall?(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)("p",{className:y.default.helpText,children:"仅更新当前 Desktop 插件,由原生插件管理器使用应用配置的安装源下载并安装指定版本。配对和会话保留,不更新 Web,不自动退出应用;安装完成后需重新打开 Desktop。"}),(0,p.jsx)("button",{type:"button",className:y.default.primaryButton,disabled:a||n,onClick:()=>void r.install(),children:"更新 Desktop 插件"})]}):null,(0,p.jsxs)("small",{className:y.default.updateChecked,children:["最近检查 ",new Date(e.advice.checkedAt).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})]})]}):null,t?.phase!=="idle"&&t?(0,p.jsxs)("div",{role:"status","aria-live":"polite",children:[a?(0,p.jsx)("progress",{className:y.default.updateProgress,"aria-label":"Desktop 插件更新中"}):null,(0,p.jsx)("p",{children:t.message})]}):null,o?(0,p.jsx)("p",{role:"alert",children:o}):null]})}var i=require("react/jsx-runtime");function se({ok:r,busy:e=!1}){return(0,i.jsx)("span",{className:c.default.statusDot,"data-state":e?"busy":r?"ready":"off","aria-hidden":!0})}function V({title:r,detail:e,ok:t,busy:n=!1}){return(0,i.jsxs)("div",{className:c.default.capability,children:[(0,i.jsx)(se,{ok:t,busy:n}),(0,i.jsxs)("div",{className:c.default.capabilityCopy,children:[(0,i.jsx)("strong",{children:r}),(0,i.jsx)("span",{children:e})]})]})}function M({describeHost:r,callManagement:e,store:t=B(r,e)}){let{loadState:n,qrState:s,status:o,runtime:a,host:l,localOrigin:u,qr:h,error:g}=(0,j.useSyncExternalStore)(t.subscribe,t.getSnapshot,t.getSnapshot),{refresh:_,generateQr:S}=t,H=(0,j.useId)(),k=o?.publicRelay,z=k?.state==="enrolling"||k?.state==="connecting",N=k?.remoteAccess?.status,T=k?.state==="online"&&N==="active",oe=N==="suspended"?"账户公网访问已暂停":N==="expired"?"公网访问已到期":N==="not_entitled"?"请在小程序中使用自助开通方式":N!=="active"?"配对后由小程序账户决定":T?"可在外网安全连接":z?"正在准备远程连接":"暂时离线",le=a?.publicDoor??o?.gate?.publicDoor,J=n==="ready"&&!!o?.lan.ip&&le?.state==="listening",O=k?.enabled===!0&&k.state!=="disabled",de=o?.agent?.agentName||l?.agentName||"DeepSeek Harness",pe=o?.agent?.hostName||l?.computerName||"当前电脑";return(0,i.jsxs)("section",{className:c.default.root,"aria-labelledby":H,children:[(0,i.jsxs)("div",{className:c.default.hero,children:[(0,i.jsxs)("div",{className:c.default.identity,children:[(0,i.jsx)("span",{className:c.default.mark,"aria-hidden":!0,children:(0,i.jsx)(ie.FishLogo,{size:28})}),(0,i.jsxs)("div",{className:c.default.identityCopy,children:[(0,i.jsx)("h3",{id:H,children:"微信连接"}),(0,i.jsxs)("p",{children:[de,(0,i.jsx)("span",{"aria-hidden":!0,children:" · "}),pe]})]})]}),(0,i.jsxs)("span",{className:c.default.overall,"data-ready":n==="ready",children:[(0,i.jsx)(se,{ok:n==="ready",busy:n==="loading"}),n==="loading"?"检测中":n==="ready"?"服务正常":"暂不可用"]})]}),a?.profileScope==="desktop"&&o&&(!o.plugin||o.plugin.runningVersion!==o.plugin.installedVersion)?(0,i.jsxs)("aside",{className:c.default.companionCard,"data-state":"self-restart-required",role:"status",children:[(0,i.jsx)("strong",{children:o.plugin?"新版插件尚未生效,请完整重启 Desktop":"刚升级插件?请先完整重启 Desktop"}),o.plugin?(0,i.jsxs)("p",{children:["正在运行 ",o.plugin.runningVersion,";已安装 ",o.plugin.installedVersion,"。"]}):(0,i.jsx)("p",{children:"当前连接服务尚不能核验运行版本,不能把安装完成当作新版已生效。"}),(0,i.jsx)("p",{children:"等当前任务结束后,选择顶部「应用 → 退出」,再重新打开 Desktop。仅关闭窗口、刷新页面或开关插件不能保证新版生效。"}),(0,i.jsx)("small",{children:"不必重装或重新配对。此操作不会升级或重启 Web。"})]}):null,(0,i.jsxs)("div",{className:c.default.capabilities,children:[(0,i.jsx)(V,{title:"局域网直连",detail:n==="loading"?"检测中":J?"已就绪":"暂不可用",ok:J,busy:n==="loading"}),(0,i.jsx)(V,{title:"远程访问",detail:oe,ok:T,busy:z||n==="loading"}),(0,i.jsx)(V,{title:"账号连接保护",detail:O?"已启用":"配对后启用",ok:O,busy:n==="loading"})]}),g!==null?(0,i.jsxs)("div",{className:c.default.notice,role:"status",children:[(0,i.jsx)("span",{children:g}),(0,i.jsx)("button",{type:"button",onClick:()=>void _(),children:"重试"})]}):null,s==="idle"?(0,i.jsxs)("div",{className:c.default.connectCard,children:[(0,i.jsxs)("div",{children:[(0,i.jsx)("strong",{children:"添加到微信"}),(0,i.jsx)("p",{children:"打开「Agent远程管理助手」→ 添加节点,扫描配对二维码。"})]}),(0,i.jsx)("button",{type:"button",className:c.default.primaryButton,disabled:n!=="ready",onClick:()=>void S(),children:"生成二维码"})]}):(0,i.jsxs)("div",{className:c.default.pairingCard,children:[(0,i.jsxs)("div",{className:c.default.pairingHead,children:[(0,i.jsxs)("div",{children:[(0,i.jsx)("strong",{children:"扫描二维码"}),(0,i.jsx)("p",{children:"小程序「设置 → 添加节点」"})]}),(0,i.jsx)("button",{type:"button",className:c.default.secondaryButton,disabled:s==="loading"||n!=="ready",onClick:()=>void S(),children:s==="loading"?"生成中…":"重新生成"})]}),(0,i.jsxs)("div",{className:c.default.qrArea,children:[n==="ready"&&s==="ready"&&h!==null?(0,i.jsx)("img",{className:c.default.qr,src:h.qrDataUrl,alt:"Agent远程管理助手配对二维码"}):(0,i.jsx)("div",{className:c.default.qrPlaceholder,"aria-live":"polite",children:n!=="ready"?"连接恢复后可查看二维码":s==="expired"?"二维码已过期,请重新生成":s==="error"?"生成失败":"正在生成…"}),s==="ready"&&h!==null?(0,i.jsxs)("small",{className:c.default.qrValidity,children:["有效至 ",new Date(h.expiresAt).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})," · 切换页面不重新生成"]}):null]}),(0,i.jsx)("p",{className:c.default.securityNote,children:h?.mode==="public-relay"?"配对后自动选择更快的连接;远程内容端到端加密。":"当前可通过同一局域网连接。"})]}),u&&t.updates?(0,i.jsx)(re,{store:t.updates}):null,a?.profileScope==="desktop"&&t.nativeUpdates?(0,i.jsx)(ae,{store:t.nativeUpdates}):null,(0,i.jsxs)("div",{className:c.default.linkedSection,children:[(0,i.jsx)("h4",{children:"另一端插件"}),(0,i.jsx)(E,{describeHost:r,callManagement:e,store:t})]}),(0,i.jsx)("p",{className:c.default.securityNote,children:"设置和插件详情共用当前节点的配对与更新状态;Web 与 Desktop 各自独立。"})]})}var _e=["slots","connection"];function we(r){let e=async(a,l)=>{let u=await r.connection.rpc.call("/wechat-remote-management",a,l??{});if(!u.ok)throw Object.assign(new Error(u.error.message),{code:u.error.code});return u.value},t=async()=>{let a=await r.connection.rpc.call("/api","wechatHost/describe",{args:{request:{}}});if(!a.ok)throw new Error(`wechatHost/describe: ${a.error.code}`);let l=a.value;if(l?.ok!==!0||l.value===void 0)throw new Error("wechatHost/describe returned an invalid result");return l.value},n=new P(t,e);r.effect(()=>()=>n.dispose(),"wechat shared page");let s=()=>ee(a=>r.get(a)),o=()=>({describeHost:t,callManagement:e,store:n});r.slots.inject("settings.section",()=>r.slots.register({name:"settings.section",id:"harness-remote",order:30,label:"微信连接",inject:o},M));for(let a of["plugins.bundle.activation","plugins.bundle.config"])r.slots.inject(a,()=>{let l=["dsh-wechat-remote","@harness-remote/dsh-wechat-remote"].map(u=>r.slots.register({name:a,key:u,inject:o},a==="plugins.bundle.activation"?E:M));return()=>l.forEach(u=>u())});r.inject(["pluginNavigation","remote.pluginManager"],()=>r.slots.inject("sidebar.footer.action",()=>r.slots.register({name:"sidebar.footer.action",id:"harness-remote",order:30,label:"微信连接",inject:()=>({openPlugin:s})},Y)))}
|
|
377
|
+
`,W="@harness-remote/dsh-wechat-remote/HarnessRemoteSettings.module.css";if(typeof document<"u"&&document.querySelector("style[data-plugin-css="+JSON.stringify(W)+"]")===null){let r=document.createElement("style");r.dataset.plugin="@harness-remote/dsh-wechat-remote",r.dataset.pluginCss=W,r.textContent=_e,document.head.appendChild(r)}G.exports={root:"hr_98198fd_root",hero:"hr_98198fd_hero",connectCard:"hr_98198fd_connectCard",pairingCard:"hr_98198fd_pairingCard",identity:"hr_98198fd_identity",mark:"hr_98198fd_mark",identityCopy:"hr_98198fd_identityCopy",pairingHead:"hr_98198fd_pairingHead",securityNote:"hr_98198fd_securityNote",sidebarButton:"hr_98198fd_sidebarButton",actions:"hr_98198fd_actions",pageDialog:"hr_98198fd_pageDialog",pageDialogContent:"hr_98198fd_pageDialogContent",helpText:"hr_98198fd_helpText",linkedSection:"hr_98198fd_linkedSection",companionCard:"hr_98198fd_companionCard",overall:"hr_98198fd_overall",statusDot:"hr_98198fd_statusDot",capabilities:"hr_98198fd_capabilities",capability:"hr_98198fd_capability",capabilityCopy:"hr_98198fd_capabilityCopy",notice:"hr_98198fd_notice",secondaryButton:"hr_98198fd_secondaryButton",primaryButton:"hr_98198fd_primaryButton",qrArea:"hr_98198fd_qrArea",qr:"hr_98198fd_qr",qrPlaceholder:"hr_98198fd_qrPlaceholder",qrValidity:"hr_98198fd_qrValidity",updateCard:"hr_98198fd_updateCard",updateLabel:"hr_98198fd_updateLabel",updateProgress:"hr_98198fd_updateProgress",updateVersions:"hr_98198fd_updateVersions",updateCommand:"hr_98198fd_updateCommand",updateChecked:"hr_98198fd_updateChecked"}});var ke={};be(ke,{apply:()=>xe,inject:()=>we});module.exports=ye(ke);var Z=require("react"),K=require("@deepseek-ai/dsh-client-ui-primitives");var R=C(x(),1),f=require("react/jsx-runtime");function X({value:r,onDecide:e,deciding:t=!1}){if(!r||r.state==="idle"||!r.message)return null;let n={"confirmation-required":"是否更新下方指定端的插件?",deferred:"已暂缓此次联动更新",pending:"联动更新待处理",busy:"等待待更新端的任务结束",preparing:"正在准备联动更新",installing:"正在安装插件更新",verifying:"正在核验更新后的恢复状态",recovering:"正在恢复更新前的插件","restart-required":"待更新端需启动或重启确认","self-restart-required":"当前端待重启,尚未生效",unverified:"两端更新状态待核实",complete:"两端插件已对齐",unavailable:"联动更新尚未确认成功"},i=["preparing","installing","verifying","recovering"].includes(r.state);return(0,f.jsxs)("aside",{className:R.default.companionCard,"data-state":r.state,role:"status","aria-live":"polite",children:[(0,f.jsx)("strong",{children:n[r.state]||"另一端插件更新状态"}),(0,f.jsx)("p",{children:r.message}),r.versions?(0,f.jsxs)("small",{children:[r.versions.current==="desktop"?"Desktop":"Web",":运行 ",r.versions.running," · 已安装 ",r.versions.installed??"未确认",";",r.versions.peer==="desktop"?"Desktop":"Web",":已安装 ",r.versions.peerInstalled??"未确认或未启用"]}):null,["confirmation-required","deferred"].includes(r.state)&&r.offerId&&e?(0,f.jsxs)("div",{children:[(0,f.jsx)("button",{type:"button",className:R.default.primaryButton,disabled:t,onClick:()=>e("approve"),children:"确认并等待空闲后更新"}),(0,f.jsx)("button",{type:"button",className:R.default.secondaryButton,disabled:t,onClick:()=>e("later"),children:"稍后再说"})]}):null,i?(0,f.jsx)("progress",{"aria-label":"联动更新进度"}):null,r.state==="busy"?(0,f.jsx)("small",{children:"不用手动停止任务。请保持两端运行,空闲后会继续检查。"}):i?(0,f.jsx)("small",{children:"请保持两端当前状态,暂勿退出、重启或重复安装。关闭本设置页不会取消更新。"}):r.state==="unavailable"?(0,f.jsx)("small",{children:"请在待更新端核对版本,必要时使用其原安装入口。"}):null]})}async function F(r,e,t=fetch){let n=await r(),i=n.gate;if(!i)throw new Error("当前节点没有提供配对入口");let o=i.profileScope==="desktop",a=i.localDoor?.port,l=Number.isSafeInteger(a)&&a>=1&&a<=65535?`http://127.0.0.1:${a}`:null;if(i.management==="authenticated-rpc")return{host:n,runtime:i,localOrigin:o?null:l,status:()=>e("status"),pairCode:()=>e("pair-code"),decide:(u,g)=>e("companion-decision",{offerId:u,action:g})};if(o||i.management==="unavailable"||!l||i.localDoor.state!=="listening")throw new Error("当前节点配对入口尚未就绪");let h=async u=>{let g=await t(l+u,{cache:"no-store"});if(!g.ok)throw new Error(`配对请求失败 (${g.status})`);return g.json()};return{host:n,runtime:i,localOrigin:l,status:()=>h("/gate/status"),pairCode:()=>h("/pair/code"),decide:async(u,g)=>{let _=await t(l+"/gate/companion/decision",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({offerId:u,action:g})});if(!_.ok)throw new Error("更新通知已变化,请刷新后重试");return _.json()}}}var q=class{constructor(e,t=globalThis.fetch){this.origin=e;this.fetcher=t.bind(globalThis)}value={check:null,checking:!1,progress:null,error:"",copied:!1,resumeFailed:!1};listeners=new Set;reading;installing=!1;disposed=!1;job=null;timer;pollGeneration=0;recovering=!1;fetcher;getSnapshot=()=>this.value;subscribe=e=>this.disposed?()=>{}:(this.listeners.add(e),this.listeners.size===1&&this.refresh(),()=>{this.listeners.delete(e)});patch(e){if(!this.disposed){this.value={...this.value,...e};for(let t of this.listeners)t()}}refresh=()=>this.disposed?Promise.resolve():this.reading?this.reading:(this.patch({checking:!0,error:"",copied:!1}),this.reading=(async()=>{try{let e=await this.fetcher(this.origin+"/gate/update/check",{signal:AbortSignal.timeout(1e4)}),t=await e.json();if(!e.ok)throw new Error(t.error||"暂时无法检查更新");if(!t.advice?.current||typeof t.advice.label!="string")throw new Error("更新检查返回信息不完整");if(this.patch({check:t}),this.installing||this.job||this.recovering)return;t.activeJob?.statusOrigin?this.follow(t.activeJob):t.lastResult?.phase==="unknown"?this.patch({progress:t.lastResult}):t.mode==="busy"?this.patch({progress:{phase:"preparing",progress:0,message:"更新仍在准备或等待确认,请稍后重新检查;不要重复安装。",terminal:!0}}):this.patch({progress:t.lastResult??null})}catch(e){this.patch({check:null,error:e instanceof Error?e.message:"暂时无法检查更新"})}finally{this.patch({checking:!1})}})().finally(()=>{this.reading=void 0}),this.reading);follow(e){if(this.disposed||this.job?.jobId===e.jobId)return;this.job=e;let t=++this.pollGeneration,n=Date.now()+10*6e4;clearTimeout(this.timer),this.patch({progress:{phase:"recovering",progress:0,message:"正在恢复更新进度…",terminal:!1}});let i=async()=>{if(this.disposed||t!==this.pollGeneration)return;let o;try{let a=await this.fetcher(e.statusOrigin+"/status",{headers:{Authorization:"Bearer "+e.statusToken},signal:AbortSignal.timeout(4e3)});if(!a.ok)throw new Error("进度暂不可用");let l=await a.json();if(l.jobId!==e.jobId||typeof l.terminal!="boolean"||typeof l.message!="string")throw new Error("进度不匹配");o=l}catch{try{let a=await this.fetcher(this.origin+"/gate/update/status",{signal:AbortSignal.timeout(3e3)}),l=await a.json();a.ok&&l.lastResult?.jobId===e.jobId&&(o=l.lastResult)}catch{}}if(!(this.disposed||t!==this.pollGeneration)){if(o&&(this.patch({progress:o}),o.terminal)){this.job=null,o.ok?this.resume(e.jobId):this.refresh();return}if(Date.now()>n){this.job=null,this.patch({progress:{phase:"unknown",progress:0,message:"暂时无法确认更新结果。请重新打开此主机 WebUI 检查版本;不要重复安装或删除节点。",terminal:!0}});return}this.timer=setTimeout(()=>void i(),1e3)}};i()}async resume(e){this.recovering=!0;let t=Date.now()+3e4,n=async()=>{try{let i=await this.fetcher(this.origin+"/gate/update/resume?job="+encodeURIComponent(e),{signal:AbortSignal.timeout(3e3)});if(!i.ok)throw new Error("尚未恢复");let o=await i.json();if(!o.url)throw new Error("缺少恢复地址");let a=new URL(o.url);if(a.origin!==window.location.origin||a.pathname!=="/"||a.username||a.password||a.hash)throw new Error("恢复地址不匹配");this.disposed||window.location.replace(a.href)}catch{if(this.disposed)return;if(Date.now()>=t){this.recovering=!1,this.patch({resumeFailed:!0});return}this.timer=setTimeout(()=>void n(),1e3)}};await n()}install=async()=>{let{check:e,checking:t,error:n,progress:i}=this.value;if(this.disposed||!e?.canInstall||t||n||i&&!i.terminal||this.installing||this.job||this.recovering)return;this.installing=!0,this.patch({error:"",check:{...e,canInstall:!1,ticket:""},progress:{phase:"download",progress:10,message:"正在下载并验证更新包;当前插件尚未替换",terminal:!1}});let o=!1;try{let a=await this.fetcher(this.origin+"/gate/update/start",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({ticket:e.ticket})}),l=await a.json();if(o=!a.ok&&typeof l.error=="string",!a.ok||!l.statusOrigin)throw new Error(l.error||"无法取得更新进度,请重新检查");this.follow(l)}catch(a){if(this.disposed)return;if(o)this.patch({progress:{phase:"failed",progress:0,message:(a instanceof Error?a.message:"更新暂不可用")+"。请重新检查更新。",terminal:!0,ok:!1}});else{try{let l=await this.fetcher(this.origin+"/gate/update/status",{signal:AbortSignal.timeout(3e3)}),h=await l.json();if(l.ok&&h.activeJob?.statusOrigin){this.follow(h.activeJob);return}}catch{}this.patch({progress:{phase:"unknown",progress:0,message:"连接中断,暂时无法确认更新结果。请稍后检查更新;不要重复安装或删除节点。",terminal:!0}})}}finally{this.installing=!1}};copyCommand=async()=>{if(!(!this.value.check?.manualCommand||this.value.checking||this.installing||this.job))try{await navigator.clipboard.writeText(this.value.check.manualCommand),this.patch({copied:!0})}catch{this.patch({error:"复制失败,请手动选中下方命令复制。"})}};dispose=()=>{this.disposed=!0,this.pollGeneration++,clearTimeout(this.timer),this.listeners.clear()}};var U=class{constructor(e){this.call=e}value={checking:!1,starting:!1,check:null,status:null,error:null};listeners=new Set;reading;timer;disposed=!1;checkRevision=0;checkInvalidated=!1;getSnapshot=()=>this.value;patch(e){if(!this.disposed){this.value={...this.value,...e};for(let t of this.listeners)t()}}subscribe=e=>this.disposed?()=>{}:(this.listeners.add(e),this.checkInvalidated?(this.refreshInvalidatedCheck(),this.active()&&this.poll()):!this.value.check&&!this.value.starting?this.refresh():this.active()&&this.poll(),()=>{this.listeners.delete(e),this.listeners.size||(clearTimeout(this.timer),this.timer=void 0)});active(){return this.value.status&&["preparing","installing","unknown","restart-required"].includes(this.value.status.phase)}invalidateCheck=()=>{this.disposed||(this.checkRevision++,this.checkInvalidated=!0,this.value.check&&this.patch({check:{...this.value.check,canInstall:!1,ticket:""}}),this.refreshInvalidatedCheck())};refreshInvalidatedCheck(){!this.disposed&&this.checkInvalidated&&this.listeners.size&&!this.value.starting&&!this.active()&&this.refresh()}refresh=()=>{if(this.disposed||this.value.starting)return Promise.resolve();if(this.reading)return this.reading;let e=this.checkRevision;return this.checkInvalidated=!1,this.patch({checking:!0,error:null}),this.reading=(async()=>{try{let t=await this.call("update-check");if(this.disposed||e!==this.checkRevision)return;if(!t?.advice||!t.status||typeof t.canInstall!="boolean")throw Error("invalid update response");this.patch({check:t,status:t.status}),this.active()&&this.poll()}catch{if(this.disposed||e!==this.checkRevision)return;let t=!1;try{let n=await this.call("status");t=!!(n&&!n.plugin)}catch{}if(this.disposed||e!==this.checkRevision)return;this.patch({check:null,error:t?"当前运行的连接服务尚不支持此更新入口。若刚安装新版,请在任务结束后退出并重新打开 Desktop;无需重新配对。仍不可用时请在原生插件管理器核对安装。":"本次更新检查未完成,不能据此判断已是最新版。请稍后重试;配对和聊天不受此次检查失败影响。"})}finally{this.patch({checking:!1})}})().finally(()=>{this.reading=void 0,this.refreshInvalidatedCheck()}),this.reading};install=async()=>{let e=this.value.check;if(!(this.disposed||this.checkInvalidated||this.value.starting||this.value.checking||!e?.canInstall||!e.ticket)){this.patch({starting:!0,error:null,check:{...e,canInstall:!1,ticket:""}});try{let t=await this.call("update-start",{ticket:e.ticket});this.patch({status:t})}catch(t){this.patch({status:t?.code==="update/not-started"?{phase:"failed",message:t instanceof Error?t.message:"尚未启动更新,请重新检查"}:{phase:"unknown",message:"更新提交结果暂未确认,正在核对;请勿重复安装。"}})}finally{this.patch({starting:!1}),this.poll(),this.refreshInvalidatedCheck()}}};poll(){this.disposed||this.timer||!this.listeners.size||!this.active()||(this.timer=setTimeout(async()=>{try{let e=await this.call("update-status");if(!e||!["idle","preparing","installing","restart-required","complete","failed","unknown"].includes(e.phase))throw Error("invalid status");this.patch({status:e,error:e.phase==="idle"?"尚未发现已接受的更新,请重新检查后再操作。":null})}catch{this.patch({error:"更新状态暂不可用;恢复连接后继续核对,不会重复安装。"})}finally{this.timer=void 0,this.poll(),this.refreshInvalidatedCheck()}},2e3))}dispose(){this.disposed=!0,this.checkRevision++,this.checkInvalidated=!1,clearTimeout(this.timer),this.listeners.clear()}};var A=()=>({loadState:"loading",qrState:"idle",status:null,host:null,runtime:null,localOrigin:null,qr:null,error:null,decisionError:null,deciding:!1}),$=r=>r?.companionUpdate?.state?["preparing","installing","verifying","recovering"].includes(r.companionUpdate.state):void 0,P=class{constructor(e,t){this.describeHost=e;this.callManagement=t}updates;nativeUpdates;value=A();listeners=new Set;timer;reading;pairing;disposed=!1;identity="";generation=0;getSnapshot=()=>this.value;subscribe=e=>this.disposed?()=>{}:(this.listeners.add(e),this.timer||(this.refresh(),this.timer=setInterval(()=>void this.refresh(),2e3)),()=>{this.listeners.delete(e),this.listeners.size||(clearInterval(this.timer),this.timer=void 0)});patch(e){if(!this.disposed){this.value={...this.value,...e};for(let t of this.listeners)t()}}async client(){let e=await F(this.describeHost,this.callManagement);if(this.disposed)throw new Error("disposed");let t=`${e.host.agentInstanceId??e.host.computerName}/${e.runtime.profileScope??"web"}`;return this.identity&&this.identity!==t&&(this.generation++,this.value=A(),this.updates?.dispose(),this.updates=void 0,this.nativeUpdates?.dispose(),this.nativeUpdates=void 0),this.identity=t,e.runtime.profileScope==="desktop"&&!this.nativeUpdates&&(this.nativeUpdates=new U(this.callManagement)),this.updates?.origin!==e.localOrigin&&(this.updates?.dispose(),this.updates=e.localOrigin?new q(e.localOrigin):void 0),this.patch({host:e.host,runtime:e.runtime,localOrigin:e.localOrigin}),e}refresh=()=>this.disposed?Promise.resolve():this.reading?this.reading:(this.reading=(async()=>{try{let e=await this.client(),t=this.generation,n={...await e.status()};if(n.companionUpdate?.state==="complete"&&!n.plugin&&(n.companionUpdate={state:"unverified",message:"当前连接服务尚未提供运行版本核验,不能确认两端已对齐。若刚安装插件,请在任务结束后退出并重新打开当前应用;无需重新配对。"}),t!==this.generation)return;let i=$(this.value.status),o=$(n);o!==void 0&&o!==i&&this.nativeUpdates?.invalidateCheck(),this.patch({status:n,runtime:n.gate??e.runtime,loadState:"ready",error:null,...this.value.qr&&this.value.qr.expiresAt<=Date.now()?{qrState:"expired",qr:null}:{}})}catch{this.patch({loadState:"error",error:"连接服务暂未就绪。请确认 DSH 正在运行,然后重试。"})}})().finally(()=>{this.reading=void 0}),this.reading);generateQr=()=>this.disposed?Promise.resolve():this.pairing?this.pairing:(this.patch({qrState:"loading",qr:null,error:null}),this.pairing=(async()=>{try{let e=await this.client(),t=this.generation,n=await e.pairCode();if(t!==this.generation)return;if(!n?.qrDataUrl?.startsWith("data:image/")||!Number.isFinite(n.expiresAt)||n.expiresAt<=Date.now())throw new Error("invalid QR");this.patch({qr:n,qrState:"ready"}),await this.refresh()}catch{this.patch({qr:null,qrState:"error",error:"暂时无法生成配对二维码,请稍后重试。"})}})().finally(()=>{this.pairing=void 0}),this.pairing);decide=async e=>{let t=this.value.status?.companionUpdate?.offerId;if(!(this.disposed||this.value.deciding||!t)){this.patch({deciding:!0,decisionError:null});try{let n=await this.client();if(this.value.status?.companionUpdate?.offerId!==t)throw new Error("stale offer");await n.decide(t,e),await this.reading,await this.refresh()}catch{this.patch({decisionError:"更新选择未确认,请刷新后核对;不要重复安装。"})}finally{this.patch({deciding:!1})}}};dispose=()=>{this.disposed=!0,this.generation++,clearInterval(this.timer),this.timer=void 0,this.listeners.clear(),this.value=A(),this.updates?.dispose(),this.updates=void 0,this.nativeUpdates?.dispose(),this.nativeUpdates=void 0}},Q=new WeakMap;function B(r,e){let t=Q.get(r);return t||(t=new P(r,e),Q.set(r,t)),t}var w=C(x(),1),v=require("react/jsx-runtime");function E({describeHost:r,callManagement:e,onDismiss:t,onOpenDetails:n,store:i=B(r,e)}){let{status:o,deciding:a,decisionError:l,error:h,loadState:u}=(0,Z.useSyncExternalStore)(i.subscribe,i.getSnapshot,i.getSnapshot),g=o?.companionUpdate,_=l||h,S=(0,v.jsxs)("div",{children:[(0,v.jsx)(X,{value:g,deciding:a||u!=="ready",onDecide:H=>void i.decide(H)}),!g&&!_?(0,v.jsx)("p",{className:w.default.helpText,children:"正在检查联动更新状态;尚未确认的更新不会自动启动。"}):g?.state==="idle"?(0,v.jsx)("p",{className:w.default.helpText,children:"未发现需要联动更新的另一端。当前节点可独立使用,不会自动安装另一端或启用已停用的插件。"}):null,_?(0,v.jsx)("p",{role:"alert",children:_}):null,t?(0,v.jsxs)("div",{className:w.default.actions,children:[n?(0,v.jsx)("button",{type:"button",className:w.default.primaryButton,onClick:n,children:"前往微信连接"}):null,(0,v.jsx)("button",{type:"button",className:w.default.secondaryButton,onClick:t,children:"关闭提示(不会确认更新)"})]}):null]});return t?(0,v.jsx)(K.Modal,{open:!0,title:"微信连接 · 联动更新",closeLabel:"关闭提示",className:w.default.pageDialog,contentClassName:w.default.pageDialogContent,onClose:t,children:S}):S}var D=require("react"),Y=C(x(),1),b=require("react/jsx-runtime");function ee({wide:r,openPlugin:e}){let[t,n]=(0,D.useState)(!1),[i,o]=(0,D.useState)(!1),a=(0,D.useRef)(!1),l=async()=>{if(!a.current){a.current=!0,o(!0);try{await e()||n(!0)}finally{a.current=!1,o(!1)}}};return(0,b.jsxs)(b.Fragment,{children:[(0,b.jsxs)("button",{type:"button",className:Y.default.sidebarButton,"data-wide":r,title:"微信连接","aria-label":"微信连接",disabled:i,onClick:()=>void l(),children:[(0,b.jsxs)("svg",{width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"1.7","aria-hidden":"true",children:[(0,b.jsx)("path",{d:"M20 11.5a8 8 0 0 1-8 8 9 9 0 0 1-3.5-.7L4 20l1.2-4.2A7.8 7.8 0 0 1 4 11.5a8 8 0 0 1 16 0Z"}),(0,b.jsx)("path",{d:"M8 11.5h.01M12 11.5h.01M16 11.5h.01",strokeWidth:"2.5",strokeLinecap:"round"})]}),r?(0,b.jsx)("span",{children:i?"正在打开…":"微信连接"}):null]}),t?(0,b.jsx)("small",{role:"status",children:"插件详情暂不可用,请打开「设置 → 微信连接」。"}):null]})}async function te(r){try{let e=r("pluginNavigation"),t=r("remote.pluginManager");if(typeof e?.openBundle!="function"||typeof t?.listBundles!="function")return!1;let n=await t.listBundles();if(!n.ok||!Array.isArray(n.value))return!1;let i=n.value.filter(a=>a.enabled&&["dsh-wechat-remote","@harness-remote/dsh-wechat-remote"].includes(a.name));if(i.length!==1)return!1;let o=r("pluginNavigation");return o!==e||typeof o.openBundle!="function"?!1:(o.openBundle(i[0].name),!0)}catch{return!1}}var j=require("react"),se=require("@deepseek-ai/dsh-client-ui-primitives"),c=C(x(),1);var re=require("react"),m=C(x(),1),d=require("react/jsx-runtime");function ne({store:r}){let{check:e,checking:t,progress:n,error:i,copied:o,resumeFailed:a}=(0,re.useSyncExternalStore)(r.subscribe,r.getSnapshot,r.getSnapshot),l=!!(n&&!n.terminal);return(0,d.jsxs)("div",{className:m.default.updateCard,children:[(0,d.jsxs)("div",{className:m.default.pairingHead,children:[(0,d.jsx)("div",{children:(0,d.jsx)("strong",{children:"当前端插件更新"})}),(0,d.jsx)("button",{type:"button",className:m.default.secondaryButton,disabled:t||l,onClick:()=>void r.refresh(),children:t?"检查中…":"检查更新"})]}),e?(0,d.jsxs)(d.Fragment,{children:[e.channel==="preview"?(0,d.jsx)("span",{className:m.default.updateLabel,"data-severity":"recommended",children:"预览通道"}):null,(0,d.jsxs)("div",{className:m.default.updateVersions,children:[(0,d.jsxs)("span",{children:["DSH ",(0,d.jsx)("strong",{children:e.advice.current.agentVersion||"未知"})]}),(0,d.jsxs)("span",{children:["插件 ",(0,d.jsx)("strong",{children:e.advice.current.pluginVersion||"未知"})]})]}),t?null:(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)("strong",{className:m.default.updateLabel,"data-severity":e.advice.severity,role:"status",children:e.advice.label}),e.advice.targetVersion?(0,d.jsxs)("p",{children:["可更新至 ",e.advice.targetVersion,e.mode==="manual"?" · 需手动更新":""]}):null,e.advice.severity==="unknown"||e.advice.severity==="required"||e.mode==="manual"||e.mode==="busy"?(0,d.jsxs)("details",{children:[(0,d.jsx)("summary",{children:e.mode==="manual"?"手动更新说明":"查看说明"}),e.reason?(0,d.jsx)("p",{children:e.reason}):null,(0,d.jsx)("p",{children:e.advice.message})]}):null,e.mode==="manual"&&e.manualCommand?(0,d.jsxs)("div",{className:m.default.updateCommand,children:[(0,d.jsx)("code",{children:e.manualCommand}),(0,d.jsx)("button",{type:"button",className:m.default.secondaryButton,disabled:l,onClick:()=>void r.copyCommand(),children:o?"已复制":"复制命令"})]}):null,e.canInstall?(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)("button",{type:"button",className:m.default.primaryButton,disabled:l||!!i,onClick:()=>void r.install(),children:"更新并重启"}),(0,d.jsx)("small",{className:m.default.updateChecked,children:"只更新当前 Web 插件;会短暂断开连接,保留原配对和会话"})]}):null,e.advice.checkedAt&&e.advice.severity!=="unknown"?(0,d.jsxs)("small",{className:m.default.updateChecked,children:["最近检查 ",new Date(e.advice.checkedAt).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})]}):null]})]}):null,i?(0,d.jsx)("p",{role:"alert",children:i}):null,n?(0,d.jsxs)("div",{role:"status","aria-live":"polite",children:[!n.terminal||n.ok===!0?(0,d.jsx)("progress",{className:m.default.updateProgress,max:100,value:n.progress}):null,(0,d.jsx)("p",{children:n.message}),a?(0,d.jsx)("p",{role:"alert",children:"插件已完成更新,但未能自动打开 WebUI。请使用 DSH 启动时显示的地址打开,无需重复更新。"}):null]}):null]})}var ae=require("react"),y=C(x(),1),p=require("react/jsx-runtime");function ie({store:r}){let{check:e,status:t,checking:n,starting:i,error:o}=(0,ae.useSyncExternalStore)(r.subscribe,r.getSnapshot,r.getSnapshot),a=i||t?.phase==="preparing"||t?.phase==="installing",l=t&&["preparing","installing","restart-required","unknown"].includes(t.phase),h=t?.phase==="restart-required"||t?.phase==="complete",u=t?.phase==="restart-required"?"已安装,待重新打开 Desktop":t?.phase==="unknown"?"更新结果待核对":a?"正在更新 Desktop 插件":t?.phase==="complete"?"更新已生效":"";return(0,p.jsxs)("div",{className:y.default.updateCard,children:[(0,p.jsxs)("div",{className:y.default.pairingHead,children:[(0,p.jsx)("strong",{children:"当前端插件更新 · Desktop"}),(0,p.jsx)("button",{type:"button",className:y.default.secondaryButton,disabled:n||a,onClick:()=>void r.refresh(),children:n?"检查中…":"检查更新"})]}),e?(0,p.jsxs)(p.Fragment,{children:[e.channel==="preview"?(0,p.jsx)("span",{className:y.default.updateLabel,"data-severity":"recommended",children:"预览通道"}):null,(0,p.jsxs)("div",{className:y.default.updateVersions,children:[(0,p.jsxs)("span",{children:["DSH ",(0,p.jsx)("strong",{children:e.advice.current.agentVersion})]}),(0,p.jsxs)("span",{children:["运行中的插件 ",(0,p.jsx)("strong",{children:e.advice.current.pluginVersion})]})]}),(0,p.jsx)("strong",{className:y.default.updateLabel,"data-severity":h?"none":e.advice.severity,children:u||e.advice.label}),h&&t.targetVersion?(0,p.jsxs)("p",{children:["已安装版本 ",t.targetVersion]}):!l&&e.advice.targetVersion?(0,p.jsxs)("p",{children:["可更新至 ",e.advice.targetVersion]}):null,!l&&e.reason?(0,p.jsx)("p",{children:e.reason}):null,!l&&e.canInstall?(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)("p",{className:y.default.helpText,children:"仅更新当前 Desktop 插件,由原生插件管理器使用应用配置的安装源下载并安装指定版本。配对和会话保留,不更新 Web,不自动退出应用;安装完成后需重新打开 Desktop。"}),(0,p.jsx)("button",{type:"button",className:y.default.primaryButton,disabled:a||n,onClick:()=>void r.install(),children:"更新 Desktop 插件"})]}):null,(0,p.jsxs)("small",{className:y.default.updateChecked,children:["最近检查 ",new Date(e.advice.checkedAt).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})]})]}):null,t?.phase!=="idle"&&t?(0,p.jsxs)("div",{role:"status","aria-live":"polite",children:[a?(0,p.jsx)("progress",{className:y.default.updateProgress,"aria-label":"Desktop 插件更新中"}):null,(0,p.jsx)("p",{children:t.message})]}):null,o?(0,p.jsx)("p",{role:"alert",children:o}):null]})}var s=require("react/jsx-runtime");function oe({ok:r,busy:e=!1}){return(0,s.jsx)("span",{className:c.default.statusDot,"data-state":e?"busy":r?"ready":"off","aria-hidden":!0})}function V({title:r,detail:e,ok:t,busy:n=!1}){return(0,s.jsxs)("div",{className:c.default.capability,children:[(0,s.jsx)(oe,{ok:t,busy:n}),(0,s.jsxs)("div",{className:c.default.capabilityCopy,children:[(0,s.jsx)("strong",{children:r}),(0,s.jsx)("span",{children:e})]})]})}function M({describeHost:r,callManagement:e,store:t=B(r,e)}){let{loadState:n,qrState:i,status:o,runtime:a,host:l,localOrigin:h,qr:u,error:g}=(0,j.useSyncExternalStore)(t.subscribe,t.getSnapshot,t.getSnapshot),{refresh:_,generateQr:S}=t,H=(0,j.useId)(),k=o?.publicRelay,z=k?.state==="enrolling"||k?.state==="connecting",N=k?.remoteAccess?.status,T=k?.state==="online"&&N==="active",le=N==="suspended"?"账户公网访问已暂停":N==="expired"?"公网访问已到期":N==="not_entitled"?"请在小程序中使用自助开通方式":N!=="active"?"配对后由小程序账户决定":T?"可在外网安全连接":z?"正在准备远程连接":"暂时离线",de=a?.publicDoor??o?.gate?.publicDoor,J=n==="ready"&&!!o?.lan.ip&&de?.state==="listening",O=k?.enabled===!0&&k.state!=="disabled",pe=o?.agent?.agentName||l?.agentName||"DeepSeek Harness",ce=o?.agent?.hostName||l?.computerName||"当前电脑";return(0,s.jsxs)("section",{className:c.default.root,"aria-labelledby":H,children:[(0,s.jsxs)("div",{className:c.default.hero,children:[(0,s.jsxs)("div",{className:c.default.identity,children:[(0,s.jsx)("span",{className:c.default.mark,"aria-hidden":!0,children:(0,s.jsx)(se.FishLogo,{size:28})}),(0,s.jsxs)("div",{className:c.default.identityCopy,children:[(0,s.jsx)("h3",{id:H,children:"微信连接"}),(0,s.jsxs)("p",{children:[pe,(0,s.jsx)("span",{"aria-hidden":!0,children:" · "}),ce]})]})]}),(0,s.jsxs)("span",{className:c.default.overall,"data-ready":n==="ready",children:[(0,s.jsx)(oe,{ok:n==="ready",busy:n==="loading"}),n==="loading"?"检测中":n==="ready"?"服务正常":"暂不可用"]})]}),a?.profileScope==="desktop"&&o&&(!o.plugin||o.plugin.runningVersion!==o.plugin.installedVersion)?(0,s.jsxs)("aside",{className:c.default.companionCard,"data-state":"self-restart-required",role:"status",children:[(0,s.jsx)("strong",{children:o.plugin?"新版插件尚未生效,请完整重启 Desktop":"刚升级插件?请先完整重启 Desktop"}),o.plugin?(0,s.jsxs)("p",{children:["正在运行 ",o.plugin.runningVersion,";已安装 ",o.plugin.installedVersion,"。"]}):(0,s.jsx)("p",{children:"当前连接服务尚不能核验运行版本,不能把安装完成当作新版已生效。"}),(0,s.jsx)("p",{children:"等当前任务结束后,选择顶部「应用 → 退出」,再重新打开 Desktop。仅关闭窗口、刷新页面或开关插件不能保证新版生效。"}),(0,s.jsx)("small",{children:"不必重装或重新配对。此操作不会升级或重启 Web。"})]}):null,(0,s.jsxs)("div",{className:c.default.capabilities,children:[(0,s.jsx)(V,{title:"局域网直连",detail:n==="loading"?"检测中":J?"已就绪":"暂不可用",ok:J,busy:n==="loading"}),(0,s.jsx)(V,{title:"远程访问",detail:le,ok:T,busy:z||n==="loading"}),(0,s.jsx)(V,{title:"账号连接保护",detail:O?"已启用":"配对后启用",ok:O,busy:n==="loading"})]}),g!==null?(0,s.jsxs)("div",{className:c.default.notice,role:"status",children:[(0,s.jsx)("span",{children:g}),(0,s.jsx)("button",{type:"button",onClick:()=>void _(),children:"重试"})]}):null,i==="idle"?(0,s.jsxs)("div",{className:c.default.connectCard,children:[(0,s.jsxs)("div",{children:[(0,s.jsx)("strong",{children:"添加到微信"}),(0,s.jsx)("p",{children:"打开「Agent远程管理助手」→ 添加节点,扫描配对二维码。"})]}),(0,s.jsx)("button",{type:"button",className:c.default.primaryButton,disabled:n!=="ready",onClick:()=>void S(),children:"生成二维码"})]}):(0,s.jsxs)("div",{className:c.default.pairingCard,children:[(0,s.jsxs)("div",{className:c.default.pairingHead,children:[(0,s.jsxs)("div",{children:[(0,s.jsx)("strong",{children:"扫描二维码"}),(0,s.jsx)("p",{children:"小程序「设置 → 添加节点」"})]}),(0,s.jsx)("button",{type:"button",className:c.default.secondaryButton,disabled:i==="loading"||n!=="ready",onClick:()=>void S(),children:i==="loading"?"生成中…":"重新生成"})]}),(0,s.jsxs)("div",{className:c.default.qrArea,children:[n==="ready"&&i==="ready"&&u!==null?(0,s.jsx)("img",{className:c.default.qr,src:u.qrDataUrl,alt:"Agent远程管理助手配对二维码"}):(0,s.jsx)("div",{className:c.default.qrPlaceholder,"aria-live":"polite",children:n!=="ready"?"连接恢复后可查看二维码":i==="expired"?"二维码已过期,请重新生成":i==="error"?"生成失败":"正在生成…"}),i==="ready"&&u!==null?(0,s.jsxs)("small",{className:c.default.qrValidity,children:["有效至 ",new Date(u.expiresAt).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})," · 切换页面不重新生成"]}):null]}),(0,s.jsx)("p",{className:c.default.securityNote,children:u?.mode==="public-relay"?"配对后自动选择更快的连接;远程内容端到端加密。":"当前可通过同一局域网连接。"})]}),h&&t.updates?(0,s.jsx)(ne,{store:t.updates}):null,a?.profileScope==="desktop"&&t.nativeUpdates?(0,s.jsx)(ie,{store:t.nativeUpdates}):null,(0,s.jsxs)("div",{className:c.default.linkedSection,children:[(0,s.jsx)("h4",{children:"另一端插件"}),(0,s.jsx)(E,{describeHost:r,callManagement:e,store:t})]}),(0,s.jsx)("p",{className:c.default.securityNote,children:"设置和插件详情共用当前节点的配对与更新状态;Web 与 Desktop 各自独立。"})]})}var we=["slots","connection"];function xe(r){let e=async(a,l)=>{let h=await r.connection.rpc.call("/wechat-remote-management",a,l??{});if(!h.ok)throw Object.assign(new Error(h.error.message),{code:h.error.code});return h.value},t=async()=>{let a=await r.connection.rpc.call("/api","wechatHost/describe",{args:{request:{}}});if(!a.ok)throw new Error(`wechatHost/describe: ${a.error.code}`);let l=a.value;if(l?.ok!==!0||l.value===void 0)throw new Error("wechatHost/describe returned an invalid result");return l.value},n=new P(t,e);r.effect(()=>()=>n.dispose(),"wechat shared page");let i=()=>te(a=>r.get(a)),o=()=>({describeHost:t,callManagement:e,store:n});r.slots.inject("settings.section",()=>r.slots.register({name:"settings.section",id:"harness-remote",order:30,label:"微信连接",inject:o},M));for(let a of["plugins.bundle.activation","plugins.bundle.config"])r.slots.inject(a,()=>{let l=["dsh-wechat-remote","@harness-remote/dsh-wechat-remote"].map(h=>r.slots.register({name:a,key:h,inject:o},a==="plugins.bundle.activation"?E:M));return()=>l.forEach(h=>h())});r.inject(["pluginNavigation","remote.pluginManager"],()=>r.slots.inject("sidebar.footer.action",()=>r.slots.register({name:"sidebar.footer.action",id:"harness-remote",order:30,label:"微信连接",inject:()=>({openPlugin:i})},ee)))}
|
|
378
378
|
|
|
379
379
|
return module.exports;}});
|
|
@@ -97,8 +97,14 @@ export function offerCompanionUpdate(home, from, source, version) {
|
|
|
97
97
|
const directory = root(home), file = path.join(directory, `${to}.json`);
|
|
98
98
|
try {
|
|
99
99
|
const existing = read(file);
|
|
100
|
-
if (existing.schema === 2 && existing.from === from && existing.version === version && existing.previous === peer.version)
|
|
101
|
-
|
|
100
|
+
if (existing.schema === 2 && existing.from === from && existing.version === version && existing.previous === peer.version) {
|
|
101
|
+
// The core may start before the CLI finishes and offer an npm-only source.
|
|
102
|
+
// Let the completed installer supply its validated local archive, but
|
|
103
|
+
// never change an approved/in-flight transaction's immutable source.
|
|
104
|
+
// A replacement gets a new ID below: old UI decisions cannot approve it.
|
|
105
|
+
if (!source || existing.source || decision(home, existing) === 'approve')
|
|
106
|
+
return existing;
|
|
107
|
+
}
|
|
102
108
|
if (versionPattern.test(existing.version) && compareVersions(existing.version, version) > 0)
|
|
103
109
|
return;
|
|
104
110
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Build identity stays with loaded code, even after package.json is replaced. */
|
|
2
|
-
export declare const PLUGIN_VERSION = "1.7.
|
|
2
|
+
export declare const PLUGIN_VERSION = "1.7.13";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Build identity stays with loaded code, even after package.json is replaced. */
|
|
2
|
-
export const PLUGIN_VERSION = '1.7.
|
|
2
|
+
export const PLUGIN_VERSION = '1.7.13';
|
|
@@ -24,11 +24,18 @@ export declare class NativeUpdateStore {
|
|
|
24
24
|
private reading?;
|
|
25
25
|
private timer?;
|
|
26
26
|
private disposed;
|
|
27
|
+
private checkRevision;
|
|
28
|
+
private checkInvalidated;
|
|
27
29
|
constructor(call: CallPairingManagement);
|
|
28
30
|
getSnapshot: () => Snapshot;
|
|
29
31
|
private patch;
|
|
30
32
|
subscribe: (listener: () => void) => () => void;
|
|
31
33
|
private active;
|
|
34
|
+
/** Companion activity changes admission, not this node's install result.
|
|
35
|
+
* Keep real job progress; invalidate its cached check/ticket and read again.
|
|
36
|
+
* Never turn a status transition into an install, approval or blind retry. */
|
|
37
|
+
invalidateCheck: () => void;
|
|
38
|
+
private refreshInvalidatedCheck;
|
|
32
39
|
refresh: () => Promise<void>;
|
|
33
40
|
install: () => Promise<void>;
|
|
34
41
|
private poll;
|
|
@@ -45,7 +45,6 @@ export interface UpdateSnapshot {
|
|
|
45
45
|
}
|
|
46
46
|
export declare class WebUpdateStore {
|
|
47
47
|
readonly origin: string;
|
|
48
|
-
private fetcher;
|
|
49
48
|
private value;
|
|
50
49
|
private listeners;
|
|
51
50
|
private reading?;
|
|
@@ -55,6 +54,7 @@ export declare class WebUpdateStore {
|
|
|
55
54
|
private timer?;
|
|
56
55
|
private pollGeneration;
|
|
57
56
|
private recovering;
|
|
57
|
+
private fetcher;
|
|
58
58
|
constructor(origin: string, fetcher?: typeof fetch);
|
|
59
59
|
getSnapshot: () => UpdateSnapshot;
|
|
60
60
|
subscribe: (listener: () => void) => (() => void);
|
|
@@ -360,7 +360,7 @@ export class PluginUpdateService {
|
|
|
360
360
|
writePrivateJsonAtomic(path.join(directory, 'package.json'), { type: 'module' });
|
|
361
361
|
// Keep the worker's complete built-in-only module closure outside the
|
|
362
362
|
// profile being updated. No model credentials serialized to disk.
|
|
363
|
-
for (const name of ['update-worker.js', 'secure-file.js', 'install-profile.js', 'install-runtime.js', 'install-lifecycle.js'])
|
|
363
|
+
for (const name of ['update-worker.js', 'update-session-verification.js', 'secure-file.js', 'install-profile.js', 'install-runtime.js', 'install-lifecycle.js'])
|
|
364
364
|
fs.copyFileSync(path.join(ownRoot, 'lib', name), path.join(directory, name));
|
|
365
365
|
await control(job, 'launch');
|
|
366
366
|
let statusOrigin = '';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Verify the current host service, not an old process's in-memory catalog.
|
|
2
|
+
* DSH owns session visibility and format admission. In a shared home an older
|
|
3
|
+
* Web process can retain a session that its cold loader no longer admits after
|
|
4
|
+
* Desktop has produced a newer generation. Requiring equal lists incorrectly
|
|
5
|
+
* rolls back a sound plugin install and cannot restore that native visibility.
|
|
6
|
+
*
|
|
7
|
+
* This does NOT establish data preservation: the transaction separately checks
|
|
8
|
+
* the original session/attachment/identity hashes and pairing binding. Keep
|
|
9
|
+
* those checks and the native idle/flush, identity, version and fence checks.
|
|
10
|
+
* Never rewrite native files, interpret generation formats, or grant access to
|
|
11
|
+
* a session the restarted host does not expose merely to reproduce a snapshot.
|
|
12
|
+
*/
|
|
13
|
+
export declare function verifySessionService(read: (method: string, payload?: Record<string, unknown>) => Promise<any>, previouslyReadable: readonly string[]): Promise<void>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Verify the current host service, not an old process's in-memory catalog.
|
|
2
|
+
* DSH owns session visibility and format admission. In a shared home an older
|
|
3
|
+
* Web process can retain a session that its cold loader no longer admits after
|
|
4
|
+
* Desktop has produced a newer generation. Requiring equal lists incorrectly
|
|
5
|
+
* rolls back a sound plugin install and cannot restore that native visibility.
|
|
6
|
+
*
|
|
7
|
+
* This does NOT establish data preservation: the transaction separately checks
|
|
8
|
+
* the original session/attachment/identity hashes and pairing binding. Keep
|
|
9
|
+
* those checks and the native idle/flush, identity, version and fence checks.
|
|
10
|
+
* Never rewrite native files, interpret generation formats, or grant access to
|
|
11
|
+
* a session the restarted host does not expose merely to reproduce a snapshot.
|
|
12
|
+
*/
|
|
13
|
+
export async function verifySessionService(read, previouslyReadable) {
|
|
14
|
+
const items = (await read('session.list'))?.items;
|
|
15
|
+
if (!Array.isArray(items) || items.some(item => !item || typeof item.sessionId !== 'string'
|
|
16
|
+
|| !item.sessionId || typeof item.running !== 'boolean')
|
|
17
|
+
|| new Set(items.map(item => item.sessionId)).size !== items.length) {
|
|
18
|
+
throw new Error('主机会话服务返回了无效的会话列表');
|
|
19
|
+
}
|
|
20
|
+
const visible = new Set(items.map(item => item.sessionId));
|
|
21
|
+
for (const sessionId of new Set(previouslyReadable)) {
|
|
22
|
+
if (visible.has(sessionId))
|
|
23
|
+
await read('session.history', { sessionId, maxMessages: 1 });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -36,6 +36,7 @@ export declare function releaseOwnedUpdateLock(lock: string, id: string): void;
|
|
|
36
36
|
export declare function validateJob(job: UpdateJob): void;
|
|
37
37
|
export declare function control(job: UpdateJob, operation: string, input?: unknown): Promise<any>;
|
|
38
38
|
export declare function durableSnapshot(job: UpdateJob): Record<string, string>;
|
|
39
|
+
export declare function assertPreserved(before: Record<string, string>, after: Record<string, string>): void;
|
|
39
40
|
/** Legacy grants acquire an owner only inside this verified, backed-up upgrade.
|
|
40
41
|
* A later actual identity replacement still invalidates the old grants normally.
|
|
41
42
|
*/
|
|
@@ -6,6 +6,7 @@ import { spawn } from 'node:child_process';
|
|
|
6
6
|
import { createHash, createPublicKey } from 'node:crypto';
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
8
|
import { writePrivateJsonAtomic } from './secure-file.js';
|
|
9
|
+
import { verifySessionService } from './update-session-verification.js';
|
|
9
10
|
import { installProfile, backupProfile, NativeInstallError, assertCliInstallOwner } from './install-profile.js';
|
|
10
11
|
import { INSTALL_PNPM_VERSION, pinInstallRuntime } from './install-runtime.js';
|
|
11
12
|
import { validateManager, startManagedHost, stopManagedHost, finishUpdateWorker } from './install-lifecycle.js';
|
|
@@ -127,7 +128,7 @@ export function durableSnapshot(job) {
|
|
|
127
128
|
}
|
|
128
129
|
return result;
|
|
129
130
|
}
|
|
130
|
-
function assertPreserved(before, after) {
|
|
131
|
+
export function assertPreserved(before, after) {
|
|
131
132
|
for (const [key, hash] of Object.entries(before))
|
|
132
133
|
if (after[key] !== hash)
|
|
133
134
|
throw new Error('升级后数据校验不一致;停止自动操作并保留备份');
|
|
@@ -385,11 +386,7 @@ export async function executeUpdate(job, progress, quiesce) {
|
|
|
385
386
|
await healthy(job, job.targetVersion);
|
|
386
387
|
await verifyFence(job);
|
|
387
388
|
assertPreserved(before, durableSnapshot(job));
|
|
388
|
-
|
|
389
|
-
if (JSON.stringify(after) !== JSON.stringify(sessionIds))
|
|
390
|
-
throw new Error('重启后会话列表不一致');
|
|
391
|
-
for (const id of readableIds)
|
|
392
|
-
await rpc(job, 'session.history', { sessionId: id, maxMessages: 1 });
|
|
389
|
+
await verifySessionService((method, payload) => rpc(job, method, payload), readableIds);
|
|
393
390
|
writePrivateJsonAtomic(path.join(job.directory, 'verification-complete.json'), { id: job.id });
|
|
394
391
|
return { phase: 'complete', progress: 100, message: '插件更新完成,DSH 已恢复;原节点无需重新配对。', terminal: true, ok: true };
|
|
395
392
|
}
|
|
@@ -430,7 +427,7 @@ export async function executeUpdate(job, progress, quiesce) {
|
|
|
430
427
|
// Do not demand healthy plugin RPC from a baseline that was already
|
|
431
428
|
// broken. Still verify native identity, sessions and durable hashes.
|
|
432
429
|
const recovery = await import(pathToFileURL(path.join(job.directory, 'native-recovery.js')).href);
|
|
433
|
-
await recovery.verifyNativeRestore(job, () => start(job),
|
|
430
|
+
await recovery.verifyNativeRestore(job, () => start(job), readableIds);
|
|
434
431
|
}
|
|
435
432
|
else {
|
|
436
433
|
start(job);
|
package/native/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harness-remote/dsh-wechat-remote",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.13",
|
|
4
4
|
"description": "Agent远程管理助手:支持 Windows、macOS 与 Linux 上的 DeepSeek Harness,提供微信配对、安全局域网直连及端到端加密公网连接",
|
|
5
5
|
"private": true,
|
|
6
6
|
"license": "MIT",
|
|
@@ -99,8 +99,8 @@
|
|
|
99
99
|
"test:native-update": "node --test scripts/test-native-update.mjs scripts/test-native-update-store.mjs",
|
|
100
100
|
"test:lifecycle": "node scripts/test-gate-state.mjs && node scripts/test-secure-lan.mjs && node scripts/test-lifecycle.mjs",
|
|
101
101
|
"test:notifications": "node --test scripts/test-task-notifications.mjs",
|
|
102
|
-
"test:client": "node scripts/test-client-bundle.mjs && node --test scripts/test-pairing-management.mjs scripts/test-pairing-http.mjs scripts/test-companion-card.mjs scripts/test-shared-page.mjs",
|
|
103
|
-
"test:updates": "node --test scripts/test-install-ownership.mjs scripts/test-companion-updates.mjs scripts/test-companion-web-idle.mjs scripts/test-update-progress-liveness.mjs && node scripts/test-update-policy.mjs && node scripts/test-update-safety.mjs && node --test scripts/test-update-download.mjs scripts/test-update-card.mjs && node scripts/test-install-capabilities.mjs && node scripts/test-install-runtime.mjs && node scripts/test-install-tool-path.mjs && node scripts/test-install-profile.mjs && node scripts/test-install-repair.mjs && node scripts/test-update-action.mjs && node scripts/test-update-architecture.mjs && node scripts/test-installer-patch.mjs && node scripts/test-installer-handshake.mjs && node scripts/test-installer-selection.mjs && node --test scripts/test-installer-discovery.mjs",
|
|
102
|
+
"test:client": "node scripts/test-client-bundle.mjs && node --test scripts/test-pairing-management.mjs scripts/test-pairing-http.mjs scripts/test-companion-card.mjs scripts/test-shared-page.mjs scripts/test-web-update-store.mjs",
|
|
103
|
+
"test:updates": "node --test scripts/test-install-ownership.mjs scripts/test-companion-updates.mjs scripts/test-companion-web-idle.mjs scripts/test-update-progress-liveness.mjs scripts/test-update-session-verification.mjs && node scripts/test-update-policy.mjs && node scripts/test-update-safety.mjs && node --test scripts/test-update-download.mjs scripts/test-update-card.mjs && node scripts/test-install-capabilities.mjs && node scripts/test-install-runtime.mjs && node scripts/test-install-tool-path.mjs && node scripts/test-install-profile.mjs && node scripts/test-install-repair.mjs && node scripts/test-update-action.mjs && node scripts/test-update-architecture.mjs && node scripts/test-installer-patch.mjs && node scripts/test-installer-handshake.mjs && node scripts/test-installer-selection.mjs && node --test scripts/test-installer-discovery.mjs",
|
|
104
104
|
"test:directory": "node scripts/test-directory.mjs",
|
|
105
105
|
"test:host-platform": "node scripts/test-host-platform.mjs && node --test scripts/test-startup-directory.mjs",
|
|
106
106
|
"test:history": "node --test scripts/test-history-read-budget.mjs scripts/test-history-page.mjs scripts/test-history-turn-evidence.mjs && node scripts/test-history.mjs && node scripts/test-turn-presentation.mjs && node scripts/test-native-turn-usage.mjs && node scripts/test-turn-activity.mjs",
|