dsh-plan-completion 0.1.0 → 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.
@@ -1,99 +1,101 @@
1
- # GitHub 发布评估 · 会话计划补全插件(2026-11-02)
2
-
3
- > **2026-11-02 晚更新:P0 清单已全部完成,状态从「不满足全 PASS」→「可按 GitHub 源码仓库发布」。**
4
- >
5
- > | 项 | 状态 |
6
- > |---|---|
7
- > | B1 git 仓库 | ✅ 已 `git init` + 提交(见 git log) |
8
- > | B2 LICENSE | ✅ MIT(`LICENSE`) |
9
- > | B3 包元数据 | ✅ `name=dsh-plan-completion`、license/repository/keywords/engines/scripts/author;`private:false`;profile 已完成改名并验证运行 |
10
- > | B4 本地绝对路径 | ✅ README/config 已无绝对路径;发布版 patch 不含 `debugDir` |
11
- > | B5 调试路由鉴权/收敛 | ✅ 默认关闭(`enableDebugRoutes:false`),生产路由仅 state/debug/diag/replan/warm |
12
- > | B6 运行时数据不入库 | ✅ `.gitignore` 排除 research/*.jsonl 等;仓库仅含源码+文档+脱敏截图 |
13
- > | B7 CI | ✅ `.github/workflows/test.yml` 已启用(GitHub Actions,Node 20/22 矩阵,push/PR 触发;README 带 badge) |
14
- > | B8 CHANGELOG/CONTRIBUTING | ✅ 已补 |
15
- >
16
- > 发布状态:GitHub 仓库 public、`main` 已推送、tag `v0.1.0` + Release 已创建;GitHub Actions 已启用并 **CI 全绿**(run [41364200848](https://github.com/Xumi0920/dsh-plan-completion/actions/runs/41364200848),Node 20/22 两 job success)。npm 账号已登录(`xumi_frm`),但**发布因账号开启 2FA 被拒(E403)**,需 `--otp` 或带 2FA bypass 的 granular token。
17
- > 包名改名已完成并在本机 profile 验证(`remove @local/...` + `install <repo>`,运行状态与计划在重装后保持)。
18
-
19
- 结论先说:**代码与文档质量达到"可以开源",但当前状态"不满足全 PASS"**。
20
- 按下面的 P0 清单做完(约 1 天工作量),即可公开;P1 是加分项。
21
-
22
- 评估范围:`<repo>`(插件源码,含 78 个单测)+ 两份设计/交付文档 + 实验归档。
23
- 结论依据:代码走查、`node --test` 实测、目录/元数据检查、敏感信息扫描。
24
-
25
- ---
26
-
27
- ## 一、总览评分
28
-
29
- | 维度 | 状态 | 说明 |
30
- |---|---|---|
31
- | 代码质量 | ✅ PASS | Host/Client 分层清晰,纯函数模块(plan/prompt/evidence/heuristics/config/store/job)可单测 |
32
- | 测试 | ✅ PASS | `node --test`:**78 tests / 78 pass / 0 fail**(含 pipeline 集成测试) |
33
- | 文档 | 🟡 部分 | README/交付说明/设计文档齐全,但 README 有 6 处本地绝对路径、缺 CHANGELOG/CONTRIBUTING |
34
- | 安全 | 🟡 部分 | 源码无密钥/密码(已扫描);但 6 个调试路由无鉴权、默认写本地日志目录 |
35
- | 隐私/数据 | 🟡 部分 | 运行日志与实验档包含 sessionId、UA、对话摘要片段;不应进公开仓库 |
36
- | 包元数据 | ❌ FAIL | `private:true`、`name=@local/...`、无 license/repository/keywords/engines/author |
37
- | 仓库工程 | ❌ FAIL | 工作区**不是 git 仓库**;无 LICENSE、.gitignore、CI workflow、CHANGELOG |
38
- | 可移植性 | 🟡 部分 | 默认 `debugDir` 可配置(已改),但 patch 示例仍带本地路径;README 路径需泛化 |
39
- | 视觉/演示资产 | 🟡 部分 | 有真机截图(归档目录),但仓库内无 screenshots/demo 目录 |
40
- | 许可证合规 | ❌ FAIL | 无 LICENSE 文件,无法合法声明开源许可 |
41
-
42
- **当前结论:6 项 PASS / 4 项部分 / 4 项 FAIL → 不满足"全 PASS"。**
43
-
44
- ---
45
-
46
- ## 二、P0 阻塞项(发布前必须修)
47
-
48
- | # | 阻塞项 | 现状 | 修复动作 |
49
- |---|---|---|---|
50
- | B1 | 无 git 仓库 | 工作区不是 repo | 只把插件目录+文档纳入仓库:`git init` → `.gitignore` → 首次提交(建议独立 repo,不要把 research/、其他项目带进去) |
51
- | B2 | 无 LICENSE | 缺失 | 选 MIT(或 Apache-2.0)→ `LICENSE` + package.json `license` 字段 |
52
- | B3 | package.json 元数据不全 | `private:true`、`@local/` 前缀、无 license/repository/author/keywords/engines | 改名 `dsh-plan-completion`(或保留 scoped),补全字段;若只做 GitHub 源码分发可保留 private=false 但不发 npm |
53
- | B4 | 本地绝对路径 | README 与文档中存在本地绝对路径;`cordis.patch.yml` 有本地 `debugDir` | README/文档改成相对路径/占位符;发布版 patch 去掉 `debugDir`(代码已支持 env 覆盖) |
54
- | B5 | 调试路由无鉴权 | `/plan-completion/experiment|autojob|steps|reset|diag|theme` | 发布版:默认关闭调试路由(config `enableDebugRoutes:false`),或加来源校验(`connection.requestRejection`) |
55
- | B6 | 运行时数据/实验档不应入库 | `research/plan-completion-runtime.jsonl`(含 sessionId/UA/对话片段)、`research/archive` | `.gitignore` 排除 `research/`、`*.jsonl`、实验/样本 JSON;如需展示数据,脱敏后放 `docs/` |
56
- | B7 | 无 CI | — | `.github/workflows/test.yml`:Node 20/22 矩阵跑 `node --test test/*.test.js`;可选 markdown lint |
57
- | B8 | 缺 CHANGELOG / CONTRIBUTING | — | 加 `CHANGELOG.md`(0.1.0 初始)+ `CONTRIBUTING.md`(本地安装/调试命令) |
58
-
59
- ## 三、P1 建议(发布后 1~2 周)
60
-
61
- 1. 仓库结构建议:
62
- ```
63
- dsh-plan-completion/ # 根即插件包(保持现在结构)
64
- ├── index.js client.js host/ locale/ test/
65
- ├── cordis.patch.yml package.json icon.svg
66
- ├── README.md LICENSE CHANGELOG.md CONTRIBUTING.md .gitignore
67
- ├── docs/ # 设计计划 v1.2、二期计划、交付说明
68
- ├── docs/screenshots/ # 建议新增 3 张(浅色/暗色/计划页)
69
- └── .github/workflows/test.yml
70
- ```
71
- 2. 安全:调试路由默认 off + 请求来源校验;README 增加「隐私说明:插件会把会话摘要发送给模型」。
72
- 3. 质量:把 client 纯逻辑抽出来补测试(目标 ≥120 用例);host 生命周期(重载不重复注册)加回归测试。
73
- 4. 发布:打 tag `v0.1.0` + GitHub Release notes(引用交付说明);可选 npm publish(需 dsh 生态约定)。
74
- 5. 版本要求:package.json 增加 `engines.dsh` 或 README 明确最低 DSH 版本(本插件依赖 `dsh.client/slots/webServer/llm/commands` 契约)。
75
-
76
- ## 四、已具备的发布优势
77
-
78
- - **契约经过探测验证**:槽位、InputActions、同源 HTTP、斜杠命令均在真机日志中有证据(`research/p0-contract.md`)。
79
- - **可量化验收**:两轮隔离测试 + 6 条真机样本 + 78 单测 + 配置生效日志,README/交付说明可复现。
80
- - **成本与预算有结论**:稳态 ~1.2k tokens/回合、patch 输出 75~113 tokens,有明确默认值与依据。
81
- - **无第三方运行时依赖**:只用 Node 标准库 + React(宿主提供),发布门槛低。
82
- - **面向失败的设计**:模型失败走启发式 fallback、CAS 版本冲突、用户编辑优先、超时降级。
83
-
84
- ## 五、发布命令草稿(确认后执行)
85
-
86
- ```powershell
87
- cd <repo>
88
- git init
89
- # 先补 LICENSE / .gitignore / CI / CHANGELOG / 元数据 / 移除本地路径与调试路由默认值
90
- git add .
91
- git commit -m "feat: initial release v0.1.0"
92
- git remote add origin https://github.com/<you>/dsh-plan-completion.git
93
- git push -u origin main
94
- git tag v0.1.0 && git push --tags
95
- ```
96
-
97
- > 建议:**先把 P0 B1~B8 全部打勾,再公开仓库**;否则许可证与数据问题会成为不可逆的历史提交。
98
-
1
+ # GitHub 发布评估 · 会话计划补全插件(2026-11-02)
2
+
3
+ > 历史文档:以下保留 v0.1.0 的交付/评估/计划记录。当前未发布修复已接入鉴权、收紧模型路由及诊断数据,详见 [security.md](security.md) 和 [CHANGELOG](../CHANGELOG.md);旧记录中的“无鉴权”、原始诊断字段与独立评审不代表当前实现。
4
+
5
+ > **2026-11-02 晚更新:P0 清单已全部完成,状态从「不满足全 PASS」→「可按 GitHub 源码仓库发布」。**
6
+ >
7
+ > | 项 | 状态 |
8
+ > |---|---|
9
+ > | B1 git 仓库 | ✅ 已 `git init` + 提交(见 git log) |
10
+ > | B2 LICENSE | ✅ MIT(`LICENSE`) |
11
+ > | B3 包元数据 | ✅ `name=dsh-plan-completion`、license/repository/keywords/engines/scripts/author;`private:false`;profile 已完成改名并验证运行 |
12
+ > | B4 本地绝对路径 | ✅ README/config 已无绝对路径;发布版 patch 不含 `debugDir` |
13
+ > | B5 调试路由鉴权/收敛 | ✅ 默认关闭(`enableDebugRoutes:false`),生产路由仅 state/debug/diag/replan/warm |
14
+ > | B6 运行时数据不入库 | ✅ `.gitignore` 排除 research/*.jsonl 等;仓库仅含源码+文档+脱敏截图 |
15
+ > | B7 CI | ✅ `.github/workflows/test.yml` 已启用(GitHub Actions,Node 20/22 矩阵,push/PR 触发;README 带 badge) |
16
+ > | B8 CHANGELOG/CONTRIBUTING | ✅ 已补 |
17
+ >
18
+ > 发布状态:GitHub 仓库 public、`main` 已推送、tag `v0.1.0` + Release 已创建;GitHub Actions 已启用并 **CI 全绿**(Node 20/22 双 job success);**npm 已发布 `dsh-plan-completion@0.1.0`**(https://www.npmjs.com/package/dsh-plan-completion,shasum `274044abe5906d0551802033b4dfe16ac05e45`,integrity `sha512-Q3bf67…Ka7D9w==`,与发布输出一致)。
19
+ > 包名改名已完成并在本机 profile 验证(`remove @local/...` + `install <repo>`,运行状态与计划在重装后保持)。
20
+
21
+ 结论先说:**代码与文档质量达到"可以开源",但当前状态"不满足全 PASS"**。
22
+ 按下面的 P0 清单做完(约 1 天工作量),即可公开;P1 是加分项。
23
+
24
+ 评估范围:`<repo>`(插件源码,含 78 个单测)+ 两份设计/交付文档 + 实验归档。
25
+ 结论依据:代码走查、`node --test` 实测、目录/元数据检查、敏感信息扫描。
26
+
27
+ ---
28
+
29
+ ## 一、总览评分
30
+
31
+ | 维度 | 状态 | 说明 |
32
+ |---|---|---|
33
+ | 代码质量 | ✅ PASS | Host/Client 分层清晰,纯函数模块(plan/prompt/evidence/heuristics/config/store/job)可单测 |
34
+ | 测试 | ✅ PASS | `node --test`:**78 tests / 78 pass / 0 fail**(含 pipeline 集成测试) |
35
+ | 文档 | 🟡 部分 | README/交付说明/设计文档齐全,但 README 有 6 处本地绝对路径、缺 CHANGELOG/CONTRIBUTING |
36
+ | 安全 | 🟡 部分 | 源码无密钥/密码(已扫描);但 6 个调试路由无鉴权、默认写本地日志目录 |
37
+ | 隐私/数据 | 🟡 部分 | 运行日志与实验档包含 sessionId、UA、对话摘要片段;不应进公开仓库 |
38
+ | 包元数据 | ❌ FAIL | `private:true`、`name=@local/...`、无 license/repository/keywords/engines/author |
39
+ | 仓库工程 | ❌ FAIL | 工作区**不是 git 仓库**;无 LICENSE、.gitignore、CI workflow、CHANGELOG |
40
+ | 可移植性 | 🟡 部分 | 默认 `debugDir` 可配置(已改),但 patch 示例仍带本地路径;README 路径需泛化 |
41
+ | 视觉/演示资产 | 🟡 部分 | 有真机截图(归档目录),但仓库内无 screenshots/demo 目录 |
42
+ | 许可证合规 | ❌ FAIL | 无 LICENSE 文件,无法合法声明开源许可 |
43
+
44
+ **当前结论:6 项 PASS / 4 项部分 / 4 项 FAIL → 不满足"全 PASS"。**
45
+
46
+ ---
47
+
48
+ ## 二、P0 阻塞项(发布前必须修)
49
+
50
+ | # | 阻塞项 | 现状 | 修复动作 |
51
+ |---|---|---|---|
52
+ | B1 | 无 git 仓库 | 工作区不是 repo | 只把插件目录+文档纳入仓库:`git init` → `.gitignore` → 首次提交(建议独立 repo,不要把 research/、其他项目带进去) |
53
+ | B2 | 无 LICENSE | 缺失 | 选 MIT(或 Apache-2.0)→ `LICENSE` + package.json `license` 字段 |
54
+ | B3 | package.json 元数据不全 | `private:true`、`@local/` 前缀、无 license/repository/author/keywords/engines | 改名 `dsh-plan-completion`(或保留 scoped),补全字段;若只做 GitHub 源码分发可保留 private=false 但不发 npm |
55
+ | B4 | 本地绝对路径 | README 与文档中存在本地绝对路径;`cordis.patch.yml` 有本地 `debugDir` | README/文档改成相对路径/占位符;发布版 patch 去掉 `debugDir`(代码已支持 env 覆盖) |
56
+ | B5 | 调试路由无鉴权 | `/plan-completion/experiment|autojob|steps|reset|diag|theme` | 发布版:默认关闭调试路由(config `enableDebugRoutes:false`),或加来源校验(`connection.requestRejection`) |
57
+ | B6 | 运行时数据/实验档不应入库 | `research/plan-completion-runtime.jsonl`(含 sessionId/UA/对话片段)、`research/archive` | `.gitignore` 排除 `research/`、`*.jsonl`、实验/样本 JSON;如需展示数据,脱敏后放 `docs/` |
58
+ | B7 | 无 CI | — | `.github/workflows/test.yml`:Node 20/22 矩阵跑 `node --test test/*.test.js`;可选 markdown lint |
59
+ | B8 | 缺 CHANGELOG / CONTRIBUTING | — | 加 `CHANGELOG.md`(0.1.0 初始)+ `CONTRIBUTING.md`(本地安装/调试命令) |
60
+
61
+ ## 三、P1 建议(发布后 1~2 周)
62
+
63
+ 1. 仓库结构建议:
64
+ ```
65
+ dsh-plan-completion/ # 根即插件包(保持现在结构)
66
+ ├── index.js client.js host/ locale/ test/
67
+ ├── cordis.patch.yml package.json icon.svg
68
+ ├── README.md LICENSE CHANGELOG.md CONTRIBUTING.md .gitignore
69
+ ├── docs/ # 设计计划 v1.2、二期计划、交付说明
70
+ ├── docs/screenshots/ # 建议新增 3 张(浅色/暗色/计划页)
71
+ └── .github/workflows/test.yml
72
+ ```
73
+ 2. 安全:调试路由默认 off + 请求来源校验;README 增加「隐私说明:插件会把会话摘要发送给模型」。
74
+ 3. 质量:把 client 纯逻辑抽出来补测试(目标 ≥120 用例);host 生命周期(重载不重复注册)加回归测试。
75
+ 4. 发布:打 tag `v0.1.0` + GitHub Release notes(引用交付说明);可选 npm publish(需 dsh 生态约定)。
76
+ 5. 版本要求:package.json 增加 `engines.dsh` 或 README 明确最低 DSH 版本(本插件依赖 `dsh.client/slots/webServer/llm/commands` 契约)。
77
+
78
+ ## 四、已具备的发布优势
79
+
80
+ - **契约经过探测验证**:槽位、InputActions、同源 HTTP、斜杠命令均在真机日志中有证据(`research/p0-contract.md`)。
81
+ - **可量化验收**:两轮隔离测试 + 6 条真机样本 + 78 单测 + 配置生效日志,README/交付说明可复现。
82
+ - **成本与预算有结论**:稳态 ~1.2k tokens/回合、patch 输出 75~113 tokens,有明确默认值与依据。
83
+ - **无第三方运行时依赖**:只用 Node 标准库 + React(宿主提供),发布门槛低。
84
+ - **面向失败的设计**:模型失败走启发式 fallback、CAS 版本冲突、用户编辑优先、超时降级。
85
+
86
+ ## 五、发布命令草稿(确认后执行)
87
+
88
+ ```powershell
89
+ cd <repo>
90
+ git init
91
+ # 先补 LICENSE / .gitignore / CI / CHANGELOG / 元数据 / 移除本地路径与调试路由默认值
92
+ git add .
93
+ git commit -m "feat: initial release v0.1.0"
94
+ git remote add origin https://github.com/<you>/dsh-plan-completion.git
95
+ git push -u origin main
96
+ git tag v0.1.0 && git push --tags
97
+ ```
98
+
99
+ > 建议:**先把 P0 B1~B8 全部打勾,再公开仓库**;否则许可证与数据问题会成为不可逆的历史提交。
100
+
99
101
 
@@ -0,0 +1,57 @@
1
+ # 安全与隐私边界
2
+
3
+ 本页描述 **v0.1.1** 的安全与隐私边界。npm `0.1.0` 为旧版本(无鉴权、含原始诊断字段),请以 registry 最新版为准。
4
+
5
+ ## 请求访问
6
+
7
+ 所有 `/plan-completion/*` 路由在读取请求体或执行操作前调用 DSH 的
8
+ `connection.requestRejection(req)`。宿主负责 Host/Origin 检查及 cookie/API peer 鉴权;
9
+ 无凭据、来源不受信任时返回 401/403,服务不可用或契约异常时返回 503,不退回“仅检查 loopback”。
10
+ 插件不反射任意 Origin、不开放跨来源凭据 CORS。
11
+
12
+ 默认只有 `state`、`replan`、`warm`。只有显式开启 `enableDebugRoutes: true` 才注册
13
+ `debug`、`diag`、`theme`、`experiment`、`autojob`、`steps`、`reset`,这些端点仍须鉴权。
14
+ DSH 是当前用户的可信宿主;本插件没有额外的多租户/逐会话 ACL。获得合法 DSH peer
15
+ 权限的客户端仍能访问该宿主内的计划,不应把这一机制当作多人共享服务的隔离边界。
16
+
17
+ 已核对本机安装版宿主源码:WebServer 不替插件鉴权;Electron 的 dsh-app HTTP 代理会
18
+ 去掉 renderer 的 Origin 并注入宿主 cookie,因此客户端继续使用 same-origin fetch。
19
+ 回归测试使用真实插件入口和模拟宿主服务;没有替换运行中的 DSH 或开展真实浏览器攻击测试。
20
+
21
+ ## 哪些内容会发送给模型
22
+
23
+ 默认 `modelCalls: true`。计划生成会把目标、近期用户/助手消息、工具错误及已有步骤证据
24
+ 组成提示词,经 DSH llm 服务发送到**宿主当前默认 provider/model**。它不一定是历史会话
25
+ 原先使用的模型,也不保证是本地模型。长度裁剪不是敏感信息脱敏;不要假定正文里的密码、
26
+ 个人信息或业务数据会被自动识别和移除。
27
+
28
+ 修复后不再遍历其他供应商挑选更快模型,也不缓存已过时的默认选择;无可用默认路由时使用启发式。
29
+ 调试实验的评审也使用同一已选路线,结果不再声称来自独立跨模型评审。
30
+ `modelCalls: false` 可关闭**本插件**的全部模型调用并使用启发式,不会关闭 DSH 主 agent 的模型调用。
31
+
32
+ `warmBackfill: true` 会在打开旧会话时读取持久化历史以生成计划,可能触发模型请求。
33
+ 设为 false 可关闭历史回填;若希望插件不向模型传输任何会话内容,同时设置 modelCalls: false。
34
+
35
+ ## 本地日志、诊断及截图
36
+
37
+ - debugDir 默认为空,不写运行日志;设置目录或 DSH_PLAN_COMPLETION_DEBUG_DIR 后才启用。
38
+ - 运行日志仅保留允许列表中的操作元数据;会话标识使用截短 SHA-256。
39
+ 不保留模型正文、原始错误、配置内容、请求 URL/查询串、UA 或客户端请求体。
40
+ 会话哈希仍是可关联的假名标识,日志不是匿名统计。
41
+ - 客户端不再发送 DOM 正文、补全建议文本或完整页面 URL。
42
+ 诊断端点再次按白名单保留布局尺寸和布尔值,忽略额外正文。
43
+ - 显式运行调试实验时,内存结果和 debugDir 下的实验 JSON 仍可能含计划、建议、
44
+ 模型评审原文等内容。应按会话内容保护,不要把 research/debug 文件提交或公开。
45
+ 这些文件不加密,也没有自动保留期限。
46
+ - 仓库现有四张截图未见密钥/cookie 等凭据,但包含昵称头像、费用用量、模型与开发任务内容。
47
+ 这类演示信息已经公开;后续公开演示宜换为虚构样本或仅截取插件区域。
48
+
49
+ ## 本次验证范围
50
+
51
+ 原代码的未鉴权访问和隐式跨 provider 发送,均用合成数据、离线 mock 复现。
52
+ 修复后的测试覆盖未授权/错误来源/鉴权服务缺失、诊断默认关闭、日志内容排除、
53
+ 默认模型切换、关闭模型调用,以及计划状态和前端生命周期。
54
+
55
+ 当前文本文件扫描未匹配常见私钥、GitHub/API/AWS 密钥模式;这不是完整 Git 历史扫描,
56
+ 也不能证明不存在所有形式的秘密。没有在真实模型端点、生产 DSH/Electron 或 npm 发布环境
57
+ 上执行端到端验收;本次运行结果不可替代部署后的兼容性验证。
@@ -0,0 +1,66 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ // DSH owns Host/Origin checking and cookie/API-peer authentication.
4
+ // Fail closed during service startup/shutdown and with incompatible hosts.
5
+ export function requestRejection(connection, req) {
6
+ try {
7
+ if (typeof connection?.requestRejection !== 'function') return 503;
8
+ const rejection = connection.requestRejection(req);
9
+ if (rejection === undefined) return undefined;
10
+ return rejection === 401 || rejection === 403 ? rejection : 503;
11
+ } catch {
12
+ return 503;
13
+ }
14
+ }
15
+
16
+ const numberKeys = new Set(['turn', 'seq', 'elapsed', 'elapsedMs', 'events', 'maxTokens',
17
+ 'textChars', 'total', 'index', 'stepCount', 'at', 'fullEvidenceChars']);
18
+ const booleanKeys = new Set(['force', 'ok', 'hasSuggestion', 'suggestion',
19
+ 'withinSuggestionDeadline', 'existed', 'cookie']);
20
+ const labelKeys = new Set(['phase', 'outcome', 'step', 'source', 'method', 'mode', 'type', 'code']);
21
+
22
+ // Keep operational metadata only. Raw errors, model text, config, URLs, user agent,
23
+ // request bodies and model/provider details may contain conversation data or credentials.
24
+ export function safeDebugRecord(record = {}) {
25
+ const safe = {};
26
+ for (const [key, value] of Object.entries(record)) {
27
+ if (numberKeys.has(key) && Number.isFinite(value)) safe[key] = value;
28
+ else if (booleanKeys.has(key) && typeof value === 'boolean') safe[key] = value;
29
+ else if (labelKeys.has(key) && typeof value === 'string' && /^[a-zA-Z0-9_./:-]{1,80}$/.test(value)) safe[key] = value;
30
+ }
31
+ if (typeof record.sessionId === 'string' && record.sessionId) {
32
+ safe.sessionHash = createHash('sha256').update(record.sessionId).digest('hex').slice(0, 16);
33
+ }
34
+ if (record.error !== undefined) safe.error = true;
35
+ if (record.usage && typeof record.usage === 'object') {
36
+ safe.usage = Object.fromEntries(Object.entries(record.usage)
37
+ .filter(([key, value]) => /^(input|output|total|prompt|completion|cached|reasoning)(Tokens|_tokens)$/.test(key)
38
+ && Number.isFinite(value) && value >= 0));
39
+ }
40
+ return safe;
41
+ }
42
+
43
+ // Browser diagnostics are untrusted input, including with a valid DSH cookie.
44
+ // Retain only layout dimensions/booleans, never arbitrary DOM text or URLs.
45
+ export function safeDiagnostics(input) {
46
+ const source = input && typeof input === 'object' && !Array.isArray(input) ? input : {};
47
+ const result = {};
48
+ for (const key of ['hasPill', 'hasEntry', 'suggestVisible', 'dockPresent', 'heroVisible']) {
49
+ if (typeof source[key] === 'boolean') result[key] = source[key];
50
+ }
51
+ if (['module-load', 'dom-check'].includes(source.phase)) result.phase = source.phase;
52
+ if (typeof source.build === 'string' && /^[a-zA-Z0-9._:-]{1,80}$/.test(source.build)) result.build = source.build;
53
+ if (Number.isFinite(source.rowHeight)) result.rowHeight = source.rowHeight;
54
+ for (const key of ['heroStyle', 'rowStyle', 'suggestStyle', 'entryStyle', 'composerStyle']) {
55
+ const style = source[key];
56
+ if (!style || typeof style !== 'object') continue;
57
+ result[key] = {};
58
+ for (const dimension of ['left', 'top', 'w', 'h']) {
59
+ if (Number.isFinite(style[dimension])) result[key][dimension] = style[dimension];
60
+ }
61
+ if (['none', 'block', 'inline', 'inline-block', 'flex', 'inline-flex', 'grid', 'contents'].includes(style.display)) {
62
+ result[key].display = style.display;
63
+ }
64
+ }
65
+ return result;
66
+ }