@dsh-edge/dsh-edge-agent 0.0.0-stage → 0.1.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/CHANGELOG.md +142 -0
- package/LICENSE +21 -0
- package/README.md +372 -2
- package/config/edge.example.json +179 -0
- package/config/privacy-policy.json +202 -0
- package/cordis.desktop-safe.patch.yml +129 -0
- package/cordis.patch.yml +382 -0
- package/cordis.yml +374 -0
- package/lib/attestation.d.ts +2 -0
- package/lib/attestation.d.ts.map +1 -0
- package/lib/attestation.js +2 -0
- package/lib/audit-log.d.ts +2 -0
- package/lib/audit-log.d.ts.map +1 -0
- package/lib/audit-log.js +2 -0
- package/lib/client.js +87 -0
- package/lib/cloud-llm-adapter.d.ts +2 -0
- package/lib/cloud-llm-adapter.d.ts.map +1 -0
- package/lib/cloud-llm-adapter.js +2 -0
- package/lib/dsh-adapter/attestation.d.ts +42 -0
- package/lib/dsh-adapter/attestation.d.ts.map +1 -0
- package/lib/dsh-adapter/attestation.js +40 -0
- package/lib/dsh-adapter/audit-log.d.ts +139 -0
- package/lib/dsh-adapter/audit-log.d.ts.map +1 -0
- package/lib/dsh-adapter/audit-log.js +376 -0
- package/lib/dsh-adapter/cloud-egress.d.ts +181 -0
- package/lib/dsh-adapter/cloud-egress.d.ts.map +1 -0
- package/lib/dsh-adapter/cloud-egress.js +257 -0
- package/lib/dsh-adapter/cloud-llm-adapter.d.ts +416 -0
- package/lib/dsh-adapter/cloud-llm-adapter.d.ts.map +1 -0
- package/lib/dsh-adapter/cloud-llm-adapter.js +1472 -0
- package/lib/dsh-adapter/contract.d.ts +80 -0
- package/lib/dsh-adapter/contract.d.ts.map +1 -0
- package/lib/dsh-adapter/contract.js +194 -0
- package/lib/dsh-adapter/desktop-cookie-janitor.d.ts +214 -0
- package/lib/dsh-adapter/desktop-cookie-janitor.d.ts.map +1 -0
- package/lib/dsh-adapter/desktop-cookie-janitor.js +409 -0
- package/lib/dsh-adapter/dsh-edge-agent.d.ts +48 -0
- package/lib/dsh-adapter/dsh-edge-agent.d.ts.map +1 -0
- package/lib/dsh-adapter/dsh-edge-agent.js +64 -0
- package/lib/dsh-adapter/edge-config.d.ts +484 -0
- package/lib/dsh-adapter/edge-config.d.ts.map +1 -0
- package/lib/dsh-adapter/edge-config.js +898 -0
- package/lib/dsh-adapter/edge-context-view.d.ts +184 -0
- package/lib/dsh-adapter/edge-context-view.d.ts.map +1 -0
- package/lib/dsh-adapter/edge-context-view.js +483 -0
- package/lib/dsh-adapter/effective-model.d.ts +85 -0
- package/lib/dsh-adapter/effective-model.d.ts.map +1 -0
- package/lib/dsh-adapter/effective-model.js +171 -0
- package/lib/dsh-adapter/etscli-shell-guard.d.ts +244 -0
- package/lib/dsh-adapter/etscli-shell-guard.d.ts.map +1 -0
- package/lib/dsh-adapter/etscli-shell-guard.js +420 -0
- package/lib/dsh-adapter/etscli-tools.d.ts +112 -0
- package/lib/dsh-adapter/etscli-tools.d.ts.map +1 -0
- package/lib/dsh-adapter/etscli-tools.js +393 -0
- package/lib/dsh-adapter/file-logger.d.ts +294 -0
- package/lib/dsh-adapter/file-logger.d.ts.map +1 -0
- package/lib/dsh-adapter/file-logger.js +564 -0
- package/lib/dsh-adapter/function-call-external.d.ts +357 -0
- package/lib/dsh-adapter/function-call-external.d.ts.map +1 -0
- package/lib/dsh-adapter/function-call-external.js +1005 -0
- package/lib/dsh-adapter/host-egress-guard.d.ts +161 -0
- package/lib/dsh-adapter/host-egress-guard.d.ts.map +1 -0
- package/lib/dsh-adapter/host-egress-guard.js +213 -0
- package/lib/dsh-adapter/host-llm-trace.d.ts +198 -0
- package/lib/dsh-adapter/host-llm-trace.d.ts.map +1 -0
- package/lib/dsh-adapter/host-llm-trace.js +344 -0
- package/lib/dsh-adapter/http-api-routes.d.ts +61 -0
- package/lib/dsh-adapter/http-api-routes.d.ts.map +1 -0
- package/lib/dsh-adapter/http-api-routes.js +79 -0
- package/lib/dsh-adapter/http-api-sessions.d.ts +105 -0
- package/lib/dsh-adapter/http-api-sessions.d.ts.map +1 -0
- package/lib/dsh-adapter/http-api-sessions.js +173 -0
- package/lib/dsh-adapter/http-api.d.ts +221 -0
- package/lib/dsh-adapter/http-api.d.ts.map +1 -0
- package/lib/dsh-adapter/http-api.js +1811 -0
- package/lib/dsh-adapter/index.d.ts +62 -0
- package/lib/dsh-adapter/index.d.ts.map +1 -0
- package/lib/dsh-adapter/index.js +97 -0
- package/lib/dsh-adapter/input-guard-messages.d.ts +56 -0
- package/lib/dsh-adapter/input-guard-messages.d.ts.map +1 -0
- package/lib/dsh-adapter/input-guard-messages.js +136 -0
- package/lib/dsh-adapter/input-guard.d.ts +508 -0
- package/lib/dsh-adapter/input-guard.d.ts.map +1 -0
- package/lib/dsh-adapter/input-guard.js +2174 -0
- package/lib/dsh-adapter/intent-prompts.d.ts +77 -0
- package/lib/dsh-adapter/intent-prompts.d.ts.map +1 -0
- package/lib/dsh-adapter/intent-prompts.js +97 -0
- package/lib/dsh-adapter/intent-router.d.ts +710 -0
- package/lib/dsh-adapter/intent-router.d.ts.map +1 -0
- package/lib/dsh-adapter/intent-router.js +2802 -0
- package/lib/dsh-adapter/intent-signals.d.ts +208 -0
- package/lib/dsh-adapter/intent-signals.d.ts.map +1 -0
- package/lib/dsh-adapter/intent-signals.js +391 -0
- package/lib/dsh-adapter/invariant-install.d.ts +16 -0
- package/lib/dsh-adapter/invariant-install.d.ts.map +1 -0
- package/lib/dsh-adapter/invariant-install.js +15 -0
- package/lib/dsh-adapter/invariant.d.ts +16 -0
- package/lib/dsh-adapter/invariant.d.ts.map +1 -0
- package/lib/dsh-adapter/invariant.js +22 -0
- package/lib/dsh-adapter/llm-adapters.d.ts +19 -0
- package/lib/dsh-adapter/llm-adapters.d.ts.map +1 -0
- package/lib/dsh-adapter/llm-adapters.js +16 -0
- package/lib/dsh-adapter/llm-internal-caller.d.ts +92 -0
- package/lib/dsh-adapter/llm-internal-caller.d.ts.map +1 -0
- package/lib/dsh-adapter/llm-internal-caller.js +100 -0
- package/lib/dsh-adapter/llm-max-turns.d.ts +55 -0
- package/lib/dsh-adapter/llm-max-turns.d.ts.map +1 -0
- package/lib/dsh-adapter/llm-max-turns.js +127 -0
- package/lib/dsh-adapter/llm-trace-store.d.ts +131 -0
- package/lib/dsh-adapter/llm-trace-store.d.ts.map +1 -0
- package/lib/dsh-adapter/llm-trace-store.js +205 -0
- package/lib/dsh-adapter/llm-trace.d.ts +450 -0
- package/lib/dsh-adapter/llm-trace.d.ts.map +1 -0
- package/lib/dsh-adapter/llm-trace.js +463 -0
- package/lib/dsh-adapter/local-llm-adapter.d.ts +141 -0
- package/lib/dsh-adapter/local-llm-adapter.d.ts.map +1 -0
- package/lib/dsh-adapter/local-llm-adapter.js +787 -0
- package/lib/dsh-adapter/local-storage.d.ts +96 -0
- package/lib/dsh-adapter/local-storage.d.ts.map +1 -0
- package/lib/dsh-adapter/local-storage.js +198 -0
- package/lib/dsh-adapter/model-router.d.ts +79 -0
- package/lib/dsh-adapter/model-router.d.ts.map +1 -0
- package/lib/dsh-adapter/model-router.js +107 -0
- package/lib/dsh-adapter/ohttp-gateway.d.ts +49 -0
- package/lib/dsh-adapter/ohttp-gateway.d.ts.map +1 -0
- package/lib/dsh-adapter/ohttp-gateway.js +32 -0
- package/lib/dsh-adapter/openai-sse.d.ts +85 -0
- package/lib/dsh-adapter/openai-sse.d.ts.map +1 -0
- package/lib/dsh-adapter/openai-sse.js +462 -0
- package/lib/dsh-adapter/pii-utils.d.ts +96 -0
- package/lib/dsh-adapter/pii-utils.d.ts.map +1 -0
- package/lib/dsh-adapter/pii-utils.js +324 -0
- package/lib/dsh-adapter/plugin-host.d.ts +38 -0
- package/lib/dsh-adapter/plugin-host.d.ts.map +1 -0
- package/lib/dsh-adapter/plugin-host.js +38 -0
- package/lib/dsh-adapter/plugin.d.ts +46 -0
- package/lib/dsh-adapter/plugin.d.ts.map +1 -0
- package/lib/dsh-adapter/plugin.js +100 -0
- package/lib/dsh-adapter/privacy-policy.d.ts +237 -0
- package/lib/dsh-adapter/privacy-policy.d.ts.map +1 -0
- package/lib/dsh-adapter/privacy-policy.js +391 -0
- package/lib/dsh-adapter/prompt-guard.d.ts +152 -0
- package/lib/dsh-adapter/prompt-guard.d.ts.map +1 -0
- package/lib/dsh-adapter/prompt-guard.js +872 -0
- package/lib/dsh-adapter/realm-skill-bridge.d.ts +365 -0
- package/lib/dsh-adapter/realm-skill-bridge.d.ts.map +1 -0
- package/lib/dsh-adapter/realm-skill-bridge.js +1180 -0
- package/lib/dsh-adapter/sanitizer-pipeline.d.ts +140 -0
- package/lib/dsh-adapter/sanitizer-pipeline.d.ts.map +1 -0
- package/lib/dsh-adapter/sanitizer-pipeline.js +431 -0
- package/lib/dsh-adapter/secure-bridge.d.ts +47 -0
- package/lib/dsh-adapter/secure-bridge.d.ts.map +1 -0
- package/lib/dsh-adapter/secure-bridge.js +35 -0
- package/lib/dsh-adapter/session-attribution.d.ts +70 -0
- package/lib/dsh-adapter/session-attribution.d.ts.map +1 -0
- package/lib/dsh-adapter/session-attribution.js +76 -0
- package/lib/dsh-adapter/sessions.d.ts +86 -0
- package/lib/dsh-adapter/sessions.d.ts.map +1 -0
- package/lib/dsh-adapter/sessions.js +179 -0
- package/lib/dsh-adapter/tcm.d.ts +55 -0
- package/lib/dsh-adapter/tcm.d.ts.map +1 -0
- package/lib/dsh-adapter/tcm.js +94 -0
- package/lib/dsh-adapter/td-bus.d.ts +87 -0
- package/lib/dsh-adapter/td-bus.d.ts.map +1 -0
- package/lib/dsh-adapter/td-bus.js +196 -0
- package/lib/dsh-adapter/tool-runner.d.ts +185 -0
- package/lib/dsh-adapter/tool-runner.d.ts.map +1 -0
- package/lib/dsh-adapter/tool-runner.js +271 -0
- package/lib/dsh-adapter/tools.d.ts +118 -0
- package/lib/dsh-adapter/tools.d.ts.map +1 -0
- package/lib/dsh-adapter/tools.js +292 -0
- package/lib/dsh-adapter/types.d.ts +23 -0
- package/lib/dsh-adapter/types.d.ts.map +1 -0
- package/lib/dsh-adapter/types.js +16 -0
- package/lib/dsh-adapter/voice-input.d.ts +30 -0
- package/lib/dsh-adapter/voice-input.d.ts.map +1 -0
- package/lib/dsh-adapter/voice-input.js +34 -0
- package/lib/dsh-adapter/web-search-tavily-bocha.d.ts +152 -0
- package/lib/dsh-adapter/web-search-tavily-bocha.d.ts.map +1 -0
- package/lib/dsh-adapter/web-search-tavily-bocha.js +289 -0
- package/lib/http-api.d.ts +2 -0
- package/lib/http-api.d.ts.map +1 -0
- package/lib/http-api.js +2 -0
- package/lib/index.d.ts +48 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +49 -0
- package/lib/intent-router.d.ts +2 -0
- package/lib/intent-router.d.ts.map +1 -0
- package/lib/intent-router.js +2 -0
- package/lib/llm-adapters.d.ts +2 -0
- package/lib/llm-adapters.d.ts.map +1 -0
- package/lib/llm-adapters.js +2 -0
- package/lib/local-llm-adapter.d.ts +2 -0
- package/lib/local-llm-adapter.d.ts.map +1 -0
- package/lib/local-llm-adapter.js +2 -0
- package/lib/local-storage.d.ts +2 -0
- package/lib/local-storage.d.ts.map +1 -0
- package/lib/local-storage.js +2 -0
- package/lib/model-router.d.ts +2 -0
- package/lib/model-router.d.ts.map +1 -0
- package/lib/model-router.js +2 -0
- package/lib/ohttp-gateway.d.ts +2 -0
- package/lib/ohttp-gateway.d.ts.map +1 -0
- package/lib/ohttp-gateway.js +2 -0
- package/lib/plugin-host.d.ts +2 -0
- package/lib/plugin-host.d.ts.map +1 -0
- package/lib/plugin-host.js +2 -0
- package/lib/privacy-policy.d.ts +2 -0
- package/lib/privacy-policy.d.ts.map +1 -0
- package/lib/privacy-policy.js +2 -0
- package/lib/prompt-guard.d.ts +2 -0
- package/lib/prompt-guard.d.ts.map +1 -0
- package/lib/prompt-guard.js +2 -0
- package/lib/sanitizer-pipeline.d.ts +2 -0
- package/lib/sanitizer-pipeline.d.ts.map +1 -0
- package/lib/sanitizer-pipeline.js +2 -0
- package/lib/secure-bridge.d.ts +2 -0
- package/lib/secure-bridge.d.ts.map +1 -0
- package/lib/secure-bridge.js +2 -0
- package/lib/sessions.d.ts +2 -0
- package/lib/sessions.d.ts.map +1 -0
- package/lib/sessions.js +2 -0
- package/lib/tcm.d.ts +2 -0
- package/lib/tcm.d.ts.map +1 -0
- package/lib/tcm.js +2 -0
- package/lib/td-bus.d.ts +2 -0
- package/lib/td-bus.d.ts.map +1 -0
- package/lib/td-bus.js +2 -0
- package/lib/tools.d.ts +2 -0
- package/lib/tools.d.ts.map +1 -0
- package/lib/tools.js +2 -0
- package/lib/voice-input.d.ts +2 -0
- package/lib/voice-input.d.ts.map +1 -0
- package/lib/voice-input.js +2 -0
- package/package.json +341 -3
- package/presets/agent-preset-edge.json +50 -0
- package/presets/edge.desktop-safe.patch.yml +503 -0
- package/presets/edge.patch.yml +557 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* desktop-cookie-janitor — §7.145 桌面版「起不来」的根因修复。
|
|
3
|
+
*
|
|
4
|
+
* ## 现象(主人现场,2026-10-28)
|
|
5
|
+
*
|
|
6
|
+
* 桌面版反复起不来,报 "DeepSeek Harness is unavailable"。审计链里能看到
|
|
7
|
+
* **18 次「插件加载完 0.1 秒就死」的启动**(每次只有 8 条事件:
|
|
8
|
+
* config.loaded → policy.loaded → boot.probe → 4 条 hook 注册,然后戛然而止)。
|
|
9
|
+
*
|
|
10
|
+
* ## 根因
|
|
11
|
+
*
|
|
12
|
+
* 桌面版每次启动都用**随机端口**起 web 服务
|
|
13
|
+
* (apps/desktop-host/src/index.ts: `args: ['--no-open','--host','127.0.0.1','--port','0']`),
|
|
14
|
+
* 宿主据此签发浏览器会话 cookie:
|
|
15
|
+
*
|
|
16
|
+
* cookie 名 = "dsh-auth-" + base64url(sha256(authority)) // authority = "127.0.0.1:<port>"
|
|
17
|
+
*
|
|
18
|
+
* **端口进了 cookie 名** ⇒ 每次启动都是一条**新名字**的 cookie ⇒ 上一轮的 cookie
|
|
19
|
+
* 既不会被命中、也不会因为端口复用而覆盖 ⇒ Chromium 的 cookie jar 无限增长。
|
|
20
|
+
*
|
|
21
|
+
* Chromium 把**同一 host 的全部 cookie** 塞进 Cookie 头(cookie 不按端口隔离,
|
|
22
|
+
* 只有名字里带了端口)⇒ Electron 外壳的 welcome 握手
|
|
23
|
+
* (app.asar/lib/main.js:8885 `net.fetch(url, {credentials:'include'})`)带着
|
|
24
|
+
* 一大堆 cookie 出发 ⇒ 宿主 web 服务是裸 `createServer(listener)`
|
|
25
|
+
* (@deepseek-ai/dsh-host-webserver/lib/index.js:566,未设 maxHeaderSize)
|
|
26
|
+
* ⇒ Node 默认 16384 B 头上限 ⇒ **431 Request Header Fields Too Large**
|
|
27
|
+
* ⇒ 外壳判定 `!authenticated.ok` ⇒ 拆掉宿主 ⇒ 「unavailable」。
|
|
28
|
+
*
|
|
29
|
+
* 实测:71 条 cookie = 16.2 KB(每条 227 B)⇒ 约 69 次启动就撞线。
|
|
30
|
+
*
|
|
31
|
+
* ## 为什么是本插件来修
|
|
32
|
+
*
|
|
33
|
+
* 实测时间线(2026-10-29 01:08:23 一次真实启动):
|
|
34
|
+
*
|
|
35
|
+
* 01:08:23.030 我们的插件加载(config.loaded)
|
|
36
|
+
* 01:08:25.979 Electron 锁住 cookie 库(welcome 握手触发网络服务)
|
|
37
|
+
*
|
|
38
|
+
* ⇒ 插件加载后有**约 3 秒的窗口**,cookie 库还没被锁 ⇒ 这时候可以把历次启动
|
|
39
|
+
* 留下的死端口 cookie 删掉。Electron 之后才打开这个库,读到的就是清理过的。
|
|
40
|
+
*
|
|
41
|
+
* 同时把宿主 web 服务的 `maxHeaderSize` 抬上去:即使清理没赶上(库已被锁),
|
|
42
|
+
* 一个已经攒胖的 jar 也不会再把启动打死 —— 两道防线,互不依赖。
|
|
43
|
+
*
|
|
44
|
+
* ## 只在桌面版生效
|
|
45
|
+
*
|
|
46
|
+
* 431 只发生在 Electron 外壳(只有它带 Chromium cookie jar)。headless profile
|
|
47
|
+
* (web / tui / dsh-edge-agent)由普通 Node 拉起,自己起 web 服务、客户端不带
|
|
48
|
+
* cookie jar ⇒ 同样场景不会发生。所以本插件用 `process.versions.electron`
|
|
49
|
+
* 判别运行环境,非桌面版一律跳过 —— 不去动无关 profile 里的宿主 web 服务。
|
|
50
|
+
*
|
|
51
|
+
* ## 不做什么
|
|
52
|
+
*
|
|
53
|
+
* - **不碰**非 `dsh-auth-*` 的 cookie(别的站点的)。
|
|
54
|
+
* - **不碰**当前活着端口的那条(= 本次会话的 cookie)。
|
|
55
|
+
* - **不删**认不出 audience 的行以外的东西;认不出的 auth cookie 视为残骸
|
|
56
|
+
* (它多半来自旧版本/旧端口),但只在 `source_port` 也拿不到时才动它。
|
|
57
|
+
* - 任何一步失败都**静默降级**(返回一份报告,绝不抛)—— 观测/家���不能拖垮启动。
|
|
58
|
+
*
|
|
59
|
+
* @module dsh-edge-agent/desktop-cookie-janitor
|
|
60
|
+
*/
|
|
61
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
62
|
+
import { existsSync } from 'node:fs';
|
|
63
|
+
import { createRequire } from 'node:module';
|
|
64
|
+
import { join } from 'node:path';
|
|
65
|
+
import { createConnection } from 'node:net';
|
|
66
|
+
export const name = 'desktop-cookie-janitor';
|
|
67
|
+
/** 无硬依赖:web 服务用 `ctx.inject(['webServer'])` 延迟取(headless profile
|
|
68
|
+
* 未必有它),cookie 库也不依赖任何服务。 */
|
|
69
|
+
export const inject = [];
|
|
70
|
+
/** 宿主签发的浏览器会话 cookie 前缀(别的 cookie 一律不碰)。 */
|
|
71
|
+
export const AUTH_COOKIE_PREFIX = 'dsh-auth-';
|
|
72
|
+
/** Node http.Server 的默认请求头上限(bytes)。宿主 web 服务没设 maxHeaderSize,
|
|
73
|
+
* 所以这就是宿主实际生效的门槛。 */
|
|
74
|
+
export const NODE_MAX_HEADER_SIZE = 16_384;
|
|
75
|
+
/** 我们把上限抬到的值(bytes)。64 KB ≈ 280 条 cookie —— 远超 Chromium 的
|
|
76
|
+
* 每域 cookie 上限(180),所以清理一旦失效也不会顶到 Chromium 的淘汰线。 */
|
|
77
|
+
export const DEFAULT_MAX_HEADER_BYTES = 65_536;
|
|
78
|
+
/** Chromium 会话 cookie 之外的请求头估值(Host / UA / sec-ch-* / Accept-* …)。 */
|
|
79
|
+
export const OTHER_HEADER_BYTES = 600;
|
|
80
|
+
/** 单条 cookie 的实测字节:name 52 + value 173 + 2 个分隔符 ≈ 227。 */
|
|
81
|
+
export const PER_COOKIE_BYTES = 227;
|
|
82
|
+
/* ── 纯逻辑(可单测) ─────────────────────────────────────────────────────── */
|
|
83
|
+
/**
|
|
84
|
+
* 从 cookie 里取出 audience(`host:port`)。
|
|
85
|
+
*
|
|
86
|
+
* 宿主签发的是 `v1.<base64url(json)>.<hmac>`,json 里带 authority;但 Chromium
|
|
87
|
+
* 在 Windows 上会用 DPAPI 加密 `value` 列 ⇒ 解析多半失败 ⇒ 退回
|
|
88
|
+
* `host_key` + `source_port`(Chromium 自己记的源端口,一直是对的)。
|
|
89
|
+
*/
|
|
90
|
+
export function authorityOf(cookie) {
|
|
91
|
+
const value = typeof cookie.value === 'string' ? cookie.value : '';
|
|
92
|
+
const parts = value.split('.');
|
|
93
|
+
if (parts.length >= 2 && /^v\d+$/.test(parts[0])) {
|
|
94
|
+
try {
|
|
95
|
+
const json = Buffer.from(parts[1], 'base64url').toString('utf8');
|
|
96
|
+
const payload = JSON.parse(json);
|
|
97
|
+
if (typeof payload.authority === 'string' && payload.authority.length > 0) {
|
|
98
|
+
return payload.authority;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
/* 值被加密了 —— 落到下面的 source_port 兜底 */
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (cookie.source_port !== undefined && cookie.source_port !== null) {
|
|
106
|
+
return `${String(cookie.host_key)}:${String(cookie.source_port)}`;
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
/** 从 audience 里取端口。 */
|
|
111
|
+
export function portOf(authority) {
|
|
112
|
+
if (typeof authority !== 'string')
|
|
113
|
+
return undefined;
|
|
114
|
+
const at = authority.lastIndexOf(':');
|
|
115
|
+
if (at < 0)
|
|
116
|
+
return undefined;
|
|
117
|
+
const port = Number(authority.slice(at + 1));
|
|
118
|
+
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : undefined;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* 请求头投影:Chromium 会把同一 host 的全部 cookie 塞进 Cookie 头。
|
|
122
|
+
*
|
|
123
|
+
* @param cookies - 参与发送的 cookie 行
|
|
124
|
+
*/
|
|
125
|
+
export function projectHeaderBytes(cookies) {
|
|
126
|
+
let bytes = OTHER_HEADER_BYTES;
|
|
127
|
+
for (const cookie of cookies) {
|
|
128
|
+
const name = String(cookie.name ?? '');
|
|
129
|
+
const value = String(cookie.value ?? '');
|
|
130
|
+
bytes += name.length + 1 + value.length + 1;
|
|
131
|
+
}
|
|
132
|
+
return bytes;
|
|
133
|
+
}
|
|
134
|
+
/** 还能撑几次启动(每次新增一条 cookie)。 */
|
|
135
|
+
export function remainingLaunches(cookies) {
|
|
136
|
+
const used = projectHeaderBytes(cookies);
|
|
137
|
+
if (used >= NODE_MAX_HEADER_SIZE)
|
|
138
|
+
return 0;
|
|
139
|
+
return Math.floor((NODE_MAX_HEADER_SIZE - 1 - used) / PER_COOKIE_BYTES);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 把 auth cookie 分成「活的」与「死的」。
|
|
143
|
+
*
|
|
144
|
+
* @param cookies - 库里的全部行(函数自己只挑 `dsh-auth-*`)
|
|
145
|
+
* @param isAlive - (port) => Promise<boolean>,端口存活探针(可注入以便自证)
|
|
146
|
+
*/
|
|
147
|
+
export async function classifyCookies(cookies, isAlive) {
|
|
148
|
+
const auth = [];
|
|
149
|
+
const foreign = [];
|
|
150
|
+
for (const cookie of cookies) {
|
|
151
|
+
if (typeof cookie.name === 'string' && cookie.name.startsWith(AUTH_COOKIE_PREFIX))
|
|
152
|
+
auth.push(cookie);
|
|
153
|
+
else
|
|
154
|
+
foreign.push(cookie);
|
|
155
|
+
}
|
|
156
|
+
const stale = [];
|
|
157
|
+
const live = [];
|
|
158
|
+
for (const cookie of auth) {
|
|
159
|
+
const authority = authorityOf(cookie);
|
|
160
|
+
const port = portOf(authority);
|
|
161
|
+
if (port === undefined) {
|
|
162
|
+
// 认不出 audience:算残骸(多半来自旧版本/旧端口),宁可清掉。
|
|
163
|
+
stale.push({ cookie, authority, port, stale: true, reason: 'unparsable-audience' });
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (await isAlive(port))
|
|
167
|
+
live.push({ cookie, authority, port, stale: false, reason: 'live-port' });
|
|
168
|
+
else
|
|
169
|
+
stale.push({ cookie, authority, port, stale: true, reason: 'dead-port' });
|
|
170
|
+
}
|
|
171
|
+
return { auth, foreign, stale, live };
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* 抬高 web 服务的请求头上限。
|
|
175
|
+
*
|
|
176
|
+
* Node 在**每条连接**建立时把 `server.maxHeaderSize` 读进 parser ⇒ 在握手
|
|
177
|
+
* 之前改好即可生效。
|
|
178
|
+
*
|
|
179
|
+
* ⚠️ 实测坑:`http.createServer()` 建出来的 server 上,`maxHeaderSize` 是
|
|
180
|
+
* **undefined**(不是 16384!)—— Node 把默认值放在 parser 里。所以
|
|
181
|
+
* 「读到的不是数字 ⇒ 不是 http server」这个判据是错的,会让这道防线永远
|
|
182
|
+
* 抬不上。真正的判据是「它像不像一个 http server」(有 listen/close)。
|
|
183
|
+
*
|
|
184
|
+
* @returns 是否真的改了(false = 没接口 / 已经够大 / 被禁用 / 不是 server)
|
|
185
|
+
*/
|
|
186
|
+
export function applyHeaderBudget(server, bytes) {
|
|
187
|
+
if (typeof bytes !== 'number' || !Number.isFinite(bytes) || bytes <= 0)
|
|
188
|
+
return false;
|
|
189
|
+
if (server === null || typeof server !== 'object')
|
|
190
|
+
return false;
|
|
191
|
+
const target = server;
|
|
192
|
+
// 只动 http server —— 别的对象不该被塞一个 maxHeaderSize。
|
|
193
|
+
if (typeof target.listen !== 'function' || typeof target.close !== 'function')
|
|
194
|
+
return false;
|
|
195
|
+
const current = target.maxHeaderSize;
|
|
196
|
+
if (typeof current === 'number' && current >= bytes)
|
|
197
|
+
return false;
|
|
198
|
+
try {
|
|
199
|
+
target.maxHeaderSize = bytes;
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/* ── 环境探测 ───────────────────────────────────────────────────────────── */
|
|
207
|
+
/** 桌面版 cookie 库的默认位置(各平台 Electron userData 目录)。 */
|
|
208
|
+
export function defaultJarPath(env = process.env) {
|
|
209
|
+
if (process.platform === 'win32') {
|
|
210
|
+
const base = env.APPDATA ?? join(env.HOME ?? '', 'AppData', 'Roaming');
|
|
211
|
+
return join(base, '@deepseek-ai', 'dsh-desktop', 'Network', 'Cookies');
|
|
212
|
+
}
|
|
213
|
+
if (process.platform === 'darwin') {
|
|
214
|
+
return join(env.HOME ?? '', 'Library', 'Application Support', '@deepseek-ai', 'dsh-desktop', 'Network', 'Cookies');
|
|
215
|
+
}
|
|
216
|
+
return join(env.XDG_CONFIG_HOME ?? join(env.HOME ?? '', '.config'), '@deepseek-ai', 'dsh-desktop', 'Network', 'Cookies');
|
|
217
|
+
}
|
|
218
|
+
/** 端口上是否还有进程在监听(= 该 cookie 的宿主还活着)。 */
|
|
219
|
+
export function probePort(port, host = '127.0.0.1', timeoutMs = 250) {
|
|
220
|
+
return new Promise((resolve) => {
|
|
221
|
+
const socket = createConnection({ host, port });
|
|
222
|
+
const done = (alive) => {
|
|
223
|
+
socket.destroy();
|
|
224
|
+
resolve(alive);
|
|
225
|
+
};
|
|
226
|
+
socket.setTimeout(timeoutMs);
|
|
227
|
+
socket.once('connect', () => done(true));
|
|
228
|
+
socket.once('timeout', () => done(false));
|
|
229
|
+
socket.once('error', () => done(false));
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* 载入 `node:sqlite`。
|
|
234
|
+
*
|
|
235
|
+
* 用 `createRequire` 而不是 `await import('node:sqlite')`:vite/vitest 的模块
|
|
236
|
+
* 解析器不认识 `node:sqlite`(会当成普通包去解析 ⇒ "Failed to load url
|
|
237
|
+
* sqlite"),`createRequire` 直接走 Node 的原生解析 ⇒ 单测跑的是**同一条**
|
|
238
|
+
* 真实路径,不是替身。
|
|
239
|
+
*/
|
|
240
|
+
function loadSqlite() {
|
|
241
|
+
const require = createRequire(import.meta.url);
|
|
242
|
+
return require('node:sqlite');
|
|
243
|
+
}
|
|
244
|
+
const READ_ROWS = `SELECT host_key, name, value, path,
|
|
245
|
+
source_port,
|
|
246
|
+
CAST(expires_utc AS TEXT) AS expires_utc
|
|
247
|
+
FROM cookies`;
|
|
248
|
+
function readRows(db) {
|
|
249
|
+
return db.prepare(READ_ROWS).all();
|
|
250
|
+
}
|
|
251
|
+
function deleteRows(db, victims) {
|
|
252
|
+
const stmt = db.prepare('DELETE FROM cookies WHERE host_key = ? AND name = ?');
|
|
253
|
+
let removed = 0;
|
|
254
|
+
for (const cookie of victims) {
|
|
255
|
+
removed += Number(stmt.run(cookie.host_key, cookie.name).changes ?? 0);
|
|
256
|
+
}
|
|
257
|
+
return removed;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* 清一次 cookie 库。
|
|
261
|
+
*
|
|
262
|
+
* 全程不抛:库不存在 / 被 Electron 锁着 / 没有 node:sqlite —— 都只写进报告。
|
|
263
|
+
*/
|
|
264
|
+
export async function cleanCookieJar(jarPath, isAlive) {
|
|
265
|
+
const report = {
|
|
266
|
+
jar: jarPath,
|
|
267
|
+
opened: false,
|
|
268
|
+
authCount: 0,
|
|
269
|
+
removed: 0,
|
|
270
|
+
projectedBytes: 0,
|
|
271
|
+
launchesLeft: 0,
|
|
272
|
+
};
|
|
273
|
+
try {
|
|
274
|
+
if (!existsSync(jarPath)) {
|
|
275
|
+
report.error = 'jar-missing';
|
|
276
|
+
return report;
|
|
277
|
+
}
|
|
278
|
+
const { DatabaseSync } = loadSqlite();
|
|
279
|
+
const db = new DatabaseSync(jarPath);
|
|
280
|
+
try {
|
|
281
|
+
const rows = readRows(db);
|
|
282
|
+
// 「库可用」= 真读到了 cookie 表(SQLite 构造是惰性的:文件损坏时构造
|
|
283
|
+
// 成功、第一次读表头才炸)。所以 opened 在读完行之后才置位。
|
|
284
|
+
report.opened = true;
|
|
285
|
+
const alive = isAlive ?? ((port) => probePort(port));
|
|
286
|
+
const groups = await classifyCookies(rows, alive);
|
|
287
|
+
report.authCount = groups.auth.length;
|
|
288
|
+
if (groups.stale.length > 0) {
|
|
289
|
+
report.removed = deleteRows(db, groups.stale.map((g) => g.cookie));
|
|
290
|
+
}
|
|
291
|
+
const after = readRows(db);
|
|
292
|
+
const survivors = after.filter((c) => typeof c.name === 'string' && c.name.startsWith(AUTH_COOKIE_PREFIX));
|
|
293
|
+
report.projectedBytes = projectHeaderBytes(survivors);
|
|
294
|
+
report.launchesLeft = remainingLaunches(survivors);
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
db.close();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
report.error = error instanceof Error ? error.message : String(error);
|
|
302
|
+
}
|
|
303
|
+
return report;
|
|
304
|
+
}
|
|
305
|
+
export const Config = Schema.object({
|
|
306
|
+
enabled: Schema.boolean().default(true),
|
|
307
|
+
maxHeaderBytes: Schema.natural().default(DEFAULT_MAX_HEADER_BYTES),
|
|
308
|
+
jarPath: Schema.string().default(''),
|
|
309
|
+
});
|
|
310
|
+
/** 宿主 web 服务(@deepseek-ai/dsh-host-webserver)注入的服务名。 */
|
|
311
|
+
export const WEB_SERVER_SERVICE = 'webServer';
|
|
312
|
+
/**
|
|
313
|
+
* 是不是跑在 Electron 里(= 桌面版)。
|
|
314
|
+
*
|
|
315
|
+
* 桌面版的宿主进程是 Electron 以 `ELECTRON_RUN_AS_NODE=1` 拉起来的
|
|
316
|
+
* (apps/desktop-host/src/index.ts 的 packageManager),所以宿主进程里
|
|
317
|
+
* `process.versions.electron` 有值(实测 44.7.0)。headless profile
|
|
318
|
+
* (web / tui / dsh-edge-agent)由普通 Node 拉起 ⇒ 没有这个字段。
|
|
319
|
+
*/
|
|
320
|
+
export function isDesktopRuntime(versions = process.versions) {
|
|
321
|
+
return typeof versions.electron === 'string' && versions.electron.length > 0;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* 从 inject 回调拿到的 ctx 代理里取出宿主 web 服务。
|
|
325
|
+
*
|
|
326
|
+
* cordis 的 `ctx.inject(deps, cb)` 把 **ctx 代理**交给回调(不是服务本身),
|
|
327
|
+
* 注入的服务要从代理上按**服务名**取(`raw.webServer`);直接读别的属性
|
|
328
|
+
* 会被代理拒绝("cannot get property X without inject")。
|
|
329
|
+
*/
|
|
330
|
+
export function webServerOf(raw) {
|
|
331
|
+
return raw?.[WEB_SERVER_SERVICE];
|
|
332
|
+
}
|
|
333
|
+
/** 立刻抬高上限(web 服务的 http server 已经存在时)。 */
|
|
334
|
+
export function raiseHeaderBudget(raw, bytes) {
|
|
335
|
+
return applyHeaderBudget(webServerOf(raw)?.server, bytes);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* 等 web 服务把 http server 建出来,再抬上限。
|
|
339
|
+
*
|
|
340
|
+
* 宿主 web 服务的 `server` 字段是在 `listen` 的 effect 里创建的
|
|
341
|
+
* (`this.server = createServer(listener)`),而 inject 回调在**服务构造**时
|
|
342
|
+
* 就触发了 ⇒ 那一刻 `server` 还是 undefined。所以这里短轮询一下:等它出现
|
|
343
|
+
* 立刻抬(赶在 Electron 的 welcome 握手之前),最多等 ~2 秒。
|
|
344
|
+
*
|
|
345
|
+
* @returns 是否抬成功
|
|
346
|
+
*/
|
|
347
|
+
export function raiseHeaderBudgetWhenReady(raw, bytes, attempts = 40, intervalMs = 50, schedule = setTimeout) {
|
|
348
|
+
return new Promise((resolve) => {
|
|
349
|
+
const web = webServerOf(raw);
|
|
350
|
+
let attempt = 0;
|
|
351
|
+
const tick = () => {
|
|
352
|
+
attempt += 1;
|
|
353
|
+
const changed = applyHeaderBudget(web?.server, bytes);
|
|
354
|
+
if (changed) {
|
|
355
|
+
resolve(true);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (web?.server !== undefined) {
|
|
359
|
+
resolve(false);
|
|
360
|
+
return;
|
|
361
|
+
} // 有 server 但抬不动
|
|
362
|
+
if (attempt >= attempts) {
|
|
363
|
+
resolve(false);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
schedule(tick, intervalMs);
|
|
367
|
+
};
|
|
368
|
+
tick();
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
export async function apply(ctx, config) {
|
|
372
|
+
const cfg = { ...Config, ...(config ?? {}) };
|
|
373
|
+
if (cfg.enabled === false)
|
|
374
|
+
return () => { };
|
|
375
|
+
if (!isDesktopRuntime()) {
|
|
376
|
+
// headless profile:无 Electron cookie jar ⇒ 没有 431 可修 ⇒ 不碰宿主。
|
|
377
|
+
ctx.logger?.info?.('[desktop-cookie-janitor] not an Electron runtime — skipped');
|
|
378
|
+
return () => { };
|
|
379
|
+
}
|
|
380
|
+
// ── 防线 1:抬高请求头上限 ────────────────────────────────────────────
|
|
381
|
+
//
|
|
382
|
+
// 我们的插件在宿主树里加载,早于 Electron 的 welcome 握手 ⇒ 来得及。
|
|
383
|
+
//
|
|
384
|
+
// cordis 的 `ctx.inject(deps, cb)` 把 **ctx 代理**交给回调(不是服务本身),
|
|
385
|
+
// 注入的服务要从代理上按**服务名**取(`raw.webServer`);直接读别的属性
|
|
386
|
+
// 会被代理拒绝("cannot get property X without inject")。
|
|
387
|
+
const injector = ctx;
|
|
388
|
+
injector.inject([WEB_SERVER_SERVICE], (raw) => {
|
|
389
|
+
try {
|
|
390
|
+
void raiseHeaderBudgetWhenReady(raw, cfg.maxHeaderBytes).then((raised) => {
|
|
391
|
+
ctx.logger?.info?.(`[desktop-cookie-janitor] maxHeaderSize ${raised ? 'raised' : 'unchanged'} → ${cfg.maxHeaderBytes}`);
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
// 拿不到 web 服务就只是没有这道防线 —— 不能拖垮启动,更不能刷错误。
|
|
396
|
+
ctx.logger?.warn?.(`[desktop-cookie-janitor] header budget skipped: ${error instanceof Error ? error.message : String(error)}`);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
// ── 防线 2:开机清掉历次启动的残骸 ────────────────────────────────────
|
|
400
|
+
//
|
|
401
|
+
// 实测这个时刻 cookie 库还没被 Electron 锁住(约 3 秒窗口):
|
|
402
|
+
// 01:08:23.030 插件加载 → 01:08:25.979 Electron 锁库。
|
|
403
|
+
const jarPath = cfg.jarPath.length > 0 ? cfg.jarPath : defaultJarPath();
|
|
404
|
+
const report = await cleanCookieJar(jarPath);
|
|
405
|
+
ctx.logger?.info?.(`[desktop-cookie-janitor] jar=${report.jar} opened=${report.opened} auth=${report.authCount}`
|
|
406
|
+
+ ` removed=${report.removed} projected=${report.projectedBytes}B launchesLeft=${report.launchesLeft}`
|
|
407
|
+
+ (report.error ? ` error=${report.error}` : ''));
|
|
408
|
+
return () => { };
|
|
409
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-edge-agent — host-half bootstrap plugin.
|
|
3
|
+
*
|
|
4
|
+
* The `name='dsh-edge-agent'` row is the aggregate plugin that cordis
|
|
5
|
+
* 0.2.1-rc.2 mounts when the patch row uses the bare package name
|
|
6
|
+
* `name: '@scope/pkg'`. The package root (`@scope/pkg`) must re-export
|
|
7
|
+
* this module so `require('@scope/pkg')` resolves to it via `package.json#main`.
|
|
8
|
+
*
|
|
9
|
+
* Lives inside `src/dsh-adapter/` to keep the adapter boundary clean:
|
|
10
|
+
* the package root (`src/index.ts`) must NOT import `@deepseek-ai/*` directly,
|
|
11
|
+
* because `verify-adapter-boundary` restricts upstream-package imports to the
|
|
12
|
+
* `src/dsh-adapter/` subtree only. The flat-layout move (commit `1d6b9a7`)
|
|
13
|
+
* promoted this plugin from `src/dsh-adapter/index.ts` to the package root,
|
|
14
|
+
* which immediately tripped the gate — moving the plugin body back into the
|
|
15
|
+
* adapter subtree restores the invariant without changing the runtime layout
|
|
16
|
+
* (`lib/index.js` is still the compiled package root, the plugin code just
|
|
17
|
+
* lives in a different source file).
|
|
18
|
+
*
|
|
19
|
+
* Plugin surface (consumer of `@deepseek-ai/*`):
|
|
20
|
+
* - name: 'dsh-edge-agent'
|
|
21
|
+
* - inject: ['auditLog', 'localStorage'] — required so `plugin.ts`
|
|
22
|
+
* can capture shutdown disposers via `auditLogShutdown(ctx)`
|
|
23
|
+
* / `storageShutdown(ctx)`. Subordinate plugins each declare
|
|
24
|
+
* their own inject in their own file.
|
|
25
|
+
* - Config: full schemastery schema (matches cordis.yml row)
|
|
26
|
+
* - apply: installFileLogger → upstreamDrift warning → plugin bootstrap
|
|
27
|
+
*/
|
|
28
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
29
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
30
|
+
export declare const name = "dsh-edge-agent";
|
|
31
|
+
export declare const inject: readonly ["auditLog"];
|
|
32
|
+
export interface Config {
|
|
33
|
+
/** TEE backend preference. Platform-dependent; auto-detected when omitted. */
|
|
34
|
+
teeType?: 'simulated' | 'trustzone' | 'keystone' | 'sgx';
|
|
35
|
+
/** Path to the encrypted storage root. */
|
|
36
|
+
storageRoot?: string;
|
|
37
|
+
/** Privacy policy file path. Hot-reloaded on change. */
|
|
38
|
+
policyPath?: string;
|
|
39
|
+
/** Default privacy posture when the policy engine cannot classify. */
|
|
40
|
+
defaultDataLevel?: 'public' | 'sanitized' | 'internal';
|
|
41
|
+
/** Allow outbound OHTTP requests even when the relay is unreachable. */
|
|
42
|
+
allowFallbackTls?: boolean;
|
|
43
|
+
/** Number of recent turns to keep in the conversation slice. */
|
|
44
|
+
sessionWindowSize?: number;
|
|
45
|
+
}
|
|
46
|
+
export declare const Config: Schema<Config>;
|
|
47
|
+
export declare function apply(ctx: Context, config: Config): Promise<() => Promise<void>>;
|
|
48
|
+
//# sourceMappingURL=dsh-edge-agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dsh-edge-agent.d.ts","sourceRoot":"","sources":["../../src/dsh-adapter/dsh-edge-agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,MAAM,MAAM,0BAA0B,CAAA;AAE7C,eAAO,MAAM,IAAI,mBAAmB,CAAA;AASpC,eAAO,MAAM,MAAM,uBAAwB,CAAA;AAE3C,MAAM,WAAW,MAAM;IACrB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,KAAK,CAAA;IACxD,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,UAAU,CAAA;IACtD,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,gEAAgE;IAChE,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,MAAM,CAahC,CAAA;AAEF,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CActF"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-edge-agent — host-half bootstrap plugin.
|
|
3
|
+
*
|
|
4
|
+
* The `name='dsh-edge-agent'` row is the aggregate plugin that cordis
|
|
5
|
+
* 0.2.1-rc.2 mounts when the patch row uses the bare package name
|
|
6
|
+
* `name: '@scope/pkg'`. The package root (`@scope/pkg`) must re-export
|
|
7
|
+
* this module so `require('@scope/pkg')` resolves to it via `package.json#main`.
|
|
8
|
+
*
|
|
9
|
+
* Lives inside `src/dsh-adapter/` to keep the adapter boundary clean:
|
|
10
|
+
* the package root (`src/index.ts`) must NOT import `@deepseek-ai/*` directly,
|
|
11
|
+
* because `verify-adapter-boundary` restricts upstream-package imports to the
|
|
12
|
+
* `src/dsh-adapter/` subtree only. The flat-layout move (commit `1d6b9a7`)
|
|
13
|
+
* promoted this plugin from `src/dsh-adapter/index.ts` to the package root,
|
|
14
|
+
* which immediately tripped the gate — moving the plugin body back into the
|
|
15
|
+
* adapter subtree restores the invariant without changing the runtime layout
|
|
16
|
+
* (`lib/index.js` is still the compiled package root, the plugin code just
|
|
17
|
+
* lives in a different source file).
|
|
18
|
+
*
|
|
19
|
+
* Plugin surface (consumer of `@deepseek-ai/*`):
|
|
20
|
+
* - name: 'dsh-edge-agent'
|
|
21
|
+
* - inject: ['auditLog', 'localStorage'] — required so `plugin.ts`
|
|
22
|
+
* can capture shutdown disposers via `auditLogShutdown(ctx)`
|
|
23
|
+
* / `storageShutdown(ctx)`. Subordinate plugins each declare
|
|
24
|
+
* their own inject in their own file.
|
|
25
|
+
* - Config: full schemastery schema (matches cordis.yml row)
|
|
26
|
+
* - apply: installFileLogger → upstreamDrift warning → plugin bootstrap
|
|
27
|
+
*/
|
|
28
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
29
|
+
export const name = 'dsh-edge-agent';
|
|
30
|
+
// The aggregator needs `auditLog` only. `plugin.ts#apply` reads
|
|
31
|
+
// `ctx.auditLog` (via `auditLogShutdown(ctx)`) and `ctx.localStorage` (via
|
|
32
|
+
// `storageShutdown(ctx)`), but both calls are wrapped in try/catch in
|
|
33
|
+
// `plugin.ts` itself, so we don't need to declare `localStorage` here.
|
|
34
|
+
// Declaring `localStorage` would make cordis hang waiting for a service
|
|
35
|
+
// the desktop-safe patch does not insert, producing the
|
|
36
|
+
// "pending (waiting for service: localStorage)" error.
|
|
37
|
+
export const inject = ['auditLog'];
|
|
38
|
+
export const Config = Schema.object({
|
|
39
|
+
teeType: Schema.union([
|
|
40
|
+
'simulated',
|
|
41
|
+
'trustzone',
|
|
42
|
+
'keystone',
|
|
43
|
+
'sgx',
|
|
44
|
+
]).description('TEE backend preference; auto-detected when omitted.'),
|
|
45
|
+
storageRoot: Schema.string().description('Path to the encrypted storage root (resolved at runtime).'),
|
|
46
|
+
policyPath: Schema.string().description('Privacy policy file path. Hot-reloaded on change.'),
|
|
47
|
+
defaultDataLevel: Schema.union(['public', 'sanitized', 'internal'])
|
|
48
|
+
.description('Default privacy posture when the policy engine cannot classify.'),
|
|
49
|
+
allowFallbackTls: Schema.boolean().description('Allow outbound OHTTP requests even when the relay is unreachable.'),
|
|
50
|
+
sessionWindowSize: Schema.natural().description('Number of recent turns to keep in the conversation slice.'),
|
|
51
|
+
});
|
|
52
|
+
export async function apply(ctx, config) {
|
|
53
|
+
// 0. Install the file-logger exporter (see ./file-logger.ts).
|
|
54
|
+
const { installFileLogger } = await import('./file-logger.js');
|
|
55
|
+
installFileLogger(ctx);
|
|
56
|
+
// 1. Drift check — warn at boot when upstream DSH has moved.
|
|
57
|
+
const { upstreamDrift } = await import('./contract.js');
|
|
58
|
+
for (const entry of await upstreamDrift()) {
|
|
59
|
+
ctx.logger?.warn?.(`[dsh-edge-agent] upstream drift: ${entry.pkg} has ${entry.installed} (expected ${entry.expected})`);
|
|
60
|
+
}
|
|
61
|
+
// 2. Delegate to the real implementation under plugin.ts.
|
|
62
|
+
const { apply: bootstrap } = await import('./plugin.js');
|
|
63
|
+
return bootstrap(ctx, config);
|
|
64
|
+
}
|