@shgroup/dsh-serenity-hooks 1.26.1 → 1.26.3
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 +254 -2
- package/lib/index.js +498 -91
- 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.26.
|
|
3
|
+
"version": "1.26.3",
|
|
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/skiff_admin + 拦截缝机械约束(safe-mode/路径守卫)+ 高级设定面板(双端口网关/账号管理)+ Skiff 认知子集角色(实验性)。适配 DSH 公开版(0.1.0-rc,deepseek-ai/deepseek-harness)。",
|
|
6
6
|
"engines": {
|
package/lib/client.js
CHANGED
|
@@ -3245,7 +3245,7 @@ window.__ModuleLoader__.load({
|
|
|
3245
3245
|
if (!res.ok) return null;
|
|
3246
3246
|
return (await res.json()).config ?? null;
|
|
3247
3247
|
}
|
|
3248
|
-
/** PUT 配置(账号 patch + 工作区白名单 + persona
|
|
3248
|
+
/** PUT 配置(账号 patch + 工作区白名单 + persona 彩蛋 + 开放容器白名单)→ 返回保存后的 wire */
|
|
3249
3249
|
async function saveConfig(patch) {
|
|
3250
3250
|
const res = await fetch(CONFIG_PATH, {
|
|
3251
3251
|
method: "PUT",
|
|
@@ -3263,7 +3263,7 @@ window.__ModuleLoader__.load({
|
|
|
3263
3263
|
}
|
|
3264
3264
|
//#endregion
|
|
3265
3265
|
//#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/AccountsEditor
|
|
3266
|
-
const css$2 = "/* AccountsEditor — DSH 设置面板「外部访问」区块(v1.22)\n * 与 SettingsSection ss-* 同设计语言:--dsw-alias-* 语义 token,明暗自适应。 */\n\n.ae-root {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n font-size: 13px;\n}\n\n.ae-warn {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warning-fg, #f59e0b);\n word-break: break-word;\n}\n\n.ae-block {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.ae-title {\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.ae-desc {\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}\n\n.ae-note {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* ── 监听设置 / 白名单输入行 ── */\n\n.ae-row {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.ae-label {\n flex: none;\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n}\n\n.ae-input {\n flex: 1;\n min-width: 0;\n padding: 6px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-module);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n outline: none;\n}\n\n.ae-input:focus {\n border-color: var(--dsw-alias-button-primary-bg, var(--dsw-alias-accent, #3b82f6));\n}\n\n.ae-input::placeholder {\n color: var(--dsw-alias-label-dimmed);\n}\n\n.ae-input.ae-port {\n flex: none;\n width: 76px;\n}\n\n/* ── 账号列表 ── */\n\n.ae-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.ae-head {\n display: flex;\n gap: 8px;\n font-size: 11px;\n font-weight: 500;\n color: var(--dsw-alias-label-dimmed);\n padding: 0 2px;\n text-transform: uppercase;\n letter-spacing: 0.02em;\n}\n\n.ae-item {\n display: flex;\n gap: 8px;\n align-items: center;\n padding: 6px 2px;\n}\n\n.ae-colUser { flex: 1.2; }\n.ae-colPass { flex: 1.6; }\n.ae-colTotp { flex: 2.2; min-width: 0; }\n.ae-colOp { flex: none; width: 48px; }\n\n/* ── v1.22.4 TOTP 第二因素 ── */\n\n.ae-totpBtn {\n flex: none;\n padding: 4px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.ae-totpBtn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.ae-totpPending {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n align-items: center;\n min-width: 0;\n}\n\n/* v1.24.6:TOTP 绑定二维码(扫码即录入;scalable SVG,白底保证扫码识别) */\n.ae-totpQr {\n flex-basis: 100%;\n display: inline-flex;\n justify-content: center;\n padding: 8px;\n background: #fff;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n}\n\n.ae-totpQr svg {\n width: 132px;\n height: 132px;\n display: block;\n}\n\n.ae-totpSecret {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n background: var(--dsw-alias-surface-raised, rgba(0, 0, 0, 0.25));\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 4px;\n padding: 2px 6px;\n color: var(--dsw-alias-label-secondary);\n word-break: break-all;\n}\n\n.ae-totpUri {\n font-size: 11px;\n color: var(--dsw-alias-accent, #3b82f6);\n text-decoration: none;\n}\n\n.ae-totpUri:hover {\n text-decoration: underline;\n}\n\n/* v1.24.7:绑定提示(去确认码后——扫码或手动录入,保存即绑定) */\n.ae-totpHint {\n flex-basis: 100%;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.ae-totpBadge {\n font-size: 12px;\n color: var(--dsw-alias-state-warning-fg, #f59e0b);\n}\n\n.ae-totpClear {\n display: flex;\n gap: 6px;\n align-items: center;\n}\n\n.ae-check {\n display: flex;\n gap: 8px;\n align-items: center;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n margin-top: 10px;\n cursor: pointer;\n}\n\n.ae-check input {\n accent-color: var(--dsw-alias-accent, #3b82f6);\n}\n\n.ae-del {\n flex: none;\n padding: 4px 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-state-danger-fg, #f87171);\n font-size: 12px;\n cursor: pointer;\n}\n\n.ae-del:hover {\n background: var(--dsw-alias-state-danger-bg, rgba(248, 113, 113, 0.12));\n}\n\n.ae-add {\n align-self: flex-start;\n padding: 5px 12px;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.ae-add:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.ae-wsSelect {\n margin-top: 8px;\n max-width: 100%;\n cursor: pointer;\n}\n\n/* ── 工作区白名单 chips ── */\n\n.ae-wsList {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.ae-wsChip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 3px 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.14));\n}\n\n.ae-wsPath {\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n font-family: var(--ds-font-family-code, monospace);\n word-break: break-all;\n}\n\n.ae-wsDel {\n flex: none;\n width: 18px;\n height: 18px;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--dsw-alias-label-dimmed);\n font-size: 13px;\n line-height: 1;\n cursor: pointer;\n}\n\n.ae-wsDel:hover {\n background: var(--dsw-alias-state-danger-bg, rgba(248, 113, 113, 0.18));\n color: var(--dsw-alias-state-danger-fg, #f87171);\n}\n\n/* ── 错误 / 操作区 ── */\n\n.ae-error {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-danger-fg, #f87171);\n word-break: break-word;\n}\n\n.ae-actions {\n display: flex;\n justify-content: flex-end;\n padding-top: 4px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n\n.ae-save {\n padding: 7px 24px;\n border: 0;\n border-radius: 8px;\n background: var(--dsw-alias-button-primary-bg, var(--dsw-alias-accent, #3b82f6));\n color: var(--dsw-alias-button-primary-fg, #fff);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n}\n\n.ae-save:hover:not(:disabled) {\n filter: brightness(1.08);\n}\n\n.ae-save:disabled {\n opacity: 0.6;\n cursor: default;\n}\n";
|
|
3266
|
+
const css$2 = "/* AccountsEditor — DSH 设置面板「外部访问」区块(v1.22)\n * 与 SettingsSection ss-* 同设计语言:--dsw-alias-* 语义 token,明暗自适应。 */\n\n.ae-root {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n font-size: 13px;\n}\n\n.ae-warn {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warning-fg, #f59e0b);\n word-break: break-word;\n}\n\n.ae-block {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.ae-title {\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.ae-desc {\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}\n\n.ae-note {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* ── 监听设置 / 白名单输入行 ── */\n\n.ae-row {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.ae-label {\n flex: none;\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n}\n\n.ae-input {\n flex: 1;\n min-width: 0;\n padding: 6px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-module);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n outline: none;\n}\n\n.ae-input:focus {\n border-color: var(--dsw-alias-button-primary-bg, var(--dsw-alias-accent, #3b82f6));\n}\n\n.ae-input::placeholder {\n color: var(--dsw-alias-label-dimmed);\n}\n\n.ae-input.ae-port {\n flex: none;\n width: 76px;\n}\n\n/* ── 账号列表 ── */\n\n.ae-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.ae-head {\n display: flex;\n gap: 8px;\n font-size: 11px;\n font-weight: 500;\n color: var(--dsw-alias-label-dimmed);\n padding: 0 2px;\n text-transform: uppercase;\n letter-spacing: 0.02em;\n}\n\n.ae-item {\n display: flex;\n gap: 8px;\n align-items: center;\n padding: 6px 2px;\n}\n\n.ae-colUser { flex: 1.2; }\n.ae-colPass { flex: 1.6; }\n.ae-colTotp { flex: 2.2; min-width: 0; }\n.ae-colOp { flex: none; width: 48px; }\n\n/* ── v1.22.4 TOTP 第二因素 ── */\n\n.ae-totpBtn {\n flex: none;\n padding: 4px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.ae-totpBtn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.ae-totpPending {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n align-items: center;\n min-width: 0;\n}\n\n/* v1.24.6:TOTP 绑定二维码(扫码即录入;scalable SVG,白底保证扫码识别) */\n.ae-totpQr {\n flex-basis: 100%;\n display: inline-flex;\n justify-content: center;\n padding: 8px;\n background: #fff;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n}\n\n.ae-totpQr svg {\n width: 132px;\n height: 132px;\n display: block;\n}\n\n.ae-totpSecret {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n background: var(--dsw-alias-surface-raised, rgba(0, 0, 0, 0.25));\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 4px;\n padding: 2px 6px;\n color: var(--dsw-alias-label-secondary);\n word-break: break-all;\n}\n\n.ae-totpUri {\n font-size: 11px;\n color: var(--dsw-alias-accent, #3b82f6);\n text-decoration: none;\n}\n\n.ae-totpUri:hover {\n text-decoration: underline;\n}\n\n/* v1.24.7:绑定提示(去确认码后——扫码或手动录入,保存即绑定) */\n.ae-totpHint {\n flex-basis: 100%;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.ae-totpBadge {\n font-size: 12px;\n color: var(--dsw-alias-state-warning-fg, #f59e0b);\n}\n\n.ae-totpClear {\n display: flex;\n gap: 6px;\n align-items: center;\n}\n\n.ae-check {\n display: flex;\n gap: 8px;\n align-items: center;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n margin-top: 10px;\n cursor: pointer;\n}\n\n.ae-check input {\n accent-color: var(--dsw-alias-accent, #3b82f6);\n}\n\n.ae-del {\n flex: none;\n padding: 4px 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-state-danger-fg, #f87171);\n font-size: 12px;\n cursor: pointer;\n}\n\n.ae-del:hover {\n background: var(--dsw-alias-state-danger-bg, rgba(248, 113, 113, 0.12));\n}\n\n.ae-add {\n align-self: flex-start;\n padding: 5px 12px;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.ae-add:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.ae-wsSelect {\n margin-top: 8px;\n max-width: 100%;\n cursor: pointer;\n}\n\n/* ── 工作区白名单 chips ── */\n\n.ae-wsList {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.ae-wsChip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 3px 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.14));\n}\n\n.ae-wsPath {\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n font-family: var(--ds-font-family-code, monospace);\n word-break: break-all;\n}\n\n.ae-wsDel {\n flex: none;\n width: 18px;\n height: 18px;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--dsw-alias-label-dimmed);\n font-size: 13px;\n line-height: 1;\n cursor: pointer;\n}\n\n.ae-wsDel:hover {\n background: var(--dsw-alias-state-danger-bg, rgba(248, 113, 113, 0.18));\n color: var(--dsw-alias-state-danger-fg, #f87171);\n}\n\n/* ── 错误 / 操作区 ── */\n\n.ae-error {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-danger-fg, #f87171);\n word-break: break-word;\n}\n\n.ae-actions {\n display: flex;\n justify-content: flex-end;\n padding-top: 4px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n\n.ae-save {\n padding: 7px 24px;\n border: 0;\n border-radius: 8px;\n background: var(--dsw-alias-button-primary-bg, var(--dsw-alias-accent, #3b82f6));\n color: var(--dsw-alias-button-primary-fg, #fff);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n}\n\n.ae-save:hover:not(:disabled) {\n filter: brightness(1.08);\n}\n\n.ae-save:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n/* ── v1.26.2 建议问答页配置(PublicAskEditor:开放容器白名单 chips + key/地址展示)── */\n\n.ae-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin: 8px 0 4px;\n}\n\n.ae-chip {\n padding: 4px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 999px;\n background: transparent;\n color: var(--dsw-alias-text-primary);\n font-size: 12px;\n cursor: pointer;\n transition: background 0.15s ease, border-color 0.15s ease;\n}\n\n.ae-chip:hover {\n border-color: var(--dsw-alias-accent, #0ba875);\n}\n\n.ae-chipOn {\n background: var(--dsw-alias-accent, #0ba875);\n border-color: var(--dsw-alias-accent, #0ba875);\n color: #fff;\n}\n\n.ae-saved {\n margin-left: 10px;\n font-size: 12px;\n color: var(--dsw-alias-state-success-fg, #4ade80);\n}\n\n.ae-err {\n margin-left: 10px;\n font-size: 12px;\n color: var(--dsw-alias-state-danger-fg, #f87171);\n}\n\n.ae-key {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n padding: 2px 8px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-l2);\n color: var(--dsw-alias-text-primary);\n word-break: break-all;\n user-select: all;\n}\n\n.ae-copy {\n flex: none;\n margin-left: 8px;\n padding: 3px 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-text-primary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.ae-copy:hover {\n border-color: var(--dsw-alias-accent, #0ba875);\n color: var(--dsw-alias-accent, #0ba875);\n}\n\n.ae-rows {\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin-top: 8px;\n}\n\n.ae-labelDim {\n opacity: 0.65;\n min-width: 96px;\n}\n";
|
|
3267
3267
|
if (typeof document !== "undefined" && document.querySelector("style[data-sp-css=\"AccountsEditor\"]") === null) {
|
|
3268
3268
|
const tag = document.createElement("style");
|
|
3269
3269
|
tag.setAttribute("data-sp-css", "AccountsEditor");
|
|
@@ -3818,6 +3818,251 @@ Instruction-following style:
|
|
|
3818
3818
|
});
|
|
3819
3819
|
}
|
|
3820
3820
|
//#endregion
|
|
3821
|
+
//#region src/client/PublicAskEditor.tsx
|
|
3822
|
+
/**
|
|
3823
|
+
* PublicAskEditor.tsx — 建议问答页配置(v1.26.2,S142 用户需求)
|
|
3824
|
+
*
|
|
3825
|
+
* 功能:
|
|
3826
|
+
* - **开放容器白名单**:从 /serenity/cccs 拉取候选容器,chips 选择哪些开放;
|
|
3827
|
+
* 空白名单 = 全部容器开放(向后兼容 v1.26.1 全局开放语义)。
|
|
3828
|
+
* - **key + 地址展示**:经 /serenity/public-ask 获取访问 key 与各开放容器 URL
|
|
3829
|
+
* (用户:配置处需能获取 key 和地址——管理员复制分享给使用者)。
|
|
3830
|
+
*
|
|
3831
|
+
* 数据通道(plugin 全局):
|
|
3832
|
+
* - GET /serenity/cccs 候选容器(node half discoverCccs)
|
|
3833
|
+
* - GET /serenity/public-ask key + 地址(x-serenity-ui 头)
|
|
3834
|
+
* - PUT /serenity/config publicAsk.allowed 白名单保存(accounts-api saveConfig)
|
|
3835
|
+
*/
|
|
3836
|
+
function PublicAskEditor(props) {
|
|
3837
|
+
const { publicAskOn } = props;
|
|
3838
|
+
const [candidates, setCandidates] = (0, react.useState)([]);
|
|
3839
|
+
const [allowed, setAllowed] = (0, react.useState)(null);
|
|
3840
|
+
const [info, setInfo] = (0, react.useState)(null);
|
|
3841
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
3842
|
+
const [error, setError] = (0, react.useState)(null);
|
|
3843
|
+
const [saved, setSaved] = (0, react.useState)(false);
|
|
3844
|
+
(0, react.useEffect)(() => {
|
|
3845
|
+
let alive = true;
|
|
3846
|
+
(async () => {
|
|
3847
|
+
try {
|
|
3848
|
+
const cfg = await fetchConfig();
|
|
3849
|
+
if (!alive) return;
|
|
3850
|
+
setAllowed(cfg?.publicAsk?.allowed ?? []);
|
|
3851
|
+
} catch {}
|
|
3852
|
+
})();
|
|
3853
|
+
return () => {
|
|
3854
|
+
alive = false;
|
|
3855
|
+
};
|
|
3856
|
+
}, []);
|
|
3857
|
+
(0, react.useEffect)(() => {
|
|
3858
|
+
let alive = true;
|
|
3859
|
+
(async () => {
|
|
3860
|
+
try {
|
|
3861
|
+
const res = await fetch("/serenity/cccs", { headers: { accept: "application/json" } });
|
|
3862
|
+
if (!res.ok) return;
|
|
3863
|
+
const body = await res.json();
|
|
3864
|
+
if (alive) setCandidates(body?.cccs ?? []);
|
|
3865
|
+
} catch {}
|
|
3866
|
+
})();
|
|
3867
|
+
return () => {
|
|
3868
|
+
alive = false;
|
|
3869
|
+
};
|
|
3870
|
+
}, []);
|
|
3871
|
+
(0, react.useEffect)(() => {
|
|
3872
|
+
if (!publicAskOn) return;
|
|
3873
|
+
let alive = true;
|
|
3874
|
+
(async () => {
|
|
3875
|
+
try {
|
|
3876
|
+
const res = await fetch("/serenity/public-ask", { headers: {
|
|
3877
|
+
accept: "application/json",
|
|
3878
|
+
"x-serenity-ui": "1"
|
|
3879
|
+
} });
|
|
3880
|
+
if (!res.ok) return;
|
|
3881
|
+
const body = await res.json();
|
|
3882
|
+
if (alive) setInfo(body);
|
|
3883
|
+
} catch {}
|
|
3884
|
+
})();
|
|
3885
|
+
return () => {
|
|
3886
|
+
alive = false;
|
|
3887
|
+
};
|
|
3888
|
+
}, [publicAskOn]);
|
|
3889
|
+
const toggleAllowed = (name) => {
|
|
3890
|
+
setAllowed((cur) => {
|
|
3891
|
+
const base = cur ?? [];
|
|
3892
|
+
return base.includes(name) ? base.filter((n) => n !== name) : [...base, name];
|
|
3893
|
+
});
|
|
3894
|
+
};
|
|
3895
|
+
const save = async () => {
|
|
3896
|
+
if (allowed === null) return;
|
|
3897
|
+
setBusy(true);
|
|
3898
|
+
setError(null);
|
|
3899
|
+
setSaved(false);
|
|
3900
|
+
try {
|
|
3901
|
+
const cfg = await saveConfig({ publicAsk: { allowed } });
|
|
3902
|
+
if (cfg) {
|
|
3903
|
+
setAllowed(cfg.publicAsk.allowed ?? []);
|
|
3904
|
+
setSaved(true);
|
|
3905
|
+
setTimeout(() => setSaved(false), 2e3);
|
|
3906
|
+
}
|
|
3907
|
+
} catch (err) {
|
|
3908
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
3909
|
+
} finally {
|
|
3910
|
+
setBusy(false);
|
|
3911
|
+
}
|
|
3912
|
+
};
|
|
3913
|
+
const allOpen = (allowed ?? []).length === 0;
|
|
3914
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3915
|
+
className: "ae-root",
|
|
3916
|
+
children: [
|
|
3917
|
+
!publicAskOn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3918
|
+
className: "ae-warn",
|
|
3919
|
+
children: "⚠️ 建议问答页当前关闭——先开启上方「建议问答页」开关,外部访问才生效。"
|
|
3920
|
+
}),
|
|
3921
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3922
|
+
className: "ae-block",
|
|
3923
|
+
children: [
|
|
3924
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
3925
|
+
className: "ae-title",
|
|
3926
|
+
children: "开放容器"
|
|
3927
|
+
}),
|
|
3928
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
3929
|
+
className: "ae-desc",
|
|
3930
|
+
children: [
|
|
3931
|
+
"允许外部问答的认知容器(URL:/c/<容器名>)。",
|
|
3932
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "未选择 = 全部容器开放" }),
|
|
3933
|
+
"; 选择后仅白名单内容器可访问。"
|
|
3934
|
+
]
|
|
3935
|
+
}),
|
|
3936
|
+
candidates.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3937
|
+
className: "ae-note",
|
|
3938
|
+
children: "暂无可选容器(/serenity/cccs 未返回条目——先在工作区打开一个 CCC)"
|
|
3939
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3940
|
+
className: "ae-chips",
|
|
3941
|
+
children: candidates.map((c) => {
|
|
3942
|
+
const on = (allowed ?? []).includes(c.name);
|
|
3943
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3944
|
+
type: "button",
|
|
3945
|
+
className: `ae-chip${on ? " ae-chipOn" : ""}`,
|
|
3946
|
+
onClick: () => toggleAllowed(c.name),
|
|
3947
|
+
title: `${c.root}(${c.roles.length} 角色)`,
|
|
3948
|
+
children: [c.name, on ? " ✓" : ""]
|
|
3949
|
+
}, c.name);
|
|
3950
|
+
})
|
|
3951
|
+
}),
|
|
3952
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3953
|
+
className: "ae-actions",
|
|
3954
|
+
children: [
|
|
3955
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3956
|
+
type: "button",
|
|
3957
|
+
className: "ae-save",
|
|
3958
|
+
disabled: busy || allowed === null,
|
|
3959
|
+
onClick: () => void save(),
|
|
3960
|
+
children: busy ? "保存中…" : "保存白名单"
|
|
3961
|
+
}),
|
|
3962
|
+
saved && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3963
|
+
className: "ae-saved",
|
|
3964
|
+
children: "✓ 已保存"
|
|
3965
|
+
}),
|
|
3966
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3967
|
+
className: "ae-err",
|
|
3968
|
+
children: error
|
|
3969
|
+
})
|
|
3970
|
+
]
|
|
3971
|
+
}),
|
|
3972
|
+
allOpen && allowed !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3973
|
+
className: "ae-note",
|
|
3974
|
+
children: "当前为「全部开放」——所有发现到的认知容器都可问答。"
|
|
3975
|
+
})
|
|
3976
|
+
]
|
|
3977
|
+
}),
|
|
3978
|
+
publicAskOn && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3979
|
+
className: "ae-block",
|
|
3980
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
3981
|
+
className: "ae-title",
|
|
3982
|
+
children: "访问 Key 与地址"
|
|
3983
|
+
}), info ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
3984
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3985
|
+
className: "ae-desc",
|
|
3986
|
+
children: "把下面的 key 与地址分享给使用者;key 已自动保存在其浏览器(localStorage)。"
|
|
3987
|
+
}),
|
|
3988
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3989
|
+
className: "ae-row",
|
|
3990
|
+
children: [
|
|
3991
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3992
|
+
className: "ae-label",
|
|
3993
|
+
children: "访问 Key"
|
|
3994
|
+
}),
|
|
3995
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
3996
|
+
className: "ae-key",
|
|
3997
|
+
children: info.key
|
|
3998
|
+
}),
|
|
3999
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4000
|
+
type: "button",
|
|
4001
|
+
className: "ae-copy",
|
|
4002
|
+
onClick: () => {
|
|
4003
|
+
navigator.clipboard?.writeText(info.key);
|
|
4004
|
+
},
|
|
4005
|
+
children: "复制"
|
|
4006
|
+
})
|
|
4007
|
+
]
|
|
4008
|
+
}),
|
|
4009
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4010
|
+
className: "ae-row",
|
|
4011
|
+
children: [
|
|
4012
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4013
|
+
className: "ae-label",
|
|
4014
|
+
children: "容器列表页"
|
|
4015
|
+
}),
|
|
4016
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
4017
|
+
className: "ae-key",
|
|
4018
|
+
children: info.listUrl
|
|
4019
|
+
}),
|
|
4020
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4021
|
+
type: "button",
|
|
4022
|
+
className: "ae-copy",
|
|
4023
|
+
onClick: () => {
|
|
4024
|
+
navigator.clipboard?.writeText(info.listUrl);
|
|
4025
|
+
},
|
|
4026
|
+
children: "复制"
|
|
4027
|
+
})
|
|
4028
|
+
]
|
|
4029
|
+
}),
|
|
4030
|
+
info.urls.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4031
|
+
className: "ae-rows",
|
|
4032
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4033
|
+
className: "ae-label",
|
|
4034
|
+
children: "各容器地址"
|
|
4035
|
+
}), info.urls.map((u) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4036
|
+
className: "ae-row",
|
|
4037
|
+
children: [
|
|
4038
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4039
|
+
className: "ae-label ae-labelDim",
|
|
4040
|
+
children: u.name
|
|
4041
|
+
}),
|
|
4042
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
4043
|
+
className: "ae-key",
|
|
4044
|
+
children: u.url
|
|
4045
|
+
}),
|
|
4046
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4047
|
+
type: "button",
|
|
4048
|
+
className: "ae-copy",
|
|
4049
|
+
onClick: () => {
|
|
4050
|
+
navigator.clipboard?.writeText(u.url);
|
|
4051
|
+
},
|
|
4052
|
+
children: "复制"
|
|
4053
|
+
})
|
|
4054
|
+
]
|
|
4055
|
+
}, u.name))]
|
|
4056
|
+
})
|
|
4057
|
+
] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4058
|
+
className: "ae-note",
|
|
4059
|
+
children: "开启后此处显示访问 key 与地址…"
|
|
4060
|
+
})]
|
|
4061
|
+
})
|
|
4062
|
+
]
|
|
4063
|
+
});
|
|
4064
|
+
}
|
|
4065
|
+
//#endregion
|
|
3821
4066
|
//#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/SettingsSection
|
|
3822
4067
|
const css = "/* SettingsSection — dsh 原生设置面板 serenity-hooks 简单配置页\n v1.21.1 看齐 dsh 自身:官方 settings 设计语言——\n section > title + intro + group(groupTitle) + rowCard 行卡;\n 全部 --dsw-alias-* token(与 ui-settings-models 同词汇表)。 */\n\n/* 页面容器(官方 .section:flex column gap 12) */\n.ss-section {\n display: flex;\n flex-direction: column;\n gap: 14px;\n max-width: 720px;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面标题(官方 .title:16px/24 500) */\n.ss-title {\n margin: 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面说明(官方 .intro:14px/22 tertiary) */\n.ss-intro {\n margin: 0;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 分组(多级标题:分组标题 + 行卡列表) */\n.ss-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 分组标题(二级:12px/18 500 secondary——官方 fieldLabel 风格) */\n.ss-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/* 行卡列表(官方 .rows:无 marker,gap 8) */\n.ss-rows {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 行卡(官方 .rowCard:border-l2 r12 padding 12/14) */\n.ss-rowCard {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n padding: 12px 14px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n\n/* 行卡文本(标题 + 说明 左列) */\n.ss-rowText {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n flex: 1;\n}\n\n/* 行卡标题(官方 .rowName:14px/22 500) */\n.ss-rowName {\n font-size: 14px;\n line-height: 22px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 行卡说明(12px/18 tertiary) */\n.ss-rowDesc {\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}\n\n/* 行卡控件(右置) */\n.ss-rowControl {\n flex: none;\n display: flex;\n align-items: center;\n}\n\n/* 开关(官方 token 的自绘 toggle:track 22px,thumb 16px) */\n.ss-switch {\n position: relative;\n flex: none;\n width: 40px;\n height: 22px;\n cursor: pointer;\n}\n\n.ss-switch input {\n position: absolute;\n opacity: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n cursor: pointer;\n}\n\n.ss-switchTrack {\n position: absolute;\n inset: 0;\n border-radius: 11px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.3));\n transition: background var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switchTrack::after {\n content: '';\n position: absolute;\n top: 3px;\n left: 3px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--dsw-alias-label-primary, #eee);\n transition: transform var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switch input:checked + .ss-switchTrack {\n background: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-switch input:checked + .ss-switchTrack::after {\n transform: translateX(18px);\n}\n\n.ss-switch input:disabled + .ss-switchTrack {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 阈值行(滑块 + 值) */\n.ss-threshold {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.ss-threshold input[type='range'] {\n width: 160px;\n}\n\n/* Skiff 调试端口输入(number;关闭时跟随 disabled 半透明) */\n.ss-portInput {\n width: 96px;\n padding: 6px 8px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-fill-l1, rgba(128, 128, 128, 0.08));\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n\n.ss-portInput:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.ss-value {\n min-width: 40px;\n font-variant-numeric: tabular-nums;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n text-align: right;\n}\n\n/* 底部帮助行 */\n.ss-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}\n\n/* 降级提示 */\n.ss-degrade {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warn-label);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n";
|
|
3823
4068
|
if (typeof document !== "undefined" && document.querySelector("style[data-sp-css=\"SettingsSection\"]") === null) {
|
|
@@ -4039,6 +4284,13 @@ Instruction-following style:
|
|
|
4039
4284
|
}) })
|
|
4040
4285
|
]
|
|
4041
4286
|
}),
|
|
4287
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4288
|
+
className: "ss-group",
|
|
4289
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
4290
|
+
className: "ss-groupTitle",
|
|
4291
|
+
children: "建议问答"
|
|
4292
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PublicAskEditor, { publicAskOn })]
|
|
4293
|
+
}),
|
|
4042
4294
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4043
4295
|
className: "ss-group",
|
|
4044
4296
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
package/lib/index.js
CHANGED
|
@@ -11,6 +11,18 @@ import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
|
11
11
|
import { createHmac, randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
|
|
12
12
|
import { deriveEventMessage } from "@deepseek-ai/dsh-session";
|
|
13
13
|
import { createServer, request } from "node:http";
|
|
14
|
+
//#region \0rolldown/runtime.js
|
|
15
|
+
var __defProp = Object.defineProperty;
|
|
16
|
+
var __exportAll = (all, no_symbols) => {
|
|
17
|
+
let target = {};
|
|
18
|
+
for (var name in all) __defProp(target, name, {
|
|
19
|
+
get: all[name],
|
|
20
|
+
enumerable: true
|
|
21
|
+
});
|
|
22
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
23
|
+
return target;
|
|
24
|
+
};
|
|
25
|
+
//#endregion
|
|
14
26
|
//#region src/ccc.ts
|
|
15
27
|
/**
|
|
16
28
|
* ccc.ts — CCC 纯逻辑层(零 DSH 依赖,可独立单测)
|
|
@@ -773,6 +785,17 @@ const CC_FS_WRITE_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
773
785
|
"touch",
|
|
774
786
|
"append"
|
|
775
787
|
]);
|
|
788
|
+
/**
|
|
789
|
+
* 凭据文件硬名单(数据面守卫,v1.26.3,S142 用户:localstore.json 需要在 read 工具的黑名单里):
|
|
790
|
+
* **任何工具**(含 read/grep/glob/cc_fs 只读子命令)命中这些相对 CCC 根的路径 → deny。
|
|
791
|
+
*
|
|
792
|
+
* 与 safeMode.blacklist(写黑名单,v1.18.5 决策只拦写)语义独立:
|
|
793
|
+
* - 黑名单 = 用户可配置的写保护(REPOSITORIES/ 等只读参考源不误伤读)
|
|
794
|
+
* - 凭据硬名单 = **结构性数据面边界**(不依赖 safe-mode 开关,凭据文件任何时候不可读)——
|
|
795
|
+
* 对齐 CCE Bounded Space + wardn "structural guarantee, not policy":localstore.json
|
|
796
|
+
* 含 API keys/tokens/SSH 密码,即使 prompt 纪律失败也无值可吐(机械保证)。
|
|
797
|
+
*/
|
|
798
|
+
const SENSITIVE_CREDENTIAL_FILES = /* @__PURE__ */ new Set(["localstore.json"]);
|
|
776
799
|
/** 判定工具是否为写类:普通工具按名;cc_fs 复合工具按子命令 action */
|
|
777
800
|
function isWriteTool(toolName, action) {
|
|
778
801
|
if (!WRITE_TOOLS.has(toolName)) return false;
|
|
@@ -805,8 +828,12 @@ function decideGuard(input) {
|
|
|
805
828
|
deny: `path escape blocked: "${pathArg}" escapes the CCC root`,
|
|
806
829
|
kind: "deny"
|
|
807
830
|
};
|
|
831
|
+
const rel = relative(root, abs).split("\\").join("/");
|
|
832
|
+
if (SENSITIVE_CREDENTIAL_FILES.has(rel)) return {
|
|
833
|
+
deny: `access blocked: "${pathArg}" is a sensitive credential file (${rel})`,
|
|
834
|
+
kind: "deny"
|
|
835
|
+
};
|
|
808
836
|
if (isWriteTool(toolName, action)) {
|
|
809
|
-
const rel = relative(root, abs).split("\\").join("/");
|
|
810
837
|
if (rel === ".serenity-safe-on" || rel === ".serenity" || rel.startsWith(".serenity-safe-on/") || rel.startsWith(".serenity/")) return {
|
|
811
838
|
deny: `CCC governance file "${rel}" is reserved for the user — agent must not write`,
|
|
812
839
|
kind: "deny"
|
|
@@ -2380,7 +2407,7 @@ function listActiveHandymen(root) {
|
|
|
2380
2407
|
}
|
|
2381
2408
|
//#endregion
|
|
2382
2409
|
//#region src/skiff-core.ts
|
|
2383
|
-
const PLUGIN_SOURCE$
|
|
2410
|
+
const PLUGIN_SOURCE$4 = {
|
|
2384
2411
|
kind: "plugin",
|
|
2385
2412
|
plugin: "dsh-serenity-hooks"
|
|
2386
2413
|
};
|
|
@@ -2479,7 +2506,7 @@ function waitIdle$1(ctx, agent) {
|
|
|
2479
2506
|
});
|
|
2480
2507
|
}
|
|
2481
2508
|
/** 读会话最后一个 assistant/message 文本(handyman 同款) */
|
|
2482
|
-
function lastAssistantText$
|
|
2509
|
+
function lastAssistantText$2(agent) {
|
|
2483
2510
|
const events = agent.session.events;
|
|
2484
2511
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
2485
2512
|
const e = events[i];
|
|
@@ -2548,10 +2575,10 @@ async function askSkiff(ctx, agent, question, eventsStart) {
|
|
|
2548
2575
|
type: "text",
|
|
2549
2576
|
text: question
|
|
2550
2577
|
}],
|
|
2551
|
-
source: PLUGIN_SOURCE$
|
|
2578
|
+
source: PLUGIN_SOURCE$4
|
|
2552
2579
|
}));
|
|
2553
2580
|
await waitIdle$1(ctx, agent);
|
|
2554
|
-
const answer = lastAssistantText$
|
|
2581
|
+
const answer = lastAssistantText$2(agent);
|
|
2555
2582
|
const trajectory = eventsToTrajectory(agent.session.events.slice(before));
|
|
2556
2583
|
return {
|
|
2557
2584
|
answer,
|
|
@@ -2961,7 +2988,7 @@ function waitIdle(ctx, agent) {
|
|
|
2961
2988
|
});
|
|
2962
2989
|
}
|
|
2963
2990
|
/** 读取会话最后一个 assistant/message 文本 */
|
|
2964
|
-
function lastAssistantText(agent) {
|
|
2991
|
+
function lastAssistantText$1(agent) {
|
|
2965
2992
|
const events = agent.session.events;
|
|
2966
2993
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
2967
2994
|
const e = events[i];
|
|
@@ -3059,7 +3086,7 @@ async function runHandymanJob(ctx, opts) {
|
|
|
3059
3086
|
}
|
|
3060
3087
|
}));
|
|
3061
3088
|
await waitIdle(ctx, workerAgent);
|
|
3062
|
-
lastResponse = lastAssistantText(workerAgent);
|
|
3089
|
+
lastResponse = lastAssistantText$1(workerAgent);
|
|
3063
3090
|
} catch {
|
|
3064
3091
|
restarts++;
|
|
3065
3092
|
if (restarts > 100) {
|
|
@@ -5248,6 +5275,14 @@ function installSettingsSection(ctx, ns, schema, entry, hooks) {
|
|
|
5248
5275
|
}
|
|
5249
5276
|
//#endregion
|
|
5250
5277
|
//#region src/settings-section.ts
|
|
5278
|
+
var settings_section_exports = /* @__PURE__ */ __exportAll({
|
|
5279
|
+
SERENITY_SETTINGS_NS: () => SERENITY_SETTINGS_NS,
|
|
5280
|
+
defaultSimpleSettings: () => defaultSimpleSettings,
|
|
5281
|
+
entryDefaults: () => entryDefaults,
|
|
5282
|
+
readSimpleSettings: () => readSimpleSettings,
|
|
5283
|
+
registerSettingsSection: () => registerSettingsSection,
|
|
5284
|
+
simpleSettingsSchema: () => simpleSettingsSchema
|
|
5285
|
+
});
|
|
5251
5286
|
/** 简单配置命名空间(dsh 设置面板的 section id / settings.yaml section) */
|
|
5252
5287
|
const SERENITY_SETTINGS_NS = "serenity-hooks";
|
|
5253
5288
|
/** schemastery schema(与 DSH 各插件 Config 同款) */
|
|
@@ -6547,7 +6582,7 @@ function registerBootstrap(ctx) {
|
|
|
6547
6582
|
}
|
|
6548
6583
|
//#endregion
|
|
6549
6584
|
//#region src/rebuild.ts
|
|
6550
|
-
const PLUGIN_SOURCE$
|
|
6585
|
+
const PLUGIN_SOURCE$3 = {
|
|
6551
6586
|
kind: "plugin",
|
|
6552
6587
|
plugin: "dsh-serenity-hooks"
|
|
6553
6588
|
};
|
|
@@ -6733,7 +6768,7 @@ function registerRebuildTurnHook(ctx) {
|
|
|
6733
6768
|
type: "text",
|
|
6734
6769
|
text: "[TRAJECTORY-REBUILD] The conversation has been cleared and rebuilt. Follow the anchor instructions above now: read the persistent trajectory (SESSION.md) and continue the work automatically from the last checkpoint."
|
|
6735
6770
|
}],
|
|
6736
|
-
source: PLUGIN_SOURCE$
|
|
6771
|
+
source: PLUGIN_SOURCE$3
|
|
6737
6772
|
}));
|
|
6738
6773
|
console.log(`[serenity-hooks] session_rebuild executed with auto-continue (turn ${payload.turn ?? "?"} ended): ${id}`);
|
|
6739
6774
|
}
|
|
@@ -7130,7 +7165,7 @@ function readContextPressure(ctx, session) {
|
|
|
7130
7165
|
return null;
|
|
7131
7166
|
}
|
|
7132
7167
|
}
|
|
7133
|
-
const PLUGIN_SOURCE$
|
|
7168
|
+
const PLUGIN_SOURCE$2 = {
|
|
7134
7169
|
kind: "plugin",
|
|
7135
7170
|
plugin: "dsh-serenity-hooks"
|
|
7136
7171
|
};
|
|
@@ -7193,7 +7228,7 @@ function registerKeeper(ctx, opts = {}) {
|
|
|
7193
7228
|
if (blocks.length === 0) return downstream;
|
|
7194
7229
|
const reminderAll = createUserMessage({
|
|
7195
7230
|
content: blocks,
|
|
7196
|
-
source: PLUGIN_SOURCE$
|
|
7231
|
+
source: PLUGIN_SOURCE$2
|
|
7197
7232
|
});
|
|
7198
7233
|
if (downstream.kind === "block") return {
|
|
7199
7234
|
kind: "block",
|
|
@@ -7373,7 +7408,10 @@ function defaultAdvancedSettings() {
|
|
|
7373
7408
|
mode: "",
|
|
7374
7409
|
overrideText: ""
|
|
7375
7410
|
},
|
|
7376
|
-
publicAsk: {
|
|
7411
|
+
publicAsk: {
|
|
7412
|
+
key: "",
|
|
7413
|
+
allowed: []
|
|
7414
|
+
}
|
|
7377
7415
|
};
|
|
7378
7416
|
}
|
|
7379
7417
|
const SCRYPT_KEYLEN = 32;
|
|
@@ -7445,7 +7483,10 @@ function mergeWithDefaults(raw) {
|
|
|
7445
7483
|
mode: typeof persona.mode === "string" ? persona.mode : def.persona.mode,
|
|
7446
7484
|
overrideText: typeof persona.overrideText === "string" ? persona.overrideText : def.persona.overrideText
|
|
7447
7485
|
},
|
|
7448
|
-
publicAsk: {
|
|
7486
|
+
publicAsk: {
|
|
7487
|
+
key: typeof publicAsk.key === "string" ? publicAsk.key : def.publicAsk.key,
|
|
7488
|
+
allowed: Array.isArray(publicAsk.allowed) ? publicAsk.allowed.filter((n) => typeof n === "string" && n !== "") : def.publicAsk.allowed
|
|
7489
|
+
}
|
|
7449
7490
|
};
|
|
7450
7491
|
}
|
|
7451
7492
|
/** 读取全局配置(文件缺失/坏 JSON → 默认值) */
|
|
@@ -7486,7 +7527,10 @@ function updateAdvancedSettings(patch) {
|
|
|
7486
7527
|
mode: typeof ps.mode === "string" ? ps.mode : current.persona.mode,
|
|
7487
7528
|
overrideText: typeof ps.overrideText === "string" ? ps.overrideText : current.persona.overrideText
|
|
7488
7529
|
} : current.persona,
|
|
7489
|
-
publicAsk: patch.publicAsk !== void 0 ? {
|
|
7530
|
+
publicAsk: patch.publicAsk !== void 0 ? {
|
|
7531
|
+
key: typeof patch.publicAsk.key === "string" ? patch.publicAsk.key : current.publicAsk.key,
|
|
7532
|
+
allowed: Array.isArray(patch.publicAsk.allowed) ? patch.publicAsk.allowed.filter((n) => typeof n === "string" && n !== "") : current.publicAsk.allowed
|
|
7533
|
+
} : current.publicAsk
|
|
7490
7534
|
};
|
|
7491
7535
|
writeAdvancedSettings(next);
|
|
7492
7536
|
return next;
|
|
@@ -7500,7 +7544,10 @@ function ensurePublicAskKey() {
|
|
|
7500
7544
|
const current = readAdvancedSettings();
|
|
7501
7545
|
if (current.publicAsk.key !== "") return current.publicAsk.key;
|
|
7502
7546
|
const key = randomBytes(32).toString("hex");
|
|
7503
|
-
updateAdvancedSettings({ publicAsk: {
|
|
7547
|
+
updateAdvancedSettings({ publicAsk: {
|
|
7548
|
+
key,
|
|
7549
|
+
allowed: current.publicAsk.allowed
|
|
7550
|
+
} });
|
|
7504
7551
|
return key;
|
|
7505
7552
|
}
|
|
7506
7553
|
/** 校验问答页访问 key(timing-safe;key 未生成 → 恒 false——没有 key 不工作) */
|
|
@@ -7546,7 +7593,8 @@ function toWire(settings) {
|
|
|
7546
7593
|
},
|
|
7547
7594
|
rebuild: settings.rebuild,
|
|
7548
7595
|
naming: settings.naming,
|
|
7549
|
-
persona: settings.persona
|
|
7596
|
+
persona: settings.persona,
|
|
7597
|
+
publicAsk: { allowed: [...settings.publicAsk.allowed] }
|
|
7550
7598
|
};
|
|
7551
7599
|
}
|
|
7552
7600
|
/**
|
|
@@ -7610,6 +7658,11 @@ function applyWirePatch(wire) {
|
|
|
7610
7658
|
if (typeof wire.persona.overrideText === "string") psPatch.overrideText = wire.persona.overrideText;
|
|
7611
7659
|
patch.persona = psPatch;
|
|
7612
7660
|
}
|
|
7661
|
+
if (wire.publicAsk !== void 0 && Array.isArray(wire.publicAsk.allowed)) {
|
|
7662
|
+
const paPatch = { ...current.publicAsk };
|
|
7663
|
+
paPatch.allowed = wire.publicAsk.allowed.filter((n) => typeof n === "string" && n !== "");
|
|
7664
|
+
patch.publicAsk = paPatch;
|
|
7665
|
+
}
|
|
7613
7666
|
return updateAdvancedSettings(patch);
|
|
7614
7667
|
}
|
|
7615
7668
|
//#endregion
|
|
@@ -8130,7 +8183,7 @@ function accIdentityText(root, configPaths = DEFAULT_SERENITY_CONFIG_PATHS, entr
|
|
|
8130
8183
|
}
|
|
8131
8184
|
return lines.join("\n");
|
|
8132
8185
|
}
|
|
8133
|
-
const PLUGIN_SOURCE = {
|
|
8186
|
+
const PLUGIN_SOURCE$1 = {
|
|
8134
8187
|
kind: "plugin",
|
|
8135
8188
|
plugin: "dsh-serenity-hooks"
|
|
8136
8189
|
};
|
|
@@ -8147,7 +8200,7 @@ function accMessage(root, configPaths, entrySkillMaxChars) {
|
|
|
8147
8200
|
}];
|
|
8148
8201
|
return createUserMessage({
|
|
8149
8202
|
content,
|
|
8150
|
-
source: PLUGIN_SOURCE
|
|
8203
|
+
source: PLUGIN_SOURCE$1
|
|
8151
8204
|
});
|
|
8152
8205
|
}
|
|
8153
8206
|
/** 每 agent 是否已注入过(进程内存态) */
|
|
@@ -8273,6 +8326,8 @@ const HANDYMEN_PATH = "/serenity/handymen";
|
|
|
8273
8326
|
const UPLOAD_PATH = "/serenity/image-upload";
|
|
8274
8327
|
const FILE_UPLOAD_PATH = "/serenity/file-upload";
|
|
8275
8328
|
const CONFIG_PATH = "/serenity/config";
|
|
8329
|
+
const CCCS_PATH = "/serenity/cccs";
|
|
8330
|
+
const PUBLIC_ASK_PATH = "/serenity/public-ask";
|
|
8276
8331
|
/** 图片落盘目录(CCC 根相对;S142 图片自动识别基础设施——粘贴图片落盘供 agent 经 CCC vlm MSM 自主处理) */
|
|
8277
8332
|
const IMAGE_UPLOAD_DIR = "_tmp/images_from_user";
|
|
8278
8333
|
const IMAGE_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -8563,6 +8618,63 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
8563
8618
|
}
|
|
8564
8619
|
}
|
|
8565
8620
|
});
|
|
8621
|
+
ctx.webServer.register({
|
|
8622
|
+
kind: "exact",
|
|
8623
|
+
path: CCCS_PATH,
|
|
8624
|
+
handler: async (req, res) => {
|
|
8625
|
+
try {
|
|
8626
|
+
if (req.method !== "GET") {
|
|
8627
|
+
sendJson$2(res, 405, { error: "method not allowed" });
|
|
8628
|
+
return;
|
|
8629
|
+
}
|
|
8630
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
8631
|
+
const root = findSerenityRoot(resolveWorkspace(ctx, {
|
|
8632
|
+
sessionId: url.searchParams.get("sessionId") ?? void 0,
|
|
8633
|
+
workspace: url.searchParams.get("workspace") ?? void 0
|
|
8634
|
+
})) ?? "";
|
|
8635
|
+
const { discoverCccs } = await Promise.resolve().then(() => skiff_debug_exports);
|
|
8636
|
+
sendJson$2(res, 200, { cccs: await discoverCccs(ctx, root) });
|
|
8637
|
+
} catch (err) {
|
|
8638
|
+
sendJson$2(res, 400, { error: err.message ?? String(err) });
|
|
8639
|
+
}
|
|
8640
|
+
}
|
|
8641
|
+
});
|
|
8642
|
+
ctx.webServer.register({
|
|
8643
|
+
kind: "exact",
|
|
8644
|
+
path: PUBLIC_ASK_PATH,
|
|
8645
|
+
handler: async (req, res) => {
|
|
8646
|
+
try {
|
|
8647
|
+
if (req.method !== "GET") {
|
|
8648
|
+
sendJson$2(res, 405, { error: "method not allowed" });
|
|
8649
|
+
return;
|
|
8650
|
+
}
|
|
8651
|
+
if (req.headers["x-serenity-ui"] !== "1") {
|
|
8652
|
+
sendJson$2(res, 403, { error: "仅限 WebUI(key 属敏感凭据)" });
|
|
8653
|
+
return;
|
|
8654
|
+
}
|
|
8655
|
+
const settings = readAdvancedSettings();
|
|
8656
|
+
const { readSimpleSettings } = await Promise.resolve().then(() => settings_section_exports);
|
|
8657
|
+
const simple = readSimpleSettings();
|
|
8658
|
+
const allowed = settings.publicAsk.allowed;
|
|
8659
|
+
const port = simple.acpHttpPort ?? 3100;
|
|
8660
|
+
const base = `http://127.0.0.1:${port}`;
|
|
8661
|
+
const containerUrls = allowed.map((name) => ({
|
|
8662
|
+
name,
|
|
8663
|
+
url: `${base}/c/${encodeURIComponent(name)}`
|
|
8664
|
+
}));
|
|
8665
|
+
sendJson$2(res, 200, {
|
|
8666
|
+
enabled: simple.publicAskEnabled ?? false,
|
|
8667
|
+
port,
|
|
8668
|
+
key: ensurePublicAskKey(),
|
|
8669
|
+
allowed,
|
|
8670
|
+
urls: containerUrls,
|
|
8671
|
+
listUrl: `${base}/`
|
|
8672
|
+
});
|
|
8673
|
+
} catch (err) {
|
|
8674
|
+
sendJson$2(res, 400, { error: err.message ?? String(err) });
|
|
8675
|
+
}
|
|
8676
|
+
}
|
|
8677
|
+
});
|
|
8566
8678
|
}
|
|
8567
8679
|
//#endregion
|
|
8568
8680
|
//#region src/seams/env.ts
|
|
@@ -9465,6 +9577,171 @@ function registerGateway(ctx) {
|
|
|
9465
9577
|
});
|
|
9466
9578
|
}
|
|
9467
9579
|
//#endregion
|
|
9580
|
+
//#region src/output-guard.ts
|
|
9581
|
+
/**
|
|
9582
|
+
* output-guard.ts — 最终输出敏感词检测 + 打回重生成(v1.26.3,S142 用户需求)
|
|
9583
|
+
*
|
|
9584
|
+
* 认知容器对外提供认知结果时,**支撑宁静号自身的凭据与机制**不应透露给用户。
|
|
9585
|
+
* 用户拍板方案(讨论稿定稿):不做工具结果 redaction(那是模型工作内存),
|
|
9586
|
+
* 改为**卡最终输出**——`agent/turn-stopping`(serial)时取 turn 最后 assistant 文本,
|
|
9587
|
+
* 命中敏感词表 → `agent.steer()` 打回重生成,直到合规或达上限。
|
|
9588
|
+
*
|
|
9589
|
+
* 敏感词表(有限,ACC 立场,用户确认"这样比较全面了"):
|
|
9590
|
+
* ① 凭据词 — localstore.json credentials 条目名 + 值精确匹配(运行时读)
|
|
9591
|
+
* ② 机制词 — 内部结构静态词(插件名/配置路径/端口/内部实现词)
|
|
9592
|
+
* ③ MSM 词 — mech-registry.json 注册的工具名(用户补充:msm_list 里的工具名)
|
|
9593
|
+
* **不含** 公开概念(宁静号/Serenity/认知容器/EAP/CCE/Neat——正常交流词,误伤灾难)
|
|
9594
|
+
*
|
|
9595
|
+
* 机制依据(DSH 官方):agent/turn-stopping 注释 "a listener that objects steers and
|
|
9596
|
+
* the machine re-reads its inbox: fresh steering runs another step, none closes the
|
|
9597
|
+
* turn"——steer 打回是官方支持的"重生成"通道(v1.22.5 rebuild 自动继续同款先例)。
|
|
9598
|
+
*/
|
|
9599
|
+
/** 插件/内部实现词(静态内置;命中即视为机制泄露) */
|
|
9600
|
+
const MECHANISM_WORDS = [
|
|
9601
|
+
"dsh-serenity-hooks",
|
|
9602
|
+
"serenity-hooks",
|
|
9603
|
+
"serenity-hooks.json",
|
|
9604
|
+
"mech-registry",
|
|
9605
|
+
"mech-registry.json",
|
|
9606
|
+
"localstore.json",
|
|
9607
|
+
".opencode/serenity.json",
|
|
9608
|
+
"AGENT_SESSIONS",
|
|
9609
|
+
"agent/session-start",
|
|
9610
|
+
"agent/turn-stopping",
|
|
9611
|
+
"tools/pre-execute",
|
|
9612
|
+
"tools/post-execute",
|
|
9613
|
+
"session_rebuild",
|
|
9614
|
+
"Trajectory Steward",
|
|
9615
|
+
"first-anchor",
|
|
9616
|
+
"拦截缝",
|
|
9617
|
+
"装配层",
|
|
9618
|
+
"acp-core",
|
|
9619
|
+
"skiff-core",
|
|
9620
|
+
"skiff-registry",
|
|
9621
|
+
"skiff-role",
|
|
9622
|
+
"output-guard"
|
|
9623
|
+
];
|
|
9624
|
+
/** 内部端口(宁静号服务面) */
|
|
9625
|
+
const MECHANISM_PORTS = [
|
|
9626
|
+
"3080",
|
|
9627
|
+
"3081",
|
|
9628
|
+
"3099",
|
|
9629
|
+
"3100"
|
|
9630
|
+
];
|
|
9631
|
+
/**
|
|
9632
|
+
* 构建敏感词表(每次调用实时构建——词源文件小,开销可接受):
|
|
9633
|
+
* ① localstore.json credentials:条目名(UPPER_SNAKE)+ 值(精确匹配)
|
|
9634
|
+
* ② 静态机制词 + 端口
|
|
9635
|
+
* ③ mech-registry 注册的 MSM 工具名(loadMsmEntries 全注册表去重)
|
|
9636
|
+
* @param root CCC 根(localstore.json / mech-registry 所在)
|
|
9637
|
+
*/
|
|
9638
|
+
function buildSensitiveTable(root) {
|
|
9639
|
+
const exact = /* @__PURE__ */ new Set();
|
|
9640
|
+
const substring = [];
|
|
9641
|
+
try {
|
|
9642
|
+
const p = join(root, "localstore.json");
|
|
9643
|
+
if (existsSync(p)) {
|
|
9644
|
+
const creds = JSON.parse(readFileSync(p, "utf-8").replace(/^\uFEFF/, ""))?.credentials;
|
|
9645
|
+
if (creds && typeof creds === "object") for (const [name, value] of Object.entries(creds)) {
|
|
9646
|
+
if (name) exact.add(name);
|
|
9647
|
+
if (typeof value === "string" && value !== "") exact.add(value);
|
|
9648
|
+
}
|
|
9649
|
+
}
|
|
9650
|
+
} catch {}
|
|
9651
|
+
for (const w of MECHANISM_WORDS) substring.push(w);
|
|
9652
|
+
for (const p of MECHANISM_PORTS) substring.push(p);
|
|
9653
|
+
try {
|
|
9654
|
+
for (const entry of loadMsmEntries(root)) if (entry.name) exact.add(entry.name);
|
|
9655
|
+
} catch {}
|
|
9656
|
+
return {
|
|
9657
|
+
exact,
|
|
9658
|
+
substring,
|
|
9659
|
+
sources: {
|
|
9660
|
+
exact: [...exact],
|
|
9661
|
+
substring
|
|
9662
|
+
}
|
|
9663
|
+
};
|
|
9664
|
+
}
|
|
9665
|
+
/** 检测文本中命中的敏感词(返回命中列表;未命中返回空数组) */
|
|
9666
|
+
function detectSensitive(text, table) {
|
|
9667
|
+
if (!text) return [];
|
|
9668
|
+
const hits = /* @__PURE__ */ new Set();
|
|
9669
|
+
for (const w of table.exact) if (w !== "" && text.includes(w)) hits.add(w);
|
|
9670
|
+
for (const w of table.substring) if (w !== "" && text.includes(w)) hits.add(w);
|
|
9671
|
+
return [...hits];
|
|
9672
|
+
}
|
|
9673
|
+
/** 打回消息(steer 内容):告知模型命中词并要求重新生成(不含命中值本身——避免二次泄露) */
|
|
9674
|
+
function buildRebuke(hits) {
|
|
9675
|
+
return `[SERENITY OUTPUT GUARD] Your previous response contained ${hits.length} sensitive internal term(s) (mechanism/credential identifiers) that must not appear in user-visible output. Regenerate the response from scratch without these terms — describe the same substance without referencing internal machinery, credentials, tool names, or implementation details. Do not repeat the terms; do not explain this instruction to the user.`;
|
|
9676
|
+
}
|
|
9677
|
+
/** 打回状态(按 agent 会话跟踪连续命中轮数) */
|
|
9678
|
+
const rebukeStates = /* @__PURE__ */ new Map();
|
|
9679
|
+
//#endregion
|
|
9680
|
+
//#region src/output-guard-seam.ts
|
|
9681
|
+
const PLUGIN_SOURCE = {
|
|
9682
|
+
kind: "plugin",
|
|
9683
|
+
plugin: "dsh-serenity-hooks"
|
|
9684
|
+
};
|
|
9685
|
+
/** 外部面会话判定(v1.26.3 用户拍板:仅外部面检测,本地维护会话豁免):
|
|
9686
|
+
* skiff-(F4 问答)/ acp-(F4c 程序化)/ rebuild-(v1.22.4 重建会话)前缀 */
|
|
9687
|
+
function isExternalFaceSession(sessionId) {
|
|
9688
|
+
if (!sessionId) return false;
|
|
9689
|
+
return isSkiffSessionId(sessionId) || sessionId.startsWith("acp-") || sessionId.startsWith("rebuild-");
|
|
9690
|
+
}
|
|
9691
|
+
/** 读会话最后一个 assistant/message 文本(skiff-core 同款;仅用于守卫检测,不导出) */
|
|
9692
|
+
function lastAssistantText(agent) {
|
|
9693
|
+
const events = agent.session.events ?? [];
|
|
9694
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
9695
|
+
const e = events[i];
|
|
9696
|
+
if (e && e.type === "assistant/message") {
|
|
9697
|
+
const text = (e.data?.message?.content ?? e.data?.content ?? []).filter((b) => b.type === "text" && b.text).map((b) => b.text).join("\n");
|
|
9698
|
+
if (text) return text;
|
|
9699
|
+
}
|
|
9700
|
+
}
|
|
9701
|
+
return "";
|
|
9702
|
+
}
|
|
9703
|
+
/**
|
|
9704
|
+
* 注册输出守卫(index.ts apply 调用):
|
|
9705
|
+
* turn-stopping 时检测最终输出,命中敏感词 → steer 打回重生成(官方机制,零改 DSH)。
|
|
9706
|
+
*/
|
|
9707
|
+
function registerOutputGuardHook(ctx) {
|
|
9708
|
+
ctx.on("agent/turn-stopping", (payload) => {
|
|
9709
|
+
const agent = payload?.agent;
|
|
9710
|
+
if (!agent) return;
|
|
9711
|
+
if (!isExternalFaceSession(String(agent.session.id ?? ""))) return;
|
|
9712
|
+
const root = findSerenityRoot(agent.session.header?.cwd ?? process.cwd());
|
|
9713
|
+
if (!root) return;
|
|
9714
|
+
const id = agent.id;
|
|
9715
|
+
const text = lastAssistantText(agent);
|
|
9716
|
+
if (!text) return;
|
|
9717
|
+
const hits = detectSensitive(text, buildSensitiveTable(root));
|
|
9718
|
+
if (hits.length === 0) {
|
|
9719
|
+
rebukeStates.delete(id);
|
|
9720
|
+
return;
|
|
9721
|
+
}
|
|
9722
|
+
const st = rebukeStates.get(id) ?? { consecutive: 0 };
|
|
9723
|
+
st.consecutive += 1;
|
|
9724
|
+
rebukeStates.set(id, st);
|
|
9725
|
+
if (st.consecutive > 3) {
|
|
9726
|
+
rebukeStates.delete(id);
|
|
9727
|
+
console.warn(`[serenity-hooks] output-guard: giving up after 3 rebukes (agent=${id}, turn=${payload.turn ?? "?"}) — response kept for manual review`);
|
|
9728
|
+
return;
|
|
9729
|
+
}
|
|
9730
|
+
console.log(`[serenity-hooks] output-guard: rebuke ${st.consecutive}/3 (agent=${id}, turn=${payload.turn ?? "?"}, hits=${hits.length})`);
|
|
9731
|
+
try {
|
|
9732
|
+
agent.steer(createUserMessage({
|
|
9733
|
+
content: [{
|
|
9734
|
+
type: "text",
|
|
9735
|
+
text: buildRebuke(hits)
|
|
9736
|
+
}],
|
|
9737
|
+
source: PLUGIN_SOURCE
|
|
9738
|
+
}));
|
|
9739
|
+
} catch (error) {
|
|
9740
|
+
console.warn(`[serenity-hooks] output-guard steer failed: ${String(error?.message ?? error)}`);
|
|
9741
|
+
}
|
|
9742
|
+
});
|
|
9743
|
+
}
|
|
9744
|
+
//#endregion
|
|
9468
9745
|
//#region node_modules/.pnpm/marked@18.0.11/node_modules/marked/lib/marked.esm.js
|
|
9469
9746
|
/**
|
|
9470
9747
|
* marked v18.0.11 - a markdown parser
|
|
@@ -11135,6 +11412,13 @@ x.lex;
|
|
|
11135
11412
|
*
|
|
11136
11413
|
* 实验性质:未开启时零资源占用(无监听、无 agent 创建)。
|
|
11137
11414
|
*/
|
|
11415
|
+
var skiff_debug_exports = /* @__PURE__ */ __exportAll({
|
|
11416
|
+
discoverCccs: () => discoverCccs,
|
|
11417
|
+
renderSkiffMarkdown: () => renderSkiffMarkdown,
|
|
11418
|
+
skiffDebugPage: () => skiffDebugPage,
|
|
11419
|
+
startSkiffDebugServer: () => startSkiffDebugServer,
|
|
11420
|
+
stopSkiffDebugServer: () => stopSkiffDebugServer
|
|
11421
|
+
});
|
|
11138
11422
|
/** 运行中的调试服务(单实例;进程级) */
|
|
11139
11423
|
let active$1 = null;
|
|
11140
11424
|
function readBody$1(req) {
|
|
@@ -11685,11 +11969,11 @@ function rpcError(id, code, message) {
|
|
|
11685
11969
|
* node:http 服务(默认关,仅监听 127.0.0.1;启停 = 人工——设置面板「Serenity」
|
|
11686
11970
|
* 页 ACP 区块开关 / 问答页开关,不随插件加载自动启动;仿 skiff 调试服务模式)。
|
|
11687
11971
|
*
|
|
11688
|
-
* - POST /
|
|
11689
|
-
* - GET /
|
|
11690
|
-
*
|
|
11691
|
-
* - POST /ask
|
|
11692
|
-
*
|
|
11972
|
+
* - POST / → JSON-RPC 2.0 单帧/批处理(acpEnabled 门控;ACP 程序化面)
|
|
11973
|
+
* - GET / → 容器列表页(publicAskEnabled 门控;v1.26.2:只列已开放容器)
|
|
11974
|
+
* - GET /c/<name> → 单容器问答页(v1.26.2:URL 体现容器名,用户只输 key)
|
|
11975
|
+
* - POST /c/<name>/ask → 该容器问答(key 校验 + allowed 白名单校验)
|
|
11976
|
+
* - POST /ask → 兼容旧形态 { key, ccc, ... }(key 校验 + allowed 校验)
|
|
11693
11977
|
*
|
|
11694
11978
|
* 与 skiff 调试页(人工测试面)共享同一会话核心(acp-core → skiff-core);
|
|
11695
11979
|
* 企业微信桥(F4c-2)同进程直调 acp-core 处理器函数,不经本端点。
|
|
@@ -11780,17 +12064,47 @@ async function handle(ctx, defaultRoot, req, res) {
|
|
|
11780
12064
|
sendJson(res, 200, await dispatchRpc(new AcpServer(ctx), body));
|
|
11781
12065
|
return;
|
|
11782
12066
|
}
|
|
11783
|
-
if (
|
|
11784
|
-
if (
|
|
12067
|
+
if (!s.publicAskEnabled) {
|
|
12068
|
+
if (req.method === "GET" && (url === "/" || url.startsWith("/c/"))) {
|
|
11785
12069
|
sendHtml(res, publicAskDisabledHtml());
|
|
11786
12070
|
return;
|
|
11787
12071
|
}
|
|
11788
|
-
|
|
12072
|
+
if (req.method === "POST" && (url === "/ask" || url.startsWith("/c/"))) {
|
|
12073
|
+
sendJson(res, 403, { error: "public ask disabled (enable in Serenity settings)" });
|
|
12074
|
+
return;
|
|
12075
|
+
}
|
|
12076
|
+
sendJson(res, 404, { error: "not found" });
|
|
11789
12077
|
return;
|
|
11790
12078
|
}
|
|
11791
|
-
|
|
11792
|
-
|
|
11793
|
-
|
|
12079
|
+
const containerAllowed = (name) => {
|
|
12080
|
+
const allowed = readAdvancedSettings().publicAsk.allowed;
|
|
12081
|
+
return allowed.length === 0 || allowed.includes(name);
|
|
12082
|
+
};
|
|
12083
|
+
const cMatch = /^\/c\/([^/]+)$/.exec(url);
|
|
12084
|
+
if (req.method === "GET" && cMatch) {
|
|
12085
|
+
const name = decodeURIComponent(cMatch[1] ?? "");
|
|
12086
|
+
const ccc = (await discoverCccs(ctx, defaultRoot || (findSerenityRoot(process.cwd()) ?? ""))).find((c) => c.name === name);
|
|
12087
|
+
if (!ccc) {
|
|
12088
|
+
sendHtml(res, publicAskContainerUnknownHtml(name));
|
|
12089
|
+
return;
|
|
12090
|
+
}
|
|
12091
|
+
if (!containerAllowed(name)) {
|
|
12092
|
+
sendHtml(res, publicAskContainerClosedHtml(name));
|
|
12093
|
+
return;
|
|
12094
|
+
}
|
|
12095
|
+
sendHtml(res, publicAskContainerPage(ccc));
|
|
12096
|
+
return;
|
|
12097
|
+
}
|
|
12098
|
+
if (req.method === "GET" && url === "/") {
|
|
12099
|
+
sendHtml(res, publicAskListPage((await discoverCccs(ctx, defaultRoot || (findSerenityRoot(process.cwd()) ?? ""))).filter((c) => containerAllowed(c.name))));
|
|
12100
|
+
return;
|
|
12101
|
+
}
|
|
12102
|
+
const aMatch = /^\/c\/([^/]+)\/ask$/.exec(url);
|
|
12103
|
+
if (req.method === "POST" && aMatch) {
|
|
12104
|
+
const name = decodeURIComponent(aMatch[1] ?? "");
|
|
12105
|
+
const ccc = (await discoverCccs(ctx, defaultRoot || (findSerenityRoot(process.cwd()) ?? ""))).find((c) => c.name === name);
|
|
12106
|
+
if (!ccc || !containerAllowed(name)) {
|
|
12107
|
+
sendJson(res, 403, { error: `container "${name}" is not open for public ask` });
|
|
11794
12108
|
return;
|
|
11795
12109
|
}
|
|
11796
12110
|
let body;
|
|
@@ -11802,49 +12116,36 @@ async function handle(ctx, defaultRoot, req, res) {
|
|
|
11802
12116
|
sendJson(res, 400, { error: "invalid JSON body" });
|
|
11803
12117
|
return;
|
|
11804
12118
|
}
|
|
11805
|
-
|
|
11806
|
-
|
|
11807
|
-
|
|
11808
|
-
|
|
11809
|
-
|
|
11810
|
-
|
|
11811
|
-
|
|
11812
|
-
|
|
11813
|
-
|
|
11814
|
-
|
|
12119
|
+
await handleAskParsed(ctx, ccc, body, res);
|
|
12120
|
+
return;
|
|
12121
|
+
}
|
|
12122
|
+
if (req.method === "POST" && url === "/ask") {
|
|
12123
|
+
let body;
|
|
12124
|
+
try {
|
|
12125
|
+
const parsed = JSON.parse(await readBody(req));
|
|
12126
|
+
if (parsed === null || typeof parsed !== "object") throw new Error("not an object");
|
|
12127
|
+
body = parsed;
|
|
12128
|
+
} catch {
|
|
12129
|
+
sendJson(res, 400, { error: "invalid JSON body" });
|
|
11815
12130
|
return;
|
|
11816
12131
|
}
|
|
11817
|
-
|
|
11818
|
-
|
|
11819
|
-
|
|
12132
|
+
const cccValue = typeof body.ccc === "string" && body.ccc !== "" ? body.ccc : void 0;
|
|
12133
|
+
const nameValue = typeof body.name === "string" && body.name !== "" ? body.name : void 0;
|
|
12134
|
+
let ccc;
|
|
12135
|
+
if (nameValue) ccc = (await discoverCccs(ctx, defaultRoot || (findSerenityRoot(process.cwd()) ?? ""))).find((c) => c.name === nameValue);
|
|
12136
|
+
else if (cccValue) {
|
|
12137
|
+
ccc = (await discoverCccs(ctx, defaultRoot || (findSerenityRoot(process.cwd()) ?? ""))).find((c) => c.root === cccValue);
|
|
12138
|
+
if (!ccc && cccValue !== "") ccc = {
|
|
12139
|
+
root: cccValue,
|
|
12140
|
+
name: cccValue.split("/").filter(Boolean).pop() ?? cccValue,
|
|
12141
|
+
roles: [...readSkiffRoles(cccValue).keys()]
|
|
12142
|
+
};
|
|
11820
12143
|
}
|
|
11821
|
-
|
|
11822
|
-
|
|
11823
|
-
const effectiveRole = roleName && explicit ? roleName : roles.keys().next().value;
|
|
11824
|
-
const effectiveCfg = (roleName && explicit ? explicit : effectiveRole ? roles.get(effectiveRole) : void 0) ?? void 0;
|
|
11825
|
-
if (!effectiveRole || !effectiveCfg) {
|
|
11826
|
-
sendJson(res, 400, { error: `no skiff role available in ccc: ${ccc}` });
|
|
12144
|
+
if (!ccc || !containerAllowed(ccc.name)) {
|
|
12145
|
+
sendJson(res, 403, { error: "container is not open for public ask" });
|
|
11827
12146
|
return;
|
|
11828
12147
|
}
|
|
11829
|
-
|
|
11830
|
-
let continued = false;
|
|
11831
|
-
if (sessionId) {
|
|
11832
|
-
const info = skiffSessionInfo(sessionId);
|
|
11833
|
-
const live = getSkiffAgent(sessionId);
|
|
11834
|
-
if (info && live && info.role === effectiveRole && info.ccc === ccc) {
|
|
11835
|
-
agent = live;
|
|
11836
|
-
continued = true;
|
|
11837
|
-
}
|
|
11838
|
-
}
|
|
11839
|
-
if (!agent) agent = (await createSkiffAgent(ctx, ccc, effectiveRole, effectiveCfg, readHandymanConfig(ccc)?.defaultModel)).agent;
|
|
11840
|
-
const result = await askSkiff(ctx, agent, question, 0);
|
|
11841
|
-
sendJson(res, 200, {
|
|
11842
|
-
answer: result.answer,
|
|
11843
|
-
answer_html: renderSkiffMarkdown(result.answer),
|
|
11844
|
-
sessionId: result.sessionId,
|
|
11845
|
-
continued,
|
|
11846
|
-
trajectory: result.trajectory
|
|
11847
|
-
});
|
|
12148
|
+
await handleAskParsed(ctx, ccc, body, res);
|
|
11848
12149
|
return;
|
|
11849
12150
|
}
|
|
11850
12151
|
sendJson(res, 404, { error: "not found" });
|
|
@@ -11852,12 +12153,51 @@ async function handle(ctx, defaultRoot, req, res) {
|
|
|
11852
12153
|
sendJson(res, 500, { error: err?.message ?? String(err) });
|
|
11853
12154
|
}
|
|
11854
12155
|
}
|
|
11855
|
-
|
|
11856
|
-
|
|
11857
|
-
|
|
11858
|
-
|
|
11859
|
-
|
|
12156
|
+
/**
|
|
12157
|
+
* 单容器问答核心(/c/<name>/ask 与 /ask 共用):
|
|
12158
|
+
* body { key, role?, question, sessionId? } → key 校验(401)→ 角色缺省取 CCC 第一个
|
|
12159
|
+
* → 会话延续(v1.25.10 语义,静默新建)→ askSkiff(0 全量轨迹) → { answer, answer_html, sessionId, continued, trajectory }
|
|
12160
|
+
*/
|
|
12161
|
+
async function handleAskParsed(ctx, ccc, body, res) {
|
|
12162
|
+
if (!verifyPublicAskKey(typeof body.key === "string" ? body.key : void 0)) {
|
|
12163
|
+
sendJson(res, 401, { error: "invalid or missing key" });
|
|
12164
|
+
return;
|
|
12165
|
+
}
|
|
12166
|
+
const root = ccc.root;
|
|
12167
|
+
const roleName = typeof body.role === "string" && body.role !== "" ? body.role : void 0;
|
|
12168
|
+
const question = typeof body.question === "string" ? body.question : "";
|
|
12169
|
+
const sessionId = typeof body.sessionId === "string" && body.sessionId !== "" ? body.sessionId : void 0;
|
|
12170
|
+
if (!question.trim()) {
|
|
12171
|
+
sendJson(res, 400, { error: "empty question" });
|
|
12172
|
+
return;
|
|
11860
12173
|
}
|
|
12174
|
+
const roles = readSkiffRoles(root);
|
|
12175
|
+
const explicit = roleName ? roles.get(roleName) : void 0;
|
|
12176
|
+
const effectiveRole = roleName && explicit ? roleName : roles.keys().next().value;
|
|
12177
|
+
const effectiveCfg = (roleName && explicit ? explicit : effectiveRole ? roles.get(effectiveRole) : void 0) ?? void 0;
|
|
12178
|
+
if (!effectiveRole || !effectiveCfg) {
|
|
12179
|
+
sendJson(res, 400, { error: `no skiff role available in ccc: ${root}` });
|
|
12180
|
+
return;
|
|
12181
|
+
}
|
|
12182
|
+
let agent;
|
|
12183
|
+
let continued = false;
|
|
12184
|
+
if (sessionId) {
|
|
12185
|
+
const info = skiffSessionInfo(sessionId);
|
|
12186
|
+
const live = getSkiffAgent(sessionId);
|
|
12187
|
+
if (info && live && info.role === effectiveRole && info.ccc === root) {
|
|
12188
|
+
agent = live;
|
|
12189
|
+
continued = true;
|
|
12190
|
+
}
|
|
12191
|
+
}
|
|
12192
|
+
if (!agent) agent = (await createSkiffAgent(ctx, root, effectiveRole, effectiveCfg, readHandymanConfig(root)?.defaultModel)).agent;
|
|
12193
|
+
const result = await askSkiff(ctx, agent, question, 0);
|
|
12194
|
+
sendJson(res, 200, {
|
|
12195
|
+
answer: result.answer,
|
|
12196
|
+
answer_html: renderSkiffMarkdown(result.answer),
|
|
12197
|
+
sessionId: result.sessionId,
|
|
12198
|
+
continued,
|
|
12199
|
+
trajectory: result.trajectory
|
|
12200
|
+
});
|
|
11861
12201
|
}
|
|
11862
12202
|
/** 问答页未启用提示页 */
|
|
11863
12203
|
function publicAskDisabledHtml() {
|
|
@@ -11868,15 +12208,28 @@ function publicAskDisabledHtml() {
|
|
|
11868
12208
|
<p>问答页未启用(请在 dsh 设置面板「Serenity」页开启「建议问答」)。</p>
|
|
11869
12209
|
</body></html>`;
|
|
11870
12210
|
}
|
|
11871
|
-
/**
|
|
11872
|
-
function
|
|
12211
|
+
/** 容器不存在提示页(URL 容器名不在已发现 CCC 中) */
|
|
12212
|
+
function publicAskContainerUnknownHtml(name) {
|
|
11873
12213
|
return `<!DOCTYPE html>
|
|
11874
|
-
<html lang="zh">
|
|
11875
|
-
<
|
|
11876
|
-
<
|
|
11877
|
-
<
|
|
11878
|
-
<
|
|
11879
|
-
|
|
12214
|
+
<html lang="zh"><head><meta charset="utf-8"><title>Serenity Public Ask</title></head>
|
|
12215
|
+
<body style="font-family:system-ui;padding:48px;text-align:center;color:#57606a">
|
|
12216
|
+
<h2>Serenity Public Ask</h2>
|
|
12217
|
+
<p>认知容器「${name}」不存在。</p>
|
|
12218
|
+
<p style="font-size:13px"><a href="/" style="color:#0ba875">返回容器列表</a></p>
|
|
12219
|
+
</body></html>`;
|
|
12220
|
+
}
|
|
12221
|
+
/** 容器未开放提示页(白名单外) */
|
|
12222
|
+
function publicAskContainerClosedHtml(name) {
|
|
12223
|
+
return `<!DOCTYPE html>
|
|
12224
|
+
<html lang="zh"><head><meta charset="utf-8"><title>Serenity Public Ask</title></head>
|
|
12225
|
+
<body style="font-family:system-ui;padding:48px;text-align:center;color:#57606a">
|
|
12226
|
+
<h2>Serenity Public Ask</h2>
|
|
12227
|
+
<p>认知容器「${name}」当前未开放问答(管理员在设置面板「开放容器」中配置)。</p>
|
|
12228
|
+
<p style="font-size:13px"><a href="/" style="color:#0ba875">返回容器列表</a></p>
|
|
12229
|
+
</body></html>`;
|
|
12230
|
+
}
|
|
12231
|
+
/** 页面基础样式(列表页 + 单容器页共用) */
|
|
12232
|
+
const ASK_CSS = `
|
|
11880
12233
|
:root { color-scheme: light dark; }
|
|
11881
12234
|
body { font-family: system-ui, -apple-system, sans-serif; margin: 0; padding: 24px; background: #f6f7f9; color: #1f2328; }
|
|
11882
12235
|
@media (prefers-color-scheme: dark) { body { background: #1a1b1e; color: #e6e6e6; } }
|
|
@@ -11889,6 +12242,11 @@ function publicAskPage(cccs, _hasKey) {
|
|
|
11889
12242
|
textarea { min-height: 72px; resize: vertical; }
|
|
11890
12243
|
button { margin-top: 12px; padding: 9px 18px; border-radius: 8px; border: 0; background: #0ba875; color: #fff; font-size: 14px; font-weight: 600; cursor: pointer; }
|
|
11891
12244
|
button:disabled { opacity: .55; cursor: wait; }
|
|
12245
|
+
.card { background: #fff; border: 1px solid #d0d7de; border-radius: 10px; padding: 14px 16px; margin-bottom: 10px; display: block; text-decoration: none; color: inherit; }
|
|
12246
|
+
@media (prefers-color-scheme: dark) { .card { background: #26282c; border-color: #3a3d42; } }
|
|
12247
|
+
.card:hover { border-color: #0ba875; }
|
|
12248
|
+
.card .cname { font-weight: 600; font-size: 15px; }
|
|
12249
|
+
.card .cmeta { opacity: .6; font-size: 12px; margin-top: 2px; }
|
|
11892
12250
|
#answer { background: #fff; border: 1px solid #d0d7de; border-radius: 8px; padding: 12px; margin-top: 16px; font-size: 14px; line-height: 1.6; min-height: 48px; word-break: break-word; }
|
|
11893
12251
|
@media (prefers-color-scheme: dark) { #answer { background: #26282c; border-color: #3a3d42; } }
|
|
11894
12252
|
#answer p { margin: 6px 0; }
|
|
@@ -11900,41 +12258,89 @@ function publicAskPage(cccs, _hasKey) {
|
|
|
11900
12258
|
.muted { opacity: .6; font-size: 13px; }
|
|
11901
12259
|
details.think { margin: 8px 0; border: 1px solid rgba(210,153,34,.4); border-radius: 8px; background: rgba(210,153,34,.06); }
|
|
11902
12260
|
details.think summary { cursor: pointer; padding: 6px 10px; font-size: 12px; color: #d29922; font-weight: 600; }
|
|
11903
|
-
|
|
12261
|
+
.empty { text-align: center; opacity: .7; padding: 24px 0; }
|
|
12262
|
+
.back { display: inline-block; margin-bottom: 12px; font-size: 13px; color: #0ba875; text-decoration: none; }
|
|
12263
|
+
.ccname { font-weight: 600; color: #0ba875; }
|
|
12264
|
+
`;
|
|
12265
|
+
/** 容器列表页(v1.26.2):只列已开放容器,点击进入 /c/<name> */
|
|
12266
|
+
function publicAskListPage(cccs) {
|
|
12267
|
+
const cards = cccs.map((c) => `
|
|
12268
|
+
<a class="card" href="/c/${encodeURIComponent(c.name)}">
|
|
12269
|
+
<div class="cname">${c.name}</div>
|
|
12270
|
+
<div class="cmeta">${c.roles.length} 个问答角色 · 点击进入</div>
|
|
12271
|
+
</a>`).join("") || "<div class=\"empty\">暂无可问答的认知容器(管理员尚未开放)</div>";
|
|
12272
|
+
return `<!DOCTYPE html>
|
|
12273
|
+
<html lang="zh">
|
|
12274
|
+
<head>
|
|
12275
|
+
<meta charset="utf-8">
|
|
12276
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
12277
|
+
<title>Serenity Public Ask</title>
|
|
12278
|
+
<style>${ASK_CSS}</style>
|
|
11904
12279
|
</head>
|
|
11905
12280
|
<body>
|
|
11906
12281
|
<main>
|
|
11907
12282
|
<h1>Serenity Public Ask</h1>
|
|
11908
|
-
<div class="sub"
|
|
12283
|
+
<div class="sub">选择认知容器开始提问(访问需 key,key 由管理员提供)</div>
|
|
12284
|
+
${cards}
|
|
12285
|
+
</main>
|
|
12286
|
+
</body>
|
|
12287
|
+
</html>`;
|
|
12288
|
+
}
|
|
12289
|
+
/** 单容器问答页(v1.26.2):URL 锁定容器,key + 角色;key 自动存 localStorage */
|
|
12290
|
+
function publicAskContainerPage(ccc) {
|
|
12291
|
+
const data = JSON.stringify({
|
|
12292
|
+
name: ccc.name,
|
|
12293
|
+
root: ccc.root,
|
|
12294
|
+
roles: ccc.roles
|
|
12295
|
+
}).replace(/</g, "\\u003c");
|
|
12296
|
+
return `<!DOCTYPE html>
|
|
12297
|
+
<html lang="zh">
|
|
12298
|
+
<head>
|
|
12299
|
+
<meta charset="utf-8">
|
|
12300
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
12301
|
+
<title>Serenity Public Ask · ${ccc.name}</title>
|
|
12302
|
+
<style>${ASK_CSS}</style>
|
|
12303
|
+
</head>
|
|
12304
|
+
<body>
|
|
12305
|
+
<main>
|
|
12306
|
+
<a class="back" href="/">← 容器列表</a>
|
|
12307
|
+
<h1>Serenity Public Ask</h1>
|
|
12308
|
+
<div class="sub">认知容器:<span class="ccname">${ccc.name}</span></div>
|
|
11909
12309
|
<label for="key">访问 Key</label>
|
|
11910
12310
|
<input id="key" type="password" placeholder="请输入访问 key" autocomplete="off">
|
|
11911
|
-
<label for="
|
|
11912
|
-
<select id="
|
|
12311
|
+
<label for="role">问答角色</label>
|
|
12312
|
+
<select id="role"></select>
|
|
11913
12313
|
<label for="q">问题</label>
|
|
11914
12314
|
<textarea id="q" placeholder="向该认知容器提问…"></textarea>
|
|
11915
12315
|
<button id="ask">提问</button>
|
|
11916
12316
|
<div id="answer" class="muted">等待提问…</div>
|
|
11917
12317
|
</main>
|
|
11918
|
-
<script id="ask-data" type="application/json">${
|
|
12318
|
+
<script id="ask-data" type="application/json">${data}<\/script>
|
|
11919
12319
|
<script>
|
|
11920
|
-
const
|
|
12320
|
+
const CCC = JSON.parse(document.getElementById('ask-data').textContent)
|
|
12321
|
+
const KEY_STORE = 'serenity-public-ask-key'
|
|
11921
12322
|
const keyInput = document.getElementById('key')
|
|
11922
|
-
const
|
|
12323
|
+
const roleSel = document.getElementById('role')
|
|
11923
12324
|
const btn = document.getElementById('ask')
|
|
11924
12325
|
const answer = document.getElementById('answer')
|
|
11925
12326
|
let sessionId = ''
|
|
11926
|
-
|
|
12327
|
+
// 角色下拉(v1.26.2 用户:3100 应能选择角色)
|
|
12328
|
+
roleSel.innerHTML = (CCC.roles || []).map((r) => '<option value="' + r + '">' + r + '</option>').join('') || '<option value="">(无角色)</option>'
|
|
12329
|
+
// key 自动从 localStorage 恢复(v1.26.2:输入一次后浏览器记住,无需每次粘贴)
|
|
12330
|
+
try { const saved = localStorage.getItem(KEY_STORE); if (saved) keyInput.value = saved } catch {}
|
|
11927
12331
|
btn.addEventListener('click', async () => {
|
|
11928
|
-
const ccc = cccSel.value
|
|
11929
12332
|
const q = document.getElementById('q').value.trim()
|
|
11930
12333
|
const key = keyInput.value.trim()
|
|
11931
|
-
|
|
12334
|
+
const role = roleSel.value || undefined
|
|
12335
|
+
if (!q) { answer.className = 'err'; answer.textContent = '请输入问题'; return }
|
|
11932
12336
|
if (!key) { answer.className = 'err'; answer.textContent = '请输入访问 key(没有 key 无法使用)'; return }
|
|
12337
|
+
// 输入即记住(保存到 localStorage;下次自动填充)
|
|
12338
|
+
try { localStorage.setItem(KEY_STORE, key) } catch {}
|
|
11933
12339
|
btn.disabled = true
|
|
11934
12340
|
answer.className = 'muted'
|
|
11935
12341
|
answer.textContent = '运行中…'
|
|
11936
12342
|
try {
|
|
11937
|
-
const res = await fetch('/ask', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key,
|
|
12343
|
+
const res = await fetch('/c/' + encodeURIComponent(CCC.name) + '/ask', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key, role, question: q, sessionId: sessionId || undefined }) })
|
|
11938
12344
|
const data = await res.json()
|
|
11939
12345
|
if (!res.ok) { sessionId = ''; throw new Error(data.error || ('HTTP ' + res.status)) }
|
|
11940
12346
|
answer.className = ''
|
|
@@ -12026,6 +12432,7 @@ function apply(ctx, config) {
|
|
|
12026
12432
|
registerSettingsSection(ctx, config);
|
|
12027
12433
|
registerGateway(ctx);
|
|
12028
12434
|
registerRebuildTurnHook(ctx);
|
|
12435
|
+
registerOutputGuardHook(ctx);
|
|
12029
12436
|
if (config.env) registerEnv(ctx);
|
|
12030
12437
|
if (config.opencodeSkills) registerOpencodeSkills(ctx);
|
|
12031
12438
|
registerBootstrap(ctx);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.3",
|
|
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": {
|