@shgroup/dsh-serenity-hooks 1.24.9 → 1.24.11
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 +205 -72
- 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.11",
|
|
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");
|
|
@@ -5190,26 +5227,38 @@ function closeSession(root, key, confirm, scope = DEFAULT_SESSION_SCOPE) {
|
|
|
5190
5227
|
return `Session "${session.dirName}" closed and marked as completed.`;
|
|
5191
5228
|
}
|
|
5192
5229
|
/**
|
|
5193
|
-
* 从会话历史(events
|
|
5194
|
-
*
|
|
5230
|
+
* 从会话历史(events)解析会话身份(进程重启恢复;只扫**当前会话**自己的历史——无跨会话串台)。
|
|
5231
|
+
*
|
|
5232
|
+
* v1.24.11 稳固化(S142 用户需求:重建后新会话必须准确知道从哪个 SESSION 恢复):
|
|
5233
|
+
* **路径规范行即可,不再要求 [SESSION CONTEXT] 标记**——`session use` 上下文与重建锚点
|
|
5234
|
+
* (buildRebuildAnchor 的 `- Persistent trajectory — SESSION.md path: <rel>` 行)同格式,
|
|
5235
|
+
* 因此**仅靠重建锚点的会话(从未显式 use)同样可恢复**。从**尾到头**扫描,最后一条
|
|
5236
|
+
* 合法路径胜出(时间序最新);会话目录名/ID 从路径本身派生(单一真相源,不猜)。
|
|
5237
|
+
* 路径可为相对(重建锚点存 rel)——绝对化与存在性校验在调用方(知道 root)执行。
|
|
5195
5238
|
*/
|
|
5196
5239
|
function parseSessionContextFromEvents(events) {
|
|
5197
5240
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
5198
5241
|
const strs = [];
|
|
5199
5242
|
collectStrings(events[i], strs);
|
|
5200
5243
|
for (const s of strs) {
|
|
5201
|
-
const
|
|
5202
|
-
if (
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
if (
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5244
|
+
const md = extractSessionMdPathFromText(s);
|
|
5245
|
+
if (!md) continue;
|
|
5246
|
+
let dirName;
|
|
5247
|
+
let filePath;
|
|
5248
|
+
if (basename(md) === SESSION_MD) {
|
|
5249
|
+
dirName = basename(dirname(md));
|
|
5250
|
+
filePath = md;
|
|
5251
|
+
} else if (isSessionDirName(basename(md))) {
|
|
5252
|
+
dirName = basename(md);
|
|
5253
|
+
filePath = join(md, SESSION_MD);
|
|
5254
|
+
} else continue;
|
|
5255
|
+
if (!isSessionDirName(dirName)) continue;
|
|
5256
|
+
const idMatch = dirName.match(/--S(\d{3,})--/);
|
|
5257
|
+
return {
|
|
5258
|
+
sessionId: idMatch ? `S${idMatch[1]}` : dirName,
|
|
5259
|
+
dirName,
|
|
5260
|
+
mdPath: filePath
|
|
5261
|
+
};
|
|
5213
5262
|
}
|
|
5214
5263
|
}
|
|
5215
5264
|
return null;
|
|
@@ -5222,6 +5271,35 @@ function collectStrings(v, out) {
|
|
|
5222
5271
|
}
|
|
5223
5272
|
if (v && typeof v === "object") for (const val of Object.values(v)) collectStrings(val, out);
|
|
5224
5273
|
}
|
|
5274
|
+
/** 规范行正则:`SESSION.md path: <路径>`(路径可含空格 → [^\r\n]+ 整行匹配) */
|
|
5275
|
+
const SESSION_MD_PATH_RE = /SESSION\.md path:\s*([^\r\n]+)/;
|
|
5276
|
+
/** 从文本提取 SESSION.md 路径(use 上下文 / 重建锚点规范行通用;无匹配返回 null)。
|
|
5277
|
+
* 同行已知尾注(如系统提示词 Session 块的 persistent-body 注释)剥除——规范行只取路径本体。 */
|
|
5278
|
+
function extractSessionMdPathFromText(text) {
|
|
5279
|
+
const m = text.match(SESSION_MD_PATH_RE);
|
|
5280
|
+
if (!m) return null;
|
|
5281
|
+
let p = m[1].trim();
|
|
5282
|
+
const suffix = p.search(/ \(the trajectory's persistent body/);
|
|
5283
|
+
if (suffix > 0) p = p.slice(0, suffix).trim();
|
|
5284
|
+
return p;
|
|
5285
|
+
}
|
|
5286
|
+
/** 会话目录名形态校验(createSession 恒带日期前缀 `YYYY-MM-DD--`) */
|
|
5287
|
+
function isSessionDirName(dirName) {
|
|
5288
|
+
return /^\d{4}-\d{2}-\d{2}--/.test(dirName) && dirName.length > 11;
|
|
5289
|
+
}
|
|
5290
|
+
/**
|
|
5291
|
+
* 约定回退(v1.24.11):AGENT_SESSIONS 下最新修改的**未完成**会话的 SESSION.md。
|
|
5292
|
+
* readAllSessions 已按「未完成优先 + mtime 降序」排序 → 首个未完成且含 SESSION.md 即最新活动。
|
|
5293
|
+
* 只作最后手段(内存/events/锚点全缺时),保证重建锚点至少指向一个真实存在的轨迹。
|
|
5294
|
+
*/
|
|
5295
|
+
function findLatestActiveSessionMd(root) {
|
|
5296
|
+
for (const s of readAllSessions(sessionsRoot(root))) {
|
|
5297
|
+
if (s.status.completed) continue;
|
|
5298
|
+
const md = join(s.path, SESSION_MD);
|
|
5299
|
+
if (existsSync(md)) return md;
|
|
5300
|
+
}
|
|
5301
|
+
return null;
|
|
5302
|
+
}
|
|
5225
5303
|
/** health 子命令(对齐 osp healthCheck:stale/stalled/ghost/drift 四类检查,文本输出) */
|
|
5226
5304
|
function healthCheck(root) {
|
|
5227
5305
|
const sessions = readAllSessions(sessionsRoot(root));
|
|
@@ -6091,10 +6169,53 @@ function buildRebuildAnchor(root, sessionName, activeMdPath, anchorMessages = DE
|
|
|
6091
6169
|
...anchorMessages.flatMap((text) => [stripAckSuffix(text), ""]),
|
|
6092
6170
|
sessionName !== "" ? `Continue the work of ${sessionName}.` : "Continue the current work.",
|
|
6093
6171
|
...sessionLine ? [sessionLine] : [],
|
|
6094
|
-
`- Persistent trajectory
|
|
6172
|
+
`- Persistent trajectory — SESSION.md path: ${rel}`,
|
|
6173
|
+
` (the trajectory's persistent body — stays in place through rebuilds)`,
|
|
6095
6174
|
`- Read that SESSION.md first (goal/decisions/progress/unresolved), then continue from the last checkpoint.`
|
|
6096
6175
|
].join("\n");
|
|
6097
6176
|
}
|
|
6177
|
+
/** 从会话 surface 首条 user 消息(重建锚点)解析 SESSION.md 路径(events 异常/缺失时的兜底) */
|
|
6178
|
+
function parseAnchorMdPath(session) {
|
|
6179
|
+
try {
|
|
6180
|
+
const nodes = [...session.surface.nodes];
|
|
6181
|
+
if (nodes.length === 0) return null;
|
|
6182
|
+
const events = session.events;
|
|
6183
|
+
if (!Array.isArray(events)) return null;
|
|
6184
|
+
const event = events[nodes[0]];
|
|
6185
|
+
if (!event) return null;
|
|
6186
|
+
const message = deriveEventMessage(event);
|
|
6187
|
+
if (!message) return null;
|
|
6188
|
+
return extractSessionMdPathFromText(message.content?.map((c) => c.type === "text" ? c.text ?? "" : "").join("") ?? "");
|
|
6189
|
+
} catch {
|
|
6190
|
+
return null;
|
|
6191
|
+
}
|
|
6192
|
+
}
|
|
6193
|
+
/** 从 SESSION.md 绝对路径派生会话名(S###;issue 目录回退完整目录名) */
|
|
6194
|
+
function sessionNameFromMdPath(mdPath) {
|
|
6195
|
+
const dirName = basename(dirname(mdPath));
|
|
6196
|
+
const idMatch = dirName.match(/--S(\d{3,})--/);
|
|
6197
|
+
return idMatch ? `S${idMatch[1]}` : dirName;
|
|
6198
|
+
}
|
|
6199
|
+
/**
|
|
6200
|
+
* 稳固的 SESSION.md 定位(多层候选 + 存在性校验,**绝不输出虚假路径**):
|
|
6201
|
+
* ① 内存活跃会话(本会话显式 use 过)→ ② events 恢复(use 标记 + 重建锚点规范行,进程重启后)
|
|
6202
|
+
* → ③ surface 首条锚点(events 异常时兜底)→ ④ AGENT_SESSIONS 约定回退(最新未完成活动目录)。
|
|
6203
|
+
* 每候选 resolve 后 existsSync 校验(相对路径按 root 解析);全部失败返回 null → 调用方报错引导。
|
|
6204
|
+
*/
|
|
6205
|
+
function resolveSessionMdPath(root, scope, session) {
|
|
6206
|
+
const candidates = [];
|
|
6207
|
+
candidates.push(getActiveSessionInfo(scope)?.mdPath ?? null);
|
|
6208
|
+
const events = session.events;
|
|
6209
|
+
if (Array.isArray(events)) candidates.push(parseSessionContextFromEvents(events)?.mdPath ?? null);
|
|
6210
|
+
candidates.push(parseAnchorMdPath(session));
|
|
6211
|
+
candidates.push(findLatestActiveSessionMd(root));
|
|
6212
|
+
for (const c of candidates) {
|
|
6213
|
+
if (!c) continue;
|
|
6214
|
+
const abs = c.startsWith(root) ? c : resolve(root, c);
|
|
6215
|
+
if (existsSync(abs)) return abs;
|
|
6216
|
+
}
|
|
6217
|
+
return null;
|
|
6218
|
+
}
|
|
6098
6219
|
const pendingRebuilds = /* @__PURE__ */ new Map();
|
|
6099
6220
|
/** 陈旧队列存活时长(毫秒):超过则丢弃(turn 异常结束/agent 崩溃时防残留误清空) */
|
|
6100
6221
|
const PENDING_TTL_MS = 6e5;
|
|
@@ -6108,19 +6229,11 @@ const PENDING_TTL_MS = 6e5;
|
|
|
6108
6229
|
async function queueRebuild(ctx, opts) {
|
|
6109
6230
|
if (!readSimpleSettings().rebuildEnabled) throw new Error("session_rebuild is disabled (rebuild.enabled=false — enable it in the dsh settings panel)");
|
|
6110
6231
|
const { root, note, dshSessionId } = opts;
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
const active = getActiveSessionInfo(scope);
|
|
6117
|
-
if (active?.mdPath) {
|
|
6118
|
-
sessionMdPath = active.mdPath;
|
|
6119
|
-
sessionName = active.sessionId ?? "";
|
|
6120
|
-
}
|
|
6121
|
-
} catch {}
|
|
6122
|
-
const mdPath = sessionMdPath ?? join(root, "AGENT_SESSIONS", "SESSION.md");
|
|
6123
|
-
const anchor = buildRebuildAnchor(root, sessionName, mdPath);
|
|
6232
|
+
const session = ctx.sessions?.get?.(dshSessionId);
|
|
6233
|
+
if (!session) throw new Error(`Unable to locate dsh session ${dshSessionId} (session may be closed)`);
|
|
6234
|
+
const mdPath = resolveSessionMdPath(root, dshSessionId, session);
|
|
6235
|
+
if (!mdPath) throw new Error("Unable to determine the active SESSION.md — no session context found in this conversation. Run \"session use <S###>\" first to activate the trajectory to resume, then retry session_rebuild.");
|
|
6236
|
+
const anchor = buildRebuildAnchor(root, getActiveSessionInfo(dshSessionId)?.sessionId ?? sessionNameFromMdPath(mdPath), mdPath);
|
|
6124
6237
|
pendingRebuilds.set(dshSessionId, {
|
|
6125
6238
|
anchor,
|
|
6126
6239
|
queuedAt: Date.now()
|
|
@@ -6128,7 +6241,7 @@ async function queueRebuild(ctx, opts) {
|
|
|
6128
6241
|
return {
|
|
6129
6242
|
queued: true,
|
|
6130
6243
|
anchor,
|
|
6131
|
-
sessionMdPath
|
|
6244
|
+
sessionMdPath: mdPath
|
|
6132
6245
|
};
|
|
6133
6246
|
}
|
|
6134
6247
|
/**
|
|
@@ -6521,8 +6634,15 @@ function scanSerenityDirs(skillsRoot) {
|
|
|
6521
6634
|
if (!existsSync(skillsRoot)) return [];
|
|
6522
6635
|
return readdirSync(skillsRoot).filter((name) => isSerenityEntry(name)).map((name) => join(skillsRoot, name)).filter((dir) => statSync(dir).isDirectory() && existsSync(join(dir, "SKILL.md"))).sort();
|
|
6523
6636
|
}
|
|
6637
|
+
/** 防穿越:skill 名必须是简单目录名(拒绝分隔符与 `..`/`.` 路径段) */
|
|
6638
|
+
function isSafeSkillName(name) {
|
|
6639
|
+
if (name.length === 0) return false;
|
|
6640
|
+
if (name.includes("/") || name.includes("\\")) return false;
|
|
6641
|
+
return !name.split(/[/\\]/).some((seg) => seg === ".." || seg === ".");
|
|
6642
|
+
}
|
|
6524
6643
|
/** 按名在两个 skills 根下定位 SKILL.md(.dsh/skills 优先,其次 .opencode/skills) */
|
|
6525
6644
|
function findSkillMd(root, name) {
|
|
6645
|
+
if (!isSafeSkillName(name)) return null;
|
|
6526
6646
|
for (const base of [".dsh", ".opencode"]) {
|
|
6527
6647
|
const p = resolve(root, base, "skills", name, "SKILL.md");
|
|
6528
6648
|
if (existsSync(p)) return p;
|
|
@@ -6887,8 +7007,8 @@ function readPersonaSettings() {
|
|
|
6887
7007
|
};
|
|
6888
7008
|
}
|
|
6889
7009
|
}
|
|
6890
|
-
/** 过滤掉对 agent 隐藏的内容(safe-mode 是用户能力,不对 agent
|
|
6891
|
-
const HIDDEN_LINES = /安全模式|safe-mode|\.serenity-safe-on
|
|
7010
|
+
/** 过滤掉对 agent 隐藏的内容(safe-mode 是用户能力,不对 agent 提及)。无 g flag:`.test()` 须逐行独立无状态,避免 lastIndex 游移漏滤(问题A) */
|
|
7011
|
+
const HIDDEN_LINES = /安全模式|safe-mode|\.serenity-safe-on/;
|
|
6892
7012
|
function sanitizeSkillContent(content) {
|
|
6893
7013
|
return content.split("\n").filter((line) => !HIDDEN_LINES.test(line)).join("\n");
|
|
6894
7014
|
}
|
|
@@ -7451,8 +7571,14 @@ function registerContext(ctx, opts = {}) {
|
|
|
7451
7571
|
if (loadSerenityConfig(root, configPaths).hooks?.autoRestoreSession ?? true) {
|
|
7452
7572
|
const info = parseSessionContextFromEvents(agent.session.events ?? []);
|
|
7453
7573
|
if (info) {
|
|
7454
|
-
|
|
7455
|
-
|
|
7574
|
+
const abs = info.mdPath.startsWith(root) ? info.mdPath : resolve(root, info.mdPath);
|
|
7575
|
+
if (existsSync(abs)) {
|
|
7576
|
+
setActiveSessionInfo(scope, {
|
|
7577
|
+
...info,
|
|
7578
|
+
mdPath: abs
|
|
7579
|
+
});
|
|
7580
|
+
console.log(`[serenity-hooks] ↻ 从历史恢复激活会话: ${info.dirName}`);
|
|
7581
|
+
}
|
|
7456
7582
|
}
|
|
7457
7583
|
}
|
|
7458
7584
|
} catch {}
|
|
@@ -7876,9 +8002,15 @@ function listOpencodeSkillDirs(root) {
|
|
|
7876
8002
|
if (!existsSync(dir)) return [];
|
|
7877
8003
|
return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join(dir, e.name)).filter((d) => existsSync(join(d, "SKILL.md")));
|
|
7878
8004
|
}
|
|
7879
|
-
/** 加载一个 opencode skill(frontmatter 解析 +
|
|
8005
|
+
/** 加载一个 opencode skill(frontmatter 解析 + 正文);读失败(EPERM/占用等)返回 null,单目录容错 */
|
|
7880
8006
|
function loadOpencodeSkill(skillDir) {
|
|
7881
|
-
|
|
8007
|
+
let raw;
|
|
8008
|
+
try {
|
|
8009
|
+
raw = readFileSync(join(skillDir, "SKILL.md"), "utf-8");
|
|
8010
|
+
} catch {
|
|
8011
|
+
return null;
|
|
8012
|
+
}
|
|
8013
|
+
const { meta, content } = parseFrontmatter(raw);
|
|
7882
8014
|
return {
|
|
7883
8015
|
name: meta.name,
|
|
7884
8016
|
dir: skillDir,
|
|
@@ -7902,7 +8034,7 @@ function registerOpencodeSkills(ctx) {
|
|
|
7902
8034
|
const candidates = [];
|
|
7903
8035
|
for (const dir of listOpencodeSkillDirs(root)) {
|
|
7904
8036
|
const skill = loadOpencodeSkill(dir);
|
|
7905
|
-
if (!skill.meta.name) continue;
|
|
8037
|
+
if (!skill || !skill.meta.name) continue;
|
|
7906
8038
|
candidates.push({
|
|
7907
8039
|
name: skill.meta.name,
|
|
7908
8040
|
description: skill.meta.description || "(opencode skill)",
|
|
@@ -7930,6 +8062,7 @@ function registerOpencodeSkills(ctx) {
|
|
|
7930
8062
|
async get(candidate) {
|
|
7931
8063
|
const dir = candidate.locator;
|
|
7932
8064
|
const skill = loadOpencodeSkill(dir);
|
|
8065
|
+
if (!skill) return void 0;
|
|
7933
8066
|
return {
|
|
7934
8067
|
name: skill.meta.name,
|
|
7935
8068
|
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.11",
|
|
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": {
|