@shgroup/dsh-serenity-hooks 1.28.0 → 1.28.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dsh.plugin.json +1 -1
- package/lib/client.js +12 -37
- package/lib/config-ops.d.ts +13 -0
- package/lib/gateway-dsh-auth.d.ts +47 -0
- package/lib/gateway-proxy.d.ts +18 -1
- package/lib/gateway.d.ts +2 -2
- package/lib/index.d.ts +2 -1
- package/lib/index.js +233 -74
- package/lib/seams/bootstrap.d.ts +9 -0
- package/lib/session-ops.d.ts +11 -0
- package/lib/settings-section-BfVxgCZy.js +91 -0
- package/lib/settings-section.d.ts +5 -1
- package/lib/{skiff-debug-DIf_Uenr.js → skiff-debug-CBU6T2F_.js} +14 -3
- package/package.json +18 -16
- package/lib/settings-section-DnX9TJxt.js +0 -2124
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.28.
|
|
3
|
+
"version": "1.28.2",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "宁静号 ACC harness(Native Cordis 插件):真实 DSH 工具 cc_fs/session/acc_msm/eap/neat/cce/handyman/session_rebuild/skiff_admin + 拦截缝机械约束(safe-mode/路径守卫)+ 高级设定面板(双端口网关/账号管理)+ Skiff 认知子集角色(实验性)。适配 DSH 公开版(0.1.0-rc,deepseek-ai/deepseek-harness)。",
|
|
6
6
|
"engines": {
|
package/lib/client.js
CHANGED
|
@@ -3206,44 +3206,18 @@ window.__ModuleLoader__.load({
|
|
|
3206
3206
|
return null;
|
|
3207
3207
|
}
|
|
3208
3208
|
const CONFIG_PATH = "/serenity/config";
|
|
3209
|
-
/**
|
|
3210
|
-
* 获取已有工作区列表(需求 1:白名单从已有工作区选择而非手输)。
|
|
3211
|
-
* 走 DSH 原生 RPC `POST /api/workspace.list`(JSON RPC 形态)。
|
|
3212
|
-
* 信封必须是完整 ClientRequest:`{ type: 'client-request', rpcId, method, payload }`
|
|
3213
|
-
* (api/rpc.ts wire 契约——缺 type/method → clientRequestSchema 校验失败 → bad-request)。
|
|
3214
|
-
* 返回 { path, title }[];失败返回空数组(面板显示"暂无可选工作区")。
|
|
3215
|
-
*/
|
|
3216
|
-
async function fetchWorkspaces() {
|
|
3217
|
-
try {
|
|
3218
|
-
const res = await fetch("/api/workspace.list", {
|
|
3219
|
-
method: "POST",
|
|
3220
|
-
headers: { "content-type": "application/json" },
|
|
3221
|
-
body: JSON.stringify({
|
|
3222
|
-
type: "client-request",
|
|
3223
|
-
rpcId: `ws-${Date.now().toString(36)}`,
|
|
3224
|
-
method: "workspace.list",
|
|
3225
|
-
payload: {}
|
|
3226
|
-
})
|
|
3227
|
-
});
|
|
3228
|
-
if (!res.ok) return [];
|
|
3229
|
-
const items = (await res.json())?.result?.value?.items;
|
|
3230
|
-
if (!Array.isArray(items)) return [];
|
|
3231
|
-
return items.filter((i) => typeof i.path === "string" && i.path !== "").map((i) => ({
|
|
3232
|
-
path: i.path,
|
|
3233
|
-
title: typeof i.title === "string" && i.title !== "" ? i.title : i.path
|
|
3234
|
-
}));
|
|
3235
|
-
} catch {
|
|
3236
|
-
return [];
|
|
3237
|
-
}
|
|
3238
|
-
}
|
|
3239
|
-
/** GET 配置(含账号列表;无 hash) */
|
|
3209
|
+
/** GET 配置(含账号列表 + knownWorkspaces;无 hash) */
|
|
3240
3210
|
async function fetchConfig() {
|
|
3241
3211
|
const res = await fetch(CONFIG_PATH, { headers: {
|
|
3242
3212
|
accept: "application/json",
|
|
3243
3213
|
"x-serenity-ui": "1"
|
|
3244
3214
|
} });
|
|
3245
3215
|
if (!res.ok) return null;
|
|
3246
|
-
|
|
3216
|
+
const body = await res.json();
|
|
3217
|
+
return {
|
|
3218
|
+
config: body.config ?? null,
|
|
3219
|
+
knownWorkspaces: body.knownWorkspaces ?? []
|
|
3220
|
+
};
|
|
3247
3221
|
}
|
|
3248
3222
|
/** PUT 配置(账号 patch + 工作区白名单 + persona 彩蛋 + 开放容器白名单)→ 返回保存后的 wire */
|
|
3249
3223
|
async function saveConfig(patch) {
|
|
@@ -3303,9 +3277,10 @@ window.__ModuleLoader__.load({
|
|
|
3303
3277
|
const [saved, setSaved] = (0, react.useState)(false);
|
|
3304
3278
|
(0, react.useEffect)(() => {
|
|
3305
3279
|
let alive = true;
|
|
3306
|
-
|
|
3280
|
+
fetchConfig().then((resp) => {
|
|
3307
3281
|
if (!alive) return;
|
|
3308
|
-
|
|
3282
|
+
const cfg = resp?.config ?? null;
|
|
3283
|
+
setKnownWorkspaces(resp?.knownWorkspaces ?? []);
|
|
3309
3284
|
if (cfg) {
|
|
3310
3285
|
setConfig(cfg);
|
|
3311
3286
|
setHost(cfg.gateway.host);
|
|
@@ -3734,8 +3709,8 @@ Instruction-following style:
|
|
|
3734
3709
|
let alive = true;
|
|
3735
3710
|
fetchConfig().then((cfg) => {
|
|
3736
3711
|
if (!alive || !cfg) return;
|
|
3737
|
-
setMode(cfg.persona?.mode ?? "");
|
|
3738
|
-
setOverrideText(cfg.persona?.overrideText ?? "");
|
|
3712
|
+
setMode(cfg.config?.persona?.mode ?? "");
|
|
3713
|
+
setOverrideText(cfg.config?.persona?.overrideText ?? "");
|
|
3739
3714
|
setLoaded(true);
|
|
3740
3715
|
});
|
|
3741
3716
|
return () => {
|
|
@@ -3893,7 +3868,7 @@ Instruction-following style:
|
|
|
3893
3868
|
try {
|
|
3894
3869
|
const cfg = await fetchConfig();
|
|
3895
3870
|
if (!alive) return;
|
|
3896
|
-
setAllowed(cfg?.publicAsk?.allowed ?? []);
|
|
3871
|
+
setAllowed(cfg?.config?.publicAsk?.allowed ?? []);
|
|
3897
3872
|
} catch {}
|
|
3898
3873
|
})();
|
|
3899
3874
|
return () => {
|
package/lib/config-ops.d.ts
CHANGED
|
@@ -150,6 +150,19 @@ export interface AdvancedSettingsWire {
|
|
|
150
150
|
}
|
|
151
151
|
/** 持久化 → wire(剥离 passHash/totpSecret;只留布尔) */
|
|
152
152
|
export declare function toWire(settings: AdvancedSettings): AdvancedSettingsWire;
|
|
153
|
+
/**
|
|
154
|
+
* 已知工作区投影(v1.28.0 适配 0.1.2-rc.1 A2 方案 A′):
|
|
155
|
+
* rc.1 workspace.list unary 删除 → AccountsEditor 白名单下拉的数据源改走 gateway 自有
|
|
156
|
+
* /serenity/config 的 knownWorkspaces(host workspaceRegistry.list() 投影,白名单过滤)。
|
|
157
|
+
* 纯函数可单测。allowPrefixes 空 = 全部放行(向后兼容默认)。
|
|
158
|
+
*/
|
|
159
|
+
export declare function projectKnownWorkspaces(workspaces: Array<{
|
|
160
|
+
path?: string;
|
|
161
|
+
title?: string;
|
|
162
|
+
}>, allowPrefixes: readonly string[]): Array<{
|
|
163
|
+
path: string;
|
|
164
|
+
title: string;
|
|
165
|
+
}>;
|
|
153
166
|
/**
|
|
154
167
|
* wire → 持久化(面板 PUT 用):
|
|
155
168
|
* accounts 元素可选带 `pass`:非空 → 重新 hash;空/缺省 → 保留现有 hash(按 id 匹配)。
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gateway-dsh-auth.ts — 3081 网关对 DSH 主端口 BrowserAuth 的适配(v1.28.2,S142 用户 bug)
|
|
3
|
+
*
|
|
4
|
+
* 背景:DSH 0.1.2-rc.1 新增 BrowserAuth(browser-auth.ts)——主端口所有 /api 请求与
|
|
5
|
+
* index 渲染前必须持有 authority 绑定的签名 cookie(`dsh-auth-<sha256(Host)>`)。
|
|
6
|
+
* 3081 网关反代请求到主端口时 Host 已改写为 loopback(127.0.0.1:<mainPort>,过信任栅栏),
|
|
7
|
+
* 但**没有 dsh browser cookie** → 上游 401 "dsh web authentication required"。
|
|
8
|
+
*
|
|
9
|
+
* 方案(S142 用户拍板:不用 cookie 落盘绕,直接解决——内存换取 + 缓存,用户无感):
|
|
10
|
+
* dsp 与 DSH **同进程** → 经 `ctx.connection`(HostConnectionHandle)官方通道
|
|
11
|
+
* (authenticatedUrl + authorizeIndex——web-app openBrowser 同款)在**内存**换取
|
|
12
|
+
* authority=127.0.0.1:<mainPort> 的 dsh cookie,缓存后注入所有反代上游请求。
|
|
13
|
+
* 零落盘、无 HTTP 往返、不复制官方 HMAC 逻辑(版本漂移风险为零)。
|
|
14
|
+
*
|
|
15
|
+
* 兼容性:connection 服务缺失(旧 dsh / 非 web 装配)→ cookie 换取不可用 →
|
|
16
|
+
* getDshCookie 返回 undefined → 反代不注入(旧形态无 BrowserAuth,天然兼容)。
|
|
17
|
+
*/
|
|
18
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
19
|
+
/** ctx.connection 服务的最小可测面(HostConnectionHandle 子集;避免硬依赖官方类型) */
|
|
20
|
+
export interface DshConnectionLike {
|
|
21
|
+
/** 给干净 baseUrl 追加本进程 launch token → 首次登录 URL */
|
|
22
|
+
authenticatedUrl(baseUrl: string): string;
|
|
23
|
+
/** 认证一个 index 请求:token 匹配则种 cookie(写 res)并返回 false;cookie 有效返回 true */
|
|
24
|
+
authorizeIndex(request: IncomingMessage, response: ServerResponse): boolean;
|
|
25
|
+
}
|
|
26
|
+
/** 从一组 set-cookie 头提取 `dsh-auth-*` cookie(browser-auth sessionCookie 首段) */
|
|
27
|
+
export declare function pickDshCookie(setCookieHeader: string | string[] | undefined): string | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* 把 dsh browser cookie 合并进现有 Cookie 头(保持外部既有 cookie——serenity_session 等)。
|
|
30
|
+
* 现有头可为 string / string[](node 头形态);无现有 → 只返回 dsh cookie。
|
|
31
|
+
*/
|
|
32
|
+
export declare function mergeCookieHeader(existing: string | string[] | number | undefined, dshCookie: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* 内存换取 authority 绑定的 dsh browser cookie(S142 拍板方案核心)。
|
|
35
|
+
* 用官方通道在进程内完成 token→cookie 交换:构造一个对 `http://<authority>/?token=…`
|
|
36
|
+
* 的 index 请求 → connection.authorizeIndex 校验 token → 写 set-cookie → 提取 cookie。
|
|
37
|
+
* @returns cookie 头值(如 `dsh-auth-xxx=v1.…`);authority/connection 异常 → undefined
|
|
38
|
+
*/
|
|
39
|
+
export declare function exchangeDshCookie(connection: DshConnectionLike, authority: string): string | undefined;
|
|
40
|
+
/** 可注入的 cookie 提供者(gateway 反代上游请求头用;无 connection/换取失败 → undefined) */
|
|
41
|
+
export type DshCookieProvider = () => string | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* 构建一个带内存缓存的 dsh cookie 提供者(S142 拍板:内存缓存,不落盘)。
|
|
44
|
+
* 惰性换取:首次调用才向 connection 换;失败返回 undefined(不缓存失败——下次重试)。
|
|
45
|
+
* 调用方可周期性失效缓存重换(cookie 30 天有效;进程重启 token 仍在 → 换一次长期可用)。
|
|
46
|
+
*/
|
|
47
|
+
export declare function createDshCookieProvider(connection: DshConnectionLike | undefined, authority: string): DshCookieProvider;
|
package/lib/gateway-proxy.d.ts
CHANGED
|
@@ -29,8 +29,25 @@ export declare function filterWorkspaceList(body: string, allowPrefixes: readonl
|
|
|
29
29
|
export declare function workspaceAllowed(allowPrefixes: readonly string[], path: string | undefined): boolean;
|
|
30
30
|
/** 构造 workspace.create 拒绝的 JSON RPC 响应体(code=forbidden) */
|
|
31
31
|
export declare function workspaceDenyResponse(rpcId: string): string;
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* 在 HTML 的 </head> 前注入 polyfill(幂等:含 marker 则跳过)
|
|
34
|
+
*/
|
|
33
35
|
export declare function injectPolyfillHtml(html: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* 反代 HTML 响应注入变换(v1.28.2 修复,S142 白屏根因):
|
|
38
|
+
* 上游按 Accept-Encoding 可能返回 gzip/br/deflate 压缩的 HTML——注入必须在**明文**上进行,
|
|
39
|
+
* 把压缩字节 toString 当文本注入会破坏压缩流(找不到 </head> → polyfill 前置 → HTML 损坏
|
|
40
|
+
* → 浏览器白屏,用户实测 https://dsh.notfoundhome.cc 登录后白屏)。
|
|
41
|
+
*
|
|
42
|
+
* @param raw - 上游响应完整 body(可能压缩)
|
|
43
|
+
* @param upstreamHeaders - 上游响应头(node IncomingHttpHeaders 形态)
|
|
44
|
+
* @returns { body, headers } 注入后的明文 body + 修正后的响应头(去 content-encoding/transfer-encoding、
|
|
45
|
+
* 重算 content-length);解压失败返回 null(调用方应原样透传避免破坏)
|
|
46
|
+
*/
|
|
47
|
+
export declare function transformHtmlForProxy(raw: Buffer, upstreamHeaders: Record<string, unknown>): {
|
|
48
|
+
body: string;
|
|
49
|
+
headers: Record<string, string | number | string[]>;
|
|
50
|
+
} | null;
|
|
34
51
|
/**
|
|
35
52
|
* 反代请求头构造(v1.22.1 信任栅栏修复,纯逻辑可测):
|
|
36
53
|
* DSH isTrustedApiRequest 要求 Origin.host === Host.host——Host 改写为 loopback 后
|
package/lib/gateway.d.ts
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import type { Context } from 'cordis';
|
|
29
29
|
import { type Server } from 'node:http';
|
|
30
|
-
export { RANDOM_UUID_POLYFILL, injectPolyfillHtml, filterWorkspaceList, workspaceAllowed, workspaceDenyResponse, buildProxyHeaders, } from './gateway-proxy.js';
|
|
30
|
+
export { RANDOM_UUID_POLYFILL, injectPolyfillHtml, filterWorkspaceList, workspaceAllowed, workspaceDenyResponse, buildProxyHeaders, transformHtmlForProxy, } from './gateway-proxy.js';
|
|
31
31
|
export { loginPageHtml, verifyGatewayLogin, SESSION_TTL_MS, issueToken, revokeToken, validateToken, FAIL_LOCK_THRESHOLD, FAIL_LOCK_BASE_MS, FAIL_LOCK_MAX_MS, getFailState, resetFailState, isAccountLocked, recordLoginFailure, accountLockRemaining, newCsrfToken, isCsrfValid, csrfFromRequest, safeEqual, originAllowed, cookieValue, } from './gateway-auth.js';
|
|
32
32
|
declare module 'cordis' {
|
|
33
33
|
interface Events {
|
|
@@ -66,7 +66,7 @@ export declare function startGateway(config: GatewayConfig, getAccounts: () => r
|
|
|
66
66
|
user: string;
|
|
67
67
|
passHash: string;
|
|
68
68
|
totpSecret?: string;
|
|
69
|
-
}[]): {
|
|
69
|
+
}[], getDshCookie?: () => string | undefined): {
|
|
70
70
|
server: Server;
|
|
71
71
|
dispose: () => void;
|
|
72
72
|
};
|
package/lib/index.d.ts
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
import type { Context } from 'cordis';
|
|
15
15
|
import z from '@deepseek-ai/schemastery';
|
|
16
16
|
export declare const name = "dsh-serenity-hooks";
|
|
17
|
-
/** 主动调用的服务;其余(agent 事件)随 harness 装配必然存在
|
|
17
|
+
/** 主动调用的服务;其余(agent 事件)随 harness 装配必然存在
|
|
18
|
+
* (v1.28.0 适配 0.1.2-rc.1:+ 'settings'——B4 settings 服务由 provider 插件加载后才有) */
|
|
18
19
|
export declare const inject: string[];
|
|
19
20
|
/** 插件配置(cordis.yml 提供;进程级) */
|
|
20
21
|
export interface Config {
|