@shgroup/dsh-serenity-hooks 1.24.9 → 1.24.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dsh.plugin.json +1 -1
- package/lib/client.js +221 -234
- package/lib/index.js +92 -41
- package/package.json +1 -1
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.10",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "宁静号 ACC harness(Native Cordis 插件):真实 DSH 工具 cc_fs/session/acc_msm/eap/neat/cce/handyman/session_rebuild + 拦截缝机械约束(safe-mode/路径守卫)+ 高级设定面板(双端口网关/账号管理)。适配 DSH 公开版(0.1.0-rc,deepseek-ai/deepseek-harness)。",
|
|
6
6
|
"engines": {
|
package/lib/client.js
CHANGED
|
@@ -8,7 +8,7 @@ window.__ModuleLoader__.load({
|
|
|
8
8
|
let react = require("react");
|
|
9
9
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
10
|
//#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/SafeModePanel
|
|
11
|
-
const css$3 = "/* SafeModePanel — 会话头部 Serenity
|
|
11
|
+
const css$3 = "/* SafeModePanel — 会话头部 Serenity 状态胶囊(v1.24.10 胶囊改版,对齐 OcgoDockEntry pill)\n * 胶囊 999px 全圆 → 点击 → 自绘 popover(340px 右上角卡片,外点/Escape 关闭)\n * popover 内:分组(groupTitle 标题 + rowCard 行卡)——看齐官方 settings 设计语言\n * 遵循 web-styling.md:颜色只用 --dsw-alias-* 语义 token(明暗自适应)+ --sp-safe-green 统一绿家族;\n * 前缀 sp- 防冲突。 */\n\n/* ── 统一绿色变量家族(一处改全改;emerald 500~600 区间保证深色对比度) ── */\n\n.sp-root {\n --sp-safe-green: #0ba875; /* emerald-550:SAFE ON 主色 */\n --sp-safe-green-deep: #059669; /* emerald-600:渐变深端 */\n}\n\n/* ── 头部状态胶囊(可点击) ── */\n\n.sp-card {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 9px; /* 无固定 height:由内容撑高(胶囊) */\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2, color-mix(in srgb, var(--dsw-alias-bg-module) 70%, transparent));\n color: inherit;\n font-size: 12px;\n line-height: 1;\n cursor: pointer;\n transition: background var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease),\n border-color var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease);\n}\n\n.sp-card:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n border-color: var(--dsw-alias-border-l2);\n}\n\n.sp-card:focus-visible {\n outline: 2px solid var(--dsw-alias-button-ghost-active-border);\n outline-offset: 1px;\n}\n\n.sp-card:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n/* 绿点 = Serenity 在线(恒亮绿,不随 SAFE 变);离线/未激活 → 灰 */\n.sp-dot {\n flex: none;\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--dsw-alias-state-success-primary);\n}\n\n.sp-dotOff {\n background: var(--dsw-alias-label-dimmed);\n}\n\n.sp-cardInactive {\n opacity: 0.85;\n}\n\n.sp-cardInactive .sp-brand {\n color: var(--dsw-alias-label-secondary);\n font-weight: 500;\n}\n\n.sp-brand {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n/* 版本号:monospace + translateY(1px) 下沉对齐(与文字基线齐平) */\n.sp-ver {\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n font-family: var(--ds-font-family-code, monospace);\n transform: translateY(1px);\n}\n\n/* 1px 深灰分隔线(两侧紧凑) */\n.sp-sep {\n flex: none;\n width: 1px;\n height: 12px;\n margin: 0 2px;\n background: var(--dsw-alias-label-tertiary);\n}\n\n.sp-shield {\n display: inline-flex;\n width: 14px;\n height: 14px;\n flex: none;\n}\n\n.sp-shield svg {\n width: 14px;\n height: 14px;\n}\n\n.sp-statusWord {\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0;\n white-space: nowrap;\n}\n\n/* SAFE OFF(未保护):灰——低姿态提醒,非错误(用户自己的开关选择) */\n.sp-cardUnprotected .sp-shield {\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-cardUnprotected .sp-statusWord {\n color: var(--dsw-alias-label-secondary);\n}\n\n/* SAFE ON(保护中):emerald 绿——安心(盾牌实色 + SAFE 文字渐变质感) */\n.sp-cardProtected .sp-shield {\n color: var(--sp-safe-green);\n}\n\n.sp-cardProtected .sp-statusWord {\n color: var(--sp-safe-green);\n background: linear-gradient(135deg, var(--sp-safe-green) 0%, var(--sp-safe-green-deep) 100%);\n -webkit-background-clip: text;\n background-clip: text;\n -webkit-text-fill-color: transparent;\n}\n\n/* ── Mac 风格滑块(v1.24.10:轨道 24×13 / thumb 11px 灰白 #f2f4f6 无图标 / cubic-bezier 平滑) ── */\n\n.sp-switch {\n display: inline-block;\n width: 24px;\n height: 13px;\n border-radius: 999px;\n position: relative;\n flex: none;\n cursor: pointer;\n transition: background 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.sp-switchOff {\n background: var(--dsw-alias-border-l2);\n}\n\n.sp-switchOn {\n background: var(--sp-safe-green);\n}\n\n.sp-switchThumb {\n position: absolute;\n top: 1px;\n width: 11px;\n height: 11px;\n border-radius: 50%;\n background: #f2f4f6; /* Mac 灰白 thumb(明暗通用) */\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);\n transition: left 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.sp-switchOff .sp-switchThumb {\n left: 1px;\n}\n\n.sp-switchOn .sp-switchThumb {\n left: 12px; /* 24 - 11 - 1 */\n}\n\n/* CSS 特异性坑(S013 指南):.sp-switch:hover (0,2,0) 会覆盖 .sp-switchOn (0,1,0)\n * → ON 态 hover 绿底消失。必须同特异性且后定义补回。 */\n.sp-switch:hover {\n filter: brightness(1.08);\n}\n\n.sp-switchOn:hover {\n background: var(--sp-safe-green);\n}\n\n.sp-switchBusy {\n opacity: 0.6;\n cursor: default;\n pointer-events: none;\n}\n\n.sp-chev {\n color: var(--dsw-alias-label-dimmed);\n flex: none;\n}\n\n/* ── 自绘 popover(340px 右上角小卡片,替代官方 Modal) ── */\n\n.sp-root {\n position: relative;\n display: inline-flex;\n}\n\n.sp-pop {\n position: absolute;\n top: calc(100% + 8px);\n right: 0;\n width: 340px;\n max-width: calc(100vw - 24px);\n z-index: 2000;\n box-sizing: border-box;\n padding: 14px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 14px;\n background: var(--dsw-alias-bg-module, #1e1e1e);\n box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);\n}\n\n.sp-popBody {\n display: flex;\n flex-direction: column;\n gap: 14px;\n max-height: calc(100vh - 140px);\n overflow-y: auto;\n}\n\n/* ── 分组(官方 settings 语言:groupTitle + rows) ── */\n\n.sp-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.sp-groupHead {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.sp-groupTitle {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-rows {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n/* 行卡(rowCard:border + radius + padding;值与 label 分离、长内容可换行) */\n.sp-row {\n display: flex;\n align-items: baseline;\n gap: 12px;\n min-width: 0;\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-2, color-mix(in srgb, var(--dsw-alias-bg-module) 60%, transparent));\n}\n\n.sp-label {\n flex: none;\n width: 76px;\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n}\n\n.sp-value {\n flex: 1;\n min-width: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n font-family: var(--ds-font-family-code, monospace);\n word-break: break-all;\n overflow-wrap: anywhere;\n}\n\n/* 安全模式动作区 */\n.sp-actions {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-2, color-mix(in srgb, var(--dsw-alias-bg-module) 60%, transparent));\n}\n\n.sp-toggle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 24px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n font-size: 12px;\n cursor: pointer;\n transition: background var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease);\n}\n\n.sp-toggleLg {\n height: 30px;\n padding: 0 16px;\n font-size: 13px;\n}\n\n.sp-toggleOn {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent);\n color: var(--dsw-alias-state-error-primary);\n border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 45%, transparent);\n}\n\n.sp-toggleOff {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-toggleIcon {\n flex: none;\n}\n\n.sp-toggleDesc {\n flex: 1;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* handyman 运行状态区块 */\n\n.sp-handymenToggle {\n flex: none;\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n cursor: pointer;\n}\n\n.sp-handymenToggle:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.sp-handymenList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-top: 2px;\n}\n\n.sp-handymanItem {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n min-width: 0;\n}\n\n.sp-handymanHead {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n}\n\n.sp-handymanLabel {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.sp-handymanRound {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n}\n\n.sp-handymanTime {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n}\n\n.sp-handymanResp {\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.sp-handymenMore {\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n padding: 4px 12px;\n}\n\n/* 底部提示(配置入口引导) */\n.sp-hint {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n padding: 8px 12px;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: 10px;\n}\n";
|
|
12
12
|
if (typeof document !== "undefined" && document.querySelector("style[data-sp-css=\"SafeModePanel\"]") === null) {
|
|
13
13
|
const tag = document.createElement("style");
|
|
14
14
|
tag.setAttribute("data-sp-css", "SafeModePanel");
|
|
@@ -19,7 +19,7 @@ window.__ModuleLoader__.load({
|
|
|
19
19
|
//#region src/client/SafeModePanel.tsx
|
|
20
20
|
const API = "/serenity/status";
|
|
21
21
|
const HANDYMEN_API = "/serenity/handymen";
|
|
22
|
-
/** 盾牌 SVG
|
|
22
|
+
/** 盾牌 SVG(v1.24.5 校准:fill=none 纯描边;ON=绿 emerald / OFF=灰 label-secondary 由状态类控制) */
|
|
23
23
|
const SHIELD_SVG = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
24
24
|
viewBox: "0 0 16 16",
|
|
25
25
|
fill: "none",
|
|
@@ -39,45 +39,18 @@ window.__ModuleLoader__.load({
|
|
|
39
39
|
strokeLinejoin: "round"
|
|
40
40
|
})]
|
|
41
41
|
});
|
|
42
|
-
/** 滑块 thumb 图标(v1.24.4 方案 O 滑块变体:OFF=琥珀 ✗ / ON=绿 ✓,开关隐喻表达开关状态) */
|
|
43
|
-
const SWITCH_OFF_ICON = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
44
|
-
viewBox: "0 0 12 12",
|
|
45
|
-
fill: "none",
|
|
46
|
-
width: 8,
|
|
47
|
-
height: 8,
|
|
48
|
-
"aria-hidden": "true",
|
|
49
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
50
|
-
d: "M3 3L9 9M9 3L3 9",
|
|
51
|
-
stroke: "currentColor",
|
|
52
|
-
strokeWidth: 1.8,
|
|
53
|
-
strokeLinecap: "round"
|
|
54
|
-
})
|
|
55
|
-
});
|
|
56
|
-
const SWITCH_ON_ICON = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
57
|
-
viewBox: "0 0 12 12",
|
|
58
|
-
fill: "none",
|
|
59
|
-
width: 8,
|
|
60
|
-
height: 8,
|
|
61
|
-
"aria-hidden": "true",
|
|
62
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
63
|
-
d: "M2 6.2L4.8 9L10 3.4",
|
|
64
|
-
stroke: "currentColor",
|
|
65
|
-
strokeWidth: 1.8,
|
|
66
|
-
strokeLinecap: "round",
|
|
67
|
-
strokeLinejoin: "round"
|
|
68
|
-
})
|
|
69
|
-
});
|
|
70
42
|
/** 拼接类名(等价 clsx 最小子集,避免额外依赖) */
|
|
71
43
|
function cx(...parts) {
|
|
72
44
|
return parts.filter(Boolean).join(" ");
|
|
73
45
|
}
|
|
74
|
-
/** 会话头部:
|
|
46
|
+
/** 会话头部:Serenity 状态胶囊(点击 → 自绘 popover,纯状态展示) */
|
|
75
47
|
function SafeModePanel(props) {
|
|
76
48
|
const [status, setStatus] = (0, react.useState)(null);
|
|
77
49
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
78
50
|
const [open, setOpen] = (0, react.useState)(false);
|
|
79
51
|
const [handymen, setHandymen] = (0, react.useState)([]);
|
|
80
52
|
const [handymenOpen, setHandymenOpen] = (0, react.useState)(false);
|
|
53
|
+
const rootRef = (0, react.useRef)(null);
|
|
81
54
|
const sessionId = props.sessionId;
|
|
82
55
|
const refresh = (0, react.useCallback)(async () => {
|
|
83
56
|
const qs = sessionId ? `?sessionId=${encodeURIComponent(sessionId)}` : "";
|
|
@@ -110,6 +83,21 @@ window.__ModuleLoader__.load({
|
|
|
110
83
|
clearInterval(timer);
|
|
111
84
|
};
|
|
112
85
|
}, [open, sessionId]);
|
|
86
|
+
(0, react.useEffect)(() => {
|
|
87
|
+
if (!open) return;
|
|
88
|
+
const onDocMouseDown = (e) => {
|
|
89
|
+
if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
|
|
90
|
+
};
|
|
91
|
+
const onKey = (e) => {
|
|
92
|
+
if (e.key === "Escape") setOpen(false);
|
|
93
|
+
};
|
|
94
|
+
document.addEventListener("mousedown", onDocMouseDown);
|
|
95
|
+
document.addEventListener("keydown", onKey);
|
|
96
|
+
return () => {
|
|
97
|
+
document.removeEventListener("mousedown", onDocMouseDown);
|
|
98
|
+
document.removeEventListener("keydown", onKey);
|
|
99
|
+
};
|
|
100
|
+
}, [open]);
|
|
113
101
|
const toggle = async (on) => {
|
|
114
102
|
setBusy(true);
|
|
115
103
|
try {
|
|
@@ -140,231 +128,230 @@ window.__ModuleLoader__.load({
|
|
|
140
128
|
});
|
|
141
129
|
const inCcc = status.root !== null;
|
|
142
130
|
const runningHandymen = handymen.filter((l) => !l.done);
|
|
143
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}),
|
|
156
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
157
|
-
className: cx("sp-ver"),
|
|
158
|
-
children: ["v", status.accVersion]
|
|
159
|
-
}),
|
|
160
|
-
inCcc && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
161
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: cx("sp-sep") }),
|
|
131
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
132
|
+
className: cx("sp-root"),
|
|
133
|
+
ref: rootRef,
|
|
134
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
135
|
+
type: "button",
|
|
136
|
+
className: cx("sp-card", inCcc ? status.safeModeOn ? "sp-cardProtected" : "sp-cardUnprotected" : "sp-cardInactive"),
|
|
137
|
+
"aria-haspopup": "dialog",
|
|
138
|
+
"aria-expanded": open,
|
|
139
|
+
onClick: () => setOpen((v) => !v),
|
|
140
|
+
title: inCcc ? `Serenity v${status.accVersion} — ${status.root}(${status.safeModeOn ? "SAFE ON — 写工具已隐藏" : "SAFE OFF — 写工具全开,无保护"})` : "Serenity(未激活)",
|
|
141
|
+
children: [
|
|
142
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: cx("sp-dot", inCcc ? void 0 : "sp-dotOff") }),
|
|
162
143
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
163
|
-
className: cx("sp-
|
|
164
|
-
children:
|
|
144
|
+
className: cx("sp-brand"),
|
|
145
|
+
children: "Serenity"
|
|
165
146
|
}),
|
|
166
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
167
|
-
className: cx("sp-
|
|
168
|
-
children: "
|
|
147
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
148
|
+
className: cx("sp-ver"),
|
|
149
|
+
children: ["v", status.accVersion]
|
|
169
150
|
}),
|
|
170
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
},
|
|
180
|
-
|
|
181
|
-
className: cx("sp-
|
|
182
|
-
|
|
151
|
+
inCcc && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
152
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: cx("sp-sep") }),
|
|
153
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
154
|
+
className: cx("sp-shield"),
|
|
155
|
+
children: SHIELD_SVG
|
|
156
|
+
}),
|
|
157
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
158
|
+
className: cx("sp-statusWord"),
|
|
159
|
+
children: "SAFE"
|
|
160
|
+
}),
|
|
161
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
162
|
+
className: cx("sp-switch", status.safeModeOn ? "sp-switchOn" : "sp-switchOff", busy ? "sp-switchBusy" : void 0),
|
|
163
|
+
role: "switch",
|
|
164
|
+
"aria-checked": status.safeModeOn,
|
|
165
|
+
title: status.safeModeOn ? "safe-mode ON(点击关闭)" : "safe-mode OFF(点击开启)",
|
|
166
|
+
onClick: (e) => {
|
|
167
|
+
e.stopPropagation();
|
|
168
|
+
if (busy) return;
|
|
169
|
+
toggle(!status.safeModeOn);
|
|
170
|
+
},
|
|
171
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: cx("sp-switchThumb") })
|
|
183
172
|
})
|
|
173
|
+
] }),
|
|
174
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {
|
|
175
|
+
size: 12,
|
|
176
|
+
className: cx("sp-chev")
|
|
184
177
|
})
|
|
185
|
-
]
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
178
|
+
]
|
|
179
|
+
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
180
|
+
className: cx("sp-pop"),
|
|
181
|
+
role: "dialog",
|
|
182
|
+
"aria-label": "CCC 状态栏",
|
|
183
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
184
|
+
className: cx("sp-popBody"),
|
|
185
|
+
children: inCcc ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
186
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
187
|
+
className: cx("sp-group"),
|
|
188
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
189
|
+
className: cx("sp-groupTitle"),
|
|
190
|
+
children: "运行环境"
|
|
191
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
192
|
+
className: cx("sp-rows"),
|
|
193
|
+
children: [
|
|
194
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
195
|
+
className: cx("sp-row"),
|
|
196
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
197
|
+
className: cx("sp-label"),
|
|
198
|
+
children: "CCC"
|
|
199
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
200
|
+
className: cx("sp-value"),
|
|
201
|
+
title: status.root ?? void 0,
|
|
202
|
+
children: status.root
|
|
203
|
+
})]
|
|
204
|
+
}),
|
|
205
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
206
|
+
className: cx("sp-row"),
|
|
207
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
208
|
+
className: cx("sp-label"),
|
|
209
|
+
children: "Handyman 模型"
|
|
210
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
211
|
+
className: cx("sp-value"),
|
|
212
|
+
title: status.handymanModel ? `handyman.models: ${status.handymanModel}` : void 0,
|
|
213
|
+
children: status.handymanModel ?? "未配置"
|
|
214
|
+
})]
|
|
215
|
+
}),
|
|
216
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
217
|
+
className: cx("sp-row"),
|
|
218
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
219
|
+
className: cx("sp-label"),
|
|
220
|
+
children: "守卫"
|
|
221
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
222
|
+
className: cx("sp-value"),
|
|
223
|
+
children: [
|
|
224
|
+
"blacklist ",
|
|
225
|
+
status.blacklist.length,
|
|
226
|
+
" 条",
|
|
227
|
+
status.threshold !== null ? ` · keeper 阈值 ${status.threshold}` : ""
|
|
228
|
+
]
|
|
229
|
+
})]
|
|
230
|
+
})
|
|
231
|
+
]
|
|
232
|
+
})]
|
|
233
|
+
}),
|
|
234
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
235
|
+
className: cx("sp-group"),
|
|
236
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
237
|
+
className: cx("sp-groupTitle"),
|
|
238
|
+
children: "安全模式"
|
|
239
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
240
|
+
className: cx("sp-actions"),
|
|
241
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
242
|
+
type: "button",
|
|
243
|
+
className: cx("sp-toggle", status.safeModeOn ? "sp-toggleOn" : "sp-toggleOff", "sp-toggleLg"),
|
|
244
|
+
disabled: busy,
|
|
245
|
+
onClick: () => void toggle(!status.safeModeOn),
|
|
246
|
+
children: [
|
|
247
|
+
status.safeModeOn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
|
|
248
|
+
size: 14,
|
|
249
|
+
className: cx("sp-toggleIcon")
|
|
250
|
+
}),
|
|
251
|
+
"safe-mode ",
|
|
252
|
+
status.safeModeOn ? "ON" : "OFF"
|
|
253
|
+
]
|
|
254
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
255
|
+
className: cx("sp-toggleDesc"),
|
|
256
|
+
children: status.safeModeOn ? "写工具已隐藏,无人值守安全航行" : "写工具可用(点击开启)"
|
|
257
|
+
})]
|
|
258
|
+
})]
|
|
259
|
+
}),
|
|
260
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
261
|
+
className: cx("sp-group"),
|
|
207
262
|
children: [
|
|
208
263
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
209
|
-
className: cx("sp-
|
|
210
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
211
|
-
className: cx("sp-
|
|
212
|
-
children: "
|
|
213
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
264
|
+
className: cx("sp-groupHead"),
|
|
265
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
266
|
+
className: cx("sp-groupTitle"),
|
|
267
|
+
children: "Handyman 运行"
|
|
268
|
+
}), handymen.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
269
|
+
type: "button",
|
|
270
|
+
className: cx("sp-handymenToggle"),
|
|
271
|
+
onClick: () => setHandymenOpen((v) => !v),
|
|
272
|
+
children: handymenOpen ? "收起" : "详情"
|
|
217
273
|
})]
|
|
218
274
|
}),
|
|
219
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
220
|
-
className: cx("sp-
|
|
221
|
-
children:
|
|
222
|
-
className: cx("sp-
|
|
223
|
-
children: "
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
275
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
276
|
+
className: cx("sp-rows"),
|
|
277
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
278
|
+
className: cx("sp-row"),
|
|
279
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
280
|
+
className: cx("sp-label"),
|
|
281
|
+
children: "状态"
|
|
282
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
283
|
+
className: cx("sp-value"),
|
|
284
|
+
children: runningHandymen.length > 0 ? `运行中 ${runningHandymen.length}${handymen.length > 0 ? ` / 共 ${handymen.length}` : ""}` : "无运行中 handyman"
|
|
285
|
+
})]
|
|
286
|
+
})
|
|
229
287
|
}),
|
|
230
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
231
|
-
className: cx("sp-
|
|
232
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
233
|
-
className: cx("sp-
|
|
234
|
-
children: "
|
|
235
|
-
|
|
236
|
-
|
|
288
|
+
handymenOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
289
|
+
className: cx("sp-handymenList"),
|
|
290
|
+
children: [handymen.slice(0, 3).map((l) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
291
|
+
className: cx("sp-handymanItem"),
|
|
292
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
293
|
+
className: cx("sp-handymanHead"),
|
|
294
|
+
children: [
|
|
295
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
296
|
+
className: cx("sp-handymanLabel"),
|
|
297
|
+
title: l.label,
|
|
298
|
+
children: l.label
|
|
299
|
+
}),
|
|
300
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
301
|
+
className: cx("sp-handymanRound"),
|
|
302
|
+
children: l.done ? "✓ 完成" : `R${l.round}`
|
|
303
|
+
}),
|
|
304
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
305
|
+
className: cx("sp-handymanTime"),
|
|
306
|
+
children: new Date(l.updated).toLocaleTimeString()
|
|
307
|
+
})
|
|
308
|
+
]
|
|
309
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
310
|
+
className: cx("sp-handymanResp"),
|
|
311
|
+
title: l.lastResponse,
|
|
312
|
+
children: l.lastResponse.slice(0, 80) || (l.done ? "已完成" : "(等待首轮响应)")
|
|
313
|
+
})]
|
|
314
|
+
}, l.label)), handymen.length > 3 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
315
|
+
className: cx("sp-handymenMore"),
|
|
237
316
|
children: [
|
|
238
|
-
"
|
|
239
|
-
|
|
240
|
-
"
|
|
241
|
-
status.threshold !== null ? ` · keeper 阈值 ${status.threshold}` : ""
|
|
317
|
+
"… 共 ",
|
|
318
|
+
handymen.length,
|
|
319
|
+
" 个 handyman(其余见等待界面)"
|
|
242
320
|
]
|
|
243
321
|
})]
|
|
244
322
|
})
|
|
245
323
|
]
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
|
|
324
|
+
}),
|
|
325
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
326
|
+
className: cx("sp-hint"),
|
|
327
|
+
children: [
|
|
328
|
+
"💡 开关/阈值/账号/工作区白名单请在 ",
|
|
329
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "设置 → Serenity" }),
|
|
330
|
+
" 中配置。"
|
|
331
|
+
]
|
|
332
|
+
})
|
|
333
|
+
] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
249
334
|
className: cx("sp-group"),
|
|
250
335
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
251
336
|
className: cx("sp-groupTitle"),
|
|
252
|
-
children: "
|
|
253
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
254
|
-
className: cx("sp-
|
|
255
|
-
children:
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
className: cx("sp-toggleIcon")
|
|
264
|
-
}),
|
|
265
|
-
"safe-mode ",
|
|
266
|
-
status.safeModeOn ? "ON" : "OFF"
|
|
267
|
-
]
|
|
268
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
269
|
-
className: cx("sp-toggleDesc"),
|
|
270
|
-
children: status.safeModeOn ? "写工具已隐藏,无人值守安全航行" : "写工具可用(点击开启)"
|
|
271
|
-
})]
|
|
272
|
-
})]
|
|
273
|
-
}),
|
|
274
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
275
|
-
className: cx("sp-group"),
|
|
276
|
-
children: [
|
|
277
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
278
|
-
className: cx("sp-groupHead"),
|
|
279
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
280
|
-
className: cx("sp-groupTitle"),
|
|
281
|
-
children: "Handyman 运行"
|
|
282
|
-
}), handymen.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
283
|
-
type: "button",
|
|
284
|
-
className: cx("sp-handymenToggle"),
|
|
285
|
-
onClick: () => setHandymenOpen((v) => !v),
|
|
286
|
-
children: handymenOpen ? "收起" : "详情"
|
|
287
|
-
})]
|
|
288
|
-
}),
|
|
289
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
290
|
-
className: cx("sp-rows"),
|
|
291
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
292
|
-
className: cx("sp-row"),
|
|
293
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
294
|
-
className: cx("sp-label"),
|
|
295
|
-
children: "状态"
|
|
296
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
297
|
-
className: cx("sp-value"),
|
|
298
|
-
children: runningHandymen.length > 0 ? `运行中 ${runningHandymen.length}${handymen.length > 0 ? ` / 共 ${handymen.length}` : ""}` : "无运行中 handyman"
|
|
299
|
-
})]
|
|
300
|
-
})
|
|
301
|
-
}),
|
|
302
|
-
handymenOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
303
|
-
className: cx("sp-handymenList"),
|
|
304
|
-
children: [handymen.slice(0, 3).map((l) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
305
|
-
className: cx("sp-handymanItem"),
|
|
306
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
307
|
-
className: cx("sp-handymanHead"),
|
|
308
|
-
children: [
|
|
309
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
310
|
-
className: cx("sp-handymanLabel"),
|
|
311
|
-
title: l.label,
|
|
312
|
-
children: l.label
|
|
313
|
-
}),
|
|
314
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
315
|
-
className: cx("sp-handymanRound"),
|
|
316
|
-
children: l.done ? "✓ 完成" : `R${l.round}`
|
|
317
|
-
}),
|
|
318
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
319
|
-
className: cx("sp-handymanTime"),
|
|
320
|
-
children: new Date(l.updated).toLocaleTimeString()
|
|
321
|
-
})
|
|
322
|
-
]
|
|
323
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
324
|
-
className: cx("sp-handymanResp"),
|
|
325
|
-
title: l.lastResponse,
|
|
326
|
-
children: l.lastResponse.slice(0, 80) || (l.done ? "已完成" : "(等待首轮响应)")
|
|
327
|
-
})]
|
|
328
|
-
}, l.label)), handymen.length > 3 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
329
|
-
className: cx("sp-handymenMore"),
|
|
330
|
-
children: [
|
|
331
|
-
"… 共 ",
|
|
332
|
-
handymen.length,
|
|
333
|
-
" 个 handyman(其余见等待界面)"
|
|
334
|
-
]
|
|
337
|
+
children: "状态"
|
|
338
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
339
|
+
className: cx("sp-rows"),
|
|
340
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
341
|
+
className: cx("sp-row"),
|
|
342
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
343
|
+
className: cx("sp-label"),
|
|
344
|
+
children: "ACC"
|
|
345
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
346
|
+
className: cx("sp-value"),
|
|
347
|
+
children: "未激活(工作区不在 CCC 内)"
|
|
335
348
|
})]
|
|
336
349
|
})
|
|
337
|
-
]
|
|
338
|
-
}),
|
|
339
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
340
|
-
className: cx("sp-hint"),
|
|
341
|
-
children: [
|
|
342
|
-
"💡 开关/阈值/账号/工作区白名单请在 ",
|
|
343
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "设置 → Serenity" }),
|
|
344
|
-
" 中配置。"
|
|
345
|
-
]
|
|
350
|
+
})]
|
|
346
351
|
})
|
|
347
|
-
] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
348
|
-
className: cx("sp-group"),
|
|
349
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
350
|
-
className: cx("sp-groupTitle"),
|
|
351
|
-
children: "状态"
|
|
352
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
353
|
-
className: cx("sp-rows"),
|
|
354
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
355
|
-
className: cx("sp-row"),
|
|
356
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
357
|
-
className: cx("sp-label"),
|
|
358
|
-
children: "ACC"
|
|
359
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
360
|
-
className: cx("sp-value"),
|
|
361
|
-
children: "未激活(工作区不在 CCC 内)"
|
|
362
|
-
})]
|
|
363
|
-
})
|
|
364
|
-
})]
|
|
365
352
|
})
|
|
366
|
-
})
|
|
367
|
-
})
|
|
353
|
+
})]
|
|
354
|
+
});
|
|
368
355
|
}
|
|
369
356
|
//#endregion
|
|
370
357
|
//#region src/client/image-fallback-api.ts
|
package/lib/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
|
6
6
|
import { homedir, platform } from "node:os";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { promisify } from "node:util";
|
|
9
|
+
import { platform as platform$1 } from "node:process";
|
|
9
10
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
10
11
|
import { createHmac, randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
|
|
11
12
|
import { deriveEventMessage } from "@deepseek-ai/dsh-session";
|
|
@@ -136,12 +137,15 @@ function readBlacklist(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
|
|
|
136
137
|
}
|
|
137
138
|
return out;
|
|
138
139
|
}
|
|
139
|
-
/** 匹配黑名单规则;命中返回条目,未命中返回 null。前缀匹配 / regex: 前缀(对齐 osp
|
|
140
|
+
/** 匹配黑名单规则;命中返回条目,未命中返回 null。前缀匹配 / regex: 前缀(对齐 osp)。Windows:规则反斜杠归一化为正斜杠与 rel 对齐 */
|
|
140
141
|
function matchBlacklist(relPath, rules) {
|
|
141
142
|
for (const rule of rules) if (rule.pattern.startsWith("regex:")) try {
|
|
142
143
|
if (new RegExp(rule.pattern.slice(6)).test(relPath)) return rule;
|
|
143
144
|
} catch {}
|
|
144
|
-
else
|
|
145
|
+
else {
|
|
146
|
+
const pat = rule.pattern.split("\\").join("/");
|
|
147
|
+
if (relPath.startsWith(pat)) return rule;
|
|
148
|
+
}
|
|
145
149
|
return null;
|
|
146
150
|
}
|
|
147
151
|
//#endregion
|
|
@@ -212,7 +216,8 @@ function validateWritePath(root, target) {
|
|
|
212
216
|
const absPath = target.startsWith("/") ? resolve(target) : resolveInside(root, target);
|
|
213
217
|
if (!pathInside(resolve(root), absPath)) throw new Error(`cc-fs: path "${target}" resolves to "${absPath}" which is outside serenity root "${root}"`);
|
|
214
218
|
const rel = relative(root, absPath).split("\\").join("/");
|
|
215
|
-
|
|
219
|
+
const relCi = process.platform === "win32" ? rel.toLowerCase() : rel;
|
|
220
|
+
if (relCi.endsWith("/mech-registry.json") && /(^|\/)(opencode|\.opencode)\/skills\//.test(relCi)) throw new Error(`cc-fs: refusing to directly modify mech-registry.json — use acc_msm register/deregister instead`);
|
|
216
221
|
if (existsSync(absPath)) try {
|
|
217
222
|
const real = realpathSync(absPath);
|
|
218
223
|
if (!pathInside(resolve(root), real)) throw new Error(`cc-fs: path "${target}" resolves via symlink to "${real}" outside serenity root "${root}"`);
|
|
@@ -356,6 +361,8 @@ function runCcFs(root, args) {
|
|
|
356
361
|
if (!args.src || !args.dst) throw new Error("mv requires src + dst");
|
|
357
362
|
const srcAbs = validateWritePath(root, args.src);
|
|
358
363
|
const dstAbs = validateWritePath(root, args.dst);
|
|
364
|
+
assertNotProtected(root, srcAbs, `mv src ${args.src}`);
|
|
365
|
+
assertNotProtected(root, dstAbs, `mv dst ${args.dst}`);
|
|
359
366
|
if (!existsSync(srcAbs)) throw new Error(`mv: source not found: ${args.src}`);
|
|
360
367
|
if (existsSync(dstAbs)) throw new Error(`mv: destination already exists: ${args.dst}`);
|
|
361
368
|
const parentDir = dirname(dstAbs);
|
|
@@ -367,6 +374,8 @@ function runCcFs(root, args) {
|
|
|
367
374
|
if (!args.src || !args.dst) throw new Error("cp requires src + dst");
|
|
368
375
|
const srcAbs = validateWritePath(root, args.src);
|
|
369
376
|
const dstAbs = validateWritePath(root, args.dst);
|
|
377
|
+
assertNotProtected(root, srcAbs, `cp src ${args.src}`);
|
|
378
|
+
assertNotProtected(root, dstAbs, `cp dst ${args.dst}`);
|
|
370
379
|
if (!existsSync(srcAbs)) throw new Error(`cp: source not found: ${args.src}`);
|
|
371
380
|
if (existsSync(dstAbs)) throw new Error(`cp: destination already exists: ${args.dst}`);
|
|
372
381
|
if (statSync(srcAbs).isDirectory() && !(args.recursive ?? false)) throw new Error(`cp: source is a directory, use recursive to copy: ${args.src}`);
|
|
@@ -408,8 +417,8 @@ function runCcFs(root, args) {
|
|
|
408
417
|
const revealPath = statSync(absPath).isDirectory() ? absPath : dirname(absPath);
|
|
409
418
|
execFileSync("xdg-open", [revealPath], { timeout: 1e4 });
|
|
410
419
|
} else if (os === "win32") {
|
|
411
|
-
const
|
|
412
|
-
const child = spawn("explorer",
|
|
420
|
+
const winPath = statSync(absPath).isDirectory() ? absPath : /[\s,]/.test(absPath) ? dirname(absPath) : `/select,${absPath}`;
|
|
421
|
+
const child = spawn("explorer", [winPath], {
|
|
413
422
|
detached: true,
|
|
414
423
|
stdio: "ignore",
|
|
415
424
|
windowsHide: false
|
|
@@ -453,7 +462,7 @@ function runCcFs(root, args) {
|
|
|
453
462
|
if (hasGlobChars) {
|
|
454
463
|
const regexStr = "^" + pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".") + "$";
|
|
455
464
|
try {
|
|
456
|
-
return new RegExp(regexStr).test(name);
|
|
465
|
+
return new RegExp(regexStr, platform() === "win32" ? "i" : void 0).test(name);
|
|
457
466
|
} catch {
|
|
458
467
|
return name.includes(pattern);
|
|
459
468
|
}
|
|
@@ -1496,6 +1505,27 @@ const gitTool = defineTool({
|
|
|
1496
1505
|
*/
|
|
1497
1506
|
const execFileAsync = promisify(execFile);
|
|
1498
1507
|
const MSM_TIMEOUT_MS = 6e5;
|
|
1508
|
+
const BUN_EXE_CANDIDATES = [
|
|
1509
|
+
() => join(process.env["USERPROFILE"] ?? process.env["HOME"] ?? "", ".bun", "bin", "bun.exe"),
|
|
1510
|
+
() => join(process.env["APPDATA"] ?? "", "npm", "node_modules", "bun", "bin", "bun.exe"),
|
|
1511
|
+
() => join(process.env["LOCALAPPDATA"] ?? "", "npm", "node_modules", "bun", "bin", "bun.exe")
|
|
1512
|
+
];
|
|
1513
|
+
let bunExeCache;
|
|
1514
|
+
function bunExecutablePath() {
|
|
1515
|
+
if (bunExeCache !== void 0) return bunExeCache;
|
|
1516
|
+
if (platform$1 !== "win32") {
|
|
1517
|
+
bunExeCache = "bun";
|
|
1518
|
+
return bunExeCache;
|
|
1519
|
+
}
|
|
1520
|
+
for (const cand of BUN_EXE_CANDIDATES) try {
|
|
1521
|
+
if (existsSync(cand())) {
|
|
1522
|
+
bunExeCache = cand();
|
|
1523
|
+
return bunExeCache;
|
|
1524
|
+
}
|
|
1525
|
+
} catch {}
|
|
1526
|
+
bunExeCache = null;
|
|
1527
|
+
return null;
|
|
1528
|
+
}
|
|
1499
1529
|
/** CCC 名:从 .serenity 首行解析(对齐 osp readSerenityCccName) */
|
|
1500
1530
|
function readCccName(root) {
|
|
1501
1531
|
try {
|
|
@@ -1709,7 +1739,7 @@ function runMsm(root, args) {
|
|
|
1709
1739
|
const { entry, businessArgs, fmtJson, hasHelp, protocol } = prepareExec(root, args);
|
|
1710
1740
|
const p = protocolResult(protocol);
|
|
1711
1741
|
if (p !== void 0) return p;
|
|
1712
|
-
|
|
1742
|
+
const baseOpts = {
|
|
1713
1743
|
cwd: root,
|
|
1714
1744
|
encoding: "utf-8",
|
|
1715
1745
|
timeout: MSM_TIMEOUT_MS,
|
|
@@ -1719,21 +1749,23 @@ function runMsm(root, args) {
|
|
|
1719
1749
|
"pipe"
|
|
1720
1750
|
],
|
|
1721
1751
|
env: buildMsmEnv(root)
|
|
1722
|
-
}
|
|
1752
|
+
};
|
|
1753
|
+
const bunBin = bunExecutablePath();
|
|
1754
|
+
let r = bunBin ? spawnSync(bunBin, [entry.path, ...businessArgs], baseOpts) : {
|
|
1755
|
+
error: { code: "ENOENT" },
|
|
1756
|
+
status: null,
|
|
1757
|
+
stdout: "",
|
|
1758
|
+
stderr: "",
|
|
1759
|
+
pid: 0,
|
|
1760
|
+
signal: null
|
|
1761
|
+
};
|
|
1723
1762
|
if (r.error && isBunMissing(r.error)) r = spawnSync(NPX_BIN, [
|
|
1724
1763
|
"tsx",
|
|
1725
1764
|
entry.path,
|
|
1726
1765
|
...businessArgs
|
|
1727
1766
|
], {
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
timeout: MSM_TIMEOUT_MS,
|
|
1731
|
-
stdio: [
|
|
1732
|
-
"pipe",
|
|
1733
|
-
"pipe",
|
|
1734
|
-
"pipe"
|
|
1735
|
-
],
|
|
1736
|
-
env: buildMsmEnv(root)
|
|
1767
|
+
...baseOpts,
|
|
1768
|
+
shell: platform$1 === "win32"
|
|
1737
1769
|
});
|
|
1738
1770
|
return msmExecResult(entry.name, r.status ?? 2, r.stdout ?? "", r.stderr ?? "", fmtJson, hasHelp);
|
|
1739
1771
|
}
|
|
@@ -1747,7 +1779,8 @@ function runMsm(root, args) {
|
|
|
1747
1779
|
if (!existsSync(scriptAbs)) throw new Error(`MSM script not found: "${path}"`);
|
|
1748
1780
|
if (loadMsmEntries(root).some((e) => e.name === name)) throw new Error(`MSM already registered: "${name}"`);
|
|
1749
1781
|
const regPath = registryPathFor(root, skill);
|
|
1750
|
-
const
|
|
1782
|
+
const raw = existsSync(regPath) ? readFileSync(regPath, "utf-8").replace(/^\uFEFF/, "") : "";
|
|
1783
|
+
const isV1Wrapped = raw !== "" && !Array.isArray(JSON.parse(raw));
|
|
1751
1784
|
const entries = existsSync(regPath) ? parseRegistry(readFileSync(regPath, "utf-8")) : [];
|
|
1752
1785
|
let flags;
|
|
1753
1786
|
if (args.flags) try {
|
|
@@ -1794,7 +1827,8 @@ function runMsm(root, args) {
|
|
|
1794
1827
|
case "deregister": {
|
|
1795
1828
|
const name = args.name ?? "";
|
|
1796
1829
|
for (const regPath of findRegistries(root)) {
|
|
1797
|
-
const
|
|
1830
|
+
const dRaw = readFileSync(regPath, "utf-8").replace(/^\uFEFF/, "");
|
|
1831
|
+
const isV1Wrapped = !Array.isArray(JSON.parse(dRaw));
|
|
1798
1832
|
const entries = parseRegistry(readFileSync(regPath, "utf-8"));
|
|
1799
1833
|
const idx = entries.findIndex((e) => e.name === name);
|
|
1800
1834
|
if (idx >= 0) {
|
|
@@ -1828,8 +1862,9 @@ function runMsm(root, args) {
|
|
|
1828
1862
|
case "check": {
|
|
1829
1863
|
const entries = loadMsmEntries(root);
|
|
1830
1864
|
const issues = [];
|
|
1831
|
-
const
|
|
1832
|
-
|
|
1865
|
+
const norm = (p) => p.split("\\").join("/").toLowerCase();
|
|
1866
|
+
const registeredPaths = new Set(entries.map((e) => norm(e.path)));
|
|
1867
|
+
for (const scriptPath of scanSkillScripts(root)) if (!registeredPaths.has(norm(scriptPath))) issues.push({
|
|
1833
1868
|
name: scriptPath,
|
|
1834
1869
|
check: "M3",
|
|
1835
1870
|
detail: "script not registered in mech-registry"
|
|
@@ -1980,15 +2015,20 @@ async function runMsmAsync(root, args) {
|
|
|
1980
2015
|
const { entry, businessArgs, fmtJson, hasHelp, protocol } = prepareExec(root, args);
|
|
1981
2016
|
const p = protocolResult(protocol);
|
|
1982
2017
|
if (p !== void 0) return p;
|
|
2018
|
+
const bunBin = bunExecutablePath();
|
|
2019
|
+
const baseOpts = {
|
|
2020
|
+
cwd: root,
|
|
2021
|
+
encoding: "utf-8",
|
|
2022
|
+
timeout: MSM_TIMEOUT_MS,
|
|
2023
|
+
maxBuffer: 67108864,
|
|
2024
|
+
env: buildMsmEnv(root)
|
|
2025
|
+
};
|
|
1983
2026
|
try {
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
env: buildMsmEnv(root)
|
|
1990
|
-
});
|
|
1991
|
-
return msmExecResult(entry.name, 0, r.stdout, r.stderr, fmtJson, hasHelp);
|
|
2027
|
+
if (bunBin) {
|
|
2028
|
+
const r = await execFileAsync(bunBin, [entry.path, ...businessArgs], baseOpts);
|
|
2029
|
+
return msmExecResult(entry.name, 0, r.stdout, r.stderr, fmtJson, hasHelp);
|
|
2030
|
+
}
|
|
2031
|
+
throw { code: "ENOENT" };
|
|
1992
2032
|
} catch (e) {
|
|
1993
2033
|
const err = e;
|
|
1994
2034
|
if (isBunMissing(err)) try {
|
|
@@ -1997,11 +2037,8 @@ async function runMsmAsync(root, args) {
|
|
|
1997
2037
|
entry.path,
|
|
1998
2038
|
...businessArgs
|
|
1999
2039
|
], {
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
timeout: MSM_TIMEOUT_MS,
|
|
2003
|
-
maxBuffer: 67108864,
|
|
2004
|
-
env: buildMsmEnv(root)
|
|
2040
|
+
...baseOpts,
|
|
2041
|
+
shell: platform$1 === "win32"
|
|
2005
2042
|
});
|
|
2006
2043
|
return msmExecResult(entry.name, 0, r.stdout, r.stderr, fmtJson, hasHelp);
|
|
2007
2044
|
} catch (e2) {
|
|
@@ -2337,9 +2374,9 @@ function handymanPresetInheritance(parentCtx) {
|
|
|
2337
2374
|
* 进度文件(handyman-<label>.md/.json)、续跑、轮次 prompt 结构、stop token。
|
|
2338
2375
|
* 不兼容旧 loop- 进度文件(用户拍板:仅新 handyman- 前缀)。
|
|
2339
2376
|
*/
|
|
2340
|
-
/** label 脱敏(Windows 审计问题 17):非法字符 → '-'
|
|
2377
|
+
/** label 脱敏(Windows 审计问题 17):非法字符 → '-',去尾点/空格,限长(按码点截断,修复代理对切散 U+FFFD) */
|
|
2341
2378
|
function sanitizeLabel(label) {
|
|
2342
|
-
return label.replace(/[<>:"/\\|?*\u0000-\u001f]/g, "-").replace(/[ .]+$/g, "").slice(0, 50);
|
|
2379
|
+
return [...label.replace(/[<>:"/\\|?*\u0000-\u001f]/g, "-").replace(/[ .]+$/g, "")].slice(0, 50).join("");
|
|
2343
2380
|
}
|
|
2344
2381
|
function handymanProgressPaths(root, label) {
|
|
2345
2382
|
const dir = join(root, "AGENT_SESSIONS");
|
|
@@ -6521,8 +6558,15 @@ function scanSerenityDirs(skillsRoot) {
|
|
|
6521
6558
|
if (!existsSync(skillsRoot)) return [];
|
|
6522
6559
|
return readdirSync(skillsRoot).filter((name) => isSerenityEntry(name)).map((name) => join(skillsRoot, name)).filter((dir) => statSync(dir).isDirectory() && existsSync(join(dir, "SKILL.md"))).sort();
|
|
6523
6560
|
}
|
|
6561
|
+
/** 防穿越:skill 名必须是简单目录名(拒绝分隔符与 `..`/`.` 路径段) */
|
|
6562
|
+
function isSafeSkillName(name) {
|
|
6563
|
+
if (name.length === 0) return false;
|
|
6564
|
+
if (name.includes("/") || name.includes("\\")) return false;
|
|
6565
|
+
return !name.split(/[/\\]/).some((seg) => seg === ".." || seg === ".");
|
|
6566
|
+
}
|
|
6524
6567
|
/** 按名在两个 skills 根下定位 SKILL.md(.dsh/skills 优先,其次 .opencode/skills) */
|
|
6525
6568
|
function findSkillMd(root, name) {
|
|
6569
|
+
if (!isSafeSkillName(name)) return null;
|
|
6526
6570
|
for (const base of [".dsh", ".opencode"]) {
|
|
6527
6571
|
const p = resolve(root, base, "skills", name, "SKILL.md");
|
|
6528
6572
|
if (existsSync(p)) return p;
|
|
@@ -6887,8 +6931,8 @@ function readPersonaSettings() {
|
|
|
6887
6931
|
};
|
|
6888
6932
|
}
|
|
6889
6933
|
}
|
|
6890
|
-
/** 过滤掉对 agent 隐藏的内容(safe-mode 是用户能力,不对 agent
|
|
6891
|
-
const HIDDEN_LINES = /安全模式|safe-mode|\.serenity-safe-on
|
|
6934
|
+
/** 过滤掉对 agent 隐藏的内容(safe-mode 是用户能力,不对 agent 提及)。无 g flag:`.test()` 须逐行独立无状态,避免 lastIndex 游移漏滤(问题A) */
|
|
6935
|
+
const HIDDEN_LINES = /安全模式|safe-mode|\.serenity-safe-on/;
|
|
6892
6936
|
function sanitizeSkillContent(content) {
|
|
6893
6937
|
return content.split("\n").filter((line) => !HIDDEN_LINES.test(line)).join("\n");
|
|
6894
6938
|
}
|
|
@@ -7876,9 +7920,15 @@ function listOpencodeSkillDirs(root) {
|
|
|
7876
7920
|
if (!existsSync(dir)) return [];
|
|
7877
7921
|
return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join(dir, e.name)).filter((d) => existsSync(join(d, "SKILL.md")));
|
|
7878
7922
|
}
|
|
7879
|
-
/** 加载一个 opencode skill(frontmatter 解析 +
|
|
7923
|
+
/** 加载一个 opencode skill(frontmatter 解析 + 正文);读失败(EPERM/占用等)返回 null,单目录容错 */
|
|
7880
7924
|
function loadOpencodeSkill(skillDir) {
|
|
7881
|
-
|
|
7925
|
+
let raw;
|
|
7926
|
+
try {
|
|
7927
|
+
raw = readFileSync(join(skillDir, "SKILL.md"), "utf-8");
|
|
7928
|
+
} catch {
|
|
7929
|
+
return null;
|
|
7930
|
+
}
|
|
7931
|
+
const { meta, content } = parseFrontmatter(raw);
|
|
7882
7932
|
return {
|
|
7883
7933
|
name: meta.name,
|
|
7884
7934
|
dir: skillDir,
|
|
@@ -7902,7 +7952,7 @@ function registerOpencodeSkills(ctx) {
|
|
|
7902
7952
|
const candidates = [];
|
|
7903
7953
|
for (const dir of listOpencodeSkillDirs(root)) {
|
|
7904
7954
|
const skill = loadOpencodeSkill(dir);
|
|
7905
|
-
if (!skill.meta.name) continue;
|
|
7955
|
+
if (!skill || !skill.meta.name) continue;
|
|
7906
7956
|
candidates.push({
|
|
7907
7957
|
name: skill.meta.name,
|
|
7908
7958
|
description: skill.meta.description || "(opencode skill)",
|
|
@@ -7930,6 +7980,7 @@ function registerOpencodeSkills(ctx) {
|
|
|
7930
7980
|
async get(candidate) {
|
|
7931
7981
|
const dir = candidate.locator;
|
|
7932
7982
|
const skill = loadOpencodeSkill(dir);
|
|
7983
|
+
if (!skill) return void 0;
|
|
7933
7984
|
return {
|
|
7934
7985
|
name: skill.meta.name,
|
|
7935
7986
|
description: skill.meta.description || candidate.description,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.10",
|
|
4
4
|
"description": "宁静号 ACC harness — Native Cordis 插件(DeepSeek Harness 运行时)。真实 DSH 工具注册(cc_fs/session/acc_msm 等 9 工具)+ 拦截缝机械约束(safe-mode/路径守卫/会话落盘)+ 系统提示词注入(ACC/CCE/Constraints/SKILL/Session 五块)。适配 DSH 公开版(deepseek-ai/deepseek-harness 0.1.0-rc)。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|