@mingmaple/dsh-handover 0.9.13 → 0.9.22
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 +46 -19
- package/docs/migrate.md +6 -6
- package/docs/releases/0.9.14.md +18 -0
- package/docs/releases/0.9.22.md +34 -0
- package/docs/troubleshoot.md +34 -10
- package/package.json +71 -71
- package/scripts/blank-dsh.mjs +29 -23
- package/scripts/blank-smoke.mjs +261 -78
- package/scripts/e2e-migration.mjs +2 -2
- package/scripts/office.mjs +3 -3
- package/src/client.js +159 -120
- package/src/index.mjs +65 -58
- package/src/lib/api.mjs +9 -7
- package/src/lib/brief.mjs +2 -2
- package/src/lib/config.mjs +3 -3
- package/src/lib/consent.mjs +54 -1
- package/src/lib/docmap.mjs +143 -0
- package/src/lib/gates.mjs +25 -1
- package/src/lib/guide.mjs +35 -44
- package/src/lib/lint.mjs +3 -2
- package/src/lib/manual.mjs +48 -9
- package/src/lib/mover.mjs +10 -10
- package/src/lib/names.mjs +14 -19
- package/src/lib/office.mjs +22 -14
- package/src/lib/pack-ops.mjs +2 -2
- package/src/lib/peer.mjs +4 -4
- package/src/lib/role.mjs +8 -5
- package/src/lib/scaffold.mjs +42 -20
- package/src/lib/schedule.mjs +1 -2
- package/src/lib/session-create.mjs +1 -1
- package/src/lib/session-ops.mjs +49 -20
- package/src/lib/sessions.mjs +1 -1
- package/src/lib/status.mjs +205 -45
- package/src/lib/succession.mjs +27 -11
- package/src/lib/zstd-log.mjs +1 -1
- package/src/selftest.mjs +6 -6
- package/src/templates/Prompt_Templates.md +13 -13
- package/src/templates/Runtime_Manual.md +56 -30
- package/src/templates/rules/Handover_Rules.md +2 -1
- package/src/templates/rules/Migration_Rules.md +1 -1
- package/src/templates/standards/README.md +25 -0
- package/src/tools/ho_backup.mjs +135 -0
- package/src/tools/ho_checkpoint.mjs +12 -11
- package/src/tools/ho_docmap.mjs +153 -0
- package/src/tools/ho_gate.mjs +2 -1
- package/src/tools/ho_office.mjs +1 -1
- package/src/tools/ho_sessions.mjs +3 -4
- package/src/tools/ho_status.mjs +246 -0
- package/src/tools/ho_succeed.mjs +1 -1
- package/src/tools/ho_workspace.mjs +86 -14
- package/src/tools/peer.mjs +205 -0
- package/src/tools/ho_brief.mjs +0 -57
- package/src/tools/ho_diag.mjs +0 -87
- package/src/tools/ho_drift.mjs +0 -41
- package/src/tools/ho_init.mjs +0 -82
- package/src/tools/ho_pack.mjs +0 -38
- package/src/tools/ho_phase.mjs +0 -37
- package/src/tools/ho_snapshot.mjs +0 -104
- package/src/tools/ho_tokens.mjs +0 -57
- package/src/tools/peer_list.mjs +0 -37
- package/src/tools/peer_new.mjs +0 -75
- package/src/tools/peer_peek.mjs +0 -41
- package/src/tools/peer_rename.mjs +0 -31
- package/src/tools/peer_send.mjs +0 -52
- package/src/tools/peer_status.mjs +0 -46
|
@@ -15,7 +15,7 @@ const argIdx = process.argv.indexOf('--dist');
|
|
|
15
15
|
const dist = argIdx !== -1 ? path.resolve(process.argv[argIdx + 1]) : path.join(repo, 'dist');
|
|
16
16
|
|
|
17
17
|
const imp = (rel) => import(pathToFileURL(path.join(dist, rel)).href);
|
|
18
|
-
const {
|
|
18
|
+
const { pmBackupTool } = await imp('tools/ho_backup.mjs');
|
|
19
19
|
const { resolveConfig } = await imp('lib/config.mjs');
|
|
20
20
|
|
|
21
21
|
const fails = [];
|
|
@@ -47,7 +47,7 @@ const env = {
|
|
|
47
47
|
selfId: () => null,
|
|
48
48
|
selfCwd: () => null,
|
|
49
49
|
};
|
|
50
|
-
const packTool =
|
|
50
|
+
const packTool = pmBackupTool(env);
|
|
51
51
|
/** action 分流:pack/verify/restore 都走同一个工具(合并口径)。 */
|
|
52
52
|
const run = (args) => packTool.execute(args, null);
|
|
53
53
|
|
package/scripts/office.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* 命令行查在任登记表:`node scripts/office.mjs 全局 全局3
|
|
3
|
+
* 命令行查在任登记表:`node scripts/office.mjs 全局 全局3 Demo1`
|
|
4
4
|
*
|
|
5
5
|
* 与工具 ho_office 同一份实现、同一份记录(_Control_Center/records/Office.json),
|
|
6
6
|
* 区别只是不起会话也能查。工作区根按 --root → DSH_OFFICE_ROOT → 从 cwd 向上找 _Control_Center 的顺序解析。
|
|
@@ -32,14 +32,14 @@ for (let i = 0; i < argv.length; i++) {
|
|
|
32
32
|
if (a === '--all' || a === '-a') { all = true; continue; }
|
|
33
33
|
if (a === '-h' || a === '--help') {
|
|
34
34
|
console.log('用法:node scripts/office.mjs [查询…] [--all] [--json] [--root <工作区根>]');
|
|
35
|
-
console.log('示例:node scripts/office.mjs 全局 全局3
|
|
35
|
+
console.log('示例:node scripts/office.mjs 全局 全局3 Demo1');
|
|
36
36
|
process.exit(0);
|
|
37
37
|
}
|
|
38
38
|
queries.push(a);
|
|
39
39
|
}
|
|
40
40
|
if (!root) root = walkUp(process.cwd());
|
|
41
41
|
if (!root) {
|
|
42
|
-
console.error('找不到工作区根(没有 _Control_Center):用 --root 指定,例如 --root
|
|
42
|
+
console.error('找不到工作区根(没有 _Control_Center):用 --root 指定,例如 --root <全局根绝对路径>');
|
|
43
43
|
process.exit(2);
|
|
44
44
|
}
|
|
45
45
|
|
package/src/client.js
CHANGED
|
@@ -64,7 +64,6 @@ window.__ModuleLoader__.load({
|
|
|
64
64
|
document.head.appendChild(tag);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
/** 依次尝试候选地址取状态;全失败时抛出带全部原因的错。 */
|
|
68
67
|
/** 带超时的 fetch(旧通道回退用;没有超时就会永远「读取中」)。 */
|
|
69
68
|
async function fetchWithTimeout(url, opts, ms) {
|
|
70
69
|
const ac = new AbortController();
|
|
@@ -76,6 +75,7 @@ window.__ModuleLoader__.load({
|
|
|
76
75
|
}
|
|
77
76
|
}
|
|
78
77
|
|
|
78
|
+
/** 依次尝试候选地址取状态;全失败时抛出带全部原因的错。 */
|
|
79
79
|
async function fetchStatus() {
|
|
80
80
|
const reasons = [];
|
|
81
81
|
for (const url of ENDPOINTS) {
|
|
@@ -107,7 +107,7 @@ window.__ModuleLoader__.load({
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
|
-
* 可折叠表格:先显示 pageSize
|
|
110
|
+
* 可折叠表格:先显示 pageSize 行,点击「再展开」继续追加 pageSize 行。
|
|
111
111
|
* 列宽由 widths 百分比给定(表头固定、不横向滚动)。
|
|
112
112
|
*/
|
|
113
113
|
function Folded(props) {
|
|
@@ -142,7 +142,7 @@ window.__ModuleLoader__.load({
|
|
|
142
142
|
return "可用(参数 " + (conn.rpc.call.length || "?") + ")";
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
/**
|
|
145
|
+
/** 与超时竞速:宿主 rpc.call 不理会 AbortSignal,仅中止请求无法结束等待,页面会停在「读取中」。 */
|
|
146
146
|
function raceTimeout(promise, ms, label) {
|
|
147
147
|
return new Promise((resolve, reject) => {
|
|
148
148
|
const timer = setTimeout(() => reject(new Error(label + " 超时 " + ms + " ms")), ms);
|
|
@@ -160,7 +160,7 @@ window.__ModuleLoader__.load({
|
|
|
160
160
|
// "[object Object]/api" 并报 invalid RPC target。
|
|
161
161
|
const rpcObj = conn && conn.rpc && typeof conn.rpc.call === "function" ? conn.rpc : null;
|
|
162
162
|
if (!rpcObj) throw new Error("宿主没有 connection.rpc(载体=" + carrierKind() + ")");
|
|
163
|
-
// 不用 AbortController 主动中止:宿主处理可能超过 8
|
|
163
|
+
// 不用 AbortController 主动中止:宿主处理可能超过 8 秒,只做与超时竞速,
|
|
164
164
|
// 让请求在宿主侧自然完成,慢一点也能拿到结果。
|
|
165
165
|
const ac = new AbortController();
|
|
166
166
|
const limit = ms || 30000;
|
|
@@ -267,19 +267,19 @@ window.__ModuleLoader__.load({
|
|
|
267
267
|
const [globalMsg, setGlobalMsg] = React.useState("");
|
|
268
268
|
const [globalBad, setGlobalBad] = React.useState(false);
|
|
269
269
|
const [working, setWorking] = React.useState(false);
|
|
270
|
-
//
|
|
270
|
+
// 全局前置提示词
|
|
271
271
|
const [extra, setExtra] = React.useState("");
|
|
272
272
|
const [extraMsg, setExtraMsg] = React.useState("");
|
|
273
273
|
const [extraBad, setExtraBad] = React.useState(false);
|
|
274
274
|
const [channel, setChannel] = React.useState("");
|
|
275
|
-
//
|
|
275
|
+
// 迁移:探针 / 复制 / 状态
|
|
276
276
|
const [mvTo, setMvTo] = React.useState("");
|
|
277
277
|
const [mvBusy, setMvBusy] = React.useState(false);
|
|
278
278
|
const [mvMsg, setMvMsg] = React.useState("");
|
|
279
279
|
const [mvBad, setMvBad] = React.useState(false);
|
|
280
280
|
const [mvProbe, setMvProbe] = React.useState(null);
|
|
281
281
|
const [mvStatus, setMvStatus] = React.useState(null);
|
|
282
|
-
//
|
|
282
|
+
// 删除旧备份的确认词。
|
|
283
283
|
const [delWord, setDelWord] = React.useState("");
|
|
284
284
|
|
|
285
285
|
const load = React.useCallback(() => {
|
|
@@ -335,7 +335,7 @@ window.__ModuleLoader__.load({
|
|
|
335
335
|
setWorking(true); setSetupBad(false); setSetupMsg("");
|
|
336
336
|
post("register", { path: created, title: "全局" }, 20000)
|
|
337
337
|
.then((r) => {
|
|
338
|
-
if (r && r.ok) setSetupMsg("已注册工作区:" + (r.title || "全局") + "(" + (r.id || "?") + "
|
|
338
|
+
if (r && r.ok) setSetupMsg("已注册工作区:" + (r.title || "全局") + "(" + (r.id || "?") + ")。点击「刷新」后可继续。");
|
|
339
339
|
else { setSetupBad(true); setSetupMsg(((r && r.error) || "注册失败") + (r && r.hint ? ";" + r.hint : "")); }
|
|
340
340
|
})
|
|
341
341
|
.catch((e) => { setSetupBad(true); setSetupMsg("请求未完成:" + (e && e.message ? e.message : String(e))); })
|
|
@@ -389,7 +389,7 @@ window.__ModuleLoader__.load({
|
|
|
389
389
|
.then((r) => {
|
|
390
390
|
if (r && r.ok) {
|
|
391
391
|
if (r.unchanged) setExtraMsg(r.note || "内容未变,已跳过写入。");
|
|
392
|
-
else setExtraMsg((r.chars ? "已保存,共 " + r.chars + "
|
|
392
|
+
else setExtraMsg((r.chars ? "已保存,共 " + r.chars + " 字符。" : "已清空,不再注册提示段。") + (r.cacheNote ? " " + r.cacheNote : ""));
|
|
393
393
|
}
|
|
394
394
|
else { setExtraBad(true); setExtraMsg((r && r.error) || "保存失败"); }
|
|
395
395
|
})
|
|
@@ -442,7 +442,7 @@ window.__ModuleLoader__.load({
|
|
|
442
442
|
.then(() => setWorking(false));
|
|
443
443
|
}, [load]);
|
|
444
444
|
|
|
445
|
-
//
|
|
445
|
+
// 迁移:探针 → 复制并校验 → 状态 → 清旧备份
|
|
446
446
|
const refreshMove = React.useCallback(() => {
|
|
447
447
|
rpc("move.status", {}, 10000).then((st) => setMvStatus(st)).catch(() => {});
|
|
448
448
|
}, []);
|
|
@@ -479,20 +479,20 @@ window.__ModuleLoader__.load({
|
|
|
479
479
|
if (!r) return;
|
|
480
480
|
setMvBad(false);
|
|
481
481
|
setMvMsg(r.already
|
|
482
|
-
? "
|
|
483
|
-
: "
|
|
482
|
+
? "已处于切换完成状态(原路径为链接),无需再次切换。"
|
|
483
|
+
: "已就绪:完全退出 DSH 后约 1 秒完成切换,等待 10 秒再打开。日志:" + (r.log || ""));
|
|
484
484
|
refreshMove();
|
|
485
485
|
});
|
|
486
486
|
}, [callMove, refreshMove]);
|
|
487
487
|
|
|
488
|
-
//
|
|
488
|
+
// 压缩授权:只有用户在此授权,压缩才排得进;模型不能自批。
|
|
489
489
|
const [csSid, setCsSid] = React.useState("");
|
|
490
490
|
const [csMsg, setCsMsg] = React.useState("");
|
|
491
491
|
const doGrant = React.useCallback((kind) => {
|
|
492
492
|
setWorking(true); setCsMsg("");
|
|
493
493
|
post("consent.grant", { session_id: csSid, kind: kind === "succeed" ? "succeed" : "compact" }, 15000)
|
|
494
494
|
.then((r) => {
|
|
495
|
-
if (r && r.ok) { setCsMsg("已授权会话 " + r.sessionId + "
|
|
495
|
+
if (r && r.ok) { setCsMsg("已授权会话 " + r.sessionId + ":5 分钟内可" + (r.kind === "succeed" ? "换任" : "压缩") + "一次,用后失效。"); load(); }
|
|
496
496
|
else setCsMsg("授权未完成:" + ((r && r.error) || "原因未知"));
|
|
497
497
|
})
|
|
498
498
|
.catch((e) => setCsMsg("请求未完成:" + (e && e.message ? e.message : String(e))))
|
|
@@ -502,7 +502,7 @@ window.__ModuleLoader__.load({
|
|
|
502
502
|
setWorking(true); setCsMsg("");
|
|
503
503
|
post("consent.grant", { session_id: sid, kind: kind === "succeed" ? "succeed" : "compact" }, 15000)
|
|
504
504
|
.then((r) => {
|
|
505
|
-
if (r && r.ok) { setCsMsg("已授权会话 " + r.sessionId + "
|
|
505
|
+
if (r && r.ok) { setCsMsg("已授权会话 " + r.sessionId + ":5 分钟内可" + (r.kind === "succeed" ? "换任" : "压缩") + "一次,用后失效。"); load(); }
|
|
506
506
|
else setCsMsg("授权未完成:" + ((r && r.error) || "原因未知"));
|
|
507
507
|
})
|
|
508
508
|
.catch((e) => setCsMsg("请求未完成:" + (e && e.message ? e.message : String(e))))
|
|
@@ -539,7 +539,7 @@ window.__ModuleLoader__.load({
|
|
|
539
539
|
const doCleanup = React.useCallback(() => {
|
|
540
540
|
if (String(delWord || "").trim() !== "删除") {
|
|
541
541
|
setMvBad(true);
|
|
542
|
-
setMvMsg("
|
|
542
|
+
setMvMsg("确认词不正确。输入「删除」后重试。");
|
|
543
543
|
return;
|
|
544
544
|
}
|
|
545
545
|
callMove("move.cleanup", { confirm: true }, 180000).then((r) => {
|
|
@@ -557,13 +557,13 @@ window.__ModuleLoader__.load({
|
|
|
557
557
|
const head = h("div", { className: "dshh-head" },
|
|
558
558
|
h("h2", { className: "dshh-title" }, "dsh-handover"),
|
|
559
559
|
h("button", { className: "dshh-btn", onClick: load, disabled: busy, type: "button" }, busy ? (data ? "正在刷新" : "正在读取") : "刷新"),
|
|
560
|
-
h("span", { className: "dshh-
|
|
560
|
+
diag.lastError ? h("span", { className: "dshh-bad" }, "读取失败(通道 " + (channel || carrierKind()) + "):" + diag.lastError.slice(0, 90)) : null,
|
|
561
561
|
data ? h("span", { className: "dshh-muted" }, "读数时间 " + data.generatedAt) : null);
|
|
562
562
|
|
|
563
563
|
if (err && !data) {
|
|
564
564
|
return h("div", { className: "dshh-root" }, head,
|
|
565
565
|
h("div", { className: "dshh-bad" }, "无法取得状态:" + err),
|
|
566
|
-
h("div", { className: "dshh-muted" }, "
|
|
566
|
+
h("div", { className: "dshh-muted" }, "插件刚安装时,确认已在配置中启用并重启 DSH。"));
|
|
567
567
|
}
|
|
568
568
|
if (!data) {
|
|
569
569
|
return h("div", { className: "dshh-root" }, head, h("div", { className: "dshh-muted" }, "正在读取状态数据。"));
|
|
@@ -577,7 +577,7 @@ window.__ModuleLoader__.load({
|
|
|
577
577
|
const allSessions = (data.sessions && Array.isArray(data.sessions.all)) ? data.sessions.all : [];
|
|
578
578
|
const byActivity = allSessions.slice().sort((a, b) => String(b.seen || "").localeCompare(String(a.seen || "")));
|
|
579
579
|
|
|
580
|
-
//
|
|
580
|
+
// 排序原则:高频操作在前,只读数据居中,说明文字在后。
|
|
581
581
|
const sections = [];
|
|
582
582
|
|
|
583
583
|
// 一、首次安装与全局会话:未接入时给一键部署;已接入但还没有全局会话时给补建入口。
|
|
@@ -590,15 +590,17 @@ window.__ModuleLoader__.load({
|
|
|
590
590
|
if (notReady) {
|
|
591
591
|
const msg = setupMsg ? h("div", { className: setupBad ? "dshh-bad" : "dshh-ok" }, setupMsg) : null;
|
|
592
592
|
const steps = (setup && setup.steps) || [];
|
|
593
|
+
const notes = Array.isArray(data.notes) ? data.notes : [];
|
|
593
594
|
sections.push(card("首次安装",
|
|
594
595
|
h("div", null,
|
|
595
|
-
h("div", { className: "dshh-muted" }, "
|
|
596
|
+
h("div", { className: "dshh-muted" }, "本页与工具已可用;接入工作区后显示闸门、交接体检与会话水位。"),
|
|
596
597
|
h("ol", { className: "dshh-list" }, steps.map((s, i) => h("li", { key: i }, s))),
|
|
598
|
+
notes.length ? h("ul", { className: "dshh-list" }, notes.map((n, i) => h("li", { key: i }, n))) : null,
|
|
597
599
|
h("input", {
|
|
598
600
|
className: "dshh-input",
|
|
599
601
|
type: "text",
|
|
600
602
|
value: parent,
|
|
601
|
-
placeholder: "
|
|
603
|
+
placeholder: "填写绝对路径:全局根 _Workspace 将建在该目录下",
|
|
602
604
|
onChange: (e) => setParent(e.target.value),
|
|
603
605
|
}),
|
|
604
606
|
h("div", { className: "dshh-row" },
|
|
@@ -610,78 +612,89 @@ window.__ModuleLoader__.load({
|
|
|
610
612
|
} else if (globalCount === 0) {
|
|
611
613
|
sections.push(card("全局会话",
|
|
612
614
|
h("div", null,
|
|
613
|
-
h("div", { className: "dshh-muted" }, "
|
|
615
|
+
h("div", { className: "dshh-muted" }, "工作区已接入,尚无「全局」会话。点击下方按钮新建,插件会将其登记为全局角色。"),
|
|
614
616
|
h("div", { className: "dshh-muted" }, "全局根:" + (ws.root || "")),
|
|
615
617
|
h("div", { className: "dshh-row" },
|
|
616
618
|
h("button", { className: "dshh-btn", type: "button", disabled: working, onClick: makeGlobalSession }, working ? "正在创建" : "创建全局会话")),
|
|
617
619
|
globalMsg ? h("div", { className: globalBad ? "dshh-bad" : "dshh-ok" }, globalMsg) : null)));
|
|
618
620
|
}
|
|
619
621
|
|
|
620
|
-
//
|
|
621
|
-
const consentRows = Array.isArray(data.consent) ? data.consent : [];
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
622
|
+
// 二、授权:唯一合法的「用户同意」入口,置顶;只列在任会话,超过 5 个折叠。
|
|
623
|
+
const consentRows = (Array.isArray(data.consent) ? data.consent : []).filter((r) => r.onduty !== false);
|
|
624
|
+
const dutyRows = byActivity.filter((r) => r.onduty !== false);
|
|
625
|
+
const grantRow = (kind) => (r, i) => h("tr", { key: i },
|
|
626
|
+
h("td", null, r.title || "(无标题)"),
|
|
627
|
+
h("td", { className: "dshh-code dshh-num" }, shortSid(r.sid)),
|
|
628
|
+
h("td", null, h("button", {
|
|
629
|
+
className: "dshh-btn",
|
|
630
|
+
type: "button",
|
|
631
|
+
disabled: working,
|
|
632
|
+
title: r.sid,
|
|
633
|
+
onClick: () => doGrantSid(r.sid, kind),
|
|
634
|
+
}, kind === "succeed" ? "授权换任" : "授权压缩")));
|
|
635
|
+
const grantTable = (kind) => h(Folded, {
|
|
636
|
+
rows: dutyRows,
|
|
637
|
+
pageSize: 5,
|
|
638
|
+
cols: ["会话", "短号", "操作"],
|
|
639
|
+
widths: ["58%", "12%", "30%"],
|
|
640
|
+
render: grantRow(kind),
|
|
641
|
+
});
|
|
642
|
+
const authKids = [];
|
|
643
|
+
if (dutyRows.length) {
|
|
644
|
+
authKids.push(h("div", { className: "dshh-muted" }, "点击「授权压缩」后,该会话 5 分钟内可压缩一次,用后失效;也可在会话中发送 /allow-compact。"));
|
|
645
|
+
authKids.push(grantTable("compact"));
|
|
646
|
+
authKids.push(h("div", { className: "dshh-muted" }, "换任同样须授权:点击「授权换任」后 5 分钟内可换任一次;也可在会话中发送 /allow-succeed。"));
|
|
647
|
+
authKids.push(grantTable("succeed"));
|
|
648
|
+
} else {
|
|
649
|
+
authKids.push(h("div", { className: "dshh-muted" }, "暂无可点选的在任会话;可在下方直接填会话 id 授权。"));
|
|
650
|
+
}
|
|
651
|
+
authKids.push(h("div", { className: "dshh-row" },
|
|
652
|
+
h("input", {
|
|
653
|
+
className: "dshh-inputSm",
|
|
654
|
+
type: "text",
|
|
655
|
+
value: csSid,
|
|
656
|
+
placeholder: "或填写会话 id 或 8 位短号",
|
|
657
|
+
onChange: (e) => setCsSid(e.target.value),
|
|
658
|
+
}),
|
|
659
|
+
h("button", { className: "dshh-btn", type: "button", disabled: working || !csSid, onClick: () => doGrant("compact") }, working ? "正在授权" : "授权压缩"),
|
|
660
|
+
h("button", { className: "dshh-btn", type: "button", disabled: working || !csSid, onClick: () => doGrant("succeed") }, "授权换任")));
|
|
661
|
+
if (consentRows.length) {
|
|
662
|
+
authKids.push(h("div", { className: "dshh-ok" }, "有授权"));
|
|
663
|
+
authKids.push(h("table", { className: "dshh-table" },
|
|
664
|
+
h("colgroup", null, h("col", { style: { width: "62%" } }), h("col", { style: { width: "18%" } }), h("col", { style: { width: "20%" } })),
|
|
665
|
+
h("thead", null, h("tr", null, h("th", null, "已授权会话"), h("th", null, "剩余"), h("th", null, "操作"))),
|
|
666
|
+
h("tbody", null, consentRows.map((r, i) => h("tr", { key: i },
|
|
667
|
+
h("td", { className: "dshh-code", title: r.sessionId }, shortSid(r.sessionId) + (r.kind === "succeed" ? " · 换任" : " · 压缩")),
|
|
668
|
+
h("td", { className: "dshh-num" }, Math.round((r.leftMs || 0) / 1000) + " 秒"),
|
|
669
|
+
h("td", null, h("button", { className: "dshh-btn", type: "button", disabled: working, onClick: () => doRevoke(r.sessionId, r.kind) }, "撤销")))))));
|
|
670
|
+
}
|
|
671
|
+
if (csMsg) authKids.push(h("div", { className: "dshh-ok" }, csMsg));
|
|
672
|
+
sections.push(card("授权", h("div", null, authKids)));
|
|
664
673
|
|
|
665
674
|
// 三、移动全局根目录:常态折叠为一行状态。
|
|
666
675
|
{
|
|
667
676
|
const st = mvStatus;
|
|
668
677
|
const phaseLabel = {
|
|
669
678
|
none: "尚无迁移记录",
|
|
670
|
-
verified: "
|
|
679
|
+
verified: "已复制并校验,待切换",
|
|
671
680
|
switched: "已切换,原路径为链接",
|
|
672
|
-
"switched+cleaned": "
|
|
681
|
+
"switched+cleaned": "已切换,旧备份已删除",
|
|
673
682
|
partial: "校验有差异,需重新复制",
|
|
674
683
|
};
|
|
675
|
-
|
|
676
|
-
const
|
|
684
|
+
// 旧备份是否保留不影响使用,因此不计入「需要处理」;需要处理的只有会妨碍使用的情形。
|
|
685
|
+
const hasOlds = !!(st && st.phase === "switched" && (st.olds || []).length);
|
|
686
|
+
const attention = !!(st && (st.linkBroken || st.srcExists === false || st.phase === "verified" || st.phase === "partial" || (st.retarget && st.retarget.needed)));
|
|
687
|
+
const stateText = st
|
|
688
|
+
? "状态:" + (phaseLabel[st.phase] || st.phase) + (st.dst ? "(" + st.dst + ")" : "") + (hasOlds ? ";旧备份保留,不影响使用" : "")
|
|
689
|
+
: "正在读取迁移状态";
|
|
677
690
|
const kids = [];
|
|
678
|
-
kids.push(h("div", { className: "dshh-muted" }, "
|
|
691
|
+
kids.push(h("div", { className: "dshh-muted" }, "切换只改名与建链接,不改动文件内容。"));
|
|
679
692
|
if (st && st.phase && st.phase.indexOf("switched") === 0) {
|
|
680
|
-
kids.push(h("div", { className: "dshh-muted" }, "
|
|
693
|
+
kids.push(h("div", { className: "dshh-muted" }, "原路径为指向新位置的链接;若已删除,可用「重建链接」恢复。"));
|
|
681
694
|
}
|
|
682
695
|
if (st && (st.srcExists === false || st.linkBroken)) {
|
|
683
696
|
kids.push(h("div", { className: "dshh-bad" }, st.linkBroken
|
|
684
|
-
? "链接指向的新位置不存在:" + (st.dst || "") + "
|
|
697
|
+
? "链接指向的新位置不存在:" + (st.dst || "") + "。应先确认真实数据位置;本页不改动文件。"
|
|
685
698
|
: "原路径不存在(可能已被删除):" + (st.src || "") + "。数据仍在 " + (st.dst || "") + "。"));
|
|
686
699
|
if (st.canRepair) {
|
|
687
700
|
kids.push(h("div", { className: "dshh-row" },
|
|
@@ -698,7 +711,7 @@ window.__ModuleLoader__.load({
|
|
|
698
711
|
h("li", null, "回到本页,状态应为「已切换,原路径为链接」。")));
|
|
699
712
|
kids.push(h("div", { className: "dshh-row" },
|
|
700
713
|
h("button", { className: "dshh-btn", type: "button", disabled: mvBusy, onClick: doSwitch }, mvBusy ? "正在准备" : "准备切换(退出 DSH 后自动完成)")));
|
|
701
|
-
kids.push(h("div", { className: "dshh-muted" }, "
|
|
714
|
+
kids.push(h("div", { className: "dshh-muted" }, "助手未运行时手工执行:" + ((st.script && st.script.helper) || "")));
|
|
702
715
|
}
|
|
703
716
|
if (st && st.retarget && st.retarget.needed) {
|
|
704
717
|
kids.push(h("div", { className: "dshh-warn" }, "DSH 登记仍指向旧路径:" + st.retarget.total + " 处,涉及 " + st.retarget.files.length + " 个文件。"));
|
|
@@ -712,8 +725,8 @@ window.__ModuleLoader__.load({
|
|
|
712
725
|
if (st && st.retarget && st.retarget.needed === false) {
|
|
713
726
|
kids.push(h("div", { className: "dshh-ok" }, "DSH 登记已指向新位置:" + (st.dst || "") + "。"));
|
|
714
727
|
}
|
|
715
|
-
if (
|
|
716
|
-
kids.push(h("div", { className: "dshh-muted" }, "
|
|
728
|
+
if (hasOlds) {
|
|
729
|
+
kids.push(h("div", { className: "dshh-muted" }, "旧备份:" + st.olds.map((o) => o.path + "(" + fmtBytes(o.bytes) + ")").join("、") + "。保留不影响使用;删除后无法恢复。"));
|
|
717
730
|
kids.push(h("div", { className: "dshh-row" },
|
|
718
731
|
h("input", {
|
|
719
732
|
className: "dshh-inputSm",
|
|
@@ -722,7 +735,7 @@ window.__ModuleLoader__.load({
|
|
|
722
735
|
placeholder: "输入「删除」以确认",
|
|
723
736
|
onChange: (e) => setDelWord(e.target.value),
|
|
724
737
|
}),
|
|
725
|
-
h("button", { className: "dshh-btn", type: "button", disabled: mvBusy || delWord.trim() !== "删除", onClick: doCleanup }, "
|
|
738
|
+
h("button", { className: "dshh-btn", type: "button", disabled: mvBusy || delWord.trim() !== "删除", onClick: doCleanup }, "删除旧备份(不可撤销)")));
|
|
726
739
|
}
|
|
727
740
|
kids.push(h("div", { className: "dshh-row" },
|
|
728
741
|
h("input", {
|
|
@@ -755,7 +768,23 @@ window.__ModuleLoader__.load({
|
|
|
755
768
|
h("div", { className: "dshh-wrap" }, kids)))));
|
|
756
769
|
}
|
|
757
770
|
|
|
758
|
-
//
|
|
771
|
+
// 四、会话角色:按 DSH 工作区分组,默认全部折叠;组内按全局、项目、杂项、普通排列。
|
|
772
|
+
const ROLE_ORDER = { global: 0, project: 1, misc: 2, plain: 3 };
|
|
773
|
+
const byRole = byActivity.slice().sort((a, b) => {
|
|
774
|
+
const ra = ROLE_ORDER[a.role] === undefined ? 9 : ROLE_ORDER[a.role];
|
|
775
|
+
const rb = ROLE_ORDER[b.role] === undefined ? 9 : ROLE_ORDER[b.role];
|
|
776
|
+
return ra !== rb ? ra - rb : String(b.seen || "").localeCompare(String(a.seen || ""));
|
|
777
|
+
});
|
|
778
|
+
const wsList = (ss && Array.isArray(ss.workspaces)) ? ss.workspaces : [];
|
|
779
|
+
const roleGroups = [];
|
|
780
|
+
const claimed = {};
|
|
781
|
+
for (const w of wsList) {
|
|
782
|
+
const rows = byRole.filter((r) => r.wsKey === w.key);
|
|
783
|
+
claimed[w.key] = true;
|
|
784
|
+
if (rows.length) roleGroups.push({ key: w.key, title: w.title || w.path || "工作区", path: w.path || "", rows });
|
|
785
|
+
}
|
|
786
|
+
const restRows = byRole.filter((r) => !claimed[r.wsKey]);
|
|
787
|
+
if (restRows.length) roleGroups.push({ key: "", title: "未归属工作区", path: "", rows: restRows });
|
|
759
788
|
const roleRow = (r, i) => {
|
|
760
789
|
const draft = (roleSel && roleSel[r.sid]) || {};
|
|
761
790
|
const role = draft.role !== undefined ? draft.role : (r.role || "plain");
|
|
@@ -777,10 +806,10 @@ window.__ModuleLoader__.load({
|
|
|
777
806
|
value: role,
|
|
778
807
|
onChange: (e) => setRoleValue(r.sid, { role: e.target.value }),
|
|
779
808
|
},
|
|
780
|
-
h("option", { value: "plain" }, "普通"),
|
|
781
809
|
h("option", { value: "global" }, "全局"),
|
|
782
810
|
h("option", { value: "project" }, "项目"),
|
|
783
|
-
h("option", { value: "misc" }, "杂项")
|
|
811
|
+
h("option", { value: "misc" }, "杂项"),
|
|
812
|
+
h("option", { value: "plain" }, "普通"))),
|
|
784
813
|
h("td", null, wide ? h("input", {
|
|
785
814
|
className: "dshh-inputSm",
|
|
786
815
|
type: "text",
|
|
@@ -792,22 +821,25 @@ window.__ModuleLoader__.load({
|
|
|
792
821
|
};
|
|
793
822
|
sections.push(card("会话角色",
|
|
794
823
|
h("div", null,
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
824
|
+
roleGroups.length
|
|
825
|
+
? roleGroups.map((g, gi) => h("details", { key: gi },
|
|
826
|
+
h("summary", { className: "dshh-sum" }, g.title + "(" + g.rows.length + " 个会话)"),
|
|
827
|
+
h("div", { className: "dshh-wrap" },
|
|
828
|
+
g.path ? h("div", { className: "dshh-muted dshh-code" }, g.path) : null,
|
|
829
|
+
h(Folded, {
|
|
830
|
+
rows: g.rows,
|
|
831
|
+
pageSize: 5,
|
|
832
|
+
cols: ["会话", "短号", "水位", "角色", "项目或任务名", "操作"],
|
|
833
|
+
widths: ["30%", "9%", "7%", "12%", "20%", "22%"],
|
|
834
|
+
render: roleRow,
|
|
835
|
+
}))))
|
|
836
|
+
: h("div", { className: "dshh-muted" }, "暂无可设置角色的会话。空白会话不列出。"),
|
|
805
837
|
roleMsg ? h("div", { className: roleBad ? "dshh-bad" : "dshh-ok" }, roleMsg) : null)));
|
|
806
838
|
|
|
807
839
|
// 五、全局前置提示词:默认折叠,只显示已保存字数。
|
|
808
840
|
sections.push(card("全局前置提示词",
|
|
809
841
|
h("div", null,
|
|
810
|
-
h("div", { className: "dshh-muted" }, "
|
|
842
|
+
h("div", { className: "dshh-muted" }, "只注入你填写的内容,不含插件约束;对全部会话生效,留空保存即不注册提示段。"),
|
|
811
843
|
h("details", null,
|
|
812
844
|
h("summary", { className: "dshh-sum" }, "已保存 " + (extra ? extra.length : 0) + " 字符,点击编辑"),
|
|
813
845
|
h("textarea", {
|
|
@@ -822,12 +854,13 @@ window.__ModuleLoader__.load({
|
|
|
822
854
|
h("span", { className: "dshh-muted" }, extra.length + " 字符")),
|
|
823
855
|
extraMsg ? h("div", { className: extraBad ? "dshh-bad" : "dshh-ok" }, extraMsg) : null))));
|
|
824
856
|
|
|
825
|
-
//
|
|
857
|
+
// 六、会话水位:只读数据区从这里开始;只列在任会话(全局、项目、杂项各一行)。
|
|
826
858
|
if (ss) {
|
|
827
|
-
const byRecent = (ss.rows || []).slice().sort((a, b) => String(b.seen || "").localeCompare(String(a.seen || "")));
|
|
859
|
+
const byRecent = (ss.rows || []).filter((r) => r.onduty !== false).slice().sort((a, b) => String(b.seen || "").localeCompare(String(a.seen || "")));
|
|
828
860
|
const waterRow = (r, i) => h("tr", { key: i },
|
|
829
|
-
h("td", null, r.project),
|
|
861
|
+
h("td", null, r.dutyName || r.dutyKey || r.title || r.project || "(无名)"),
|
|
830
862
|
h("td", { className: "dshh-code dshh-num" }, shortSid(r.sid)),
|
|
863
|
+
h("td", null, r.dutyKind || r.roleLabel || ""),
|
|
831
864
|
h("td", { className: "dshh-num" }, r.pct === null || r.pct === undefined ? "?" : r.pct.toFixed(1) + "%"),
|
|
832
865
|
h("td", null, r.state),
|
|
833
866
|
h("td", null, r.active ? "活跃" : "沉默"),
|
|
@@ -836,17 +869,17 @@ window.__ModuleLoader__.load({
|
|
|
836
869
|
h("div", null,
|
|
837
870
|
h("div", { className: "dshh-muted" }, ss.verdict + "。活跃线 " + ss.cutoff + ",回溯 " + ss.silentHours + " 小时。"),
|
|
838
871
|
(ss.notify || []).length
|
|
839
|
-
? h("div", null, "本轮需通知:" + ss.notify.map((r) => r.
|
|
872
|
+
? h("div", null, "本轮需通知:" + ss.notify.map((r) => (r.name || r.key || "") + "(" + shortSid(r.sid) + ")").join("、"))
|
|
840
873
|
: h("div", { className: "dshh-muted" }, "本轮无需通知。"),
|
|
841
874
|
byRecent.length
|
|
842
875
|
? h(Folded, {
|
|
843
876
|
rows: byRecent,
|
|
844
877
|
pageSize: 5,
|
|
845
|
-
cols: ["
|
|
846
|
-
widths: ["
|
|
878
|
+
cols: ["会话", "短号", "类别", "水位", "状态", "活跃", "最后活动"],
|
|
879
|
+
widths: ["20%", "11%", "7%", "8%", "8%", "7%", "31%"],
|
|
847
880
|
render: waterRow,
|
|
848
881
|
})
|
|
849
|
-
: h("div", { className: "dshh-muted" }, "
|
|
882
|
+
: h("div", { className: "dshh-muted" }, "暂无可显示的在任会话。"))));
|
|
850
883
|
}
|
|
851
884
|
|
|
852
885
|
// 七、通用约束条闸门
|
|
@@ -868,20 +901,25 @@ window.__ModuleLoader__.load({
|
|
|
868
901
|
h("tbody", null, rows)))));
|
|
869
902
|
}
|
|
870
903
|
|
|
871
|
-
//
|
|
904
|
+
// 八、交接文档体检:全局一行,项目与杂项同一张表;问题列只留问题本身。
|
|
872
905
|
if (ho) {
|
|
873
906
|
const cleanReason = (x) => String(x || "").replace(/([^)]*)/g, "").replace(/\s+/g, " ").trim();
|
|
874
|
-
const
|
|
907
|
+
const rows = (ho.projects || []).map((r) => Object.assign({}, r, { kind: "项目" }))
|
|
908
|
+
.concat((ho.misc || []).map((r) => Object.assign({}, r, { kind: "杂项" })));
|
|
909
|
+
const prows = rows.map((r, i) => h("tr", { key: i },
|
|
910
|
+
h("td", null, r.kind),
|
|
875
911
|
h("td", null, r.name),
|
|
876
|
-
h("td", null, r.verdict === "通过" ? verdict("通过", true) : r.verdict === "缺节" ? h("span", { className: "dshh-warn" },
|
|
912
|
+
h("td", null, r.verdict === "通过" ? verdict("通过", true) : (r.verdict === "缺节" || r.verdict === "读不到" || r.verdict === "未找到") ? h("span", { className: "dshh-warn" }, r.verdict) : verdict(r.verdict, false)),
|
|
877
913
|
h("td", null, (r.reasons || []).map(cleanReason).filter(Boolean).join(";"))));
|
|
878
914
|
sections.push(card("交接文档体检",
|
|
879
915
|
h("div", null,
|
|
880
916
|
h("div", null, "全局 Global_Handover.md:", ho.global.verdict === "通过" ? verdict("通过", true) : verdict(ho.global.verdict, false),
|
|
881
917
|
(ho.global.reasons || []).length ? h("span", { className: "dshh-muted" }, (ho.global.reasons || []).map(cleanReason).filter(Boolean).join(";")) : null),
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
918
|
+
prows.length
|
|
919
|
+
? h("table", { className: "dshh-table" },
|
|
920
|
+
h("thead", null, h("tr", null, h("th", null, "类别"), h("th", null, "名称"), h("th", null, "结论"), h("th", null, "问题"))),
|
|
921
|
+
h("tbody", null, prows))
|
|
922
|
+
: h("div", { className: "dshh-muted" }, "暂无已登记的项目或杂项。"))));
|
|
885
923
|
}
|
|
886
924
|
|
|
887
925
|
// 九、插件信息
|
|
@@ -890,26 +928,21 @@ window.__ModuleLoader__.load({
|
|
|
890
928
|
["工具数", String(p.tools)],
|
|
891
929
|
["压缩调度器", p.scheduler],
|
|
892
930
|
["注入项", (p.inject || []).join("、")],
|
|
893
|
-
["初始化", (p.initErrors && p.initErrors.length) ? h("span", { className: "dshh-bad" }, p.initErrors.join(";")) : verdict("
|
|
931
|
+
["初始化", (p.initErrors && p.initErrors.length) ? h("span", { className: "dshh-bad" }, p.initErrors.join(";")) : verdict("正常", true)],
|
|
894
932
|
["工作区", ws.found ? h("span", { className: "dshh-code" }, ws.root) : verdict("未接入(未找到 _Control_Center)", false)],
|
|
895
933
|
])));
|
|
896
934
|
|
|
897
|
-
//
|
|
935
|
+
// 十、最近一次会话间通信判定:只有存在判定时才出现。
|
|
898
936
|
const g = data.guardLast;
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
?
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
])
|
|
906
|
-
: h("div", { className: "dshh-muted" }, "本进程尚未收到会话间通信请求,暂无判定。")));
|
|
907
|
-
|
|
908
|
-
if ((data.notes || []).length) {
|
|
909
|
-
sections.push(card("待处理", h("ul", { className: "dshh-list" }, data.notes.map((n, i) => h("li", { key: i }, n)))));
|
|
937
|
+
if (g) {
|
|
938
|
+
sections.push(card("最近一次会话间通信判定", kv([
|
|
939
|
+
["结论", g.allow ? verdict("允许", true) : verdict("拒绝", false)],
|
|
940
|
+
["依据", g.source + (g.title ? ";会话标题 " + g.title : "")],
|
|
941
|
+
["会话", h("span", { className: "dshh-code" }, g.sid || "-")],
|
|
942
|
+
])));
|
|
910
943
|
}
|
|
911
944
|
|
|
912
|
-
//
|
|
945
|
+
// 末、工具明细与使用说明:参考性质,放最后,默认折叠。
|
|
913
946
|
const gd = data.guide;
|
|
914
947
|
if (gd) {
|
|
915
948
|
const sit = (gd.situations || []).map((r, i) => h("tr", { key: i },
|
|
@@ -921,16 +954,22 @@ window.__ModuleLoader__.load({
|
|
|
921
954
|
h("table", { className: "dshh-table" }, h("tbody", null, (gr.tools || []).map((t, j) => h("tr", { key: j },
|
|
922
955
|
h("td", { className: "dshh-code" }, t[0]),
|
|
923
956
|
h("td", { className: "dshh-muted" }, t[1])))))));
|
|
957
|
+
const toolCount = (gd.groups || []).reduce((a, gr) => a + ((gr.tools || []).length), 0);
|
|
958
|
+
if (toolCount) {
|
|
959
|
+
sections.push(card("工具明细",
|
|
960
|
+
h("details", null,
|
|
961
|
+
h("summary", { className: "dshh-sum" }, "共 " + toolCount + " 个工具,点击展开"),
|
|
962
|
+
h("div", { className: "dshh-wrap" }, groups))));
|
|
963
|
+
}
|
|
924
964
|
sections.push(card("使用说明",
|
|
925
965
|
h("details", null,
|
|
926
|
-
h("summary", { className: "dshh-sum" }, "
|
|
966
|
+
h("summary", { className: "dshh-sum" }, "交互方式、常见请求与约定"),
|
|
927
967
|
h("div", { className: "dshh-wrap" },
|
|
928
968
|
(gd.intro || []).map((t, i) => h("div", { key: i }, t)),
|
|
929
969
|
h("div", { className: "dshh-sub" }, "常见请求与对应动作"),
|
|
930
970
|
h("table", { className: "dshh-table" },
|
|
931
971
|
h("thead", null, h("tr", null, h("th", null, "时机"), h("th", null, "可以这样说"), h("th", null, "插件会执行的动作"))),
|
|
932
972
|
h("tbody", null, sit)),
|
|
933
|
-
groups,
|
|
934
973
|
h("div", { className: "dshh-sub" }, "约定"),
|
|
935
974
|
h("ul", { className: "dshh-list" }, (gd.rules || []).map((t, i) => h("li", { key: i }, t)))))));
|
|
936
975
|
}
|