@shgroup/dsh-serenity-hooks 1.59.1 → 1.59.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/acp-auth.d.ts +42 -1
- package/lib/acp-face.d.ts +41 -1
- package/lib/client.js +9 -3
- package/lib/host/access.d.ts +15 -2
- package/lib/host/contract.d.ts +8 -0
- package/lib/index.js +267 -31
- package/package.json +17 -17
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.59.
|
|
3
|
+
"version": "1.59.3",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——14 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 轨迹日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.7-rc.1 与 0.2.0-rc.1(双档,deepseek-ai/deepseek-harness)。",
|
|
6
6
|
"engines": {
|
package/lib/acp-auth.d.ts
CHANGED
|
@@ -69,6 +69,34 @@ export declare function resetAcpToken(root: string, accountId: string): string;
|
|
|
69
69
|
export declare function safeEqual(a: string, b: string): boolean;
|
|
70
70
|
/** token 指纹(诊断面用;不可逆) */
|
|
71
71
|
export declare function tokenFingerprint(token: string): string;
|
|
72
|
+
/**
|
|
73
|
+
* 该账户的**主会话**键(`IM_ACP_SESSION_<ACCOUNT_ID>`)。
|
|
74
|
+
*
|
|
75
|
+
* 🔴 为什么放在 **credential** 节而不是 config 节:它跟 token 一样是**每账户一值的键控数据**
|
|
76
|
+
* (键由 `accountId` 派生、随账户生命周期存亡)。放 config 节要另造一层 `section.key`
|
|
77
|
+
* 结构,而这一层的唯一读者就是本模块 ⇒ 多一层结构 = 多一处会漂的形状。
|
|
78
|
+
*
|
|
79
|
+
* ⚠️ 它**不是凭据**(会话 id 不是秘密)—— 共享 credential 节只是为了复用它既有的
|
|
80
|
+
* 「按 accountId 派生键 + 删账户时一并清理」这套语义,不引入第二套键规则。
|
|
81
|
+
*/
|
|
82
|
+
export declare function acpSessionKeyFor(accountId: string): string;
|
|
83
|
+
/**
|
|
84
|
+
* 读该账户的主会话 id(不存在 ⇒ `null`)。
|
|
85
|
+
*
|
|
86
|
+
* 🔴 **读路径无副作用**(与 `readAcpToken` 同纪律):不存在的含义是「这个账户还没有主会话」,
|
|
87
|
+
* 而那时的正确行为是**让 App 去建**(`session/list` 回空数组),不是替它编一个。
|
|
88
|
+
*/
|
|
89
|
+
export declare function readAcpMainSession(root: string, accountId: string): string | null;
|
|
90
|
+
/**
|
|
91
|
+
* 记下该账户的主会话 id(**写操作**;由 `session/new` 的出站响应触发)。
|
|
92
|
+
*
|
|
93
|
+
* 🔴 **后写覆盖先写**(last-wins):owner 要的是"一条长期对话",而"再建一条"是用户
|
|
94
|
+
* 在 App 里显式点「新对话」的结果 ⇒ 那时他要的就是**新的那条**。
|
|
95
|
+
* ⛔ **被否的两种做法**:① 已有主会话就拒建(用户会看到"点了没反应");
|
|
96
|
+
* ② 把 `session/new` 改写成 `session/resume`(响应形状不同,要让客户端认一条
|
|
97
|
+
* **不存在于协议里**的响应 —— 那是拿协议兼容性换一个我们本来就有的语义)。
|
|
98
|
+
*/
|
|
99
|
+
export declare function writeAcpMainSession(root: string, accountId: string, sessionId: string): void;
|
|
72
100
|
/**
|
|
73
101
|
* 从一次 HTTP upgrade 请求里取出候选 token。
|
|
74
102
|
*
|
|
@@ -174,6 +202,13 @@ export interface AcpAccountStatus {
|
|
|
174
202
|
enabled: boolean;
|
|
175
203
|
hasToken: boolean;
|
|
176
204
|
tokenFingerprint: string | null;
|
|
205
|
+
/**
|
|
206
|
+
* 该账户的**主会话** id(owner「每账户固定一条长期对话」的那一条;`null` = 还没建)。
|
|
207
|
+
*
|
|
208
|
+
* 🔴 会话 id **不是秘密**,故这里给明文(与 token 不同)—— 它的用途是**可诊断**:
|
|
209
|
+
* 面板/排障时要能回答"这台手机此刻钉的是哪条对话",否则出问题时只能靠猜。
|
|
210
|
+
*/
|
|
211
|
+
mainSession: string | null;
|
|
177
212
|
/** 该账户此刻**能不能被认证闸接受**(三条判据全真) */
|
|
178
213
|
usable: boolean;
|
|
179
214
|
/** 不能用的原因(能用 ⇒ null)—— 直接指向该改哪个字段 */
|
|
@@ -228,5 +263,11 @@ export declare function normalizeAcpAccount(input: unknown): (ImAcpAccount & {
|
|
|
228
263
|
}) | null;
|
|
229
264
|
/** 增/改一条账户(按 accountId 定位;写入后返回全表) */
|
|
230
265
|
export declare function upsertAcpAccount(root: string, account: ImAcpAccount, paths?: string[]): ImAcpAccount[];
|
|
231
|
-
/**
|
|
266
|
+
/**
|
|
267
|
+
* 删一条账户(**同时清掉它的 token + 主会话**,避免留死键)。
|
|
268
|
+
*
|
|
269
|
+
* 🔴 主会话键**必须一起清**:否则日后用同一个 `accountId` 重建账户时,它会**静默继承**
|
|
270
|
+
* 那条旧会话 —— 而那条会话的 cwd/角色都是上一代账户的。这个失效**没有任何信号**
|
|
271
|
+
* (列表上它看着就是一条正常会话)。
|
|
272
|
+
*/
|
|
232
273
|
export declare function removeAcpAccount(root: string, accountId: string, paths?: string[]): ImAcpAccount[];
|
package/lib/acp-face.d.ts
CHANGED
|
@@ -80,7 +80,7 @@ export declare function acpFacePort(): number | null;
|
|
|
80
80
|
*/
|
|
81
81
|
export declare function acpFaceEnabled(): boolean;
|
|
82
82
|
/**
|
|
83
|
-
* 把客户端给的 `cwd` 归一化成宿主要求的**绝对路径**(v1.59.
|
|
83
|
+
* 把客户端给的 `cwd` 归一化成宿主要求的**绝对路径**(v1.59.1)。
|
|
84
84
|
*
|
|
85
85
|
* ## 为什么需要
|
|
86
86
|
* 宿主 `dsh-acp` 在每次 `session/new` 都有这一行(`lib/index.js` 逐字):
|
|
@@ -107,6 +107,45 @@ export declare function acpFaceEnabled(): boolean;
|
|
|
107
107
|
* @param cccRoot 本连接归属的 CCC 根
|
|
108
108
|
*/
|
|
109
109
|
export declare function normalizeClientCwd(raw: unknown, cccRoot: string): string;
|
|
110
|
+
/**
|
|
111
|
+
* 一条连接的**会话钉住**状态(owner「每账户固定一条」的执行单元)。
|
|
112
|
+
*
|
|
113
|
+
* 生命周期 = 一条 WS 连接。⚠️ **跨连接的真相在 localstore**(`IM_ACP_SESSION_<ACCOUNT_ID>`);
|
|
114
|
+
* 本结构只是"这条连接上,请求 id 对应哪个方法"的记账 + 那两个方法的处理口径。
|
|
115
|
+
*/
|
|
116
|
+
interface AcpSessionPin {
|
|
117
|
+
/** 本连接归属的 CCC 根(合成 `SessionInfo.cwd` 时用 —— 那也是我们当初钉的值) */
|
|
118
|
+
root: string;
|
|
119
|
+
/** 本连接归属的账户(主会话键由它派生) */
|
|
120
|
+
accountId: string;
|
|
121
|
+
/** 在飞的请求:`id` → 方法名(**出站改写唯一的方法来源**) */
|
|
122
|
+
pending: Map<string, string>;
|
|
123
|
+
}
|
|
124
|
+
/** 建一条连接的钉住状态 */
|
|
125
|
+
export declare function createAcpSessionPin(root: string, accountId: string): AcpSessionPin;
|
|
126
|
+
/**
|
|
127
|
+
* 入站记账:把"这条请求的方法"记住,供出站按 `id` 反查。
|
|
128
|
+
*
|
|
129
|
+
* 判据:**既要有 `id`,又要有 `method`** —— 只满足一个的是响应(`id` 无 `method`)
|
|
130
|
+
* 或通知(`method` 无 `id`)⇒ 与我们无关,不记。
|
|
131
|
+
*/
|
|
132
|
+
export declare function recordAcpPending(pin: AcpSessionPin, msg: unknown): void;
|
|
133
|
+
/**
|
|
134
|
+
* 出站改写(本功能的落点)。返回**要发给客户端的那条消息**。
|
|
135
|
+
*
|
|
136
|
+
* | 原响应 | 改写后 |
|
|
137
|
+
* |---|---|
|
|
138
|
+
* | `session/list` | `{sessions:[该账户那一条]}`;**没有主会话 ⇒ `{sessions:[]}`** |
|
|
139
|
+
* | `session/new` | 原样,**同时**把 `result.sessionId` 记为该账户的主会话 |
|
|
140
|
+
* | 其余(含所有 error 响应、所有通知) | 原样 |
|
|
141
|
+
*
|
|
142
|
+
* 🔴 **没有主会话时回空数组,绝不回退成全量列表** —— 退回去正是 owner 说
|
|
143
|
+
* 「23 个对我没什么用」的那个东西。空数组会让 App 引导用户新建,而新建即建立主会话。
|
|
144
|
+
*
|
|
145
|
+
* 🔴 `nextCursor` **一律丢掉**:收窄到一条之后,分页游标失去意义;
|
|
146
|
+
* 留着它会让客户端继续翻页(而后续页仍会被收窄成同一条 ⇒ 前端可能出现重复项)。
|
|
147
|
+
*/
|
|
148
|
+
export declare function rewriteAcpResponse(pin: AcpSessionPin, msg: unknown): unknown;
|
|
110
149
|
/** 面的运行统计(`dashboard` / 诊断面用;**只读快照**) */
|
|
111
150
|
export interface AcpFaceStatus {
|
|
112
151
|
active: boolean;
|
|
@@ -202,3 +241,4 @@ export declare function resolveAcpSelection(ctx: Context): {
|
|
|
202
241
|
* 绝不抛出(抛出会让整个插件加载失败,而它只是一个可选面)。
|
|
203
242
|
*/
|
|
204
243
|
export declare function registerAcpFace(ctx: Context): void;
|
|
244
|
+
export {};
|
package/lib/client.js
CHANGED
|
@@ -3643,7 +3643,7 @@ window.__ModuleLoader__.load({
|
|
|
3643
3643
|
//#endregion
|
|
3644
3644
|
//#region src/client/AcpAccountsEditor.tsx
|
|
3645
3645
|
/**
|
|
3646
|
-
* AcpAccountsEditor.tsx — 「ACP 账户台账」配置区块(v1.59.
|
|
3646
|
+
* AcpAccountsEditor.tsx — 「ACP 账户台账」配置区块(v1.59.1,owner 2026-10-17 令)
|
|
3647
3647
|
*
|
|
3648
3648
|
* ## 为什么要有它
|
|
3649
3649
|
* owner 原话:「**面板里只放个开关不放具体的条目吗?**」
|
|
@@ -3824,7 +3824,8 @@ window.__ModuleLoader__.load({
|
|
|
3824
3824
|
}),
|
|
3825
3825
|
status && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3826
3826
|
className: "ss-detailIntro",
|
|
3827
|
-
children: `通道:${status.channel}${status.channel === "acp" ? "" : "(未选 ACP ⇒ 账户不生效)"} / 面开关:${status.enabled ? "开" : "关"}\n可用账户:${status.accounts.filter((a) => a.usable).length} / ${status.accounts.length}
|
|
3827
|
+
children: `通道:${status.channel}${status.channel === "acp" ? "" : "(未选 ACP ⇒ 账户不生效)"} / 面开关:${status.enabled ? "开" : "关"}\n可用账户:${status.accounts.filter((a) => a.usable).length} / ${status.accounts.length}
|
|
3828
|
+
每个可用账户在客户端里**只看到自己那一条对话**(第一次发消息时建立,之后一直用它)。`
|
|
3828
3829
|
}),
|
|
3829
3830
|
status && status.accounts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
|
|
3830
3831
|
className: "ss-table",
|
|
@@ -3832,12 +3833,17 @@ window.__ModuleLoader__.load({
|
|
|
3832
3833
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "账户" }),
|
|
3833
3834
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "角色" }),
|
|
3834
3835
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "状态" }),
|
|
3836
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "对话" }),
|
|
3835
3837
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "token" }),
|
|
3836
3838
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {})
|
|
3837
3839
|
] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: status.accounts.map((a) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
3838
3840
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: a.accountId }), a.name ? ` · ${a.name}` : ""] }),
|
|
3839
3841
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: a.role ?? "—" }),
|
|
3840
3842
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: a.usable ? "✅ 可用" : `⚠️ ${a.blockedBy ?? "不可用"}` }),
|
|
3843
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
3844
|
+
title: a.mainSession ?? void 0,
|
|
3845
|
+
children: a.mainSession ? `…${a.mainSession.slice(-8)}` : "(还没建,客户端发第一条消息时建立)"
|
|
3846
|
+
}),
|
|
3841
3847
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: a.hasToken ? `…${a.tokenFingerprint ?? ""}` : "(无)" }),
|
|
3842
3848
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3843
3849
|
type: "button",
|
|
@@ -4589,7 +4595,7 @@ Instruction-following style:
|
|
|
4589
4595
|
}
|
|
4590
4596
|
//#endregion
|
|
4591
4597
|
//#region \0sp-css:src/client/SettingsSection
|
|
4592
|
-
const css$2 = "/* 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/* ── v1.29 需求②:可展开行(整行点击 → 行内详设)── */\n.ss-expandable {\n flex-wrap: wrap; /* detail 换行占满整行 */\n cursor: default;\n}\n.ss-expandable .ss-rowText {\n cursor: pointer;\n}\n.ss-expandable .ss-rowText:hover .ss-rowName {\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n.ss-expanded {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n.ss-expandMark {\n display: inline-block;\n margin-left: 6px;\n font-size: 10px;\n color: var(--dsw-alias-label-tertiary);\n transition: transform 0.15s ease;\n}\n.ss-expanded .ss-expandMark {\n transform: rotate(90deg);\n}\n.ss-rowDetail {\n flex-basis: 100%;\n width: 100%;\n border-top: 1px solid var(--dsw-alias-border-l2);\n margin-top: 10px;\n padding-top: 12px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n min-width: 0;\n}\n.ss-detailStack {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n.ss-detailIntro {\n margin: 0;\n font-size: 12px;\n line-height: 19px;\n color: var(--dsw-alias-label-secondary);\n white-space: pre-line;\n word-break: break-word;\n overflow-wrap: anywhere;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.06));\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;v1.27.5 紧凑化:默认单行截断,hover title 看全文) */\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 display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\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-border-l3);\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-foreground);\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-brand-primary);\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-bg-layer-2);\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/* 自主轨迹「立即唤起」按钮(v1.26.14 调试语义;官方 button 语言:primary 弱化) */\n.ss-wakeBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-wakeBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-wakeBtn:disabled {\n opacity: 0.4;\n cursor: default;\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\n/* 错误提示(v1.29 会话清理等) */\n.ss-error {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-error-primary, #ef4444);\n background: rgba(239, 68, 68, 0.08);\n border-radius: 8px;\n padding: 8px 10px;\n white-space: pre-line;\n word-break: break-word;\n}\n\n/* ── v1.27.5 紧凑化:折叠组(details/summary)── */\n.ss-collapse {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n overflow: hidden;\n}\n\n.ss-collapseHead {\n display: flex;\n align-items: baseline;\n gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n list-style: none;\n user-select: none;\n}\n\n.ss-collapseHead::-webkit-details-marker {\n display: none;\n}\n\n.ss-collapseHead::before {\n content: '▸';\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n transition: transform 0.15s ease;\n}\n\n.ss-collapse[open] > .ss-collapseHead::before {\n transform: rotate(90deg);\n}\n\n.ss-collapseHead:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.06));\n}\n\n.ss-collapseTitle {\n font-size: 13px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.ss-collapseDesc {\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n}\n\n.ss-collapseBody {\n padding: 10px 14px 14px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n\n/* ── v1.27.5 紧凑化:只读定义列表(dl/dt/dd 2 列网格)── */\n.ss-defList {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 6px 20px;\n margin: 0;\n width: 100%;\n min-width: 0;\n}\n\n.ss-defItem {\n display: flex;\n flex-direction: column;\n gap: 1px;\n min-width: 0;\n}\n\n.ss-defTerm {\n font-size: 11px;\n line-height: 16px;\n font-weight: 500;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.ss-defValue {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n word-break: break-word;\n overflow-wrap: anywhere;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* ── v1.27.5 紧凑化:开关 + 端口内联行 ── */\n.ss-switchRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n/* ── v1.59.2 ACP 账户台账表(owner 2026-10-17:「面板里只放个开关不放具体的条目吗?」)\n *\n * 用**表格**而不是卡片列表:台账天生是\"一行一条、列对齐可比\"的数据\n * (账户 / 角色 / 状态 / token 指纹),卡片会把可比性拆散。\n * 窄屏靠横向滚动兜底——不折行,折行后\"哪一格对应哪一列\"就看不出来了。 */\n.ss-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 12px;\n overflow-x: auto;\n display: block;\n}\n\n.ss-table th,\n.ss-table td {\n padding: 4px 8px;\n text-align: left;\n border-bottom: 1px solid var(--dsw-alias-border-l2);\n white-space: nowrap;\n}\n\n.ss-table th {\n color: var(--dsw-alias-label-tertiary);\n font-weight: 600;\n}\n\n.ss-table td code {\n font-size: 11px;\n padding: 1px 4px;\n border-radius: 3px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n}\n\n.ss-table button {\n margin-left: 4px;\n}\n\n/* ── v1.27.6 「?」帮助浮层(hover/焦点显示三行以上严谨说明)── */\n.ss-help {\n position: relative;\n display: inline-flex;\n align-items: center;\n margin-top: 2px;\n}\n\n.ss-helpMark {\n flex: none;\n width: 16px;\n height: 16px;\n padding: 0;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 50%;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-tertiary);\n font-size: 10px;\n line-height: 1;\n font-weight: 600;\n cursor: help;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n transition: border-color 0.15s ease, color 0.15s ease;\n}\n\n.ss-helpMark:hover,\n.ss-helpMark:focus-visible {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n outline: none;\n}\n\n.ss-helpTip {\n position: absolute;\n left: 0;\n top: calc(100% + 6px);\n z-index: 30;\n width: 300px;\n max-width: 60vw;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-overlay);\n box-shadow: 0 6px 24px rgba(0, 0, 0, 0.28);\n font-size: 12px;\n line-height: 19px;\n font-weight: 400;\n color: var(--dsw-alias-label-primary);\n white-space: pre-line;\n word-break: break-word;\n opacity: 0;\n pointer-events: none;\n transform: translateY(-4px);\n transition: opacity 0.15s ease, transform 0.15s ease;\n}\n\n.ss-help:hover .ss-helpTip,\n.ss-helpMark:focus-visible + .ss-helpTip {\n opacity: 1;\n pointer-events: auto;\n transform: translateY(0);\n}\n\n/* 折叠组内帮助浮层定位修正(左侧行卡不会溢出面板) */\n.ss-rowCard .ss-helpTip {\n left: 0;\n}\n\n/* ── 微信桥(F4c-3,v1.27.0 实验性)── */\n\n/* CCC 选择器(下拉) */\n.ss-select {\n max-width: 280px;\n padding: 6px 8px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* 扫码绑定按钮(复用 wakeBtn 语言) */\n.ss-loginBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-loginBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-loginBtn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 移除账号按钮(危险语义:红边框) */\n.ss-removeBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-removeBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-error-primary, #ef4444);\n color: var(--dsw-alias-state-error-primary, #ef4444);\n}\n\n/* 二维码展示行 */\n.ss-qrRow {\n align-items: center;\n}\n\n.ss-qrSvg {\n flex: none;\n width: 120px; /* v1.27.5 紧凑化:160 → 120 */\n height: 120px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: #fff; /* 二维码需要白底才能被微信扫出 */\n padding: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.ss-qrSvg svg {\n width: 100%;\n height: 100%;\n}\n\n/* 路由表 JSON 编辑框 */\n.ss-routesEditor {\n width: 100%;\n box-sizing: border-box;\n margin-top: 6px;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-family: var(--ds-font-family-code);\n resize: vertical;\n}\n";
|
|
4598
|
+
const css$2 = "/* 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/* ── v1.29 需求②:可展开行(整行点击 → 行内详设)── */\n.ss-expandable {\n flex-wrap: wrap; /* detail 换行占满整行 */\n cursor: default;\n}\n.ss-expandable .ss-rowText {\n cursor: pointer;\n}\n.ss-expandable .ss-rowText:hover .ss-rowName {\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n.ss-expanded {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n.ss-expandMark {\n display: inline-block;\n margin-left: 6px;\n font-size: 10px;\n color: var(--dsw-alias-label-tertiary);\n transition: transform 0.15s ease;\n}\n.ss-expanded .ss-expandMark {\n transform: rotate(90deg);\n}\n.ss-rowDetail {\n flex-basis: 100%;\n width: 100%;\n border-top: 1px solid var(--dsw-alias-border-l2);\n margin-top: 10px;\n padding-top: 12px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n min-width: 0;\n}\n.ss-detailStack {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n.ss-detailIntro {\n margin: 0;\n font-size: 12px;\n line-height: 19px;\n color: var(--dsw-alias-label-secondary);\n white-space: pre-line;\n word-break: break-word;\n overflow-wrap: anywhere;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.06));\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;v1.27.5 紧凑化:默认单行截断,hover title 看全文) */\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 display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\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-border-l3);\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-foreground);\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-brand-primary);\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-bg-layer-2);\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/* 自主轨迹「立即唤起」按钮(v1.26.14 调试语义;官方 button 语言:primary 弱化) */\n.ss-wakeBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-wakeBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-wakeBtn:disabled {\n opacity: 0.4;\n cursor: default;\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\n/* 错误提示(v1.29 会话清理等) */\n.ss-error {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-error-primary, #ef4444);\n background: rgba(239, 68, 68, 0.08);\n border-radius: 8px;\n padding: 8px 10px;\n white-space: pre-line;\n word-break: break-word;\n}\n\n/* ── v1.27.5 紧凑化:折叠组(details/summary)── */\n.ss-collapse {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n overflow: hidden;\n}\n\n.ss-collapseHead {\n display: flex;\n align-items: baseline;\n gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n list-style: none;\n user-select: none;\n}\n\n.ss-collapseHead::-webkit-details-marker {\n display: none;\n}\n\n.ss-collapseHead::before {\n content: '▸';\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n transition: transform 0.15s ease;\n}\n\n.ss-collapse[open] > .ss-collapseHead::before {\n transform: rotate(90deg);\n}\n\n.ss-collapseHead:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.06));\n}\n\n.ss-collapseTitle {\n font-size: 13px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.ss-collapseDesc {\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n}\n\n.ss-collapseBody {\n padding: 10px 14px 14px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n\n/* ── v1.27.5 紧凑化:只读定义列表(dl/dt/dd 2 列网格)── */\n.ss-defList {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 6px 20px;\n margin: 0;\n width: 100%;\n min-width: 0;\n}\n\n.ss-defItem {\n display: flex;\n flex-direction: column;\n gap: 1px;\n min-width: 0;\n}\n\n.ss-defTerm {\n font-size: 11px;\n line-height: 16px;\n font-weight: 500;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.ss-defValue {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n word-break: break-word;\n overflow-wrap: anywhere;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* ── v1.27.5 紧凑化:开关 + 端口内联行 ── */\n.ss-switchRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n/* ── v1.59.1 ACP 账户台账表(owner 2026-10-17:「面板里只放个开关不放具体的条目吗?」)\n *\n * 用**表格**而不是卡片列表:台账天生是\"一行一条、列对齐可比\"的数据\n * (账户 / 角色 / 状态 / token 指纹),卡片会把可比性拆散。\n * 窄屏靠横向滚动兜底——不折行,折行后\"哪一格对应哪一列\"就看不出来了。 */\n.ss-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 12px;\n overflow-x: auto;\n display: block;\n}\n\n.ss-table th,\n.ss-table td {\n padding: 4px 8px;\n text-align: left;\n border-bottom: 1px solid var(--dsw-alias-border-l2);\n white-space: nowrap;\n}\n\n.ss-table th {\n color: var(--dsw-alias-label-tertiary);\n font-weight: 600;\n}\n\n.ss-table td code {\n font-size: 11px;\n padding: 1px 4px;\n border-radius: 3px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n}\n\n.ss-table button {\n margin-left: 4px;\n}\n\n/* ── v1.27.6 「?」帮助浮层(hover/焦点显示三行以上严谨说明)── */\n.ss-help {\n position: relative;\n display: inline-flex;\n align-items: center;\n margin-top: 2px;\n}\n\n.ss-helpMark {\n flex: none;\n width: 16px;\n height: 16px;\n padding: 0;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 50%;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-tertiary);\n font-size: 10px;\n line-height: 1;\n font-weight: 600;\n cursor: help;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n transition: border-color 0.15s ease, color 0.15s ease;\n}\n\n.ss-helpMark:hover,\n.ss-helpMark:focus-visible {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n outline: none;\n}\n\n.ss-helpTip {\n position: absolute;\n left: 0;\n top: calc(100% + 6px);\n z-index: 30;\n width: 300px;\n max-width: 60vw;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-overlay);\n box-shadow: 0 6px 24px rgba(0, 0, 0, 0.28);\n font-size: 12px;\n line-height: 19px;\n font-weight: 400;\n color: var(--dsw-alias-label-primary);\n white-space: pre-line;\n word-break: break-word;\n opacity: 0;\n pointer-events: none;\n transform: translateY(-4px);\n transition: opacity 0.15s ease, transform 0.15s ease;\n}\n\n.ss-help:hover .ss-helpTip,\n.ss-helpMark:focus-visible + .ss-helpTip {\n opacity: 1;\n pointer-events: auto;\n transform: translateY(0);\n}\n\n/* 折叠组内帮助浮层定位修正(左侧行卡不会溢出面板) */\n.ss-rowCard .ss-helpTip {\n left: 0;\n}\n\n/* ── 微信桥(F4c-3,v1.27.0 实验性)── */\n\n/* CCC 选择器(下拉) */\n.ss-select {\n max-width: 280px;\n padding: 6px 8px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* 扫码绑定按钮(复用 wakeBtn 语言) */\n.ss-loginBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-loginBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-loginBtn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 移除账号按钮(危险语义:红边框) */\n.ss-removeBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-removeBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-error-primary, #ef4444);\n color: var(--dsw-alias-state-error-primary, #ef4444);\n}\n\n/* 二维码展示行 */\n.ss-qrRow {\n align-items: center;\n}\n\n.ss-qrSvg {\n flex: none;\n width: 120px; /* v1.27.5 紧凑化:160 → 120 */\n height: 120px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: #fff; /* 二维码需要白底才能被微信扫出 */\n padding: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.ss-qrSvg svg {\n width: 100%;\n height: 100%;\n}\n\n/* 路由表 JSON 编辑框 */\n.ss-routesEditor {\n width: 100%;\n box-sizing: border-box;\n margin-top: 6px;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-family: var(--ds-font-family-code);\n resize: vertical;\n}\n";
|
|
4593
4599
|
if (typeof document !== "undefined" && document.querySelector("style[data-sp-css=\"SettingsSection\"]") === null) {
|
|
4594
4600
|
const tag = document.createElement("style");
|
|
4595
4601
|
tag.setAttribute("data-sp-css", "SettingsSection");
|
package/lib/host/access.d.ts
CHANGED
|
@@ -86,14 +86,27 @@ export declare function hostWeb(ctx: unknown): HostWeb | undefined;
|
|
|
86
86
|
/**
|
|
87
87
|
* `ctx.subagents`(lazy;v1.31.3:cheap-worker foreground 模式的委派正门)。
|
|
88
88
|
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
89
|
+
* 🔴 **形状对照宿主 `0.2.1-rc.1`(v1.59.3 适配)**:旧成员 `start(name, request)` **已消失**,
|
|
90
|
+
* 改由 `startActivation(spec)` 承担(实名由 `spec.provider` 指定,我们仍传 `'spawn'`)。
|
|
91
|
+
* - 旧:`start(name: string, request: SubagentStartRequest): Promise<SubagentRun>`
|
|
92
|
+
* - 新:`startActivation(spec: SubagentActivationSpec): Promise<SubagentActivation>`(两个重载)
|
|
93
|
+
* 实测依据(**类型面与运行时都核过**,不是只看一边):
|
|
94
|
+
* `_tmp/host-0.2.1-rc.1/…/dsh-subagent/lib/types/index.d.ts:135,145` +
|
|
95
|
+
* 同包 `lib/index.js` 里**只有** `async startActivation(spec)`。
|
|
96
|
+
* ⚠️ **两个方法都保留为可选**:本仓仍声明支持 `0.1.7-rc.1` 双档(`VERIFIED_HOST_BANDS`),
|
|
97
|
+
* 而 0.1.7 那代同样只有 `start` —— **删掉 `start` 会让旧档在类型面说谎**。
|
|
98
|
+
* 调用方按"先新后旧"选用,见 `cheap-worker.ts` 的 `resolveSubagentStarter()`。
|
|
99
|
+
*
|
|
100
|
+
* 本模块只做形状收口;语义与错误处理归调用方。
|
|
91
101
|
*
|
|
92
102
|
* 注(v1.31.4):本服务**不在** dsp 的 `inject` 列表内,因此走 `hostInjected` 的
|
|
93
103
|
* 回落分支(`ctx.get`)——见 `hostInjected` 的 v1.31.4 说明与真实 cordis 用例。
|
|
94
104
|
*/
|
|
95
105
|
interface HostSubagents {
|
|
106
|
+
/** 宿主 `0.1.7-rc.*` / `0.2.0-rc.*`(旧形态;0.2.1-rc.1 起已移除) */
|
|
96
107
|
start?: (name: string, request: unknown) => Promise<unknown>;
|
|
108
|
+
/** 宿主 `0.2.1-rc.1+`(新形态;`spec.provider` 即旧 `name`) */
|
|
109
|
+
startActivation?: (spec: unknown) => Promise<unknown>;
|
|
97
110
|
}
|
|
98
111
|
export declare function hostSubagents(ctx: unknown): HostSubagents | undefined;
|
|
99
112
|
/**
|
package/lib/host/contract.d.ts
CHANGED
|
@@ -82,6 +82,14 @@ export declare const HOST_EVENTS: readonly HostEventContract[];
|
|
|
82
82
|
* - ⇒ 双档是**唯一不把本机宿主堵死的写法**(两档在真 semver 下均 PASS)。
|
|
83
83
|
* 档上界取 `<0.2.0` / `<0.3.0`(即 `^0.2.0-rc.1` 的 caret 上界):第二档同时覆盖
|
|
84
84
|
* `0.2.0` 正式版与后续 `0.2.0-rc.N`。
|
|
85
|
+
*
|
|
86
|
+
* 🆕 **第三档 `^0.2.1-rc.1`(v1.59.3 适配,2026-10-17)** —— 为什么非加不可:
|
|
87
|
+
* - npm 的 `^0.2.0-rc.1` 对**预发布**只放行**同一 `major.minor.patch`** 的 prerelease
|
|
88
|
+
* ⇒ **`0.2.1-rc.1` 不满足它**(patch 位不同)⇒ 宿主门会**整包拒绝安装**(D98 同族)。
|
|
89
|
+
* - 而第二档的 `ceiling: '0.3.0'` 本来就"包含" 0.2.1 —— 那是**我们自己的手写比较器**的语义;
|
|
90
|
+
* 两个判据(真 semver 门 vs 手写区间)在这里**给出相反答案**,故必须把第三档显式写出来,
|
|
91
|
+
* 让**派生串**({@link REQUIRED_HOST_RANGE})里真的出现 `^0.2.1-rc.1`。
|
|
92
|
+
* ⚠️ **加档不是收窄**:前两档原样保留(0.1.7 / 0.2.0 两代仍被声明支持)。
|
|
85
93
|
*/
|
|
86
94
|
export declare const VERIFIED_HOST_BANDS: readonly {
|
|
87
95
|
floor: string;
|
package/lib/index.js
CHANGED
|
@@ -9178,12 +9178,68 @@ function parseForegroundJobs(raw) {
|
|
|
9178
9178
|
}
|
|
9179
9179
|
return jobs;
|
|
9180
9180
|
}
|
|
9181
|
+
/**
|
|
9182
|
+
* 选出一个可用的"启动子 agent"函数(**先新后旧**)。
|
|
9183
|
+
*
|
|
9184
|
+
* 🔴 **为什么需要这一层**(v1.59.3 适配,2026-10-17):
|
|
9185
|
+
* 宿主 `0.2.1-rc.1` **移除了** `SubagentRuntime.start(name, request)`,改由
|
|
9186
|
+
* `startActivation(spec)` 承担;而 `0.1.7-rc.*` / `0.2.0-rc.*` **只有** `start`。
|
|
9187
|
+
* 本仓仍声明支持双档(`VERIFIED_HOST_BANDS`)⇒ **不能只留一边**。
|
|
9188
|
+
*
|
|
9189
|
+
* 形状差异(逐字取自两端 `.d.ts`):
|
|
9190
|
+
* · 旧:`start('spawn', {label?, prompt, parent, signal, agentOptions, toolFilter})`
|
|
9191
|
+
* · 新:`startActivation({provider:'spawn', label, request:{prompt, parent, agentOptions}, signal, delivery})`
|
|
9192
|
+
* —— 注意 `label` 在新 spec 里是**必填**(`readonly label: string`),故这里补默认值。
|
|
9193
|
+
*
|
|
9194
|
+
* @returns 一个统一签名的启动器;两者都不存在 ⇒ `undefined`(由调用方报**可诊断**的错)
|
|
9195
|
+
*/
|
|
9196
|
+
function resolveSubagentStarter(subagents) {
|
|
9197
|
+
if (typeof subagents.startActivation === "function") {
|
|
9198
|
+
const fn = subagents.startActivation;
|
|
9199
|
+
return (req) => fn({
|
|
9200
|
+
provider: "spawn",
|
|
9201
|
+
label: req.label,
|
|
9202
|
+
request: {
|
|
9203
|
+
prompt: req.prompt,
|
|
9204
|
+
parent: req.parent,
|
|
9205
|
+
agentOptions: req.agentOptions,
|
|
9206
|
+
...req.cwd === void 0 ? {} : { cwd: req.cwd }
|
|
9207
|
+
},
|
|
9208
|
+
signal: req.signal,
|
|
9209
|
+
delivery: "caller"
|
|
9210
|
+
});
|
|
9211
|
+
}
|
|
9212
|
+
if (typeof subagents.start === "function") {
|
|
9213
|
+
const fn = subagents.start;
|
|
9214
|
+
return (req) => fn("spawn", {
|
|
9215
|
+
label: req.label,
|
|
9216
|
+
prompt: req.prompt,
|
|
9217
|
+
parent: req.parent,
|
|
9218
|
+
signal: req.signal,
|
|
9219
|
+
agentOptions: req.agentOptions,
|
|
9220
|
+
toolFilter: { deny: ["cheap-worker"] },
|
|
9221
|
+
...req.cwd === void 0 ? {} : { cwd: req.cwd }
|
|
9222
|
+
});
|
|
9223
|
+
}
|
|
9224
|
+
}
|
|
9181
9225
|
/** 拼接 SubagentResult.output(ContentBlock[])中的文本块 */
|
|
9182
9226
|
function subagentOutputText(output) {
|
|
9183
9227
|
if (!Array.isArray(output)) return "";
|
|
9184
9228
|
return output.filter((b) => typeof b === "object" && b !== null).filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("");
|
|
9185
9229
|
}
|
|
9186
9230
|
/**
|
|
9231
|
+
* 任务首行 → 子会话 label(截断)。
|
|
9232
|
+
*
|
|
9233
|
+
* 🔴 为什么必须有一个:新宿主 `SubagentActivationSpec.label` 是**必填**(`readonly label: string`),
|
|
9234
|
+
* 而我们对外把 `label` 当可选参数 ⇒ 缺省时必须自己造一个,否则 spec 不合法。
|
|
9235
|
+
* 取任务首行而不是空串:子会话在列表里要能被人认出来(空串等于没有标识)。
|
|
9236
|
+
*/
|
|
9237
|
+
function firstLineLabel(task) {
|
|
9238
|
+
const line = task.split(/\r?\n/)[0]?.trim() ?? "";
|
|
9239
|
+
const clipped = line.length > 60 ? `${line.slice(0, 60)}…` : line;
|
|
9240
|
+
return clipped === "" ? "task" : clipped;
|
|
9241
|
+
}
|
|
9242
|
+
/**
|
|
9187
9243
|
* providerPreflight — 在**创建任何 worker 之前**判定「本机有没有该 provider 的 adapter」。
|
|
9188
9244
|
*
|
|
9189
9245
|
* 返回**可执行**的错误文本;`null` = 可用,**或无法取证**(服务缺失 / 形状不符 / 读取抛错
|
|
@@ -9231,7 +9287,8 @@ async function runForegroundJob(ctx, opts) {
|
|
|
9231
9287
|
const { task, label, model, models, parent, signal } = opts;
|
|
9232
9288
|
requireWhitelistedModel(model, models);
|
|
9233
9289
|
const subagents = hostSubagents(ctx);
|
|
9234
|
-
|
|
9290
|
+
const starter = subagents === void 0 ? void 0 : resolveSubagentStarter(subagents);
|
|
9291
|
+
if (starter === void 0) throw new Error("cheap-worker: host subagents service unavailable — neither ctx.subagents.startActivation (0.2.1-rc.1+) nor ctx.subagents.start (0.1.7-rc.* / 0.2.0-rc.*) yielded a starter function. Check `dashboard health` (host-contract section, service \"subagents\").");
|
|
9235
9292
|
const preflight = providerPreflight(ctx, model);
|
|
9236
9293
|
if (preflight !== null) return {
|
|
9237
9294
|
mode: "foreground",
|
|
@@ -9242,8 +9299,8 @@ async function runForegroundJob(ctx, opts) {
|
|
|
9242
9299
|
diagnostic: preflight
|
|
9243
9300
|
};
|
|
9244
9301
|
const { provider, model: modelName } = splitModel(model);
|
|
9245
|
-
const run = await
|
|
9246
|
-
|
|
9302
|
+
const run = await starter({
|
|
9303
|
+
label: label ?? firstLineLabel(task),
|
|
9247
9304
|
prompt: [{
|
|
9248
9305
|
type: "text",
|
|
9249
9306
|
text: task
|
|
@@ -9253,8 +9310,7 @@ async function runForegroundJob(ctx, opts) {
|
|
|
9253
9310
|
agentOptions: {
|
|
9254
9311
|
...provider === void 0 ? {} : { provider },
|
|
9255
9312
|
model: modelName
|
|
9256
|
-
}
|
|
9257
|
-
toolFilter: { deny: ["cheap-worker"] }
|
|
9313
|
+
}
|
|
9258
9314
|
});
|
|
9259
9315
|
try {
|
|
9260
9316
|
const result = run.result === void 0 ? {} : await run.result;
|
|
@@ -9265,7 +9321,7 @@ async function runForegroundJob(ctx, opts) {
|
|
|
9265
9321
|
model,
|
|
9266
9322
|
stopReason,
|
|
9267
9323
|
output: subagentOutputText(result.output),
|
|
9268
|
-
...typeof run.id === "string" ? { childId: run.id } : {},
|
|
9324
|
+
...typeof (run.childId ?? run.id) === "string" ? { childId: run.childId ?? run.id } : {},
|
|
9269
9325
|
...typeof result.diagnostic === "string" && result.diagnostic !== "" ? { diagnostic: result.diagnostic } : {}
|
|
9270
9326
|
};
|
|
9271
9327
|
} finally {
|
|
@@ -15005,7 +15061,7 @@ const FILE_UPLOAD_PATH = "/serenity/file-upload";
|
|
|
15005
15061
|
const CONFIG_PATH = "/serenity/config";
|
|
15006
15062
|
const CCCS_PATH = "/serenity/cccs";
|
|
15007
15063
|
const WEIXIN_PATH = "/serenity/weixin";
|
|
15008
|
-
/** 🔴 v1.59.
|
|
15064
|
+
/** 🔴 v1.59.1:ACP 账户台账(面板数据面;形状照 `/serenity/weixin`) */
|
|
15009
15065
|
const ACP_PATH = "/serenity/acp";
|
|
15010
15066
|
/** 图片落盘目录(CCC 根相对;S142 图片自动识别基础设施——粘贴图片落盘供 agent 经 CCC vlm MSM 自主处理) */
|
|
15011
15067
|
const IMAGE_UPLOAD_DIR = "_tmp/images_from_user";
|
|
@@ -16124,14 +16180,29 @@ const HOST_SERVICES = [
|
|
|
16124
16180
|
* - ⇒ 双档是**唯一不把本机宿主堵死的写法**(两档在真 semver 下均 PASS)。
|
|
16125
16181
|
* 档上界取 `<0.2.0` / `<0.3.0`(即 `^0.2.0-rc.1` 的 caret 上界):第二档同时覆盖
|
|
16126
16182
|
* `0.2.0` 正式版与后续 `0.2.0-rc.N`。
|
|
16183
|
+
*
|
|
16184
|
+
* 🆕 **第三档 `^0.2.1-rc.1`(v1.59.3 适配,2026-10-17)** —— 为什么非加不可:
|
|
16185
|
+
* - npm 的 `^0.2.0-rc.1` 对**预发布**只放行**同一 `major.minor.patch`** 的 prerelease
|
|
16186
|
+
* ⇒ **`0.2.1-rc.1` 不满足它**(patch 位不同)⇒ 宿主门会**整包拒绝安装**(D98 同族)。
|
|
16187
|
+
* - 而第二档的 `ceiling: '0.3.0'` 本来就"包含" 0.2.1 —— 那是**我们自己的手写比较器**的语义;
|
|
16188
|
+
* 两个判据(真 semver 门 vs 手写区间)在这里**给出相反答案**,故必须把第三档显式写出来,
|
|
16189
|
+
* 让**派生串**({@link REQUIRED_HOST_RANGE})里真的出现 `^0.2.1-rc.1`。
|
|
16190
|
+
* ⚠️ **加档不是收窄**:前两档原样保留(0.1.7 / 0.2.0 两代仍被声明支持)。
|
|
16127
16191
|
*/
|
|
16128
|
-
const VERIFIED_HOST_BANDS = [
|
|
16129
|
-
|
|
16130
|
-
|
|
16131
|
-
|
|
16132
|
-
|
|
16133
|
-
|
|
16134
|
-
|
|
16192
|
+
const VERIFIED_HOST_BANDS = [
|
|
16193
|
+
{
|
|
16194
|
+
floor: "0.1.7-rc.1",
|
|
16195
|
+
ceiling: "0.2.0"
|
|
16196
|
+
},
|
|
16197
|
+
{
|
|
16198
|
+
floor: "0.2.0-rc.1",
|
|
16199
|
+
ceiling: "0.3.0"
|
|
16200
|
+
},
|
|
16201
|
+
{
|
|
16202
|
+
floor: "0.2.1-rc.1",
|
|
16203
|
+
ceiling: "0.3.0"
|
|
16204
|
+
}
|
|
16205
|
+
];
|
|
16135
16206
|
/**
|
|
16136
16207
|
* 与 `package.json` 的 `peerDependencies` **逐字同串**的宿主版本范围(由 {@link VERIFIED_HOST_BANDS} 派生)。
|
|
16137
16208
|
*
|
|
@@ -21953,6 +22024,7 @@ function registerWeixinSendApi(ctx) {
|
|
|
21953
22024
|
var acp_auth_exports = /* @__PURE__ */ __exportAll({
|
|
21954
22025
|
acpChannelEnabled: () => acpChannelEnabled,
|
|
21955
22026
|
acpChannelStatus: () => acpChannelStatus,
|
|
22027
|
+
acpSessionKeyFor: () => acpSessionKeyFor,
|
|
21956
22028
|
acpTokenKeyFor: () => acpTokenKeyFor,
|
|
21957
22029
|
cwdInsideRoot: () => cwdInsideRoot,
|
|
21958
22030
|
ensureAcpChannelTokens: () => ensureAcpChannelTokens,
|
|
@@ -21961,6 +22033,7 @@ var acp_auth_exports = /* @__PURE__ */ __exportAll({
|
|
|
21961
22033
|
extractToken: () => extractToken,
|
|
21962
22034
|
listAcpRoots: () => listAcpRoots,
|
|
21963
22035
|
normalizeAcpAccount: () => normalizeAcpAccount,
|
|
22036
|
+
readAcpMainSession: () => readAcpMainSession,
|
|
21964
22037
|
readAcpToken: () => readAcpToken,
|
|
21965
22038
|
readImSettings: () => readImSettings,
|
|
21966
22039
|
removeAcpAccount: () => removeAcpAccount,
|
|
@@ -21969,7 +22042,8 @@ var acp_auth_exports = /* @__PURE__ */ __exportAll({
|
|
|
21969
22042
|
safeEqual: () => safeEqual,
|
|
21970
22043
|
saveAcpAccounts: () => saveAcpAccounts,
|
|
21971
22044
|
tokenFingerprint: () => tokenFingerprint,
|
|
21972
|
-
upsertAcpAccount: () => upsertAcpAccount
|
|
22045
|
+
upsertAcpAccount: () => upsertAcpAccount,
|
|
22046
|
+
writeAcpMainSession: () => writeAcpMainSession
|
|
21973
22047
|
});
|
|
21974
22048
|
/** 多账户凭据键前缀(`IM_ACP_TOKEN_<ACCOUNT_ID>`;与微信桥 `WEIXIN_<ID>_<FIELD>` 同构) */
|
|
21975
22049
|
const ACP_TOKEN_PREFIX = "IM_ACP_TOKEN_";
|
|
@@ -22045,6 +22119,49 @@ function safeEqual(a, b) {
|
|
|
22045
22119
|
function tokenFingerprint(token) {
|
|
22046
22120
|
return createHash("sha256").update(token).digest("hex").slice(0, 12);
|
|
22047
22121
|
}
|
|
22122
|
+
/** 主会话键前缀(`IM_ACP_SESSION_<ACCOUNT_ID>`;与 `IM_ACP_TOKEN_<ACCOUNT_ID>` 同族同规则) */
|
|
22123
|
+
const ACP_SESSION_PREFIX = "IM_ACP_SESSION_";
|
|
22124
|
+
/**
|
|
22125
|
+
* 该账户的**主会话**键(`IM_ACP_SESSION_<ACCOUNT_ID>`)。
|
|
22126
|
+
*
|
|
22127
|
+
* 🔴 为什么放在 **credential** 节而不是 config 节:它跟 token 一样是**每账户一值的键控数据**
|
|
22128
|
+
* (键由 `accountId` 派生、随账户生命周期存亡)。放 config 节要另造一层 `section.key`
|
|
22129
|
+
* 结构,而这一层的唯一读者就是本模块 ⇒ 多一层结构 = 多一处会漂的形状。
|
|
22130
|
+
*
|
|
22131
|
+
* ⚠️ 它**不是凭据**(会话 id 不是秘密)—— 共享 credential 节只是为了复用它既有的
|
|
22132
|
+
* 「按 accountId 派生键 + 删账户时一并清理」这套语义,不引入第二套键规则。
|
|
22133
|
+
*/
|
|
22134
|
+
function acpSessionKeyFor(accountId) {
|
|
22135
|
+
return `${ACP_SESSION_PREFIX}${accountKeyPart(accountId)}`;
|
|
22136
|
+
}
|
|
22137
|
+
/**
|
|
22138
|
+
* 读该账户的主会话 id(不存在 ⇒ `null`)。
|
|
22139
|
+
*
|
|
22140
|
+
* 🔴 **读路径无副作用**(与 `readAcpToken` 同纪律):不存在的含义是「这个账户还没有主会话」,
|
|
22141
|
+
* 而那时的正确行为是**让 App 去建**(`session/list` 回空数组),不是替它编一个。
|
|
22142
|
+
*/
|
|
22143
|
+
function readAcpMainSession(root, accountId) {
|
|
22144
|
+
try {
|
|
22145
|
+
const value = getEntry(root, "credential", acpSessionKeyFor(accountId));
|
|
22146
|
+
return typeof value === "string" && value.trim() !== "" ? value.trim() : null;
|
|
22147
|
+
} catch {
|
|
22148
|
+
return null;
|
|
22149
|
+
}
|
|
22150
|
+
}
|
|
22151
|
+
/**
|
|
22152
|
+
* 记下该账户的主会话 id(**写操作**;由 `session/new` 的出站响应触发)。
|
|
22153
|
+
*
|
|
22154
|
+
* 🔴 **后写覆盖先写**(last-wins):owner 要的是"一条长期对话",而"再建一条"是用户
|
|
22155
|
+
* 在 App 里显式点「新对话」的结果 ⇒ 那时他要的就是**新的那条**。
|
|
22156
|
+
* ⛔ **被否的两种做法**:① 已有主会话就拒建(用户会看到"点了没反应");
|
|
22157
|
+
* ② 把 `session/new` 改写成 `session/resume`(响应形状不同,要让客户端认一条
|
|
22158
|
+
* **不存在于协议里**的响应 —— 那是拿协议兼容性换一个我们本来就有的语义)。
|
|
22159
|
+
*/
|
|
22160
|
+
function writeAcpMainSession(root, accountId, sessionId) {
|
|
22161
|
+
const id = String(sessionId ?? "").trim();
|
|
22162
|
+
if (id === "") return;
|
|
22163
|
+
writeEntry(root, "credential", acpSessionKeyFor(accountId), id);
|
|
22164
|
+
}
|
|
22048
22165
|
/**
|
|
22049
22166
|
* 从一次 HTTP upgrade 请求里取出候选 token。
|
|
22050
22167
|
*
|
|
@@ -22223,6 +22340,7 @@ function acpChannelStatus(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
|
|
|
22223
22340
|
enabled,
|
|
22224
22341
|
hasToken: token !== null,
|
|
22225
22342
|
tokenFingerprint: token ? tokenFingerprint(token) : null,
|
|
22343
|
+
mainSession: readAcpMainSession(root, accountId),
|
|
22226
22344
|
usable: usable.has(accountId),
|
|
22227
22345
|
blockedBy
|
|
22228
22346
|
});
|
|
@@ -22304,11 +22422,17 @@ function upsertAcpAccount(root, account, paths = DEFAULT_SERENITY_CONFIG_PATHS)
|
|
|
22304
22422
|
else list.push(account);
|
|
22305
22423
|
return saveAcpAccounts(root, list, paths);
|
|
22306
22424
|
}
|
|
22307
|
-
/**
|
|
22425
|
+
/**
|
|
22426
|
+
* 删一条账户(**同时清掉它的 token + 主会话**,避免留死键)。
|
|
22427
|
+
*
|
|
22428
|
+
* 🔴 主会话键**必须一起清**:否则日后用同一个 `accountId` 重建账户时,它会**静默继承**
|
|
22429
|
+
* 那条旧会话 —— 而那条会话的 cwd/角色都是上一代账户的。这个失效**没有任何信号**
|
|
22430
|
+
* (列表上它看着就是一条正常会话)。
|
|
22431
|
+
*/
|
|
22308
22432
|
function removeAcpAccount(root, accountId, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
|
|
22309
22433
|
const next = saveAcpAccounts(root, (readImSettings(root, paths).acp.accounts ?? []).filter((a) => a.accountId !== accountId), paths);
|
|
22310
|
-
try {
|
|
22311
|
-
unsetEntry(root, "credential",
|
|
22434
|
+
for (const key of [acpTokenKeyFor(accountId), acpSessionKeyFor(accountId)]) try {
|
|
22435
|
+
unsetEntry(root, "credential", key);
|
|
22312
22436
|
} catch {}
|
|
22313
22437
|
return next;
|
|
22314
22438
|
}
|
|
@@ -22500,7 +22624,7 @@ function acpFaceEnabled() {
|
|
|
22500
22624
|
return readSimpleSettings().acpFaceEnabled;
|
|
22501
22625
|
}
|
|
22502
22626
|
/**
|
|
22503
|
-
* 把客户端给的 `cwd` 归一化成宿主要求的**绝对路径**(v1.59.
|
|
22627
|
+
* 把客户端给的 `cwd` 归一化成宿主要求的**绝对路径**(v1.59.1)。
|
|
22504
22628
|
*
|
|
22505
22629
|
* ## 为什么需要
|
|
22506
22630
|
* 宿主 `dsh-acp` 在每次 `session/new` 都有这一行(`lib/index.js` 逐字):
|
|
@@ -22538,13 +22662,20 @@ function normalizeClientCwd(raw, cccRoot) {
|
|
|
22538
22662
|
* 实测注意(P0′):`dsh-acp` 写出的每条消息**自带换行**,客户端同样 ⇒ 按行切分即正确。
|
|
22539
22663
|
* ⚠️ **空行要丢**;**非 JSON 帧要容忍**(桥/客户端可能插入控制帧)。
|
|
22540
22664
|
*
|
|
22541
|
-
* 🔴 v1.59.
|
|
22665
|
+
* 🔴 v1.59.1:**入站消息在这里做一次 `cwd` 归一化**(见 {@link normalizeClientCwd})。
|
|
22542
22666
|
* 为什么是这里:`session/new` 与 `session/resume` 都带 `cwd`,而这是**唯一**能同时
|
|
22543
22667
|
* 拦到两者、且属于我们自己的缝(再往下就进 `dsh-acp` 了,那里改了就是改宿主)。
|
|
22544
22668
|
*
|
|
22669
|
+
* 🔴 v1.59.2:这条缝同时承担 **owner「每账户固定一条长期对话」** 的双向改写 ——
|
|
22670
|
+
* 入站 `recordAcpPending`(记 id→method)+ 出站 `rewriteAcpResponse`(收窄 list / 记 new)。
|
|
22671
|
+
* ⚠️ 两件事都必须在**同一个** `ws` 上做:`pending` 是**每条连接一份**的,
|
|
22672
|
+
* 拆到两处会让它失去"这条连接上请求与响应配对"这个唯一用途。
|
|
22673
|
+
*
|
|
22545
22674
|
* @param cccRoot 本连接归属的 CCC 根(token 反查所得)
|
|
22675
|
+
* @param accountId 本连接归属的账户键(主会话键由它派生)
|
|
22546
22676
|
*/
|
|
22547
|
-
function wsToAcpStream(ws, onWarn, cccRoot) {
|
|
22677
|
+
function wsToAcpStream(ws, onWarn, cccRoot, accountId) {
|
|
22678
|
+
const pin = createAcpSessionPin(cccRoot, accountId);
|
|
22548
22679
|
return {
|
|
22549
22680
|
readable: new ReadableStream({ start(controller) {
|
|
22550
22681
|
let buf = "";
|
|
@@ -22562,6 +22693,7 @@ function wsToAcpStream(ws, onWarn, cccRoot) {
|
|
|
22562
22693
|
onWarn(line.slice(0, 120));
|
|
22563
22694
|
continue;
|
|
22564
22695
|
}
|
|
22696
|
+
recordAcpPending(pin, msg);
|
|
22565
22697
|
controller.enqueue(normalizeInboundCwd(msg, cccRoot));
|
|
22566
22698
|
}
|
|
22567
22699
|
});
|
|
@@ -22577,30 +22709,133 @@ function wsToAcpStream(ws, onWarn, cccRoot) {
|
|
|
22577
22709
|
});
|
|
22578
22710
|
} }),
|
|
22579
22711
|
writable: new WritableStream({ write(msg) {
|
|
22580
|
-
ws.send(`${JSON.stringify(msg)}\n`);
|
|
22712
|
+
ws.send(`${JSON.stringify(rewriteAcpResponse(pin, msg))}\n`);
|
|
22581
22713
|
} })
|
|
22582
22714
|
};
|
|
22583
22715
|
}
|
|
22584
22716
|
/**
|
|
22585
|
-
*
|
|
22717
|
+
* 需要把 `cwd` 钉到本连接 CCC 根的方法(**必须逐个列出**)。
|
|
22586
22718
|
*
|
|
22587
|
-
* 🔴
|
|
22588
|
-
*
|
|
22719
|
+
* 🔴 **v1.59.1 的第一版判据错了**:只判「params 里有没有 `cwd`」⇒ 漏掉 `session/list`。
|
|
22720
|
+
* 宿主 `listSessions` 的过滤是
|
|
22721
|
+
* `if (params.cwd !== void 0 && !sameDirectory(header.cwd, params.cwd)) return`
|
|
22722
|
+
* —— **不给 cwd 就返回全机器所有会话**。实测后果:手机 App 列表里冒出
|
|
22723
|
+
* 23 条别的 CCC 的会话,全部显示为 `UNKNOWN` / `New Chat`
|
|
22724
|
+
* (owner:「**我需要是指定CCC的,不然没办法用**」)。
|
|
22725
|
+
* ⇒ 🆕 **判据:凡"应当被 CCC 收窄"的读接口,都必须把 `cwd` 钉成 CCC 根,不能依赖客户端传** ——
|
|
22726
|
+
* token 已经定了 CCC,让客户端再声明一次既冗余、又可以被静默漏掉。
|
|
22727
|
+
*/
|
|
22728
|
+
const CWD_PINNED_METHODS = /* @__PURE__ */ new Set([
|
|
22729
|
+
"session/new",
|
|
22730
|
+
"session/resume",
|
|
22731
|
+
"session/list"
|
|
22732
|
+
]);
|
|
22733
|
+
/**
|
|
22734
|
+
* 把一条入站消息的 `cwd` 归一化(**含"没给就补上"**)。
|
|
22735
|
+
*
|
|
22736
|
+
* | 情形 | 结果 |
|
|
22737
|
+
* |---|---|
|
|
22738
|
+
* | 方法不在 {@link CWD_PINNED_METHODS} | 原样(不碰别的消息) |
|
|
22739
|
+
* | 给了非绝对路径(`serenity` / `a` / 空串) | → 本连接 CCC 根 |
|
|
22740
|
+
* | 给了绝对路径 | 原样(越界由 `cwdInsideRoot` 拒) |
|
|
22741
|
+
* | **压根没给**(`session/list` 常见) | **→ 本连接 CCC 根**(否则列出全机器) |
|
|
22589
22742
|
*/
|
|
22590
22743
|
function normalizeInboundCwd(msg, cccRoot) {
|
|
22591
22744
|
if (typeof msg !== "object" || msg === null) return msg;
|
|
22592
22745
|
const m = msg;
|
|
22593
|
-
if (typeof m.
|
|
22594
|
-
|
|
22595
|
-
const normalized = normalizeClientCwd(m.params.cwd, cccRoot);
|
|
22746
|
+
if (typeof m.method !== "string" || !CWD_PINNED_METHODS.has(m.method)) return msg;
|
|
22747
|
+
const params = typeof m.params === "object" && m.params !== null ? m.params : {};
|
|
22596
22748
|
return {
|
|
22597
22749
|
...msg,
|
|
22598
22750
|
params: {
|
|
22599
|
-
...
|
|
22600
|
-
cwd:
|
|
22751
|
+
...params,
|
|
22752
|
+
cwd: normalizeClientCwd(params.cwd, cccRoot)
|
|
22601
22753
|
}
|
|
22602
22754
|
};
|
|
22603
22755
|
}
|
|
22756
|
+
/** 请求 id 的 Map 键(区分 `1` 与 `"1"` —— JSON-RPC 允许两者,混用也不该串号) */
|
|
22757
|
+
function requestIdKey(id) {
|
|
22758
|
+
return `${typeof id}:${String(id)}`;
|
|
22759
|
+
}
|
|
22760
|
+
/** 记账上限(防"客户端发一堆请求又从不收响应"把内存撑大) */
|
|
22761
|
+
const MAX_PENDING = 256;
|
|
22762
|
+
/** 建一条连接的钉住状态 */
|
|
22763
|
+
function createAcpSessionPin(root, accountId) {
|
|
22764
|
+
return {
|
|
22765
|
+
root,
|
|
22766
|
+
accountId,
|
|
22767
|
+
pending: /* @__PURE__ */ new Map()
|
|
22768
|
+
};
|
|
22769
|
+
}
|
|
22770
|
+
/**
|
|
22771
|
+
* 入站记账:把"这条请求的方法"记住,供出站按 `id` 反查。
|
|
22772
|
+
*
|
|
22773
|
+
* 判据:**既要有 `id`,又要有 `method`** —— 只满足一个的是响应(`id` 无 `method`)
|
|
22774
|
+
* 或通知(`method` 无 `id`)⇒ 与我们无关,不记。
|
|
22775
|
+
*/
|
|
22776
|
+
function recordAcpPending(pin, msg) {
|
|
22777
|
+
if (typeof msg !== "object" || msg === null) return;
|
|
22778
|
+
const m = msg;
|
|
22779
|
+
if (m.id === void 0 || m.id === null) return;
|
|
22780
|
+
if (typeof m.method !== "string") return;
|
|
22781
|
+
const key = requestIdKey(m.id);
|
|
22782
|
+
pin.pending.set(key, m.method);
|
|
22783
|
+
while (pin.pending.size > MAX_PENDING) {
|
|
22784
|
+
const oldest = pin.pending.keys().next();
|
|
22785
|
+
if (oldest.done) break;
|
|
22786
|
+
pin.pending.delete(oldest.value);
|
|
22787
|
+
}
|
|
22788
|
+
}
|
|
22789
|
+
/**
|
|
22790
|
+
* 出站改写(本功能的落点)。返回**要发给客户端的那条消息**。
|
|
22791
|
+
*
|
|
22792
|
+
* | 原响应 | 改写后 |
|
|
22793
|
+
* |---|---|
|
|
22794
|
+
* | `session/list` | `{sessions:[该账户那一条]}`;**没有主会话 ⇒ `{sessions:[]}`** |
|
|
22795
|
+
* | `session/new` | 原样,**同时**把 `result.sessionId` 记为该账户的主会话 |
|
|
22796
|
+
* | 其余(含所有 error 响应、所有通知) | 原样 |
|
|
22797
|
+
*
|
|
22798
|
+
* 🔴 **没有主会话时回空数组,绝不回退成全量列表** —— 退回去正是 owner 说
|
|
22799
|
+
* 「23 个对我没什么用」的那个东西。空数组会让 App 引导用户新建,而新建即建立主会话。
|
|
22800
|
+
*
|
|
22801
|
+
* 🔴 `nextCursor` **一律丢掉**:收窄到一条之后,分页游标失去意义;
|
|
22802
|
+
* 留着它会让客户端继续翻页(而后续页仍会被收窄成同一条 ⇒ 前端可能出现重复项)。
|
|
22803
|
+
*/
|
|
22804
|
+
function rewriteAcpResponse(pin, msg) {
|
|
22805
|
+
if (typeof msg !== "object" || msg === null) return msg;
|
|
22806
|
+
const m = msg;
|
|
22807
|
+
if (m.method !== void 0) return msg;
|
|
22808
|
+
const method = m.id === void 0 || m.id === null ? void 0 : pin.pending.get(requestIdKey(m.id));
|
|
22809
|
+
if (method === void 0) return msg;
|
|
22810
|
+
if (m.error !== void 0) return msg;
|
|
22811
|
+
if (method === "session/new") {
|
|
22812
|
+
const sessionId = m.result?.sessionId;
|
|
22813
|
+
if (typeof sessionId === "string" && sessionId.trim() !== "") try {
|
|
22814
|
+
writeAcpMainSession(pin.root, pin.accountId, sessionId);
|
|
22815
|
+
} catch (err) {
|
|
22816
|
+
console.warn(`[serenity-hooks] ⚠ 面 E:记录主会话失败(CCC=${pin.root} 账户=${pin.accountId}): ${String(err?.message ?? err)}`);
|
|
22817
|
+
}
|
|
22818
|
+
return msg;
|
|
22819
|
+
}
|
|
22820
|
+
if (method === "session/list") {
|
|
22821
|
+
const pinned = readAcpMainSession(pin.root, pin.accountId);
|
|
22822
|
+
if (!pinned) return {
|
|
22823
|
+
...m,
|
|
22824
|
+
result: { sessions: [] }
|
|
22825
|
+
};
|
|
22826
|
+
const list = m.result?.sessions;
|
|
22827
|
+
const hit = Array.isArray(list) ? list.find((s) => s?.sessionId === pinned) : void 0;
|
|
22828
|
+
const sessions = hit !== void 0 ? [hit] : [{
|
|
22829
|
+
sessionId: pinned,
|
|
22830
|
+
cwd: pin.root
|
|
22831
|
+
}];
|
|
22832
|
+
return {
|
|
22833
|
+
...m,
|
|
22834
|
+
result: { sessions }
|
|
22835
|
+
};
|
|
22836
|
+
}
|
|
22837
|
+
return msg;
|
|
22838
|
+
}
|
|
22604
22839
|
function newState() {
|
|
22605
22840
|
return {
|
|
22606
22841
|
handle: null,
|
|
@@ -22681,9 +22916,10 @@ ACP 面:认证失败(${verdict.reason})。请在该 CCC 的 localstore 生
|
|
|
22681
22916
|
try {
|
|
22682
22917
|
const connRoot = verdict.root;
|
|
22683
22918
|
const connRole = verdict.role;
|
|
22919
|
+
const connAccount = verdict.accountId;
|
|
22684
22920
|
const stream = wsToAcpStream(ws, (line) => {
|
|
22685
22921
|
console.log(`[serenity-hooks] 面 E:丢弃一条非 JSON 帧(前 120 字): ${line}`);
|
|
22686
|
-
}, connRoot);
|
|
22922
|
+
}, connRoot, connAccount);
|
|
22687
22923
|
const fiber = ctx.plugin({
|
|
22688
22924
|
inject: ACP_REQUIRED_SERVICES,
|
|
22689
22925
|
apply: (scoped) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.59.
|
|
3
|
+
"version": "1.59.3",
|
|
4
4
|
"description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:14 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.7-rc.1 与 0.2.0-rc.1(双档)。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -67,22 +67,22 @@
|
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"@deepseek-ai/cordis": "^4.0.4",
|
|
70
|
-
"@deepseek-ai/dsh-acp": "^0.1.7-rc.1 || ^0.2.0-rc.1",
|
|
71
|
-
"@deepseek-ai/dsh-agent": "^0.1.7-rc.1 || ^0.2.0-rc.1",
|
|
72
|
-
"@deepseek-ai/dsh-agent-loop": "^0.1.7-rc.1 || ^0.2.0-rc.1",
|
|
73
|
-
"@deepseek-ai/dsh-agent-preset-registry": "^0.1.7-rc.1 || ^0.2.0-rc.1",
|
|
74
|
-
"@deepseek-ai/dsh-compaction": "^0.1.7-rc.1 || ^0.2.0-rc.1",
|
|
75
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.1 || ^0.2.0-rc.1",
|
|
76
|
-
"@deepseek-ai/dsh-llm": "^0.1.7-rc.1 || ^0.2.0-rc.1",
|
|
77
|
-
"@deepseek-ai/dsh-session": "^0.1.7-rc.1 || ^0.2.0-rc.1",
|
|
78
|
-
"@deepseek-ai/dsh-session-title": "^0.1.7-rc.1 || ^0.2.0-rc.1",
|
|
79
|
-
"@deepseek-ai/dsh-shell": "^0.1.7-rc.1 || ^0.2.0-rc.1",
|
|
80
|
-
"@deepseek-ai/dsh-shell-env": "^0.1.7-rc.1 || ^0.2.0-rc.1",
|
|
81
|
-
"@deepseek-ai/dsh-skill": "^0.1.7-rc.1 || ^0.2.0-rc.1",
|
|
82
|
-
"@deepseek-ai/dsh-system-prompt": "^0.1.7-rc.1 || ^0.2.0-rc.1",
|
|
83
|
-
"@deepseek-ai/dsh-tools": "^0.1.7-rc.1 || ^0.2.0-rc.1",
|
|
84
|
-
"@deepseek-ai/dsh-web": "^0.1.7-rc.1 || ^0.2.0-rc.1",
|
|
85
|
-
"@deepseek-ai/dsh-web-fetch-http": "^0.1.7-rc.1 || ^0.2.0-rc.1",
|
|
70
|
+
"@deepseek-ai/dsh-acp": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
|
|
71
|
+
"@deepseek-ai/dsh-agent": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
|
|
72
|
+
"@deepseek-ai/dsh-agent-loop": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
|
|
73
|
+
"@deepseek-ai/dsh-agent-preset-registry": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
|
|
74
|
+
"@deepseek-ai/dsh-compaction": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
|
|
75
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
|
|
76
|
+
"@deepseek-ai/dsh-llm": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
|
|
77
|
+
"@deepseek-ai/dsh-session": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
|
|
78
|
+
"@deepseek-ai/dsh-session-title": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
|
|
79
|
+
"@deepseek-ai/dsh-shell": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
|
|
80
|
+
"@deepseek-ai/dsh-shell-env": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
|
|
81
|
+
"@deepseek-ai/dsh-skill": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
|
|
82
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
|
|
83
|
+
"@deepseek-ai/dsh-tools": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
|
|
84
|
+
"@deepseek-ai/dsh-web": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
|
|
85
|
+
"@deepseek-ai/dsh-web-fetch-http": "^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
|
|
86
86
|
"@deepseek-ai/schemastery": "^3.18.4",
|
|
87
87
|
"cordis": "^4.0.0-rc.7"
|
|
88
88
|
},
|