@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-serenity-hooks",
3
- "version": "1.28.0",
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
- return (await res.json()).config ?? null;
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
- Promise.all([fetchConfig(), fetchWorkspaces()]).then(([cfg, wss]) => {
3280
+ fetchConfig().then((resp) => {
3307
3281
  if (!alive) return;
3308
- setKnownWorkspaces(wss);
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 () => {
@@ -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;
@@ -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
- /** 在 HTML 的 </head> 前注入 polyfill(幂等:含 marker 则跳过) */
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 {