Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
- **`loaded()` 只认联网来源。** `snapshot()` 灌入的磁盘行与刷新失败后的空目录都不再被当成选择器首屏:冷启动"先聊天再开选择器"不会画出无年龄校验的旧目录,刷新失败后也不会先闪一次空列表。(`src/gateway-facts.ts`)
- **可见性监听按任意卸载顺序解绑。** `unsubscribe` 句柄从函数局部提升到模块层,启动循环的 surface 先卸载时不再泄漏 `visibilitychange` 监听。(`src/client/panel.ts`)
- **账户卡片首屏接线补真实渲染测试。** 新增 `tests/accounts-card-render.test.ts`(`react-test-renderer`):首屏必须调 `onRefreshAuto()`、刷新按钮必须调 `onRefresh()`;把组件改回旧接线时该用例失败。此前只有"两个入口存在"的断言,回退后测试仍全绿。
- **非安全上下文(`http://` 局域网访问)下设置页不再整块空白(issue #102)。** 页面身份原来直接取 `crypto.randomUUID()`,而该 API 只在安全上下文存在:以 `http://<局域网 IP>:3080` 打开时它是 `undefined`,控制器又在 `AccountsCard` 的 `useState(createEnrollment)` 惰性初始化里构造,异常被设置页插槽的错误边界接住——左侧「Command Code」标签正常、右侧内容区空白且无任何提示。新增 `src/client/random-id.ts` 的 `randomId()`:`randomUUID` 可用时照旧,否则退回非安全上下文同样可用的 `crypto.getRandomValues`,按 RFC 4122 拼出同形 v4;`AccountEnrollmentController` 的默认 `newId` 改用它。页面身份只是宿主把开通过程绑到单个浏览器页面的分组键,不参与鉴权,因此不要求 HTTPS。(`src/client/random-id.ts`、`src/client/enrollment.ts`)

用例:`tests/adapter.test.ts` 新增「凭据解析与图片读取并行」「目录已在内存时不再等一次联网刷新」「超时定级复用已序列化的正文」「同一份工具 schema 只归一化一次」「用量卡片与选择器共享同一份账户事实」「凭据缺失时预算门不抢跑」「双超限一次报数」;`tests/gateway-facts.test.ts` 新增 4 条 `loaded()` 来源判据;`tests/panel.test.ts` 与 `tests/usage-client.test.ts` 新增自动/显式刷新、可见性与任意卸载顺序解绑用例;`tests/accounts-card-render.test.ts` 新增 2 条真实渲染接线用例。**验证边界**:`npm run typecheck` 退出码 0;`npm ci` 干净安装后 `npm test` **1125/1125** 通过;`npm run build` 重建 `lib/`。未在真实宿主中人工验证。

issue #102 的用例:`tests/random-id.test.ts` 3 条——安全上下文走平台 `randomUUID`(哨兵值分辨分支,不走自行拼装)、非安全上下文退回 `getRandomValues` 时 100 个身份互不相同且为 v4、非安全上下文下构造 `AccountEnrollmentController` 并把页面身份带给宿主。三处定向变异各命中一条:关闭快速路径只失败第一条,把版本位置 0 只失败后两条,把默认 `newId` 改回 `() => crypto.randomUUID()` 只失败第三条。**验证边界**:`npm run typecheck` 退出码 0;`npm ci` 干净安装后 `npm test` **1129/1129** 通过;`npm run build` 重建 `lib/`(`lib/client.js` 含 `randomId`,`lib/index.js` 与 `lib/index.d.ts` 字节不变)。未在真实 NAS/局域网环境人工验证。

## [0.15.4] - 2026-10-24

### Changed
Expand Down
1 change: 1 addition & 0 deletions docs/agent-reference/client-and-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Task-specific reference moved from the former root `AGENTS.md`. All source and t
- **账号开通**:两种新增方式统一使用 `src/enrollment.ts`;`src/client/enrollment.ts` 由每次设置页挂载持有,`AddAccountPanel` 只负责输入和意图。面板按设计稿:「网页登录/粘贴密钥」分段切换 → 备注名 →(粘贴模式)密钥框 → 右侧「取消」+「开始登录/添加」;进行中只剩「取消开通」,命名失败为「取消/接受已有名称/重试保存名称」,判定在导出纯函数 `addAccountPanelView`(`tests/section.test.ts`)。切回网页登录会清空已填密钥。离开设置页取消未完成过程,已登录待命名则接受已有名称;命名失败保留账号,清理失败保留恢复记录并提供手动重试。默认或已有账号的 `login*` 契约继续使用原控制器。
- **自动打开授权页**:新增、默认和已有账号登录均在首次收到宿主授权地址后通过 `src/client/login-page.ts` 打开新页面,不预先打开空白页;同一尝试的轮询不重复打开,已取消尝试或已卸载的新增账号面板不会因迟到地址打开页面。浏览器可能拦截异步打开,界面始终保留授权链接及手动打开提示;打开失败不取消宿主登录。使用 `noopener,noreferrer` 隔离来源页,不能根据返回 `null` 判断是否拦截。
- **页面生命期**:`enrollmentWatch({ pageId })` 是持续远端流,先确认就绪再允许 `enrollmentBegin`。所有过程调用带页面身份;宿主将它与实际调用对端共同绑定。单次调用完成不结束过程;页面卸载主动取消并释放流,刷新或关闭由网关流取消信号触发收尾。DSH 的 `OperatorPeer` 是宿主共享作用域,不能用它冒充单个浏览器连接。载体断开不会自动重建原过程。
- **页面身份不依赖安全上下文(issue #102)**:页面身份由 `src/client/random-id.ts` 的 `randomId()` 生成,不直接调 `crypto.randomUUID()`——后者只在安全上下文存在,以 `http://<局域网 IP>:3080` 这类非 HTTPS、非 localhost 地址打开时是 `undefined`,而控制器在 `AccountsCard` 的 `useState(createEnrollment)` 惰性初始化里构造,那里的异常会被设置页插槽的错误边界接住,表现为「左侧标签正常、右侧内容区空白且无提示」。`randomUUID` 不可用时退回非安全上下文同样可用的 `crypto.getRandomValues`,按 RFC 4122 置入版本位与变体位,产出与 `randomUUID` 同形的 v4。页面身份只是宿主把开通过程绑到单个浏览器页面的分组键(宿主按不透明字符串比较,不校验 UUID 格式),不参与鉴权,因此不需要安全上下文。默认值经构造参数 `newId` 注入,测试可替换;覆盖见 `tests/random-id.test.ts`。
- **开通远端契约**:双方共享 `src/enrollment-wire.ts` 的七个严格描述符:`enrollmentBegin`、`enrollmentStatus`、`enrollmentCancel`、`enrollmentName`、`enrollmentRetry`、`enrollmentPending` 和流式 `enrollmentWatch`。前六个返回状态或恢复列表,观察流返回就绪确认;所有输入、输出使用严格编解码器。密钥仅允许在手动开始输入中出现,不进入状态、日志或设置文档。旧宿主缺少该契约时明确提示更新,不回退到旧的页面副作用编排。**状态消息只传语义键**:`EnrollmentState.messageKey` 取 `EnrollmentMessageKey`(22 个:宿主 15 个、页面控制器 7 个),`parseEnrollmentState` 校验枚举成员并拒绝自由文本;文案集中在设置页词典、渲染时才按界面语言取,因此宿主与页面不会各写一份,`phase` 也不必再兼作文案判据(同一 `phase` 对应多种语义,如 `naming` 的「正在保存名称」与「名称保存失败」)。宿主抛出的异常消息不在此列:它们不上屏,页面在所有失败路径都用自己这一侧的键覆盖。

- **登录总期限**:浏览器回调成功只关闭监听,不撤销登录计时器。身份验证带取消信号;超时、取消、替换尝试或销毁会中止验证,迟到结果不能开始存储或覆盖状态。期限同样约束等待存储的状态;宿主已发出的凭据写入不能凭空撤销,不能声称提供跨服务事务。
Expand Down
9 changes: 9 additions & 0 deletions docs/agent-reference/repository-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ src/enrollment-wire.ts Shared strict enrollment Remotes and page watch stream;
also owns the `EnrollmentMessageKey` vocabulary the states
carry (semantic keys, localized by the settings page).
src/client/enrollment.ts Page-owned enrollment controller and stream lifetime.
src/client/random-id.ts Page identity for the settings page: `crypto.randomUUID`
when the context is secure, otherwise an RFC 4122 v4 over
`crypto.getRandomValues` (issue #102 — `http://` LAN
access has no `randomUUID`, and the controller is built
during React render).
src/commands.ts The /commandcode usage dashboard command.
src/command-locales.ts zh/en copy for the /commandcode command (Host-side
plain constants, no ctx.locale).
Expand Down Expand Up @@ -223,6 +228,10 @@ tests/volatile-config.test.ts the volatile helpers: `.volatile()` marking,
tests/card.test.ts Models-page provider-card tests (posture logic, key
write path, login affordance parity).
tests/snapshot-store.test.ts Snapshot-store notification and disposal tests.
tests/random-id.test.ts Page identity for the settings page (issue #102): v4
shape, the `getRandomValues` fallback when
`crypto.randomUUID` is absent (non-secure context), and
constructing the enrollment controller there.
tests/update.test.ts update-hint tests (semver compare, payload parse,
throttle cache, failure semantics).
tests/usage-wire.test.ts usage-Remote schema + descriptor tests.
Expand Down
1 change: 1 addition & 0 deletions docs/决策记录.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
6. **2026-10-02 — 已登录且只差命名时离开,接受已有名称。**取消和页面断开均保留账号与凭据;确认写入失败时继续保留待处理记录。
7. **2026-10-02 — 页面生命期绑定持续远端流。**初步计划的对端作用域绑定已被真实宿主源码否定:DSH 0.2.0-rc.2 的 `OperatorPeer` 由宿主连接服务持有,所有浏览器请求共享。用户重新选择持续远端流。每页使用独立 `pageId`,先收到 `enrollmentWatch` 的就绪确认,再开始开通;卸载主动取消,刷新或关闭后由网关流取消信号触发宿主收尾。不依赖卸载事件完成异步请求,不另建心跳租约。
8. **2026-10-02 — 恢复只依据可核查事实。**宿主重启后展示日志,由用户处理;不自动重新登录或重新写密钥。若“已登录”阶段自身未能落盘,仍只有未完成日志,重启后需人工核查和处理;不能把存在凭据等同于成功登录。不承诺浏览器关闭瞬间检测、宿主崩溃后的自动补偿或真实账号端到端已验证。
9. **2026-10-27 — 页面身份不依赖安全上下文(issue #102)。** **现象**:NAS 上的 DSH 以 Docker 运行、只经 `http://<局域网 IP>:3080` 访问(无 HTTPS、无反向代理),设置页「Command Code」标签正常但右侧整块空白,控制台只有 `TypeError: crypto.randomUUID is not a function` 与 `slot entry crashed in 'settings.section'`。**定因**:`crypto.randomUUID()` 是安全上下文专属 API,`http://` 下为 `undefined`(`crypto.getRandomValues` 仍可用);插件只有一处用它,且是 `AccountEnrollmentController` 构造函数的默认参数、构造时立刻调用,而控制器在 `AccountsCard` 的 `useState(createEnrollment)` 惰性初始化里构造——异常落在 React 渲染期,被设置页插槽的错误边界接住,于是标签已注册、内容区不渲染。**选择**:新增 `src/client/random-id.ts` 的 `randomId()`,`randomUUID` 可用时照旧,否则用 `getRandomValues` 按 RFC 4122 置入版本位与变体位拼同形 v4,`newId` 默认值改用它。**未采用**:(a) 要求用户上 HTTPS 或反向代理——把插件缺陷转成部署要求,且「标签在、内容空、无提示」这类故障极难自查;(b) 彻底降级到 `Math.random` 或时间戳——页面身份虽不参与鉴权,但没有理由放弃 `getRandomValues` 这条在非安全上下文同样可用的密码学随机源。**边界**:`pageId` 只是宿主把开通过程绑到单个浏览器页面的分组键,宿主按不透明字符串比较、不校验 UUID 格式,因此退回后的 v4 与 `randomUUID` 的输出可互换;控制器接口、宿主契约(`src/enrollment-wire.ts`)与开通过程本身零变化。**核验**:`tests/random-id.test.ts` 3 条用例——安全上下文走平台 `randomUUID`(用哨兵值分辨分支)、非安全上下文的兜底产出 100 个互不相同的 v4、非安全上下文下构造控制器并把页面身份带给宿主;三处定向变异各命中一条(关闭快速路径/版本位置 0/控制器默认值改回 `crypto.randomUUID()`)。**未做**:真实 NAS/局域网环境端到端验证(本机为安全上下文,只能用去掉 `randomUUID` 的方式模拟)。

## 五、流响应收束

Expand Down
Loading
Loading