@shgroup/dsh-serenity-hooks 1.35.0 → 1.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/README.md +56 -0
- package/assets/serenity-voyage-preview.png +0 -0
- package/assets/serenity-voyage.html +2755 -0
- package/dsh.plugin.json +1 -1
- package/lib/client.js +14 -5
- package/lib/index.js +138 -68
- package/lib/seams/keeper.d.ts +10 -4
- package/lib/voyage-page.d.ts +45 -0
- package/package.json +4 -1
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.36.1",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——11 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/im-bridge/acc-diag;后两个按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 轨迹日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.5-rc.2(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 状态胶囊(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);\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-overlay);\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);\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);\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";
|
|
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);\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/* ── 自绘 popover(卡片,替代官方 Modal)\n v1.36.1(所有者:「卡片做成双倍宽,左侧是信息,右侧是动画」):340 → **680px**(双倍)。\n 胶囊在头部右端,卡片 `right:0` 对齐 ⇒ 加宽只会**向左**长;再叠 `min(…, 100vw-24px)` 兜住窄视口。 */\n.sp-pop {\n position: absolute;\n top: calc(100% + 8px);\n right: 0;\n width: min(680px, 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-overlay);\n box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);\n}\n\n/* ── 航行动画栏:宁静号心智模型(ACC 层资产 /serenity/voyage,S142 §12.44 → v1.36.1 改右栏)\n v1.36.1 改版(所有者:「卡片做成双倍宽,左侧是信息,右侧是动画」):\n 动画从**背景层**改为**右栏**。这一改动解开了 v1.36.0 的取舍死结——\n 旧布局里动画垫在正文底下,必须靠 0.75 的卡片底色把它压住才读得清字;\n 分段之后两者不再争同一片像素 ⇒ 动画可以亮、正文可以干净,不必再互相妥协。\n ⚠️ 动画本身仍是「卡片模式」(词元压到 22%、铭牌层关闭),本栏只负责**给位置**,不改作品。 */\n.sp-popVoyage {\n overflow: hidden; /* 裁掉 iframe 在 14px 圆角外的溢出 */\n /* 分段后不再需要 0.75 的重压暗:压到 0.9 只压整体观感,不与正文争对比度。\n 若所有者觉得动画太亮/太暗,这仍是主旋钮(次旋钮 = iframe 的 blur)。 */\n background: rgba(6, 11, 14, 0.9);\n display: flex;\n flex-direction: row;\n /* 两栏都从**同一行高**分配(row 轴 cross = 高度):左栏按内容、右栏吃满行高。\n 行高 = 左栏内容高度,且被 max-height 封顶 ⇒ 信息长时内部滚动,动画不把行撑破。 */\n align-items: stretch;\n gap: 12px;\n padding: 12px;\n max-height: calc(100vh - 120px);\n}\n/* 左栏:信息(正文)——固定 340px,与旧卡片同宽,保证原有排印不变 */\n.sp-popVoyage > .sp-popBody {\n position: relative;\n z-index: 1;\n order: 1;\n flex: none;\n width: 340px;\n min-width: 0;\n}\n/* 右栏:动画——吃掉剩余宽度,并吃满左栏高度 */\n.sp-voyage {\n order: 2;\n flex: 1 1 auto;\n min-width: 0;\n position: relative;\n pointer-events: none; /* 纯展示:不挡点击、不抢焦点 */\n overflow: hidden;\n border-radius: 10px;\n min-height: 260px; /* 动画的下限:信息很少时也不塌成一条 */\n}\n.sp-voyage iframe {\n display: block;\n width: 100%;\n height: 100%;\n border: 0;\n /* ⚠️ 用 iframe 自身的 filter 而**不是** backdrop-filter:iframe 内容不参与本文档的\n backdrop(跨文档不采样)⇒ backdrop-filter 对它无效,只有 filter 才是可靠的杠杆。\n v1.36.1:动画移入独立右栏后不再压在文字后面 ⇒ 磨砂可以收得很轻(1.2 → 0.6px),\n 既保留柔化,又让飞船细节(舷窗/引擎环)比旧版清楚。 */\n filter: blur(0.6px);\n}\n\n/* 窄屏(放不下两栏)⇒ 退回单栏:信息在上、动画在下(DOM 顺序即如此,无需改结构) */\n@media (max-width: 860px) {\n .sp-pop {\n /* 两栏放不下时,卡片回到\"不超过视口\"的旧行为 */\n width: min(420px, calc(100vw - 24px));\n }\n .sp-popVoyage {\n flex-direction: column;\n align-items: stretch;\n }\n .sp-popVoyage > .sp-popBody {\n width: auto; /* 单栏下不再固定 340px,跟随卡片宽度 */\n flex: none; /* 按内容高(仍受自身 max-height 约束) */\n }\n .sp-voyage {\n flex: none;\n height: 200px; /* 单栏时给动画明确高度,避免被 flex 压扁 */\n min-height: 0;\n }\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);\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);\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");
|
|
@@ -176,11 +176,11 @@ window.__ModuleLoader__.load({
|
|
|
176
176
|
className: cx("sp-chev")
|
|
177
177
|
})
|
|
178
178
|
]
|
|
179
|
-
}), open && /* @__PURE__ */ (0, react_jsx_runtime.
|
|
180
|
-
className: cx("sp-pop"),
|
|
179
|
+
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
180
|
+
className: cx("sp-pop", "sp-popVoyage"),
|
|
181
181
|
role: "dialog",
|
|
182
182
|
"aria-label": "CCC 状态栏",
|
|
183
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
183
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
184
184
|
className: cx("sp-popBody"),
|
|
185
185
|
children: inCcc ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
186
186
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -349,7 +349,16 @@ window.__ModuleLoader__.load({
|
|
|
349
349
|
})
|
|
350
350
|
})]
|
|
351
351
|
})
|
|
352
|
-
})
|
|
352
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
353
|
+
className: cx("sp-voyage"),
|
|
354
|
+
"aria-hidden": "true",
|
|
355
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("iframe", {
|
|
356
|
+
src: `/serenity/voyage?card=1&v=${encodeURIComponent(status.accVersion)}`,
|
|
357
|
+
title: "",
|
|
358
|
+
loading: "lazy",
|
|
359
|
+
tabIndex: -1
|
|
360
|
+
}, status.accVersion)
|
|
361
|
+
})]
|
|
353
362
|
})]
|
|
354
363
|
});
|
|
355
364
|
}
|
package/lib/index.js
CHANGED
|
@@ -4626,8 +4626,8 @@ var KeeperTracker = class {
|
|
|
4626
4626
|
return this.score;
|
|
4627
4627
|
}
|
|
4628
4628
|
};
|
|
4629
|
-
function reminderText(code
|
|
4630
|
-
return `${eventToken("checkpoint")} Score threshold reached
|
|
4629
|
+
function reminderText(code) {
|
|
4630
|
+
return `${eventToken("checkpoint")} Score threshold reached. Please acknowledge with [${ACK_PREFIX}-${code}] once progress is synced to the working session (acc-session show). No need to interrupt your work — just acknowledge inline and keep going.`;
|
|
4631
4631
|
}
|
|
4632
4632
|
/**
|
|
4633
4633
|
* F2 rebuild 提示(v1.21;v1.22.1 对齐"轨迹跟踪器"概念;v1.23.0 英化 + 载体关系;
|
|
@@ -4642,18 +4642,24 @@ function reminderText(code, score) {
|
|
|
4642
4642
|
* escalated=true(v1.23.3):连续多轮超阈值仍未 rebuild → 升级强制语气
|
|
4643
4643
|
* (STOP and rebuild now,持续注入直到调用 container_trajectory rebuild)。
|
|
4644
4644
|
*
|
|
4645
|
-
* 需求①(S142
|
|
4646
|
-
*
|
|
4645
|
+
* 需求①(S142 用户拍板,v1.28.0):百分比比例 → K 数值——**判定**改为绝对
|
|
4646
|
+
* `projectedTokens ≥ thresholdK*1000`(触发语义,未变)。
|
|
4647
|
+
* 🔴 2026-09-16(S142 所有者指令,D64):**判定用 K,但注入文本里不再给任何数值**——
|
|
4648
|
+
* 原文案 `Context usage at NNNK (threshold NNNK)` 属于"向 LLM 透露上下文消耗与限制",
|
|
4649
|
+
* 是非必要信息(LLM 不据此决策,只占注意力)。⇒ 参数退化为仅 `escalated`。
|
|
4650
|
+
* ⚠️ 触发判定与提醒行为**一字未改**(仍读 projectedTokens / thresholdK 比对;仍升级催);
|
|
4651
|
+
* 变的只是**注入文本不再携带 tokensK / thresholdK**。
|
|
4652
|
+
* 使用者若要查看当前占用/阈值:看面板(`rebuildThresholdK` 滑块 + 状态显示)——那是给人看的。
|
|
4647
4653
|
*
|
|
4648
4654
|
* v1.31.1 交接协议(S142 用户需求"要求 LLM 将当前手头事项写在 SESSION.md 尾部,并要求
|
|
4649
4655
|
* rebuild 后去读并处理"):**写侧** = 这里(两条文案都要求把 in-flight 事项写在
|
|
4650
4656
|
* SESSION.md 末尾的 `IN_FLIGHT_HEADING` 之下);**读侧** = rebuild 锚点(rebuild.ts)。
|
|
4651
4657
|
* 两侧共用 trajectory-assistant 的标题常量(单一真相源)。
|
|
4652
4658
|
*/
|
|
4653
|
-
function rebuildReminderText(
|
|
4659
|
+
function rebuildReminderText(escalated = false) {
|
|
4654
4660
|
const handover = ` Before rebuilding, write your current in-flight items (the exact step you are in the middle of, what is not finished yet, and what the next action is) at the very end of SESSION.md under the heading "${IN_FLIGHT_HEADING}" — the rebuilt conversation reads that section first and continues from it.`;
|
|
4655
|
-
if (escalated) return `${eventToken("limitMandatory")} Context usage
|
|
4656
|
-
return `${eventToken("limit")} Context usage
|
|
4661
|
+
if (escalated) return `${eventToken("limitMandatory")} Context usage has reached the configured rebuild threshold — you have been reminded repeatedly and have NOT called the container_trajectory rebuild action. This is now mandatory: STOP at the current task step, preserve valuable cognition into the CCC skills (or write a new-skill proposal into SESSION.md), then call container_trajectory rebuild immediately, passing --summary "<content summary ≤20 chars>" (required; the dsh session title is renamed to S###-YYYY-MM-DD-<summary> after rebuild).${handover} The conversation will be cleared and rebuilt in place; SESSION.md is the persistent trajectory and stays in place — identity continues from it. Do not continue working without rebuilding; this reminder persists until you call container_trajectory rebuild.`;
|
|
4662
|
+
return `${eventToken("limit")} Context usage has reached the configured rebuild threshold. This session is the rebuildable carrier of the trajectory: SESSION.md is the persistent body, this conversation is only a temporary work copy. Before rebuilding: if this conversation produced valuable cognition, revise the relevant existing skill of this CCC (structure it with eap); if a new skill is warranted, write a short proposal into SESSION.md for the user to review — do not create it yourself.${handover} ACT NOW: at the next natural pause (end of the current task step), call the container_trajectory rebuild action — passing --summary "<content summary ≤20 chars>" describing the next work phase (required; the dsh session title is renamed to S###-YYYY-MM-DD-<summary> after rebuild) — to clear and rebuild this conversation: the current copy is discarded, identity continues from SESSION.md. If you are in the middle of an unbreakable step, continue it, then rebuild at its end. Do not ignore this; rebuild is the expected action, not an option.`;
|
|
4657
4663
|
}
|
|
4658
4664
|
/** 读取会话 contextPressure 投影(sessionProjections 可选服务;未装配返回 null) */
|
|
4659
4665
|
function readContextPressure(ctx, session) {
|
|
@@ -4798,7 +4804,7 @@ function registerKeeper(ctx, opts = {}) {
|
|
|
4798
4804
|
const code = tracker.ack();
|
|
4799
4805
|
blocks.push({
|
|
4800
4806
|
type: "text",
|
|
4801
|
-
text: reminderText(code
|
|
4807
|
+
text: reminderText(code)
|
|
4802
4808
|
});
|
|
4803
4809
|
}
|
|
4804
4810
|
if (skiffRebuild && readSimpleSettings().rebuildEnabled) {
|
|
@@ -4806,7 +4812,6 @@ function registerKeeper(ctx, opts = {}) {
|
|
|
4806
4812
|
if (session) {
|
|
4807
4813
|
const pressure = readContextPressure(ctx, session);
|
|
4808
4814
|
if (pressure && pressure.projectedTokens > 0) {
|
|
4809
|
-
const tokensK = pressure.projectedTokens / 1e3;
|
|
4810
4815
|
const thresholdK = readSimpleSettings().rebuildThresholdK;
|
|
4811
4816
|
if (pressure.projectedTokens >= thresholdK * 1e3) {
|
|
4812
4817
|
const key = exec.agent?.session?.id ?? "global";
|
|
@@ -4815,7 +4820,7 @@ function registerKeeper(ctx, opts = {}) {
|
|
|
4815
4820
|
const escalated = st.consecutive >= REBUILD_ESCALATE_AFTER;
|
|
4816
4821
|
blocks.push({
|
|
4817
4822
|
type: "text",
|
|
4818
|
-
text: rebuildReminderText(
|
|
4823
|
+
text: rebuildReminderText(escalated)
|
|
4819
4824
|
});
|
|
4820
4825
|
rebuildReminderStates.set(key, st);
|
|
4821
4826
|
}
|
|
@@ -6472,7 +6477,7 @@ function readBody$4(req, maxBytes) {
|
|
|
6472
6477
|
req.on("error", reject);
|
|
6473
6478
|
});
|
|
6474
6479
|
}
|
|
6475
|
-
function sendJson$
|
|
6480
|
+
function sendJson$4(res, code, body) {
|
|
6476
6481
|
const payload = JSON.stringify(body);
|
|
6477
6482
|
res.writeHead(code, {
|
|
6478
6483
|
"content-type": "application/json; charset=utf-8",
|
|
@@ -6497,7 +6502,7 @@ function senderIsWebUi(req) {
|
|
|
6497
6502
|
*/
|
|
6498
6503
|
function requireWebUi(req, res, message) {
|
|
6499
6504
|
if (senderIsWebUi(req)) return true;
|
|
6500
|
-
sendJson$
|
|
6505
|
+
sendJson$4(res, 403, { error: message });
|
|
6501
6506
|
return false;
|
|
6502
6507
|
}
|
|
6503
6508
|
/**
|
|
@@ -6530,7 +6535,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6530
6535
|
handler: async (req, res) => {
|
|
6531
6536
|
try {
|
|
6532
6537
|
if (req.method !== "GET") {
|
|
6533
|
-
sendJson$
|
|
6538
|
+
sendJson$4(res, 405, { error: "method not allowed" });
|
|
6534
6539
|
return;
|
|
6535
6540
|
}
|
|
6536
6541
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
@@ -6540,12 +6545,12 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6540
6545
|
});
|
|
6541
6546
|
const root = cccRootForCwd(workspace);
|
|
6542
6547
|
if (!root) {
|
|
6543
|
-
sendJson$
|
|
6548
|
+
sendJson$4(res, 200, { handymen: [] });
|
|
6544
6549
|
return;
|
|
6545
6550
|
}
|
|
6546
|
-
sendJson$
|
|
6551
|
+
sendJson$4(res, 200, { handymen: listActiveHandymen(root) });
|
|
6547
6552
|
} catch (err) {
|
|
6548
|
-
sendJson$
|
|
6553
|
+
sendJson$4(res, 400, { error: err.message ?? String(err) });
|
|
6549
6554
|
}
|
|
6550
6555
|
}
|
|
6551
6556
|
});
|
|
@@ -6555,7 +6560,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6555
6560
|
handler: async (req, res) => {
|
|
6556
6561
|
try {
|
|
6557
6562
|
if (req.method !== "POST") {
|
|
6558
|
-
sendJson$
|
|
6563
|
+
sendJson$4(res, 405, { error: "method not allowed" });
|
|
6559
6564
|
return;
|
|
6560
6565
|
}
|
|
6561
6566
|
if (!requireWebUi(req, res, "图片落盘仅限 WebUI(client 专用)")) return;
|
|
@@ -6567,12 +6572,12 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6567
6572
|
});
|
|
6568
6573
|
const root = cccRootForCwd(workspace);
|
|
6569
6574
|
if (!root) {
|
|
6570
|
-
sendJson$
|
|
6575
|
+
sendJson$4(res, 404, { error: `no CCC found from workspace: ${workspace}` });
|
|
6571
6576
|
return;
|
|
6572
6577
|
}
|
|
6573
|
-
sendJson$
|
|
6578
|
+
sendJson$4(res, 200, { path: saveImageToTmp(root, body.mediaType ?? "", body.data ?? "") });
|
|
6574
6579
|
} catch (err) {
|
|
6575
|
-
sendJson$
|
|
6580
|
+
sendJson$4(res, 400, { error: err.message ?? String(err) });
|
|
6576
6581
|
}
|
|
6577
6582
|
}
|
|
6578
6583
|
});
|
|
@@ -6582,7 +6587,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6582
6587
|
handler: async (req, res) => {
|
|
6583
6588
|
try {
|
|
6584
6589
|
if (req.method !== "POST") {
|
|
6585
|
-
sendJson$
|
|
6590
|
+
sendJson$4(res, 405, { error: "method not allowed" });
|
|
6586
6591
|
return;
|
|
6587
6592
|
}
|
|
6588
6593
|
if (!requireWebUi(req, res, "文件落盘仅限 WebUI(client 专用)")) return;
|
|
@@ -6594,12 +6599,12 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6594
6599
|
});
|
|
6595
6600
|
const root = cccRootForCwd(workspace);
|
|
6596
6601
|
if (!root) {
|
|
6597
|
-
sendJson$
|
|
6602
|
+
sendJson$4(res, 404, { error: `no CCC found from workspace: ${workspace}` });
|
|
6598
6603
|
return;
|
|
6599
6604
|
}
|
|
6600
|
-
sendJson$
|
|
6605
|
+
sendJson$4(res, 200, { path: saveFileToTmp(root, body.name ?? "", body.data ?? "") });
|
|
6601
6606
|
} catch (err) {
|
|
6602
|
-
sendJson$
|
|
6607
|
+
sendJson$4(res, 400, { error: err.message ?? String(err) });
|
|
6603
6608
|
}
|
|
6604
6609
|
}
|
|
6605
6610
|
});
|
|
@@ -6615,7 +6620,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6615
6620
|
workspace: url.searchParams.get("workspace") ?? void 0
|
|
6616
6621
|
}), configPaths);
|
|
6617
6622
|
const runtime = ctx.get("codeRuntime");
|
|
6618
|
-
sendJson$
|
|
6623
|
+
sendJson$4(res, 200, {
|
|
6619
6624
|
...status,
|
|
6620
6625
|
...runtime === void 0 ? { codeRuntime: null } : { codeRuntime: {
|
|
6621
6626
|
language: runtime.language,
|
|
@@ -6634,19 +6639,19 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6634
6639
|
});
|
|
6635
6640
|
const root = cccRootForCwd(workspace);
|
|
6636
6641
|
if (!root) {
|
|
6637
|
-
sendJson$
|
|
6642
|
+
sendJson$4(res, 404, { error: `no CCC found from workspace: ${workspace}` });
|
|
6638
6643
|
return;
|
|
6639
6644
|
}
|
|
6640
6645
|
const result = setSafeMode(root, body.on === true);
|
|
6641
|
-
sendJson$
|
|
6646
|
+
sendJson$4(res, 200, {
|
|
6642
6647
|
...getStatus(workspace, configPaths),
|
|
6643
6648
|
...result
|
|
6644
6649
|
});
|
|
6645
6650
|
return;
|
|
6646
6651
|
}
|
|
6647
|
-
sendJson$
|
|
6652
|
+
sendJson$4(res, 405, { error: "method not allowed" });
|
|
6648
6653
|
} catch (err) {
|
|
6649
|
-
sendJson$
|
|
6654
|
+
sendJson$4(res, 400, { error: err.message ?? String(err) });
|
|
6650
6655
|
}
|
|
6651
6656
|
}
|
|
6652
6657
|
});
|
|
@@ -6662,7 +6667,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6662
6667
|
try {
|
|
6663
6668
|
known = projectKnownWorkspaces(hostService(ctx, "workspaceRegistry")?.list?.() ?? [], settings.gateway.workspaces);
|
|
6664
6669
|
} catch {}
|
|
6665
|
-
sendJson$
|
|
6670
|
+
sendJson$4(res, 200, {
|
|
6666
6671
|
config: toWire(settings),
|
|
6667
6672
|
knownWorkspaces: known
|
|
6668
6673
|
});
|
|
@@ -6674,12 +6679,12 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6674
6679
|
try {
|
|
6675
6680
|
ctx.emit?.("serenity/config-updated");
|
|
6676
6681
|
} catch {}
|
|
6677
|
-
sendJson$
|
|
6682
|
+
sendJson$4(res, 200, { config: toWire(saved) });
|
|
6678
6683
|
return;
|
|
6679
6684
|
}
|
|
6680
|
-
sendJson$
|
|
6685
|
+
sendJson$4(res, 405, { error: "method not allowed" });
|
|
6681
6686
|
} catch (err) {
|
|
6682
|
-
sendJson$
|
|
6687
|
+
sendJson$4(res, 400, { error: err.message ?? String(err) });
|
|
6683
6688
|
}
|
|
6684
6689
|
}
|
|
6685
6690
|
});
|
|
@@ -6689,7 +6694,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6689
6694
|
handler: async (req, res) => {
|
|
6690
6695
|
try {
|
|
6691
6696
|
if (req.method !== "GET") {
|
|
6692
|
-
sendJson$
|
|
6697
|
+
sendJson$4(res, 405, { error: "method not allowed" });
|
|
6693
6698
|
return;
|
|
6694
6699
|
}
|
|
6695
6700
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
@@ -6698,12 +6703,12 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6698
6703
|
workspace: url.searchParams.get("workspace") ?? void 0
|
|
6699
6704
|
});
|
|
6700
6705
|
const root = cccRootForCwd(workspace) ?? "";
|
|
6701
|
-
sendJson$
|
|
6706
|
+
sendJson$4(res, 200, { cccs: await listCccs(ctx, {
|
|
6702
6707
|
defaultRoot: root,
|
|
6703
6708
|
withRoles: true
|
|
6704
6709
|
}) });
|
|
6705
6710
|
} catch (err) {
|
|
6706
|
-
sendJson$
|
|
6711
|
+
sendJson$4(res, 400, { error: err.message ?? String(err) });
|
|
6707
6712
|
}
|
|
6708
6713
|
}
|
|
6709
6714
|
});
|
|
@@ -6716,14 +6721,14 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6716
6721
|
if (req.method === "PUT") {
|
|
6717
6722
|
const raw = await readBody$4(req, 16384);
|
|
6718
6723
|
if (JSON.parse(raw).action !== "rotate") {
|
|
6719
|
-
sendJson$
|
|
6724
|
+
sendJson$4(res, 400, { error: "unsupported action (expected \"rotate\")" });
|
|
6720
6725
|
return;
|
|
6721
6726
|
}
|
|
6722
|
-
sendJson$
|
|
6727
|
+
sendJson$4(res, 200, { key: rotatePublicAskKey() });
|
|
6723
6728
|
return;
|
|
6724
6729
|
}
|
|
6725
6730
|
if (req.method !== "GET") {
|
|
6726
|
-
sendJson$
|
|
6731
|
+
sendJson$4(res, 405, { error: "method not allowed" });
|
|
6727
6732
|
return;
|
|
6728
6733
|
}
|
|
6729
6734
|
const settings = readAdvancedSettings();
|
|
@@ -6736,7 +6741,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6736
6741
|
name,
|
|
6737
6742
|
url: `${base}/c/${encodeURIComponent(name)}`
|
|
6738
6743
|
}));
|
|
6739
|
-
sendJson$
|
|
6744
|
+
sendJson$4(res, 200, {
|
|
6740
6745
|
enabled: simple.publicAskEnabled ?? false,
|
|
6741
6746
|
port,
|
|
6742
6747
|
key: ensurePublicAskKey(),
|
|
@@ -6745,7 +6750,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6745
6750
|
listUrl: `${base}/`
|
|
6746
6751
|
});
|
|
6747
6752
|
} catch (err) {
|
|
6748
|
-
sendJson$
|
|
6753
|
+
sendJson$4(res, 400, { error: err.message ?? String(err) });
|
|
6749
6754
|
}
|
|
6750
6755
|
}
|
|
6751
6756
|
});
|
|
@@ -6759,7 +6764,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6759
6764
|
if (req.method === "GET") {
|
|
6760
6765
|
const chk = requireCcc(url.searchParams.get("ccc") ?? void 0);
|
|
6761
6766
|
if (!chk.ok) {
|
|
6762
|
-
sendJson$
|
|
6767
|
+
sendJson$4(res, 400, { error: chk.error });
|
|
6763
6768
|
return;
|
|
6764
6769
|
}
|
|
6765
6770
|
const { readWeixinSettings } = await import("./weixin-route-B2ajFypI.js").then((n) => n.u);
|
|
@@ -6773,7 +6778,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6773
6778
|
enabled: a.enabled !== false,
|
|
6774
6779
|
bound: readWeixinCredential(chk.root, a.accountId) !== null
|
|
6775
6780
|
}));
|
|
6776
|
-
sendJson$
|
|
6781
|
+
sendJson$4(res, 200, {
|
|
6777
6782
|
enabled: settings.enabled === true,
|
|
6778
6783
|
botType: settings.botType ?? void 0,
|
|
6779
6784
|
accounts,
|
|
@@ -6783,14 +6788,14 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6783
6788
|
return;
|
|
6784
6789
|
}
|
|
6785
6790
|
if (req.method !== "POST") {
|
|
6786
|
-
sendJson$
|
|
6791
|
+
sendJson$4(res, 405, { error: "method not allowed" });
|
|
6787
6792
|
return;
|
|
6788
6793
|
}
|
|
6789
6794
|
const raw = await readBody$4(req, 131072);
|
|
6790
6795
|
const body = JSON.parse(raw);
|
|
6791
6796
|
const chk = requireCcc(body.ccc);
|
|
6792
6797
|
if (!chk.ok) {
|
|
6793
|
-
sendJson$
|
|
6798
|
+
sendJson$4(res, 400, { error: chk.error });
|
|
6794
6799
|
return;
|
|
6795
6800
|
}
|
|
6796
6801
|
const root = chk.root;
|
|
@@ -6807,7 +6812,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6807
6812
|
startedAt: Date.now(),
|
|
6808
6813
|
polling: false
|
|
6809
6814
|
});
|
|
6810
|
-
sendJson$
|
|
6815
|
+
sendJson$4(res, 200, {
|
|
6811
6816
|
qrcode: qr.qrcode,
|
|
6812
6817
|
qrcode_img_content: qr.qrcode_img_content,
|
|
6813
6818
|
loginKey
|
|
@@ -6816,31 +6821,31 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6816
6821
|
}
|
|
6817
6822
|
if (body.action === "remove-account") {
|
|
6818
6823
|
if (!body.accountId) {
|
|
6819
|
-
sendJson$
|
|
6824
|
+
sendJson$4(res, 400, { error: "missing accountId" });
|
|
6820
6825
|
return;
|
|
6821
6826
|
}
|
|
6822
6827
|
const { removeWeixinAccount } = await import("./weixin-route-B2ajFypI.js").then((n) => n.u);
|
|
6823
6828
|
const { syncCccBridge } = await Promise.resolve().then(() => weixin_bridge_exports);
|
|
6824
6829
|
removeWeixinAccount(root, body.accountId);
|
|
6825
6830
|
syncCccBridge(ctx, root);
|
|
6826
|
-
sendJson$
|
|
6831
|
+
sendJson$4(res, 200, { removed: body.accountId });
|
|
6827
6832
|
return;
|
|
6828
6833
|
}
|
|
6829
6834
|
if (body.action === "save-routes") {
|
|
6830
6835
|
const routes = body.routes ?? [];
|
|
6831
6836
|
if (!Array.isArray(routes) || routes.some((r) => typeof r?.user !== "string" || typeof r?.role !== "string" || r.user === "" || r.role === "")) {
|
|
6832
|
-
sendJson$
|
|
6837
|
+
sendJson$4(res, 400, { error: "invalid routes (expected [{user, role}, ...])" });
|
|
6833
6838
|
return;
|
|
6834
6839
|
}
|
|
6835
6840
|
const { saveWeixinRoutes } = await import("./weixin-route-B2ajFypI.js").then((n) => n.u);
|
|
6836
6841
|
const { readSkiffRoles } = await import("./skiff-role-CEHL5cek.js").then((n) => n.u);
|
|
6837
6842
|
const roles = readSkiffRoles(root);
|
|
6838
6843
|
for (const r of routes) if (!roles.has(r.role)) {
|
|
6839
|
-
sendJson$
|
|
6844
|
+
sendJson$4(res, 400, { error: `unknown role: ${r.role} (not in ${root} skiff.roles)` });
|
|
6840
6845
|
return;
|
|
6841
6846
|
}
|
|
6842
6847
|
saveWeixinRoutes(root, routes);
|
|
6843
|
-
sendJson$
|
|
6848
|
+
sendJson$4(res, 200, { saved: routes.length });
|
|
6844
6849
|
return;
|
|
6845
6850
|
}
|
|
6846
6851
|
if (body.action === "set-enabled") {
|
|
@@ -6849,7 +6854,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6849
6854
|
const { syncCccBridge } = await Promise.resolve().then(() => weixin_bridge_exports);
|
|
6850
6855
|
const settings = readWeixinSettings(root);
|
|
6851
6856
|
if (enabled && (settings.accounts ?? []).length === 0) {
|
|
6852
|
-
sendJson$
|
|
6857
|
+
sendJson$4(res, 400, { error: "启用前请先扫码绑定至少一个账号" });
|
|
6853
6858
|
return;
|
|
6854
6859
|
}
|
|
6855
6860
|
setWeixinEnabled(root, enabled);
|
|
@@ -6858,12 +6863,12 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6858
6863
|
const { stopCccBridge } = await Promise.resolve().then(() => weixin_bridge_exports);
|
|
6859
6864
|
stopCccBridge(root);
|
|
6860
6865
|
}
|
|
6861
|
-
sendJson$
|
|
6866
|
+
sendJson$4(res, 200, { enabled });
|
|
6862
6867
|
return;
|
|
6863
6868
|
}
|
|
6864
|
-
sendJson$
|
|
6869
|
+
sendJson$4(res, 400, { error: `unsupported action: ${body.action ?? ""}` });
|
|
6865
6870
|
} catch (err) {
|
|
6866
|
-
sendJson$
|
|
6871
|
+
sendJson$4(res, 400, { error: err.message ?? String(err) });
|
|
6867
6872
|
}
|
|
6868
6873
|
}
|
|
6869
6874
|
});
|
|
@@ -6874,18 +6879,18 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6874
6879
|
try {
|
|
6875
6880
|
if (!requireWebUi(req, res, "微信桥配置仅限 WebUI(client 专用)")) return;
|
|
6876
6881
|
if (req.method !== "GET") {
|
|
6877
|
-
sendJson$
|
|
6882
|
+
sendJson$4(res, 405, { error: "method not allowed" });
|
|
6878
6883
|
return;
|
|
6879
6884
|
}
|
|
6880
6885
|
const key = new URL(req.url ?? "/", "http://127.0.0.1").searchParams.get("key") ?? "";
|
|
6881
6886
|
const login = weixinLogins.get(key);
|
|
6882
6887
|
if (!login) {
|
|
6883
|
-
sendJson$
|
|
6888
|
+
sendJson$4(res, 404, { error: "login not found or expired" });
|
|
6884
6889
|
return;
|
|
6885
6890
|
}
|
|
6886
6891
|
if (Date.now() - login.startedAt > WEIXIN_LOGIN_TTL_MS) {
|
|
6887
6892
|
weixinLogins.delete(key);
|
|
6888
|
-
sendJson$
|
|
6893
|
+
sendJson$4(res, 200, { status: "expired" });
|
|
6889
6894
|
return;
|
|
6890
6895
|
}
|
|
6891
6896
|
const { pollQRStatus } = await import("./weixin-api-CtFYm53S.js").then((n) => n.u);
|
|
@@ -6898,7 +6903,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6898
6903
|
switch (status.status) {
|
|
6899
6904
|
case "confirmed": {
|
|
6900
6905
|
if (!status.bot_token) {
|
|
6901
|
-
sendJson$
|
|
6906
|
+
sendJson$4(res, 200, {
|
|
6902
6907
|
status: "error",
|
|
6903
6908
|
error: "confirmed but no bot_token"
|
|
6904
6909
|
});
|
|
@@ -6919,7 +6924,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6919
6924
|
});
|
|
6920
6925
|
syncCccBridge(ctx, login.root);
|
|
6921
6926
|
weixinLogins.delete(key);
|
|
6922
|
-
sendJson$
|
|
6927
|
+
sendJson$4(res, 200, {
|
|
6923
6928
|
status: "confirmed",
|
|
6924
6929
|
accountId,
|
|
6925
6930
|
tokenSaved: true
|
|
@@ -6928,14 +6933,14 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6928
6933
|
}
|
|
6929
6934
|
case "expired":
|
|
6930
6935
|
weixinLogins.delete(key);
|
|
6931
|
-
sendJson$
|
|
6936
|
+
sendJson$4(res, 200, { status: "expired" });
|
|
6932
6937
|
return;
|
|
6933
6938
|
default:
|
|
6934
|
-
sendJson$
|
|
6939
|
+
sendJson$4(res, 200, { status: status.status ?? "wait" });
|
|
6935
6940
|
return;
|
|
6936
6941
|
}
|
|
6937
6942
|
} catch (err) {
|
|
6938
|
-
sendJson$
|
|
6943
|
+
sendJson$4(res, 400, { error: err.message ?? String(err) });
|
|
6939
6944
|
}
|
|
6940
6945
|
}
|
|
6941
6946
|
});
|
|
@@ -6957,7 +6962,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6957
6962
|
const root = sessionsRootDir();
|
|
6958
6963
|
if (req.method === "GET") {
|
|
6959
6964
|
const { candidates } = performCleanup(root, cutoffDaysAgo(days), liveIds, { dryRun: true });
|
|
6960
|
-
sendJson$
|
|
6965
|
+
sendJson$4(res, 200, {
|
|
6961
6966
|
root,
|
|
6962
6967
|
olderThanDays: days,
|
|
6963
6968
|
dryRun: true,
|
|
@@ -6971,12 +6976,12 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6971
6976
|
return;
|
|
6972
6977
|
}
|
|
6973
6978
|
if (req.method !== "POST") {
|
|
6974
|
-
sendJson$
|
|
6979
|
+
sendJson$4(res, 405, { error: "method not allowed" });
|
|
6975
6980
|
return;
|
|
6976
6981
|
}
|
|
6977
6982
|
if (!requireWebUi(req, res, "会话清理仅限 WebUI(client 专用)")) return;
|
|
6978
6983
|
const { result } = performCleanup(root, cutoffDaysAgo(days), liveIds);
|
|
6979
|
-
sendJson$
|
|
6984
|
+
sendJson$4(res, 200, {
|
|
6980
6985
|
root,
|
|
6981
6986
|
olderThanDays: days,
|
|
6982
6987
|
dryRun: false,
|
|
@@ -6984,7 +6989,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6984
6989
|
errors: result.errors
|
|
6985
6990
|
});
|
|
6986
6991
|
} catch (err) {
|
|
6987
|
-
sendJson$
|
|
6992
|
+
sendJson$4(res, 400, { error: err.message ?? String(err) });
|
|
6988
6993
|
}
|
|
6989
6994
|
}
|
|
6990
6995
|
});
|
|
@@ -7009,6 +7014,68 @@ function requireCcc(root) {
|
|
|
7009
7014
|
};
|
|
7010
7015
|
}
|
|
7011
7016
|
//#endregion
|
|
7017
|
+
//#region src/voyage-page.ts
|
|
7018
|
+
const VOYAGE_PATH = "/serenity/voyage";
|
|
7019
|
+
/**
|
|
7020
|
+
* 资产相对包根的路径。
|
|
7021
|
+
* 🔒 它**必须**同时出现在 `package.json` 的 `files` 白名单里——否则发布包缺文件,
|
|
7022
|
+
* 路由会在运行时报「资产不可用」(同 v1.26.15 那次 `files` 漏 chunk 的事故形态)。
|
|
7023
|
+
* `dsh-develop pack-check` 的 `required` 已把它列为发布前硬断言。
|
|
7024
|
+
*/
|
|
7025
|
+
const VOYAGE_ASSET = "assets/serenity-voyage.html";
|
|
7026
|
+
/** 包根 = 本模块所在目录的上一级(构建产物在 `lib/`,与 `assets/` 同级;同 constants.ts 定位法) */
|
|
7027
|
+
function packageRootPath() {
|
|
7028
|
+
return join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
7029
|
+
}
|
|
7030
|
+
/** 资产绝对路径(导出以便测试与 pack-check 共用同一判据来源) */
|
|
7031
|
+
function voyageAssetPath() {
|
|
7032
|
+
return join(packageRootPath(), VOYAGE_ASSET);
|
|
7033
|
+
}
|
|
7034
|
+
/** 缓存(830 KB,避免每次展开卡片都读盘;进程内只读一次) */
|
|
7035
|
+
let cachedHtml = null;
|
|
7036
|
+
/** 读取资产全文。读不到即抛错(由 handler 转 500,绝不静默发空页)。 */
|
|
7037
|
+
function loadVoyageHtml(assetPath = voyageAssetPath()) {
|
|
7038
|
+
if (assetPath === voyageAssetPath()) {
|
|
7039
|
+
if (cachedHtml === null) cachedHtml = readFileSync(assetPath, "utf-8");
|
|
7040
|
+
return cachedHtml;
|
|
7041
|
+
}
|
|
7042
|
+
return readFileSync(assetPath, "utf-8");
|
|
7043
|
+
}
|
|
7044
|
+
/** 发 JSON(错误分支用;与 api.ts 的 sendJson 同形,但本模块不依赖它,保持零耦合) */
|
|
7045
|
+
function sendJson$3(res, status, body) {
|
|
7046
|
+
res.statusCode = status;
|
|
7047
|
+
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
7048
|
+
res.end(JSON.stringify(body));
|
|
7049
|
+
}
|
|
7050
|
+
/**
|
|
7051
|
+
* 注册 `/serenity/voyage`(GET)。
|
|
7052
|
+
* @param opts.assetPath 覆盖资产路径(测试用;生产不传)
|
|
7053
|
+
*/
|
|
7054
|
+
function registerVoyageApi(ctx, opts = {}) {
|
|
7055
|
+
ctx.webServer.register({
|
|
7056
|
+
kind: "exact",
|
|
7057
|
+
path: VOYAGE_PATH,
|
|
7058
|
+
handler: (req, res) => {
|
|
7059
|
+
if (req.method !== "GET") {
|
|
7060
|
+
sendJson$3(res, 405, { error: "method not allowed" });
|
|
7061
|
+
return;
|
|
7062
|
+
}
|
|
7063
|
+
let html;
|
|
7064
|
+
try {
|
|
7065
|
+
html = loadVoyageHtml(opts.assetPath ?? voyageAssetPath());
|
|
7066
|
+
} catch (err) {
|
|
7067
|
+
sendJson$3(res, 500, { error: `voyage asset unavailable: ${err?.message ?? String(err)}` });
|
|
7068
|
+
return;
|
|
7069
|
+
}
|
|
7070
|
+
res.statusCode = 200;
|
|
7071
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
7072
|
+
res.setHeader("cache-control", "private, max-age=86400");
|
|
7073
|
+
res.setHeader("content-length", String(Buffer.byteLength(html)));
|
|
7074
|
+
res.end(html);
|
|
7075
|
+
}
|
|
7076
|
+
});
|
|
7077
|
+
}
|
|
7078
|
+
//#endregion
|
|
7012
7079
|
//#region src/seams/env.ts
|
|
7013
7080
|
/** 纯解析:给定 cwd,返回可注入的 DSH_SERENITY_* 事实(非 CCC 返回空) */
|
|
7014
7081
|
function resolveSerenityEnv(cwd) {
|
|
@@ -14833,7 +14900,10 @@ function apply(ctx, config) {
|
|
|
14833
14900
|
configPaths: config.serenityConfigPaths,
|
|
14834
14901
|
entrySkillMaxChars: config.entrySkillMaxChars
|
|
14835
14902
|
});
|
|
14836
|
-
if (config.api)
|
|
14903
|
+
if (config.api) {
|
|
14904
|
+
registerStatusApi(ctx, { configPaths: config.serenityConfigPaths });
|
|
14905
|
+
registerVoyageApi(ctx);
|
|
14906
|
+
}
|
|
14837
14907
|
registerSettingsSection(ctx, config);
|
|
14838
14908
|
registerGateway(ctx);
|
|
14839
14909
|
registerRebuildTurnHook(ctx);
|